shadowsocks-libev: rewrite ss-rules
authorYousong Zhou <yszhou4tech@gmail.com>
Sun, 13 Aug 2017 08:00:19 +0000 (16:00 +0800)
committerYousong Zhou <yszhou4tech@gmail.com>
Sat, 19 Aug 2017 18:21:54 +0000 (02:21 +0800)
 - New UCI options ifnames, dst_default
 - UCI options src_ips_xxx now accept cidr as their values
 - Export ipset names as part of the interface so that it can be
   depended on and used by other programs
 - Bypass only remote servers used ss-redir instances, so that it's
   possible to let other servers to go through existing re-redir
   instances

Signed-off-by: Yousong Zhou <yszhou4tech@gmail.com>
net/shadowsocks-libev/Makefile
net/shadowsocks-libev/README.md
net/shadowsocks-libev/files/shadowsocks-libev.init
net/shadowsocks-libev/files/ss-rules

index 2bca7f44ba1d63d2f366b9623ad0813166df1478..307fac9d881da7627dd952784a3cfa7ba1f57816 100644 (file)
@@ -14,7 +14,7 @@ include $(TOPDIR)/rules.mk
 #
 PKG_NAME:=shadowsocks-libev
 PKG_VERSION:=3.0.8
-PKG_RELEASE:=6
+PKG_RELEASE:=7
 
 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
 PKG_SOURCE_URL:=https://github.com/shadowsocks/shadowsocks-libev/releases/download/v$(PKG_VERSION)
index 75790ea69cb30a71a81d39e80032cf1f48406653..bb0545c51c088945d7395b86be98560cf59a3f8a 100644 (file)
@@ -39,8 +39,9 @@ We can have multiple instances of component and `server` sections.  The relation
 
        redir_tcp               name of ss_redir section with mode tcp_only or tcp_and_udp
        redir_udp               name of ss_redir section with mode udp_only or tcp_and_udp
+       ifnames                 only apply rules on packets from these ifnames
 
-       --- incoming packets having source address in
+       --- for incoming packets having source address in
 
        src_ips_bypass          will bypass the redir chain
        src_ips_forward         will always go through the redir chain
@@ -50,10 +51,6 @@ We can have multiple instances of component and `server` sections.  The relation
 
        src_default             bypass, forward, [checkdst]
 
-       --- for local out tcp packets, the default action can be specified with
-
-       local_default           [bypass], forward, checkdst
-
        --- if the previous check result is checkdst,
        --- then packets having destination address in
 
@@ -62,6 +59,18 @@ We can have multiple instances of component and `server` sections.  The relation
        dst_ips_forward_file
        dst_ips_forward         will go through the redir chain
 
+       --- otherwise, the default action can be specified with
+
+       dst_default             [bypass], forward
+
+       --- for local out tcp packets, the default action can be specified with
+
+       local_default           [bypass], forward, checkdst
+
+ss-rules uses kernel ipset mechanism for storing addresses/networks.  Those ipsets are also part of the API and can be populated by other programs, e.g. dnsmasq with builtin ipset support.  For more details please read output of `ss-rules --help`
+
+Note also that `src_ips_xx` and `dst_ips_xx` actually also accepts cidr network representation.  Names are retained for backward compatibility coniderations
+
 ## notes and faq
 
 Useful paths and commands for debugging
