shadowsocks-libev: convert to using nft
authorYousong Zhou <yszhou4tech@gmail.com>
Wed, 23 Feb 2022 08:55:08 +0000 (08:55 +0000)
committerYousong Zhou <yszhou4tech@gmail.com>
Tue, 1 Mar 2022 02:06:48 +0000 (10:06 +0800)
It will be mostly implemented with ucode templates installed at
/usr/share/ss-rules and called from init script.  The generated nftables
rules will be stored at /etc/nftables.d/

Incompatible changes were introduced as described in the README.md file

 - Netfilter ipset was replaced with nftables sets
 - UCI options ipt_args and dst_forward_recentrst of section ss_rules
   are now deprecated.  The former does not apply to nftables.  The
   later not yet implemented with nftables.

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 [deleted file]
net/shadowsocks-libev/files/ss-rules.defaults [deleted file]
net/shadowsocks-libev/files/ss-rules/chain.uc [new file with mode: 0644]
net/shadowsocks-libev/files/ss-rules/set.uc [new file with mode: 0644]
net/shadowsocks-libev/files/ss-rules/ss-rules.uc [new file with mode: 0644]

index 1da8b2a9b87b829003a43289e11603724a80ade3..d5d26f53cd683fc04a716df73ebe010ec2349661 100644 (file)
@@ -14,7 +14,7 @@ include $(TOPDIR)/rules.mk
 #
 PKG_NAME:=shadowsocks-libev
 PKG_VERSION:=3.3.5
-PKG_RELEASE:=2
+PKG_RELEASE:=3
 
 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
 PKG_SOURCE_URL:=https://github.com/shadowsocks/shadowsocks-libev/releases/download/v$(PKG_VERSION)
@@ -89,25 +89,19 @@ define Package/shadowsocks-libev-ss-rules
   SUBMENU:=Web Servers/Proxies
   TITLE:=shadowsocks-libev ss-rules
   URL:=https://github.com/shadowsocks/shadowsocks-libev
-  DEPENDS:=+ip +ipset +iptables-mod-tproxy +kmod-ipt-nat +resolveip +shadowsocks-libev-ss-redir +shadowsocks-libev-config
+  DEPENDS:=+firewall4 \
+          +ip \
+          +resolveip \
+          +ucode \
+          +ucode-mod-fs \
+          +shadowsocks-libev-ss-redir \
+          +shadowsocks-libev-config \
+          +kmod-nft-tproxy
 endef
 
 define Package/shadowsocks-libev-ss-rules/install
