#!/bin/sh /etc/rc.common
# Copyright (C) 2020 Zxilly <zhouxinyu1001@gmail.com>
# Copyright (C) 2021 Tianling Shen <cnsztl@immortalwrt.org>

USE_PROCD=1

START=99

NAME="ua2f"
PROG="/usr/bin/$NAME"
IPT_M="iptables -t mangle"
IPT6_M="ip6tables -t mangle"

FW_DIR="/var/etc"
FW_CONF="$FW_DIR/ua2f.include"

HAS_IPT6="$(command -v ip6tables)"
HAS_NFT="$(command -v nft)"

if type extra_command >"/dev/null" 2>&1; then
	extra_command "setup_firewall"
else
	EXTRA_COMMANDS="setup_firewall"
fi

setup_firewall() {
	config_load "$NAME"

	local handle_fw
	config_get_bool handle_fw "firewall" "handle_fw" "0"

	if [ "$handle_fw" -ne "1" ]; then
		echo "Auto setup firewall disabled. You should set up it manually."
		return 1
	fi

	local handle_tls handle_intranet handle_mmtls disable_connmark
	config_get_bool handle_tls "firewall" "handle_tls" "0"
	config_get_bool handle_intranet "firewall" "handle_intranet" "0"
	config_get_bool handle_mmtls "firewall" "handle_mmtls" "0"
	config_get_bool disable_connmark "main" "disable_connmark" "0"

	if [ -n "$HAS_NFT" ]; then
		nft -f- <<-EOF
			table inet ua2f {
				set localaddr_v4 {
					type ipv4_addr;
					flags interval;
					auto-merge;
					elements = {
						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
					};
				}

				set localaddr_v6 {
					type ipv6_addr;
					flags interval;
					auto-merge;
					elements = {
						::/128, ::1/128, ::ffff:0:0/96, 100::/64, 64:ff9b::/96,
						2001::/32, 2001:10::/28, 2001:20::/28, 2001:db8::/28, 2002::/16,
						fc00::/7, fe80::/10, ff00::/8
					};
				}

				chain postrouting {
					type filter hook postrouting priority mangle -5; policy accept;

					$([ "$handle_intranet" -ne "1" ] || echo 'ip daddr @localaddr_v4 counter return;')
					$([ "$handle_intranet" -ne "1" ] || echo 'ip6 daddr @localaddr_v6 counter return;')

					tcp dport 22 counter return comment "!ua2f: bypass SSH";
					$([ "$handle_tls" -eq "1" ] || echo 'tcp dport 443 counter return comment "!ua2f: bypass HTTPS";')
					$([ "$disable_connmark" -eq "1" ] || echo 'tcp dport 80 counter ct mark set 44;')
					$([ "$disable_connmark" -eq "1" ] || echo 'ct mark 43 counter return comment "!ua2f: bypass non-http stream";')
					meta l4proto tcp ct direction original counter queue num 10010 bypass;
				}
			}
		EOF
	else
		# Flush existing rules
		$IPT_M -D POSTROUTING -p tcp -m conntrack --ctdir ORIGINAL -j ua2f 2>"/dev/null"

		if $IPT_M -N ua2f; then
			if [ "$handle_intranet" -ne "1" ]; then
				$IPT_M -A ua2f -d 10.0.0.0/8 -j RETURN
				$IPT_M -A ua2f -d 172.16.0.0/12 -j RETURN
				$IPT_M -A ua2f -d 192.168.0.0/16 -j RETURN
				$IPT_M -A ua2f -d 0.0.0.0/8 -j RETURN
				$IPT_M -A ua2f -d 127.0.0.0/8 -j RETURN
				$IPT_M -A ua2f -d 169.254.0.0/16 -j RETURN
				$IPT_M -A ua2f -d 224.0.0.0/4 -j RETURN
				$IPT_M -A ua2f -d 240.0.0.0/4 -j RETURN # 不处理流向保留地址的包
			fi

			[ "$handle_tls" -eq "1" ] || $IPT_M -A ua2f -p tcp --dport 443 -j RETURN # 不处理 HTTPS
			[ "$disable_connmark" -eq "1" ] || $IPT_M -A ua2f -p tcp --dport 80 -j CONNMARK --set-mark 44
      [ "$disable_connmark" -eq "1" ] || $IPT_M -A ua2f -m connmark --mark 43 -j RETURN # 不处理标记为非 http 的流
			[ "$handle_mmtls" -eq "1" ] || $IPT_M -A ua2f -p tcp --dport 80 -m string --string "/mmtls/" --algo bm -j RETURN # 不处理微信的mmtls
			$IPT_M -A ua2f -j NFQUEUE --queue-num 10010 --queue-bypass
		fi
		$IPT_M -A POSTROUTING -p tcp -m conntrack --ctdir ORIGINAL -j ua2f

		if [ -n "$HAS_IPT6" ]; then
			# Flush existing rules
			$IPT6_M -D POSTROUTING -p tcp -m conntrack --ctdir ORIGINAL -j ua2f 2>"/dev/null"

			if $IPT6_M -N ua2f; then
				if [ "$handle_intranet" -ne "1" ]; then
					$IPT6_M -A ua2f -d ::/128 -j RETURN
					$IPT6_M -A ua2f -d ::1/128 -j RETURN
					$IPT6_M -A ua2f -d ::ffff:0:0/96 -j RETURN
					$IPT6_M -A ua2f -d 100::/64 -j RETURN
					$IPT6_M -A ua2f -d 64:ff9b::/96 -j RETURN
					$IPT6_M -A ua2f -d 2001::/32 -j RETURN
					$IPT6_M -A ua2f -d 2001:10::/28 -j RETURN
					$IPT6_M -A ua2f -d 2001:20::/28 -j RETURN
					$IPT6_M -A ua2f -d 2001:db8::/28 -j RETURN
					$IPT6_M -A ua2f -d 2002::/16 -j RETURN
					$IPT6_M -A ua2f -d fc00::/7 -j RETURN
					$IPT6_M -A ua2f -d fe80::/10 -j RETURN
					$IPT6_M -A ua2f -d ff00::/8 -j RETURN # 不处理流向保留地址的包
				fi

				[ "$handle_tls" -eq "1" ] || $IPT6_M -A ua2f -p tcp --dport 443 -j RETURN # 不处理 HTTPS
				[ "$disable_connmark" -eq "1" ] || $IPT6_M -A ua2f -p tcp --dport 80 -j CONNMARK --set-mark 44
        [ "$disable_connmark" -eq "1" ] || $IPT6_M -A ua2f -m connmark --mark 43 -j RETURN # 不处理标记为非 http 的流
				[ "$handle_mmtls" -eq "1" ] || $IPT6_M -A ua2f -p tcp --dport 80 -m string --string "/mmtls/" --algo bm -j RETURN # 不处理微信的mmtls
				$IPT6_M -A ua2f -j NFQUEUE --queue-num 10010 --queue-bypass
			fi
			$IPT6_M -A POSTROUTING -p tcp -m conntrack --ctdir ORIGINAL -j ua2f
		fi
	fi
}