index e198c7e3b303819a11fc4e8c9e8c87ba9c2f1771..0142cc1d8a3d71ca42af9ba554fdea5c519c75f7 100644 (file)
@@ -107,7 +107,7 @@ ss_xxx() {
 
        [ -x "$bin" ] || return
        eval "$("validate_${cfgtype}_section" "$cfg" ss_validate_mklocal)"
-       "validate_${cfgtype}_section" "$cfg"
+       "validate_${cfgtype}_section" "$cfg" || return 1
        [ "$disabled" = 0 ] || return
 
        if ss_mkjson \
@@ -127,21 +127,17 @@ ss_xxx() {
 }
 
 ss_rules_cb() {
-       local cfgserver
-       local server
-
-       [ "$cfgtype" != ss_server ] || return
-       config_get cfgserver "$cfg" server
-       config_get server "$cfgserver" server
+       local cfgserver server
 
-       ss_rules_servers="$ss_rules_servers $server"
        if [ "$cfgtype" = ss_redir ]; then
+               config_get cfgserver "$cfg" server
+               config_get server "$cfgserver" server
+               ss_redir_servers="$ss_redir_servers $server"
                if [ "$mode" = tcp_only -o "$mode" = "tcp_and_udp" ]; then
                        eval "ss_rules_redir_tcp_$cfg=$local_port"
                fi
                if [ "$mode" = udp_only -o "$mode" = "tcp_and_udp" ]; then
                        eval "ss_rules_redir_udp_$cfg=$local_port"
-                       eval "ss_rules_redir_server_udp_$cfg=$server"
                fi
        fi
 }
@@ -150,48 +146,37 @@ ss_rules() {
        local cfg="ss_rules"
        local bin="$ss_bindir/ss-rules"
        local cfgtype
-       local args local_port_tcp local_port_udp server_udp
-       local i a_args d_args
+       local local_port_tcp local_port_udp
 
        [ -x "$bin" ] || return 1
        config_get cfgtype "$cfg" TYPE
        [ "$cfgtype" = ss_rules ] || return 1
 
        eval "$(validate_ss_rules_section "$cfg" ss_validate_mklocal)"
-       validate_ss_rules_section "$cfg"
+       validate_ss_rules_section "$cfg" || return 1
        [ "$disabled" = 0 ] || return 1
 
        eval local_port_tcp="\$ss_rules_redir_tcp_$redir_tcp"
        eval local_port_udp="\$ss_rules_redir_udp_$redir_udp"
-       eval server_udp="\$ss_rules_redir_server_udp_$redir_udp"
-       [ -z "$local_port_udp" ] || args="$args -U"
-       case "$local_default" in
-               forward) args="$args -O" ;;
-               checkdst) args="$args -o" ;;
-       esac
-       case "$src_default" in
-               bypass) d_args=RETURN ;;
-               forward) d_args=SS_SPEC_WAN_FW ;;
-               checkdst) d_args=SS_SPEC_WAN_AC ;;
-       esac
-       ss_rules_servers="$(echo "$ss_rules_servers" | tr ' ' '\n' | sort -u)"
-       for i in $src_ips_bypass; do a_args="b,$i $a_args"; done
-       for i in $src_ips_forward; do a_args="g,$i $a_args"; done
-       for i in $src_ips_checkdst; do a_args="n,$i $a_args"; done
+       [ -n "$local_port_tcp" -o -n "$local_port_udp" ] || return 1
+       ss_redir_servers="$(echo "$ss_redir_servers" | tr ' ' '\n' | sort -u)"
 
        "$bin" \
-                       -s "$ss_rules_servers" \
+                       -s "$ss_redir_servers" \
                        -l "$local_port_tcp" \
-                       -S "$server_udp" \
                        -L "$local_port_udp" \
-                       -B "$dst_ips_bypass_file" \
-                       -W "$dst_ips_forward_file" \
-                       -b "$dst_ips_bypass" \
-                       -w "$dst_ips_forward" \
-                       -e "$ipt_args" \
-                       -a "$a_args" \
-                       -d "$d_args" \
-                       $args \
+                       --src-default "$src_default" \
+                       --dst-default "$dst_default" \
+                       --local-default "$local_default" \
+                       --dst-bypass-file "$dst_ips_bypass_file" \
+                       --dst-forward-file "$dst_ips_forward_file" \
+                       --dst-bypass "$dst_ips_bypass" \
+                       --dst-forward "$dst_ips_forward" \
+                       --src-bypass "$src_ips_bypass" \
+                       --src-forward "$src_ips_forward" \
+                       --src-checkdst "$src_ips_checkdst" \
+                       --ifnames "$ifnames" \
+                       --ipt-extra "$ipt_args" \
                || "$bin" -f
 }
 