-       $(INSTALL_DIR) $(1)/usr/bin
-       $(INSTALL_BIN) ./files/ss-rules $(1)/usr/bin
-       $(INSTALL_DIR) $(1)/etc/uci-defaults
-       $(INSTALL_DATA) ./files/firewall.ss-rules $(1)/etc
-       $(INSTALL_BIN) ./files/ss-rules.defaults $(1)/etc/uci-defaults
-endef
-
-define Package/shadowsocks-libev-ss-rules/prerm
-#!/bin/sh
-s=firewall.ss_rules
-uci get "$$s" >/dev/null || exit 0
-uci batch <<-EOF
-       delete $$s
-       commit firewall
-EOF
+       $(INSTALL_DIR) $(1)/usr/share/ss-rules
+       $(INSTALL_DATA) ./files/ss-rules/* $(1)/usr/share/ss-rules/
 endef
 
 define Build/Prepare
index 6be10e7f99416bd7896f6f01a511d59741cdd523..736fb3bf9db6d0e6ae1b4911ca2a4786d4904d02 100644 (file)
@@ -71,9 +71,7 @@ We can have multiple instances of component and `server` sections.  The relation
 
        local_default           [bypass], forward, checkdst
 
-Bool option `dst_forward_recentrst` requires iptables/netfilter `recent` match module (`opkg install iptables-mod-conntrack-extra`).  When enabled, `ss-rules` will setup iptables rules to forward through `ss-redir` those packets whose destination have recently sent to us multiple tcp-rst.
-
-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`
+ss-rules now uses nft set for storing addresses/networks.  Those set names are also part of the API and can be populated by other programs, e.g. dnsmasq with builtin nft set support
 
 Note also that `src_ips_xx` and `dst_ips_xx` actually also accepts cidr network representation.  Option names are retained in its current form for backward compatibility coniderations
 
@@ -81,6 +79,7 @@ Note also that `src_ips_xx` and `dst_ips_xx` actually also accepts cidr network
 
 | Commit date | Commit ID | Subject | Comment |
 | ----------- | --------- | ------- | ------- |
+|             |           | shadowsocks-libev: ss-rules: convert to using nft | ss-rules now uses nftables. UCI option ipt_args and dst_forward_recentrst are now deprecated and removed |
 | 2020-08-03  | 7d7cbae75 | shadowsocks-libev: support ss-server option local_address_{v4,v6} | ss_server bind_address now deprecated, use local_address |
 | 2019-05-09  | afe7d3424 | shadowsocks-libev: move plugin options to server section | This is a revision against c19e949 committed 2019-05-06 |
 | 2017-07-02  | b61af9703 | shadowsocks-libev: rewrite | Packaging of shadowsocks-libev was rewritten from scratch |
@@ -162,7 +161,7 @@ Restart shadowsocks-libev components
 
 Check if things are in place
 
-       iptables-save | grep ss_rules
+       nft list ruleset | sed -r -n '/^\t[a-z]+ ss_rules[^ ]+ \{/,/^\t\}/p'
        netstat -lntp | grep -E '8053|1100'
        ps ww | grep ss-
 
index dc94b952fd681d05cd32102f41d753fd057fad99..0805e4019a8378ad8ef4ff4afea835828678daee 100644 (file)
@@ -12,6 +12,9 @@ START=99
 ss_confdir=/var/etc/shadowsocks-libev
 ss_bindir=/usr/bin
 
+ssrules_uc="/usr/share/ss-rules/ss-rules.uc"
+ssrules_nft="/etc/nftables.d/90-ss-rules.nft"
+
 ss_mkjson_server_conf() {
        local cfgserver
 
@@ -111,14 +114,11 @@ ss_rules_cb() {
 
 ss_rules() {
        local cfg="ss_rules"
-       local bin="$ss_bindir/ss-rules"
        local cfgtype
        local local_port_tcp local_port_udp
-       local args
+       local remote_servers
 
-       [ -x "$bin" ] || return 1
-       "$bin" -f
-       "$bin" -6 -f
+       [ -s "$ssrules_uc" ] || return 1
 
        config_get cfgtype "$cfg" TYPE
        [ "$cfgtype" = ss_rules ] || return 1
@@ -130,32 +130,41 @@ ss_rules() {
        eval local_port_tcp="\$ss_rules_redir_tcp_$redir_tcp"
        eval local_port_udp="\$ss_rules_redir_udp_$redir_udp"
        [ -n "$local_port_tcp" -o -n "$local_port_udp" ] || return 1
-       ss_redir_servers="$(echo "$ss_redir_servers" | tr ' ' '\n' | sort -u)"
-       [ "$dst_forward_recentrst" = 0 ] || args="$args --dst-forward-recentrst"
-
-       ss_rules_call
-       ss_rules_call -6
-}
+       remote_servers="$(echo $ss_redir_servers \
+               | tr ' ' '\n' \
+               | sort -u \
+               | xargs -n 1 resolveip \
+               | sort -u)"
 
-ss_rules_call() {
-       "$bin" "$@" \
-                       -s "$ss_redir_servers" \
-                       -l "$local_port_tcp" \
-                       -L "$local_port_udp" \
-                       --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" \
-                       $args \
-               || "$bin" "$@" -f
+       local tmp="/tmp/ssrules"
+       json_init
+       json_add_string o_remote_servers "$remote_servers"
+       json_add_int o_redir_tcp_port "$local_port_tcp"
+       json_add_int o_redir_udp_port "$local_port_udp"
+       json_add_string o_ifnames "$ifnames"
+       json_add_string o_local_default "$local_default"
+       json_add_string o_src_bypass "$src_ips_bypass"
+       json_add_string o_src_forward "$src_ips_forward"
+       json_add_string o_src_checkdst "$src_ips_checkdst"
+       json_add_string o_src_default "$src_default"
+       json_add_string o_dst_bypass "$dst_ips_bypass"
+       json_add_string o_dst_forward "$dst_ips_forward"
+       json_add_string o_dst_bypass_file "$dst_ips_bypass_file"
+       json_add_string o_dst_forward_file "$dst_ips_forward_file"
+       json_add_string o_dst_default "$dst_default"
+       json_dump -i >"$tmp.json"
+
+       if ucode -S -i "$ssrules_uc" -E "$tmp.json" >"$tmp.nft" \
+               && ! cmp -s "$tmp.nft" "$ssrules_nft"; then
+               echo "table inet chk {include \"$tmp.nft\";}" >"$tmp.nft.chk"
+               if nft -f "$tmp.nft.chk" -c; then
+                       mv "$tmp.nft" "$ssrules_nft"
+                       fw4 reload
+               fi
+               rm -f "$tmp.nft.chk"
+       fi
+       rm -f "$tmp.json"
+       rm -f "$tmp.nft"
 }
 
 start_service() {
@@ -170,12 +179,10 @@ start_service() {
 }
 
 stop_service() {
-       local bin="$ss_bindir/ss-rules"
-
-       [ -x "$bin" ] && {
-               "$bin" -f
-               "$bin" -6 -f
-       }
+       if [ -f "$ssrules_nft" ]; then
+               rm -f "$ssrules_nft"
+               fw4 reload
+       fi
        rm -rf "$ss_confdir"
 }
 
@@ -276,9 +283,7 @@ validate_ss_rules_section() {
                'src_default:or("bypass", "forward", "checkdst"):checkdst' \
                'dst_default:or("bypass", "forward"):bypass' \
                'local_default:or("bypass", "forward", "checkdst"):bypass' \
-               'dst_forward_recentrst:bool:0' \
-               'ifnames:maxlength(15)' \
-               'ipt_args:string'
+               'ifnames:maxlength(15)'
 }
 
 validate_ss_server_section() {
diff --git a/net/shadowsocks-libev/files/ss-rules b/net/shadowsocks-libev/files/ss-rules
deleted file mode 100755 (executable)
index ed033af..0000000
+++ /dev/null
@@ -1,298 +0,0 @@
-#!/bin/sh -e
-#
-# 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.
-#
-
-__errmsg() {
-       echo "ss-rules: $*" >&2
-}
-
-if [ "$1" = "-6" ]; then
-       if ! ip6tables -t nat -L -n &>/dev/null; then
-               __errmsg "Skipping ipv6.  Requires ip6tables-mod-nat"
-               exit 1
-       fi
-       o_use_ipv6=1; shift
-fi
-
-ss_rules_usage() {
-       cat >&2 <<EOF
-Usage: ss-rules [options]
-
-       -6              Operate on address family IPv6
-                       When present, must be the first argument
-       -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
-       --dst-forward-recentrst
-                       Forward those packets whose destinations have recently
-                       sent to us multiple tcp-rst packets
-       --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
-}
-
-o_dst_bypass4_="
-       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_dst_bypass6_="
-       ::1/128
-       ::/128
-       ::ffff:0:0/96
-       64:ff9b:1::/48
-       100::/64
-       2001:2::/48
-       2001:db8::/32
-       fe80::/10
-       2001::/23
-       fc00::/7
-"
-o_src_default=bypass
-o_dst_default=bypass
-o_local_default=bypass
-
-alias grep_af="sed -ne '/:/!p'"
-o_dst_bypass_="$o_dst_bypass4_"
-if [ -n "$o_use_ipv6" ]; then
-       alias grep_af="sed -ne /:/p"
-       alias iptables=ip6tables
-       alias iptables-save=ip6tables-save
-       alias iptables-restore=ip6tables-restore
-       alias ip="ip -6"
-       o_af=6
-       o_dst_bypass_="$o_dst_bypass6_"
-fi
-
-ss_rules_parse_args() {
-       while [ "$#" -gt 0 ]; do
-               case "$1" in
-                       -h|--help) ss_rules_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-forward-recentrst) o_dst_forward_recentrst=1; shift 1;;
-                       --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
-
-       if [ -z "$o_redir_tcp_port" -a -z "$o_redir_udp_port" ]; then
-               __errmsg "Requires at least -l or -L option"
-               return 1
-       fi
-       if [ -n "$o_dst_forward_recentrst" ] && ! iptables -m recent -h >/dev/null; then
-               __errmsg "Please install iptables-mod-conntrack-extra"
-               return 1
-       fi
-       o_remote_servers="$(for s in $o_remote_servers; do resolveip "$s" | grep_af; done)"
-}
-
-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 2>/dev/null || true
-       for setname in $(ipset -n list | grep "ss_rules${o_af}_"); do
-               ipset destroy "$setname" 2>/dev/null || true
-       done
-}
-
-ss_rules_ipset_init() {
-       ipset --exist restore <<-EOF
-               create ss_rules${o_af}_src_bypass hash:net family inet$o_af hashsize 64
-               create ss_rules${o_af}_src_forward hash:net family inet$o_af hashsize 64
-               create ss_rules${o_af}_src_checkdst hash:net family inet$o_af hashsize 64
-               create ss_rules${o_af}_dst_bypass hash:net family inet$o_af hashsize 64
-               create ss_rules${o_af}_dst_bypass_ hash:net family inet$o_af hashsize 64
-               create ss_rules${o_af}_dst_forward hash:net family inet$o_af hashsize 64
-               create ss_rules${o_af}_dst_forward_rrst_ hash:ip family inet$o_af hashsize 8 timeout 3600
-               $(ss_rules_ipset_mkadd ss_rules${o_af}_dst_bypass_ "$o_dst_bypass_ $o_remote_servers")
-               $(ss_rules_ipset_mkadd ss_rules${o_af}_src_bypass "$o_src_bypass")
-               $(ss_rules_ipset_mkadd ss_rules${o_af}_src_forward "$o_src_forward")
-               $(ss_rules_ipset_mkadd ss_rules${o_af}_src_checkdst "$o_src_checkdst")
-               $(ss_rules_ipset_mkadd ss_rules${o_af}_dst_bypass "$o_dst_bypass $(cat "$o_dst_bypass_file" 2>/dev/null)")
-               $(ss_rules_ipset_mkadd ss_rules${o_af}_dst_forward "$o_dst_forward $(cat "$o_dst_forward_file" 2>/dev/null)")
-       EOF
-}
-
-ss_rules_ipset_mkadd() {
-       local setname="$1"; shift
-       local i
-
-       for i in $*; do
-               echo "add $setname $i"
-       done | grep_af
-}
-
-ss_rules_iptchains_init() {
-       ss_rules_iptchains_init_tcp
-       ss_rules_iptchains_init_udp
-}
-
-ss_rules_iptchains_init_tcp() {
-       local local_target
-
-       [ -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${o_af}_dst_bypass_ dst -j RETURN
-               -A ss_rules_local_out $o_ipt_extra -j $local_target
-               COMMIT
-       EOF
-}
-
-ss_rules_iptchains_init_udp() {
-       [ -n "$o_redir_udp_port" ] || return 0
-       ss_rules_iptchains_init_ mangle udp
-}
-
-ss_rules_iptchains_init_() {
-       local table="$1"
-       local proto="$2"
-       local forward_rules
-       local src_default_target dst_default_target
-       local recentrst_mangle_rules recentrst_addset_rules
-
-       case "$proto" in
-               tcp)
-                       forward_rules="-A ss_rules_forward -p tcp -j REDIRECT --to-ports $o_redir_tcp_port"
-                       if [ -n "$o_dst_forward_recentrst" ]; then
-                               recentrst_mangle_rules="
-                                       *mangle
-                                       -I PREROUTING 1 -p tcp -m tcp --tcp-flags RST RST -m recent --name ss_rules_recentrst --set --rsource
-                                       COMMIT
-                               "
-                               recentrst_addset_rules="
-                                       -A ss_rules_dst -m recent --name ss_rules_recentrst --rcheck --rdest --seconds 3 --hitcount 3 -j SET --add-set ss_rules${o_af}_dst_forward_rrst_ dst --exist
-                                       -A ss_rules_dst -m set --match-set ss_rules${o_af}_dst_forward_rrst_ dst -j ss_rules_forward
-                               "
-                       fi
-                       ;;
-               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
-       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
-       sed -e '/^\s*$/d' -e 's/^\s\+//' <<-EOF | iptables-restore --noflush
-               *$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${o_af}_dst_bypass_ dst -j RETURN
-               -A ss_rules_pre_src $o_ipt_extra -j ss_rules_src
-               -A ss_rules_src -m set --match-set ss_rules${o_af}_src_bypass src -j RETURN
-               -A ss_rules_src -m set --match-set ss_rules${o_af}_src_forward src -j ss_rules_forward
-               -A ss_rules_src -m set --match-set ss_rules${o_af}_src_checkdst src -j ss_rules_dst
-               -A ss_rules_src -j $src_default_target
-               -A ss_rules_dst -m set --match-set ss_rules${o_af}_dst_bypass dst -j RETURN
-               -A ss_rules_dst -m set --match-set ss_rules${o_af}_dst_forward dst -j ss_rules_forward
-               $recentrst_addset_rules
-               -A ss_rules_dst -j $dst_default_target
-               $forward_rules
-               COMMIT
-               $recentrst_mangle_rules
-       EOF
-}
-
-ss_rules_iptchains_mkprerules() {
-       local proto="$1"
-
-       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
-}
-
-ss_rules_parse_args "$@"
-ss_rules_flush
-ss_rules_ipset_init
-ss_rules_iptchains_init
diff --git a/net/shadowsocks-libev/files/ss-rules.defaults b/net/shadowsocks-libev/files/ss-rules.defaults
deleted file mode 100755 (executable)
index c89e2d0..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/sh
-
-s=firewall.ss_rules
-uci get "$s" >/dev/null && exit 0
-uci batch <<-EOF
-       set $s=include
-       set $s.path=/etc/firewall.ss-rules
-       set $s.reload=1
-       commit firewall
-EOF
diff --git a/net/shadowsocks-libev/files/ss-rules/chain.uc b/net/shadowsocks-libev/files/ss-rules/chain.uc
new file mode 100644 (file)
index 0000000..00362f6
--- /dev/null
@@ -0,0 +1,118 @@
+{%
+function get_local_verdict() {
+       let v = o_local_default;
+       if (v == "checkdst") {
+               return "goto ss_rules_dst_" + proto;
+       } else if (v == "forward") {
+               return "goto ss_rules_forward_" + proto;
+       } else {
+               return null;
+       }
+}
+
+function get_src_default_verdict() {
+       let v = o_src_default;
+       if (v == "checkdst") {
+               return "goto ss_rules_dst_" + proto;
+       } else if (v == "forward") {
+               return "goto ss_rules_forward_" + proto;
+       } else {
+               return "accept";
+       }
+}
+
+function get_dst_default_verdict() {
+       let v = o_dst_default;
+       if (v == "forward") {
+               return "goto ss_rules_forward_" + proto;
+       } else {
+               return "accept";
+       }
+}
+
+function get_ifnames() {
+       let res = [];
+       for (let ifname in split(o_ifnames, /[ \t\n]/)) {
+               ifname = trim(ifname);
+               if (ifname) push(res, ifname);
+       }
+       return res;
+}
+
+let type, hook, priority, redir_port;
+if (proto == "tcp") {
+       type = "nat";
+       hook = "prerouting";
+       priority = -1;
+       redir_port = o_redir_tcp_port;
+} else if (proto == "udp") {
+       type = "filter";
+       hook = "prerouting";
+       priority = "mangle";
+       redir_port = o_redir_udp_port;
+       if (system("
+               set -o errexit
+               while ip rule del fwmark 1 lookup 100 2>/dev/null; do true; done
+                     ip rule add fwmark 1 lookup 100
+               ip route flush table 100 2>/dev/null || true
+               ip route add local default dev lo table 100
+       ") != 0) {
+               return ;
+       }
+} else {
+       return;
+}
+
+%}
+{% if (redir_port): %}
+
+chain ss_rules_pre_{{ proto }} {
+       type {{ type }} hook {{ hook }} priority {{ priority }};
+       meta l4proto {{ proto }}{%- let ifnames=get_ifnames(); if (length(ifnames)): %} iifname { {{join(", ", ifnames)}} }{% endif %} goto ss_rules_pre_src_{{ proto }};
+}
+
+chain ss_rules_pre_src_{{ proto }} {
+       ip daddr @ss_rules_dst_bypass_ accept;
+       ip6 daddr @ss_rules6_dst_bypass_ accept;
+       goto ss_rules_src_{{ proto }};
+}
+
+chain ss_rules_src_{{ proto }} {
+       ip saddr @ss_rules_src_bypass accept;
+       ip saddr @ss_rules_src_forward goto ss_rules_forward_{{ proto }};
+       ip saddr @ss_rules_src_checkdst goto ss_rules_dst_{{ proto }};
+       ip6 saddr @ss_rules6_src_bypass accept;
+       ip6 saddr @ss_rules6_src_forward goto ss_rules_forward_{{ proto }};
+       ip6 saddr @ss_rules6_src_checkdst goto ss_rules_dst_{{ proto }};
+       {{ get_src_default_verdict() }};
+}
+
+chain ss_rules_dst_{{ proto }} {
+       ip daddr @ss_rules_dst_bypass accept;
+       ip daddr @ss_rules_dst_forward goto ss_rules_forward_{{ proto }};
+       ip6 daddr @ss_rules6_dst_bypass accept;
+       ip6 daddr @ss_rules6_dst_forward goto ss_rules_forward_{{ proto }};
+       {{ get_dst_default_verdict() }};
+}
+
+{%   if (proto == "tcp"): %}
+chain ss_rules_forward_{{ proto }} {
+       meta l4proto tcp redirect to :{{ redir_port }};
+}
+{%   let local_verdict = get_local_verdict(); if (local_verdict): %}
+chain ss_rules_local_out {
+       type {{ type }} hook output priority -1;
+       meta l4proto != tcp accept;
+       ip daddr @ss_rules_dst_bypass_ accept;
+       ip daddr @ss_rules_dst_bypass accept;
+       ip6 daddr @ss_rules6_dst_bypass_ accept;
+       ip6 daddr @ss_rules6_dst_bypass accept;
+       {{ local_verdict }};
+}
+{%     endif %}
+{%   elif (proto == "udp"): %}
+chain ss_rules_forward_{{ proto }} {
+       meta l4proto udp meta mark set 1 tproxy to :{{ redir_port }};
+}
+{%   endif %}
+{% endif %}
diff --git a/net/shadowsocks-libev/files/ss-rules/set.uc b/net/shadowsocks-libev/files/ss-rules/set.uc
new file mode 100644 (file)
index 0000000..5947f6c
--- /dev/null
@@ -0,0 +1,113 @@
+{%
+let fs = require("fs");
+
+let o_dst_bypass4_ = "
+       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
+";
+let o_dst_bypass6_ = "
+       ::1/128
+       ::/128
+       ::ffff:0:0/96
+       64:ff9b:1::/48
+       100::/64
+       fe80::/10
+       2001::/23
+       fc00::/7
+";
+let o_dst_bypass_ = o_dst_bypass4_ + " " + o_dst_bypass6_;
+
+let set_suffix = {
+       "src_bypass": {
+               str: o_src_bypass,
+       },
+       "src_forward": {
+               str: o_src_forward,
+       },
+       "src_checkdst": {
+               str: o_src_checkdst,
+       },
+       "dst_bypass": {
+               str: o_dst_bypass,
+               file: o_dst_bypass_file,
+       },
+       "dst_bypass_": {
+               str: o_dst_bypass_,
+       },
+       "dst_forward": {
+               str: o_dst_forward,
+               file: o_dst_forward_file,
+       },
+       "dst_forward_rrst_": {},
+};
+
+function set_name(suf, af) {
+       if (af == 4) {
+               return "ss_rules_"+suf;
+       } else {
+               return "ss_rules6_"+suf;
+       }
+}
+
+function set_elements_parse(res, str, af) {
+       for (let addr in split(str, /[ \t\n]/)) {
+               addr = trim(addr);
+               if (!addr) continue;
+               if (af == 4 && index(addr, ":") != -1) continue;
+               if (af == 6 && index(addr, ":") == -1) continue;
+               push(res, addr);
+       }
+}
+
+function set_elements(suf, af) {
+       let obj = set_suffix[suf];
+       let res = [];
+       let addr;
+
+       let str = obj["str"];
+       if (str) {
+               set_elements_parse(res, str, af);
+       }
+
+       let file = obj["file"];
+       if (file) {
+               let fd = fs.open(file);
+               if (fd) {
+                       str = fd.read("all");
+                       set_elements_parse(res, str, af);
+               }
+       }
+
+       return res;
+}
+%}
+
+{% for (let suf in set_suffix): for (let af in [4, 6]): %}
+set {{ set_name(suf, af) }} {
+       type ipv{{af}}_addr;
+       flags interval;
+{%   let elems = set_elements(suf, af); if (length(elems)): %}
+       elements = {
+{%     for (let i = 0; i < length(elems); i++): %}
+               {{ elems[i] }}{% if (i < length(elems) - 1): %},{% endif %}{% print("\n") %}
+{%     endfor %}
+       }
+{%   endif %}
+}
+{% endfor; endfor %}
diff --git a/net/shadowsocks-libev/files/ss-rules/ss-rules.uc b/net/shadowsocks-libev/files/ss-rules/ss-rules.uc
new file mode 100644 (file)
index 0000000..f3955b2
--- /dev/null
@@ -0,0 +1,8 @@
+{%
+
+include("set.uc");
+include("chain.uc", {proto: "tcp"});
+include("chain.uc", {proto: "udp"});
+
+%}
+