From bb92b209ef0b375e7a0c2513dd038b3691b5a936 Mon Sep 17 00:00:00 2001 From: SunBK201 Date: Wed, 5 Nov 2025 16:28:44 +0800 Subject: [PATCH] chore: bump version to 1.3.0 --- build.sh | 2 +- ipkg/CONTROL/control | 2 +- ipkg/CONTROL/control-e | 2 +- openwrt/Makefile | 2 +- openwrt/files/luci/cbi.lua | 2 +- openwrt/files/ua3f.init | 114 +++++++++++++++++++++----------- src/internal/sniff/http.go | 78 +++++++++++++++++----- src/internal/sniff/http_test.go | 50 ++++++++++++++ src/main.go | 2 +- 9 files changed, 194 insertions(+), 60 deletions(-) create mode 100644 src/internal/sniff/http_test.go diff --git a/build.sh b/build.sh index 68e6e88..a8b343c 100755 --- a/build.sh +++ b/build.sh @@ -8,7 +8,7 @@ set -e project_name="ua3f" -release_version="1.2.1" +release_version="1.3.0" target=main.go dist=./dist release_dir=./bin diff --git a/ipkg/CONTROL/control b/ipkg/CONTROL/control index 1416ca0..e98524f 100644 --- a/ipkg/CONTROL/control +++ b/ipkg/CONTROL/control @@ -1,5 +1,5 @@ Package: ua3f -Version: 1.2.1-1 +Version: 1.3.0-1 Depends: luci-compat, ipset, iptables, iptables-mod-tproxy, iptables-mod-extra, iptables-mod-nat-extra, kmod-ipt-conntrack, iptables-mod-ipopt Source: /feed/openwrt SourceName: UA3F diff --git a/ipkg/CONTROL/control-e b/ipkg/CONTROL/control-e index 1416ca0..e98524f 100644 --- a/ipkg/CONTROL/control-e +++ b/ipkg/CONTROL/control-e @@ -1,5 +1,5 @@ Package: ua3f -Version: 1.2.1-1 +Version: 1.3.0-1 Depends: luci-compat, ipset, iptables, iptables-mod-tproxy, iptables-mod-extra, iptables-mod-nat-extra, kmod-ipt-conntrack, iptables-mod-ipopt Source: /feed/openwrt SourceName: UA3F diff --git a/openwrt/Makefile b/openwrt/Makefile index 1d80694..45d0b07 100644 --- a/openwrt/Makefile +++ b/openwrt/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=UA3F -PKG_VERSION:=1.2.1 +PKG_VERSION:=1.3.0 PKG_RELEASE:=1 # PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz diff --git a/openwrt/files/luci/cbi.lua b/openwrt/files/luci/cbi.lua index 1ac7bef..ae7dd33 100644 --- a/openwrt/files/luci/cbi.lua +++ b/openwrt/files/luci/cbi.lua @@ -3,7 +3,7 @@ local uci = require("luci.model.uci").cursor() ua3f = Map("ua3f", "UA3F", [[ - Version: 1.2.1 + Version: 1.3.0
Across the Campus we can reach every corner in the world. ]] diff --git a/openwrt/files/ua3f.init b/openwrt/files/ua3f.init index b73fbe9..e9e9c95 100755 --- a/openwrt/files/ua3f.init +++ b/openwrt/files/ua3f.init @@ -28,6 +28,8 @@ FAKEIP_RANGE="198.18.0.0/16, 198.18.0.1/15, 28.0.0.1/8" SKIP_PORTS="22,51080,51090" SET_TTL="0" +server_mode="SOCKS5" + LOG_FILE="/var/log/ua3f/ua3f.log" LOG() { @@ -76,6 +78,11 @@ shellclash_running() { } set_ua3f_group() { + if [ $server_mode = "REDIRECT" ]; then + UA3F_GID="0" + UA3F_GROUP="root" + return + fi add_skip_gids "453" if openclash_running; then UA3F_GID="65534" @@ -167,6 +174,8 @@ cleanup_tproxy_route() { nft_drop_table() { nft delete table ip "$NFT_TABLE" 2>/dev/null nft delete table inet "$UA3F_TTL_TABLE" 2>/dev/null + # nft delete table inet "$NFT_TABLE" 2>/dev/null + # nft delete chain inet fw4 "$NFT_TABLE" 2>/dev/null } nft_reinit_table() { @@ -217,32 +226,50 @@ fw_setup_nft_tproxy_tcp() { nft add rule ip $NFT_TABLE output meta l4proto tcp mark set $UA3F_FWMARK counter accept comment '"default tproxy mark. bypass sc pre pollution"' } +nft_reinit_table_redirect() { + nft_drop_table + nft add chain inet fw4 $NFT_TABLE '{ type nat hook prerouting priority dstnat - 1; }' || return 1 + + nft "add set inet fw4 $UA3F_LANSET { type ipv4_addr; flags interval; auto-merge; }" || return 1 + nft "add element inet fw4 $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 }" || return 1 +} + 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 chain ip $NFT_TABLE prerouting '{ type nat hook prerouting priority dstnat - 20; }' + nft add rule ip $NFT_TABLE prerouting iifname != "br-lan" counter return 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 mark {$UA3F_SOMARK} counter return 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 + # 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"' + # 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"' } setup_ipset_ipt() { @@ -315,34 +342,47 @@ fw_setup_ipt_redirect_tcp() { 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 -I PREROUTING -p tcp -j $UA3F_CHAIN + iptables -t nat -A $UA3F_CHAIN ! -i "br-lan" -j RETURN 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 + # 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 + # 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() { @@ -415,7 +455,7 @@ start_service() { LOG "Starting $NAME service..." - local server_mode port bind ua log_level ua_regex partial_replace set_ttl + local port bind ua log_level ua_regex partial_replace set_ttl config_get server_mode "main" "server_mode" "SOCKS5" config_get port "main" "port" "1080" config_get bind "main" "bind" "127.0.0.1" diff --git a/src/internal/sniff/http.go b/src/internal/sniff/http.go index ee611b2..528ee09 100644 --- a/src/internal/sniff/http.go +++ b/src/internal/sniff/http.go @@ -6,7 +6,67 @@ import ( ) // HTTP methods used to detect HTTP by request line. -var methods = [...]string{"GET", "POST", "HEAD", "CONNECT", "PUT", "DELETE", "OPTIONS", "PATCH", "TRACE"} +var methodBytes = [...][]byte{ + []byte("GET"), + []byte("POST"), + []byte("HEAD"), + []byte("CONNECT"), + []byte("PUT"), + []byte("DELETE"), + []byte("OPTIONS"), + []byte("PATCH"), + []byte("TRACE"), +} + +const maxMethodLen = 7 + +type Node struct { + next map[byte]*Node + end bool +} + +var root *Node + +func init() { + root = &Node{next: make(map[byte]*Node)} + for _, m := range methodBytes { + node := root + for _, c := range m { + if node.next[c] == nil { + node.next[c] = &Node{next: make(map[byte]*Node)} + } + node = node.next[c] + } + node.end = true + } +} + +// beginWithHTTPMethod peeks the first few bytes to check for known HTTP method prefixes. +func beginWithHTTPMethod(reader *bufio.Reader) (bool, error) { + node := root + var prevLen int + + for n := 3; n <= maxMethodLen; n++ { + buf, err := reader.Peek(n) + if err != nil { + return false, err + } + for i := prevLen; i < len(buf); i++ { + c := buf[i] + next, ok := node.next[c] + if !ok { + return false, nil + } + node = next + if node.end { + return true, nil + } + } + prevLen = len(buf) + } + + return false, nil +} // parseRequestLine parses "GET /foo HTTP/1.1" into its three parts. func parseRequestLine(line string) (method, requestURI, proto string, ok bool) { @@ -18,22 +78,6 @@ func parseRequestLine(line string) (method, requestURI, proto string, ok bool) { return method, requestURI, proto, true } -// beginWithHTTPMethod peeks the first few bytes to check for known HTTP method prefixes. -func beginWithHTTPMethod(reader *bufio.Reader) (bool, error) { - const maxMethodLen = 7 - var hint []byte - hint, err := reader.Peek(maxMethodLen) - if err != nil { - } - method, _, _ := strings.Cut(string(hint), " ") - for _, m := range methods { - if method == m { - return true, nil - } - } - return false, nil -} - // SniffHTTP peeks the first few bytes and checks for a known HTTP method prefix. func SniffHTTP(reader *bufio.Reader) (bool, error) { // Fast check: peek first word to see if it's a known HTTP method diff --git a/src/internal/sniff/http_test.go b/src/internal/sniff/http_test.go new file mode 100644 index 0000000..b750db2 --- /dev/null +++ b/src/internal/sniff/http_test.go @@ -0,0 +1,50 @@ +package sniff + +import ( + "bufio" + "bytes" + "errors" + "io" + "testing" +) + +func TestBeginWithHTTPMethod(t *testing.T) { + tests := []struct { + name string + input string + wantMatch bool + wantErr bool + }{ + {"GET method", "GET /index.html HTTP/1.1\r\n", true, false}, + {"POST method", "POST /submit HTTP/1.1\r\n", true, false}, + {"HEAD method", "HEAD /abc HTTP/1.0\r\n", true, false}, + {"PUT method", "PUT /resource HTTP/1.1\r\n", true, false}, + {"PATCH method", "PATCH /item HTTP/1.1\r\n", true, false}, + {"OPTIONS method", "OPTIONS * HTTP/1.1\r\n", true, false}, + {"TRACE method", "TRACE / HTTP/1.1\r\n", true, false}, + {"CONNECT method", "CONNECT example.com:443 HTTP/1.1\r\n", true, false}, + {"DELETE method", "DELETE /resource HTTP/1.1\r\n", true, false}, + {"lowercase method", "get /index.html HTTP/1.1\r\n", false, false}, + {"non-http prefix", "HELLO WORLD", false, false}, + {"empty input", "", false, true}, // peek 会返回错误 + {"short incomplete input", "GE", false, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := bufio.NewReader(bytes.NewBufferString(tt.input)) + got, err := beginWithHTTPMethod(reader) + + if (err != nil) != tt.wantErr { + // differentiate between EOF (expected) and other errors sometimes + if !tt.wantErr || !errors.Is(err, io.EOF) { + t.Fatalf("unexpected error: %v", err) + } + } + + if got != tt.wantMatch { + t.Errorf("beginWithHTTPMethod(%q) = %v, want %v", tt.input, got, tt.wantMatch) + } + }) + } +} diff --git a/src/main.go b/src/main.go index a46dffa..4fe4fef 100644 --- a/src/main.go +++ b/src/main.go @@ -11,7 +11,7 @@ import ( "github.com/sunbk201/ua3f/internal/server" ) -const version = "1.2.1" +const version = "1.3.0" func main() { cfg, showVer := config.Parse()