@@ -299,15 +284,17 @@ validate_ss_rules_section() {
                'disabled:bool:0' \
                'redir_tcp:uci("shadowsocks-libev", "@ss_redir")' \
                'redir_udp:uci("shadowsocks-libev", "@ss_redir")' \
-               'src_ips_bypass:list(ipaddr)' \
-               'src_ips_forward:list(ipaddr)' \
-               'src_ips_checkdst:list(ipaddr)' \
+               'src_ips_bypass:list(or(ip4addr,cidr4))' \
+               'src_ips_forward:list(or(ip4addr,cidr4))' \
+               'src_ips_checkdst:list(or(ip4addr,cidr4))' \
                'dst_ips_bypass_file:file' \
-               'dst_ips_bypass:list(ipaddr)' \
+               'dst_ips_bypass:list(or(ip4addr,cidr4))' \
                'dst_ips_forward_file:file' \
-               'dst_ips_forward:list(ipaddr)' \
-               'src_default:or("bypass", "forward", "checkdst")' \
-               'local_default:or("bypass", "forward", "checkdst")' \
+               'dst_ips_forward:list(or(ip4addr,cidr4))' \
+               'src_default:or("bypass", "forward", "checkdst"):checkdst' \
+               'dst_default:or("bypass", "forward"):bypass' \
+               'local_default:or("bypass", "forward", "checkdst"):bypass' \
+               'ifnames:list(maxlength(15))' \
                'ipt_args:string'
 }
 
index 8bd7264af1695fbbf76a9cb7f8f20a946c2234ba..66036043478b452be3dbc906aeaa4ce875bf4611 100755 (executable)
-#!/bin/sh
+#!/bin/sh -e
 #
-# Copyright (C) 2014-2017 Jian Chang <aa65535@live.com>
+# Copyright (C) 2017 Yousong Zhou <yszhou4tech@gmail.com>
+#
+# The design idea was derived from ss-rules by Jian Chang <aa65535@live.com>
 #
 # This is free software, licensed under the GNU General Public License v3.
 # See /LICENSE for more information.
 #
 
