#!/bin/sh /etc/rc.common # shellcheck disable=SC2034,SC1083,SC3043,SC2086 USE_PROCD=1 START=99 NAME="ua3f" PROG="/usr/bin/$NAME" SERVER_MODE="" SERVER_PORT="1080" FW_BACKEND="" NFT_TABLE="UA3F" UA3F_CHAIN="UA3F" UA3F_OUT_CHAIN="UA3F_OUTPUT" UA3F_LANSET="ua3f_localnetwork" UA3F_SOMARK="0xc9" UA3F_FWMARK="0x1c9" ROUTE_TABLE="0x1c9" UA3F_GID="65534" UA3F_GROUP="nogroup" SKIP_GIDS="" SIDECAR="OC" FAKEIP_RANGE="198.18.0.0/16, 198.18.0.1/15, 28.0.0.1/8" SKIP_PORTS="22,51080,51090" LOG_FILE="/var/log/ua3f/ua3f.log" LOG() { if [ -n "${1}" ]; then printf '[%s] %s\n' "$(date "+%Y-%m-%d %H:%M:%S")" "$1" >>"$LOG_FILE" fi } try_modprobe() { command -v modprobe >/dev/null 2>&1 && modprobe "$1" 2>/dev/null; } nft_available() { command -v nft >/dev/null 2>&1; } ipt_available() { command -v iptables >/dev/null 2>&1; } opkg_available() { command -v opkg >/dev/null 2>&1; } openclash_exists() { if opkg_available; then if opkg list-installed luci-app-openclash | grep -q 'luci-app-openclash'; then return 0 fi fi return 1 } openclash_running() { if pgrep -f "openclash" >/dev/null 2>&1; then return 0 fi return 1 } shellclash_exists() { if id -u shellclash >/dev/null 2>&1; then return 0 fi if id -u shellcrash >/dev/null 2>&1; then return 0 fi return 1 } shellclash_running() { if pgrep -f "ShellCrash" >/dev/null 2>&1; then return 0 fi return 1 } set_ua3f_group() { add_skip_gids "453" if openclash_running; then UA3F_GID="65534" UA3F_GROUP="nogroup" SIDECAR="OCSC" add_skip_gids "7890" elif shellclash_running; then UA3F_GID="7890" UA3F_GROUP="shellcrash" add_skip_gids "65534" SIDECAR="SC" elif openclash_exists; then UA3F_GID="65534" UA3F_GROUP="nogroup" add_skip_gids "7890" SIDECAR="OC" elif shellclash_exists; then UA3F_GID="7890" UA3F_GROUP="shellcrash" add_skip_gids "65534" SIDECAR="SC" else UA3F_GID="65534" UA3F_GROUP="nogroup" add_skip_gids "7890" SIDECAR="OC" fi } add_skip_gids() { for gid in "$@"; do [ -z "$gid" ] && continue case ",$SKIP_GIDS," in *,"$gid",*) ;; *) if [ -z "$SKIP_GIDS" ]; then SKIP_GIDS=$gid else SKIP_GIDS=$SKIP_GIDS,$gid fi ;; esac done } detect_backend() { if opkg_available; then if opkg list-installed kmod-nft-tproxy | grep -q 'kmod-nft-tproxy'; then FW_BACKEND="nft" return 0 else FW_BACKEND="ipt" return 0 fi fi if nft_available; then FW_BACKEND="nft" return 0 fi if ipt_available; then FW_BACKEND="ipt" return 0 fi FW_BACKEND="" return 1 } add_tproxy_route() { sysctl -w net.bridge.bridge-nf-call-iptables=0 >/dev/null 2>&1 sysctl -w net.bridge.bridge-nf-call-ip6tables=0 >/dev/null 2>&1 if ! output=$(ip rule add fwmark "$UA3F_FWMARK" table "$ROUTE_TABLE" 2>&1); then LOG "Failed to add ip rule fwmark: $output" return 1 fi if ! output=$(ip route add local 0.0.0.0/0 dev lo table "$ROUTE_TABLE" 2>&1); then LOG "Failed to add ip route local lo: $output" return 1 fi } cleanup_tproxy_route() { ip route flush table "$ROUTE_TABLE" >/dev/null 2>&1 ip rule del fwmark "$UA3F_FWMARK" table "$ROUTE_TABLE" >/dev/null 2>&1 ip rule del fwmark 0x1c9 table "$ROUTE_TABLE" >/dev/null 2>&1 } nft_drop_table() { nft delete table ip "$NFT_TABLE" 2>/dev/null; } nft_reinit_table() { nft_drop_table nft add table ip "$NFT_TABLE" || return 1 nft "add set ip $NFT_TABLE $UA3F_LANSET { type ipv4_addr; flags interval; auto-merge; }" || return 1 nft "add element ip $NFT_TABLE $UA3F_LANSET { 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.168.0.0/16, 224.0.0.0/4, 240.0.0.0/4 }" >/dev/null 2>&1 } fw_setup_nft_tproxy_tcp() { nft_reinit_table || { LOG "Failed to reinitialize nft table" return 1 } add_tproxy_route || { LOG "Failed to add tproxy route" return 1 } if [ "$SIDECAR" = "SC" ]; then nft add chain ip $NFT_TABLE sidecar '{ type filter hook prerouting priority mangle - 20; }' nft add rule ip $NFT_TABLE sidecar meta l4proto tcp mark $UA3F_FWMARK mark set 7894 tproxy to 127.0.0.1:$SERVER_PORT counter accept comment '"cap sc"' fi # PREROUTING -> UA3F nft add chain ip $NFT_TABLE prerouting '{ type filter hook prerouting priority filter + 20; }' nft add rule ip $NFT_TABLE prerouting meta l4proto != tcp counter return nft add rule ip $NFT_TABLE prerouting ct direction reply counter return nft add rule ip $NFT_TABLE prerouting mark {$UA3F_SOMARK} counter return comment '"UA3F somark, never hit"' nft add rule ip $NFT_TABLE prerouting mark {0x162} counter return comment '"354"' nft add rule ip $NFT_TABLE prerouting mark {0x1ed4} counter return comment '"sc tproxy mark 7892"' nft add rule ip $NFT_TABLE prerouting ip daddr {$FAKEIP_RANGE} counter return comment '"fakeip range"' nft add rule ip $NFT_TABLE prerouting ip daddr @$UA3F_LANSET counter return nft add rule ip $NFT_TABLE prerouting tcp dport {$SKIP_PORTS} return nft add rule ip $NFT_TABLE prerouting meta l4proto tcp mark $UA3F_FWMARK tproxy to 127.0.0.1:$SERVER_PORT counter accept comment '"cap oc"' nft add rule ip $NFT_TABLE prerouting meta l4proto tcp mark set $UA3F_FWMARK tproxy to 127.0.0.1:$SERVER_PORT counter accept comment '"default less hit. sc"' # OUTPUT -> UA3F_OUTPUT nft add chain ip $NFT_TABLE output '{ type route hook output priority filter + 20; }' nft add rule ip $NFT_TABLE output meta l4proto != tcp counter return nft add rule ip $NFT_TABLE output mark $UA3F_SOMARK counter return comment '"UA3F somark"' nft add rule ip $NFT_TABLE output ip daddr {$FAKEIP_RANGE} counter return nft add rule ip $NFT_TABLE output meta skgid {$SKIP_GIDS} counter return nft add rule ip $NFT_TABLE output ip daddr @$UA3F_LANSET counter return nft add rule ip $NFT_TABLE output tcp dport {$SKIP_PORTS} return nft add rule ip $NFT_TABLE output meta l4proto tcp meta skgid $UA3F_GID mark set $UA3F_FWMARK counter accept comment '"ghost oc"' nft add rule ip $NFT_TABLE output meta l4proto tcp mark set $UA3F_FWMARK counter accept comment '"default tproxy mark. bypass sc pre pollution"' } fw_setup_nft_redirect_tcp() { nft_reinit_table || return 1 # PREROUTING -> UA3F nft add chain ip $NFT_TABLE prerouting '{ type nat hook prerouting priority filter + 20; }' nft add rule ip $NFT_TABLE prerouting meta l4proto != tcp counter return nft add rule ip $NFT_TABLE prerouting ct direction reply counter return nft add rule ip $NFT_TABLE prerouting mark {$UA3F_SOMARK} counter return comment '"UA3F somark, never hit"' nft add rule ip $NFT_TABLE prerouting mark {0x162} counter return comment '"354"' nft add rule ip $NFT_TABLE prerouting mark {0x1ed4} counter return comment '"sc tproxy mark 7892"' nft add rule ip $NFT_TABLE prerouting ip daddr {$FAKEIP_RANGE} counter return comment '"fakeip range"' nft add rule ip $NFT_TABLE prerouting tcp dport {$SKIP_PORTS} return nft add rule ip $NFT_TABLE prerouting ip daddr @$UA3F_LANSET counter return nft add rule ip $NFT_TABLE prerouting tcp dport != {22} counter redirect to :$SERVER_PORT # OUTPUT -> UA3F_OUTPUT nft add chain ip $NFT_TABLE output '{ type nat hook output priority filter + 20; }' nft add rule ip $NFT_TABLE output meta l4proto != tcp counter return nft add rule ip $NFT_TABLE output mark $UA3F_SOMARK counter return comment '"UA3F somark"' nft add rule ip $NFT_TABLE output ip daddr {$FAKEIP_RANGE} counter return nft add rule ip $NFT_TABLE output ip daddr @$UA3F_LANSET counter return nft add rule ip $NFT_TABLE output meta skgid {$SKIP_GIDS} counter return nft add rule ip $NFT_TABLE output tcp dport {$SKIP_PORTS} return nft add rule ip $NFT_TABLE output meta l4proto tcp mark {0x1ed6} counter redirect to :$SERVER_PORT comment '"cap sc meta"' nft add rule ip $NFT_TABLE output meta skgid $UA3F_GID tcp dport != {22} counter redirect to :$SERVER_PORT comment '"cap oc"' nft add rule ip $NFT_TABLE output tcp dport != {22} counter redirect to :$SERVER_PORT comment '"cap scc"' } fw_revert_nft() { nft_drop_table cleanup_tproxy_route } setup_ipset_ipt() { cleanup_ipset_ipt ipset create $UA3F_LANSET hash:net || return 1 ipset add $UA3F_LANSET 0.0.0.0/8 ipset add $UA3F_LANSET 10.0.0.0/8 ipset add $UA3F_LANSET 100.64.0.0/10 ipset add $UA3F_LANSET 127.0.0.0/8 ipset add $UA3F_LANSET 169.254.0.0/16 ipset add $UA3F_LANSET 172.16.0.0/12 ipset add $UA3F_LANSET 192.168.0.0/16 ipset add $UA3F_LANSET 224.0.0.0/4 ipset add $UA3F_LANSET 240.0.0.0/4 } fw_setup_ipt_tproxy_tcp() { setup_ipset_ipt || return 1 add_tproxy_route || return 1 if [ "$SIDECAR" = "SC" ]; then iptables -t mangle -F SIDECAR 2>/dev/null iptables -t mangle -D PREROUTING -p tcp -j SIDECAR 2>/dev/null iptables -t mangle -X SIDECAR 2>/dev/null iptables -t mangle -N SIDECAR iptables -t mangle -I PREROUTING -p tcp -j SIDECAR iptables -t mangle -A SIDECAR -m mark --mark $UA3F_FWMARK -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port $SERVER_PORT --tproxy-mark 7894 fi # PREROUTING iptables -t mangle -F $UA3F_CHAIN 2>/dev/null iptables -t mangle -D PREROUTING -p tcp -j $UA3F_CHAIN 2>/dev/null iptables -t mangle -X $UA3F_CHAIN 2>/dev/null iptables -t mangle -N $UA3F_CHAIN iptables -t mangle -A PREROUTING -p tcp -j $UA3F_CHAIN iptables -t mangle -A $UA3F_CHAIN -m conntrack --ctdir REPLY -j RETURN iptables -t mangle -A $UA3F_CHAIN -m mark --mark $UA3F_SOMARK -j RETURN iptables -t mangle -A $UA3F_CHAIN -m mark --mark 0x162 -j RETURN iptables -t mangle -A $UA3F_CHAIN -m mark --mark 0x1ed4 -j RETURN iptables -t mangle -A $UA3F_CHAIN -d 198.18.0.0/16 -j RETURN iptables -t mangle -A $UA3F_CHAIN -d 28.0.0.1/8 -j RETURN iptables -t mangle -A $UA3F_CHAIN -d 198.18.0.1/15 -j RETURN iptables -t mangle -A $UA3F_CHAIN -p tcp -m multiport --dports $SKIP_PORTS -j RETURN iptables -t mangle -A $UA3F_CHAIN -m set --match-set $UA3F_LANSET dst -j RETURN iptables -t mangle -A $UA3F_CHAIN -p tcp -m mark --mark $UA3F_FWMARK -j TPROXY --on-ip 127.0.0.1 --on-port $SERVER_PORT iptables -t mangle -A $UA3F_CHAIN -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port $SERVER_PORT --tproxy-mark $UA3F_FWMARK # OUTPUT iptables -t mangle -F $UA3F_OUT_CHAIN 2>/dev/null iptables -t mangle -D OUTPUT -p tcp -j $UA3F_OUT_CHAIN 2>/dev/null iptables -t mangle -X $UA3F_OUT_CHAIN 2>/dev/null iptables -t mangle -N $UA3F_OUT_CHAIN iptables -t mangle -I OUTPUT -p tcp -j $UA3F_OUT_CHAIN iptables -t mangle -A $UA3F_OUT_CHAIN -m mark --mark $UA3F_SOMARK -j RETURN iptables -t mangle -A $UA3F_OUT_CHAIN -d 198.18.0.0/16 -j RETURN iptables -t mangle -A $UA3F_OUT_CHAIN -d 28.0.0.1/8 -j RETURN iptables -t mangle -A $UA3F_OUT_CHAIN -d 198.18.0.1/15 -j RETURN iptables -t mangle -A $UA3F_OUT_CHAIN -p tcp -m multiport --dports $SKIP_PORTS -j RETURN iptables -t mangle -A $UA3F_OUT_CHAIN -p tcp -m owner --gid-owner 453 -j RETURN iptables -t mangle -A $UA3F_OUT_CHAIN -m set --match-set $UA3F_LANSET dst -j RETURN iptables -t mangle -A $UA3F_OUT_CHAIN -p tcp -m owner --gid-owner $UA3F_GID -j MARK --set-mark $UA3F_FWMARK iptables -t mangle -A $UA3F_OUT_CHAIN -p tcp -j MARK --set-mark $UA3F_FWMARK } fw_setup_ipt_redirect_tcp() { setup_ipset_ipt || return 1 # PREROUTING iptables -t nat -F $UA3F_CHAIN 2>/dev/null iptables -t nat -D PREROUTING -p tcp -j $UA3F_CHAIN 2>/dev/null iptables -t nat -X $UA3F_CHAIN 2>/dev/null iptables -t nat -N $UA3F_CHAIN iptables -t nat -A PREROUTING -p tcp -j $UA3F_CHAIN iptables -t nat -A $UA3F_CHAIN -m conntrack --ctdir REPLY -j RETURN iptables -t nat -A $UA3F_CHAIN -m mark --mark $UA3F_SOMARK -j RETURN iptables -t nat -A $UA3F_CHAIN -m mark --mark 0x162 -j RETURN iptables -t nat -A $UA3F_CHAIN -m mark --mark 0x1ed4 -j RETURN iptables -t nat -A $UA3F_CHAIN -d 198.18.0.0/16 -j RETURN iptables -t nat -A $UA3F_CHAIN -d 28.0.0.1/8 -j RETURN iptables -t nat -A $UA3F_CHAIN -d 198.18.0.1/15 -j RETURN iptables -t nat -A $UA3F_CHAIN -p tcp -m multiport --dports $SKIP_PORTS -j RETURN iptables -t nat -A $UA3F_CHAIN -m set --match-set $UA3F_LANSET dst -j RETURN iptables -t nat -A $UA3F_CHAIN -p tcp -j REDIRECT --to-ports $SERVER_PORT # OUTPUT iptables -t nat -F $UA3F_OUT_CHAIN 2>/dev/null iptables -t nat -D OUTPUT -p tcp -j $UA3F_OUT_CHAIN 2>/dev/null iptables -t nat -X $UA3F_OUT_CHAIN 2>/dev/null iptables -t nat -N $UA3F_OUT_CHAIN iptables -t nat -I OUTPUT -p tcp -j $UA3F_OUT_CHAIN iptables -t nat -A $UA3F_OUT_CHAIN -m mark --mark $UA3F_SOMARK -j RETURN iptables -t nat -A $UA3F_OUT_CHAIN -d 198.18.0.0/16 -j RETURN iptables -t nat -A $UA3F_OUT_CHAIN -d 28.0.0.1/8 -j RETURN iptables -t nat -A $UA3F_OUT_CHAIN -d 198.18.0.1/15 -j RETURN iptables -t nat -A $UA3F_OUT_CHAIN -p tcp -m multiport --dports $SKIP_PORTS -j RETURN iptables -t nat -A $UA3F_OUT_CHAIN -m set --match-set $UA3F_LANSET dst -j RETURN iptables -t nat -A $UA3F_OUT_CHAIN -m owner --gid-owner 453 -j RETURN iptables -t nat -A $UA3F_OUT_CHAIN -p tcp -m mark --mark 0x1ed6 -j REDIRECT --to-ports $SERVER_PORT iptables -t nat -A $UA3F_OUT_CHAIN -p tcp -m owner --gid-owner $UA3F_GID -j REDIRECT --to-ports $SERVER_PORT iptables -t nat -A $UA3F_OUT_CHAIN -p tcp -j REDIRECT --to-ports $SERVER_PORT } cleanup_ipset_ipt() { ipset destroy $UA3F_LANSET 2>/dev/null } fw_revert_ipt() { # sidecar iptables -t mangle -F SIDECAR 2>/dev/null iptables -t mangle -D PREROUTING -p tcp -j SIDECAR 2>/dev/null iptables -t mangle -X SIDECAR 2>/dev/null # mangle iptables -t mangle -D PREROUTING -p tcp -j $UA3F_CHAIN 2>/dev/null iptables -t mangle -F $UA3F_CHAIN 2>/dev/null iptables -t mangle -X $UA3F_CHAIN 2>/dev/null iptables -t mangle -D OUTPUT -p tcp -j $UA3F_OUT_CHAIN 2>/dev/null iptables -t mangle -F $UA3F_OUT_CHAIN 2>/dev/null iptables -t mangle -X $UA3F_OUT_CHAIN 2>/dev/null # nat iptables -t nat -D PREROUTING -p tcp -j $UA3F_CHAIN 2>/dev/null iptables -t nat -F $UA3F_CHAIN 2>/dev/null iptables -t nat -X $UA3F_CHAIN 2>/dev/null iptables -t nat -D OUTPUT -p tcp -j $UA3F_OUT_CHAIN 2>/dev/null iptables -t nat -F $UA3F_OUT_CHAIN 2>/dev/null iptables -t nat -X $UA3F_OUT_CHAIN 2>/dev/null # ipset cleanup_ipset_ipt cleanup_tproxy_route } start_service() { config_load "$NAME" mkdir -p /var/log/ua3f chmod o+w /var/log/ua3f touch /var/log/ua3f/ua3f.log local enabled config_get_bool enabled "enabled" "enabled" "0" if [ "$enabled" -ne "1" ]; then return 1 fi LOG "Starting $NAME service..." local server_mode port bind ua log_level ua_regex partial_replace config_get server_mode "main" "server_mode" "SOCKS5" config_get port "main" "port" "1080" config_get bind "main" "bind" "127.0.0.1" config_get ua "main" "ua" "FFF" config_get ua_regex "main" "ua_regex" "" config_get_bool partial_replace "main" "partial_replace" 0 config_get log_level "main" "log_level" "info" SERVER_MODE="$(echo "$server_mode" | tr '[:lower:]' '[:upper:]')" SERVER_MODE="$server_mode" LOG "Server Mode: $SERVER_MODE" LOG "Port: $port" LOG "Bind: $bind" LOG "User-Agent: $ua" LOG "User-Agent Regex: $ua_regex" LOG "Log level: $log_level" LOG "Partial Replace: $partial_replace" set_ua3f_group LOG "Run as GID: $UA3F_GID, Group: $UA3F_GROUP" LOG "Skip GIDs: $SKIP_GIDS" LOG "UA3F_FWMARK: $UA3F_FWMARK" detect_backend || { LOG "No supported firewall backend found (nftables or iptables)" return 1 } LOG "Using firewall backend: $FW_BACKEND" # Always cleanup first (idempotent) if [ "$FW_BACKEND" = "nft" ]; then fw_revert_nft else fw_revert_ipt fi case "$SERVER_MODE" in SOCKS5) # No firewall interception ;; TPROXY) if [ "$FW_BACKEND" = "nft" ]; then try_modprobe nft_tproxy fw_setup_nft_tproxy_tcp || { LOG "fw_setup_nft_tproxy_tcp setup failed" return 1 } else try_modprobe xt_TPROXY fw_setup_ipt_tproxy_tcp || { LOG "fw_setup_ipt_tproxy_tcp setup failed" return 1 } fi ;; REDIRECT) if [ "$FW_BACKEND" = "nft" ]; then fw_setup_nft_redirect_tcp || { LOG "fw_setup_nft_redirect_tcp setup failed" return 1 } else fw_setup_ipt_redirect_tcp || { LOG "fw_setup_ipt_redirect_tcp setup failed" return 1 } fi ;; *) LOG "Unsupported server_mode: $SERVER_MODE" return 1 ;; esac # dump all fw rules for debug if [ "$log_level" = "debug" ]; then if [ "$FW_BACKEND" = "nft" ]; then LOG "nftables rules:" nft --handle list ruleset >>"$LOG_FILE" 2>&1 elif [ "$FW_BACKEND" = "ipt" ]; then LOG "iptables rules:" LOG "mangle table:" iptables -t mangle -L -v -n >>"$LOG_FILE" 2>&1 LOG "nat table:" iptables -t nat -L -v -n >>"$LOG_FILE" 2>&1 LOG "filter table:" iptables -t filter -L -v -n >>"$LOG_FILE" 2>&1 fi fi # dump route rules for debug if [ "$log_level" = "debug" ]; then LOG "ip rule list:" ip rule show >>"$LOG_FILE" 2>&1 fi procd_open_instance "$NAME" procd_set_param command "$PROG" procd_append_param command -m "$server_mode" procd_append_param command -p "$port" procd_append_param command -b "$bind" procd_append_param command -f "$ua" procd_append_param command -r "$ua_regex" procd_append_param command -l "$log_level" [ "$partial_replace" = "1" ] && procd_append_param command -s procd_set_param respawn procd_set_param stdout 1 procd_set_param stderr 1 procd_set_param limits nproc="unlimited" as="unlimited" memlock="unlimited" nofile="65535 65535" procd_set_param group $UA3F_GROUP LOG "$NAME service started" procd_close_instance } stop_service() { LOG "Stopping $NAME service..." fw_revert_ipt >/dev/null 2>&1 fw_revert_nft >/dev/null 2>&1 LOG "$NAME service stopped" } reload_service() { set_ua3f_group stop start } service_triggers() { procd_add_reload_trigger "$NAME" }