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()