-usage() {
-       cat <<-EOF
-               Usage: ss-rules [options]
-
-               Valid options are:
-
-                   -s <server_ips>         ip address of shadowsocks remote server
-                   -l <local_port>         port number of shadowsocks local server
-                   -S <server_ips>         ip address of shadowsocks remote UDP server
-                   -L <local_port>         port number of shadowsocks local UDP server
-                   -B <ip_list_file>       a file whose content is bypassed ip list
-                   -b <wan_ips>            wan ip of will be bypassed
-                   -W <ip_list_file>       a file whose content is forwarded ip list
-                   -w <wan_ips>            wan ip of will be forwarded
-                   -I <interface>          proxy only for the given interface
-                   -d <target>             the default target of lan access control
-                   -a <lan_hosts>          lan ip of access control, need a prefix to
-                                           define proxy type
-                   -e <extra_args>         extra arguments for iptables
-                   -o                      apply the rules to the OUTPUT chain
-                   -O                      apply the global rules to the OUTPUT chain
-                   -u                      enable udprelay mode, TPROXY is required
-                   -U                      enable udprelay mode, using different IP
-                                           and ports for TCP and UDP
-                   -f                      flush the rules
-                   -h                      show this help message and exit
+ss_usage() {
+       cat >&2 <<EOF
+Usage: ss-rules [options]
+
+       -h, --help      Show this help message then exit
+       -f, --flush     Flush rules, ipset then exit
+       -l <port>       Local port number of ss-redir with TCP mode
+       -L <port>       Local port number of ss-redir with UDP mode
+       -s <ips>        List of ip addresses of remote shadowsocks server
+       --ifnames       Only apply rules on packets from these ifnames
+       --src-bypass <ips|cidr>
+       --src-forward <ips|cidr>
+       --src-checkdst <ips|cidr>
+       --src-default <bypass|forward|checkdst>
+                       Packets will have their src ip checked in order against
+                       bypass, forward, checkdst list and will bypass, forward
+                       through, or continue to have their dst ip checked
+                       respectively on the first match.  Otherwise, --src-default
+                       decide the default action
+       --dst-bypass <ips|cidr>
+       --dst-forward <ips|cidr>
+       --dst-bypass-file <file>
+       --dst-forward-file <file>
+       --dst-default <bypass|forward>
+                       Same as with their --src-xx equivalent
+       --local-default <bypass|forward|checkdst>
+                       Default action for local out TCP traffic
+
+The following ipsets will be created by ss-rules.  They are also intended to be
+populated by other programs like dnsmasq with ipset support
+
+       ss_rules_src_bypass
+       ss_rules_src_forward
+       ss_rules_src_checkdst
+       ss_rules_dst_bypass
+       ss_rules_dst_forward
 EOF
-       exit $1
 }
 
-loger() {
-       # 1.alert 2.crit 3.err 4.warn 5.notice 6.info 7.debug
-       logger -st ss-rules[$$] -p$1 $2
+o_dst_bypass_="
+       0.0.0.0/8
+       10.0.0.0/8
+       100.64.0.0/10
+       127.0.0.0/8
+       169.254.0.0/16
+       172.16.0.0/12
+       192.0.0.0/24
+       192.0.2.0/24
+       192.31.196.0/24
+       192.52.193.0/24
+       192.88.99.0/24
+       192.168.0.0/16
+       192.175.48.0/24
+       198.18.0.0/15
+       198.51.100.0/24
+       203.0.113.0/24
+       224.0.0.0/4
+       240.0.0.0/4
+       255.255.255.255
+"
+o_src_default=bypass
+o_dst_default=bypass
+o_local_default=bypass
+
+__errmsg() {
+       echo "ss-rules: $*" >&2
 }
 
-flush_rules() {
-       iptables-save -c | grep -v "SS_SPEC" | iptables-restore -c
-       if command -v ip >/dev/null 2>&1; then
-               ip rule del fwmark 1 lookup 100 2>/dev/null
-               ip route del local default dev lo table 100 2>/dev/null
-       fi
-       for setname in $(ipset -n list | grep "ss_spec"); do
-               ipset destroy $setname 2>/dev/null
+ss_rules_parse_args() {
+       while [ "$#" -gt 0 ]; do
+               case "$1" in
+                       -h|--help) ss_usage; exit 0;;
+                       -f|--flush) ss_rules_flush; exit 0;;
+                       -l) o_redir_tcp_port="$2"; shift 2;;
+                       -L) o_redir_udp_port="$2"; shift 2;;
+                       -s) o_remote_servers="$2"; shift 2;;
+                       --ifnames) o_ifnames="$2"; shift 2;;
+                       --ipt-extra) o_ipt_extra="$2"; shift 2;;
+                       --src-default) o_src_default="$2"; shift 2;;
+                       --dst-default) o_dst_default="$2"; shift 2;;
+                       --local-default) o_local_default="$2"; shift 2;;
+                       --src-bypass) o_src_bypass="$2"; shift 2;;
+                       --src-forward) o_src_forward="$2"; shift 2;;
+                       --src-checkdst) o_src_checkdst="$2"; shift 2;;
+                       --dst-bypass) o_dst_bypass="$2"; shift 2;;
+                       --dst-forward) o_dst_forward="$2"; shift 2;;
+                       --dst-bypass-file) o_dst_bypass_file="$2"; shift 2;;
+                       --dst-forward-file) o_dst_forward_file="$2"; shift 2;;
+                       *) __errmsg "unknown option $1"; return 1;;
+               esac
        done
-       FWI=$(uci get firewall.shadowsocks.path 2>/dev/null)
-       [ -n "$FWI" ] && echo '# firewall include file' >$FWI
-       return 0
-}
 
-ipset_init() {
-       ipset -! restore <<-EOF || return 1
-               create ss_spec_src_ac hash:ip hashsize 64
-               create ss_spec_src_bp hash:ip hashsize 64
-               create ss_spec_src_fw hash:ip hashsize 64
-               create ss_spec_dst_sp hash:net hashsize 64
-               create ss_spec_dst_bp hash:net hashsize 64
-               create ss_spec_dst_fw hash:net hashsize 64
-               $(gen_lan_host_ipset_entry)
-               $(gen_special_purpose_ip | sed -e "s/^/add ss_spec_dst_sp /")
-               $(sed -e "s/^/add ss_spec_dst_bp /" ${WAN_BP_LIST:=/dev/null} 2>/dev/null)
-               $(for ip in $WAN_BP_IP; do echo "add ss_spec_dst_bp $ip"; done)
-               $(sed -e "s/^/add ss_spec_dst_fw /" ${WAN_FW_LIST:=/dev/null} 2>/dev/null)
-               $(for ip in $WAN_FW_IP; do echo "add ss_spec_dst_fw $ip"; done)
-EOF
-       return 0
-}
-
-ipt_nat() {
-       include_ac_rules nat
-       ipt="iptables -t nat"
-       $ipt -A SS_SPEC_WAN_FW -p tcp \
-               -j REDIRECT --to-ports $local_port || return 1
-       if [ -n "$OUTPUT" ]; then
-               $ipt -N SS_SPEC_WAN_DG
-               $ipt -A SS_SPEC_WAN_DG -m set --match-set ss_spec_dst_sp dst -j RETURN
-               $ipt -A SS_SPEC_WAN_DG -p tcp $EXT_ARGS -j $OUTPUT
-               $ipt -I OUTPUT 1 -p tcp -j SS_SPEC_WAN_DG
+       if [ -z "$o_redir_tcp_port" -a -z "$o_redir_udp_port" ]; then
+               __errmsg "Requires at least -l or -L option"
+               return 1
        fi
-       return $?
 }
 