start_service() {
	config_load "$NAME"

	local enabled
	config_get_bool enabled "enabled" "enabled" "0"

	if [ "$enabled" -ne "1" ]; then
		echo "UA2F disabled. You should enable it manually."
		return 1
	fi

	procd_open_instance "$NAME"
	procd_set_param command "$PROG"

	local handle_fw
	config_get_bool handle_fw "firewall" "handle_fw" "0"
	if [ "$handle_fw" -eq "1" ]; then
		setup_firewall
		[ -n "$HAS_NFT" ] || {
			mkdir -p "$FW_DIR"
			echo -e "/etc/init.d/$NAME setup_firewall" > "$FW_CONF"
		}
	fi

	procd_set_param stdout 1
	procd_set_param stderr 1
	procd_set_param respawn

	procd_close_instance
}

stop_service() {
	if [ -n "$HAS_NFT" ]; then
		nft flush table inet ua2f
		nft delete table inet ua2f
	else
		$IPT_M -D POSTROUTING -p tcp -m conntrack --ctdir ORIGINAL -j ua2f 2>"/dev/null"
		$IPT_M -F ua2f 2>"/dev/null"
		$IPT_M -X ua2f 2>"/dev/null"

		if [ -n "$HAS_IPT6" ]; then
			$IPT6_M -D POSTROUTING -p tcp -m conntrack --ctdir ORIGINAL -j ua2f 2>"/dev/null"
			$IPT6_M -F ua2f 2>"/dev/null"
			$IPT6_M -X ua2f 2>"/dev/null"
		fi

		echo > "$FW_CONF"
	fi
}

reload_service() {
	stop
	start
}

service_triggers() {
	procd_add_reload_trigger "$NAME"
}