-ipt_mangle() {
-       [ -n "$TPROXY" ] || return 0
-       if !(lsmod | grep -q TPROXY && command -v ip >/dev/null); then
-               loger 4 "TPROXY or ip not found."
-               return 0
-       fi
-       ip rule add fwmark 1 lookup 100
-       ip route add local default dev lo table 100
-       include_ac_rules mangle
-       iptables -t mangle -A SS_SPEC_WAN_FW -p udp \
-               -j TPROXY --on-port $LOCAL_PORT --tproxy-mark 0x01/0x01
-       return $?
+ss_rules_flush() {
+       local setname
+
+       iptables-save --counters | grep -v ss_rules_ | iptables-restore --counters
+       while ip rule del fwmark 1 lookup 100 2>/dev/null; do true; done
+       ip route flush table 100
+       for setname in $(ipset -n list | grep "ss_rules_"); do
+               ipset destroy "$setname" 2>/dev/null || true
+       done
 }
 
-export_ipt_rules() {
-       [ -n "$FWI" ] || return 0
-       cat <<-CAT >>$FWI
-       iptables-save -c | grep -v "SS_SPEC" | iptables-restore -c
-       iptables-restore -n <<-EOF
-       $(iptables-save | grep -E "SS_SPEC|^\*|^COMMIT" |\
-                       sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/")
+ss_rules_ipset_init() {
+       ipset --exist restore <<-EOF
+               create ss_rules_src_bypass hash:net hashsize 64
+               create ss_rules_src_forward hash:net hashsize 64
+               create ss_rules_src_checkdst hash:net hashsize 64
+               create ss_rules_dst_bypass hash:net hashsize 64
+               create ss_rules_dst_bypass_ hash:net hashsize 64
+               create ss_rules_dst_forward hash:net hashsize 64
+               $(ss_rules_ipset_mkadd ss_rules_dst_bypass_ "$o_dst_bypass_ $o_remote_servers")
+               $(ss_rules_ipset_mkadd ss_rules_src_bypass "$o_src_bypass")
+               $(ss_rules_ipset_mkadd ss_rules_src_forward "$o_src_forward")
+               $(ss_rules_ipset_mkadd ss_rules_src_checkdst "$o_src_checkdst")
+               $(ss_rules_ipset_mkadd ss_rules_dst_bypass "$o_dst_bypass $(cat "$o_dst_bypass_file" 2>/dev/null)")
+               $(ss_rules_ipset_mkadd ss_rules_dst_forward "$o_dst_forward $(cat "$o_dst_forward_file" 2>/dev/null)")
        EOF
-CAT
-       return $?
 }
 
-gen_lan_host_ipset_entry() {
-       for host in $LAN_HOSTS; do
-               case "${host:0:1}" in
-                       n|N)
-                               echo add ss_spec_src_ac ${host:2}
-                               ;;
-                       b|B)
-                               echo add ss_spec_src_bp ${host:2}
-                               ;;
-                       g|G)
-                               echo add ss_spec_src_fw ${host:2}
-                               ;;
-               esac
+ss_rules_ipset_mkadd() {
+       local setname="$1"; shift
+       local i
+
+       for i in $*; do
+               echo "add $setname $i"
        done
 }
 
-gen_special_purpose_ip() {
-       cat <<-EOF | grep -E "^([0-9]{1,3}\.){3}[0-9]{1,3}"
-               0.0.0.0/8
-               10.0.0.0/8
-               100.64.0.0/10
-               127.0.0.0/8
-               169.254.0.0/16
-               172.16.0.0/12
-               192.0.0.0/24
-               192.0.2.0/24
-               192.31.196.0/24
-               192.52.193.0/24
-               192.88.99.0/24
-               192.168.0.0/16
-               192.175.48.0/24
-               198.18.0.0/15
-               198.51.100.0/24
-               203.0.113.0/24
-               224.0.0.0/4
-               240.0.0.0/4
-               255.255.255.255
-               $server
-               $SERVER
-EOF
+ss_rules_iptchains_init() {
+       ss_rules_iptchains_init_tcp
+       ss_rules_iptchains_init_udp
 }
 
-include_ac_rules() {
-       local protocol=$([ "$1" = "mangle" ] && echo udp || echo tcp)
-       iptables-restore -n <<-EOF
-       *$1
-       :SS_SPEC_LAN_DG - [0:0]
-       :SS_SPEC_LAN_AC - [0:0]
-       :SS_SPEC_WAN_AC - [0:0]
-       :SS_SPEC_WAN_FW - [0:0]
-       -A SS_SPEC_LAN_DG -m set --match-set ss_spec_dst_sp dst -j RETURN
-       -A SS_SPEC_LAN_DG -p $protocol $EXT_ARGS -j SS_SPEC_LAN_AC
-       -A SS_SPEC_LAN_AC -m set --match-set ss_spec_src_bp src -j RETURN
-       -A SS_SPEC_LAN_AC -m set --match-set ss_spec_src_fw src -j SS_SPEC_WAN_FW
-       -A SS_SPEC_LAN_AC -m set --match-set ss_spec_src_ac src -j SS_SPEC_WAN_AC
-       -A SS_SPEC_LAN_AC -j ${LAN_TARGET:=SS_SPEC_WAN_AC}
-       -A SS_SPEC_WAN_AC -m set --match-set ss_spec_dst_fw dst -j SS_SPEC_WAN_FW
-       -A SS_SPEC_WAN_AC -m set --match-set ss_spec_dst_bp dst -j RETURN
-       -A SS_SPEC_WAN_AC -j SS_SPEC_WAN_FW
-       $(gen_prerouting_rules $protocol)
-       COMMIT
-EOF
+ss_rules_iptchains_init_tcp() {
+       local ipt="iptables -t nat"
+       local local_target
+       local forward_rules
+       local r
+
+       [ -n "$o_redir_tcp_port" ] || return 0
+
+       ss_rules_iptchains_init_ nat tcp
+
+       case "$o_local_default" in
+               checkdst) local_target=ss_rules_dst ;;
+               forward) local_target=ss_rules_forward ;;
+               bypass|*) return 0;;
+       esac
+
+       iptables-restore --noflush <<-EOF
+               *nat
+               :ss_rules_local_out -
+               -I OUTPUT 1 -p tcp -j ss_rules_local_out
+               -A ss_rules_local_out -m set --match-set ss_rules_dst_bypass_ dst -j RETURN
+               -A ss_rules_local_out -p tcp $o_ipt_extra -j $local_target -m comment --comment "local_default: $o_local_default"
+               COMMIT
+       EOF
 }
 
-gen_prerouting_rules() {
-       [ -z "$IFNAMES" ] && echo -I PREROUTING 1 -p $1 -j SS_SPEC_LAN_DG
-       for ifname in $IFNAMES; do
-               echo -I PREROUTING 1 -i $ifname -p $1 -j SS_SPEC_LAN_DG
-       done
+ss_rules_iptchains_init_udp() {
+       [ -n "$o_redir_udp_port" ] || return 0
+       ss_rules_iptchains_init_ mangle udp
 }
 
-while getopts ":s:l:S:L:B:b:W:w:I:d:a:e:oOuUfh" arg; do
-       case "$arg" in
-               s)
-                       server=$(for ip in $OPTARG; do echo $ip; done)
-                       ;;
-               l)
-                       local_port=$OPTARG
-                       ;;
-               S)
-                       SERVER=$(for ip in $OPTARG; do echo $ip; done)
-                       ;;
-               L)
-                       LOCAL_PORT=$OPTARG
-                       ;;
-               B)
-                       WAN_BP_LIST=$OPTARG
-                       ;;
-               b)
-                       WAN_BP_IP=$OPTARG
-                       ;;
-               W)
-                       WAN_FW_LIST=$OPTARG
-                       ;;
-               w)
-                       WAN_FW_IP=$OPTARG
-                       ;;
-               I)
-                       IFNAMES=$OPTARG
-                       ;;
-               d)
-                       LAN_TARGET=$OPTARG
-                       ;;
-               a)
-                       LAN_HOSTS=$OPTARG
-                       ;;
-               e)
-                       EXT_ARGS=$OPTARG
-                       ;;
-               o)
-                       OUTPUT=SS_SPEC_WAN_AC
-                       ;;
-               O)
-                       OUTPUT=SS_SPEC_WAN_FW
-                       ;;
-               u)
-                       TPROXY=1
-                       ;;
-               U)
-                       TPROXY=2
-                       ;;
-               f)
-                       flush_rules
-                       exit 0
+ss_rules_iptchains_init_() {
+       local table="$1"
+       local proto="$2"
+       local forward_rules
+       local src_default_target dst_default_target
+
+       case "$proto" in
+               tcp)
+                       forward_rules="-A ss_rules_forward -p tcp -j REDIRECT --to-ports $o_redir_tcp_port"
                        ;;
-               h)
-                       usage 0
+               udp)
+                       ip rule add fwmark 1 lookup 100
+                       ip route add local default dev lo table 100
+                       forward_rules="-A ss_rules_forward -p udp -j TPROXY --on-port "$o_redir_udp_port" --tproxy-mark 0x01/0x01"
                        ;;
        esac
-done
+       case "$o_src_default" in
+               forward) src_default_target=ss_rules_forward ;;
+               checkdst) src_default_target=ss_rules_dst ;;
+               bypass|*) src_default_target=RETURN ;;
+       esac
+       case "$o_dst_default" in
+               forward) dst_default_target=ss_rules_forward ;;
+               bypass|*) dst_default_target=RETURN ;;
+       esac
+       iptables-restore --noflush <<-EOF
+               *$table
+               :ss_rules_pre_src -
+               :ss_rules_src -
+               :ss_rules_dst -
+               :ss_rules_forward -
+               $(ss_rules_iptchains_mkprerules "$proto")
+               -A ss_rules_pre_src -m set --match-set ss_rules_dst_bypass_ dst -j RETURN
+               -A ss_rules_pre_src -p $proto $o_ipt_extra -j ss_rules_src
+               -A ss_rules_src -m set --match-set ss_rules_src_bypass src -j RETURN
+               -A ss_rules_src -m set --match-set ss_rules_src_forward src -j ss_rules_forward
+               -A ss_rules_src -m set --match-set ss_rules_src_checkdst src -j ss_rules_dst
+               -A ss_rules_src -j $src_default_target -m comment --comment "src_default: $o_src_default"
+               -A ss_rules_dst -m set --match-set ss_rules_dst_bypass dst -j RETURN
+               -A ss_rules_dst -m set --match-set ss_rules_dst_forward dst -j ss_rules_forward
+               -A ss_rules_dst -j $dst_default_target -m comment --comment "dst_default: $o_dst_default"
+               $forward_rules
+               COMMIT
+       EOF
+}
 
-[ -z "$server" -o -z "$local_port" ] && usage 2
+ss_rules_iptchains_mkprerules() {
+       local proto="$1"
 
-if [ "$TPROXY" = 1 ]; then
-       unset SERVER
-       LOCAL_PORT=$local_port
-elif [ "$TPROXY" = 2 ]; then
-       : ${SERVER:?"You must assign an ip for the udp relay server."}
-       : ${LOCAL_PORT:?"You must assign a port for the udp relay server."}
-fi
+       if [ -z "$o_ifnames" ]; then
+               echo "-I PREROUTING 1 -p $proto -j ss_rules_pre_src"
+       else
+               echo "$o_ifnames" \
+                       | tr ' ' '\n' \
+                       | sed "s/.*/-I PREROUTING 1 -i \\0 -p $proto -j ss_rules_pre_src/"
+       fi
+}
 
-flush_rules && ipset_init && ipt_nat && ipt_mangle && export_ipt_rules
-RET=$?
-[ "$RET" = 0 ] || loger 3 "Start failed!"
-exit $RET
+ss_rules_parse_args "$@"
+ss_rules_flush
+ss_rules_ipset_init
+ss_rules_iptchains_init