From 65c3d3e4e26413a5da111722bc51340612cbc7a2 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Mon, 26 Jan 2026 09:08:14 +0800 Subject: [PATCH] chore: remove unreachable code in sudoku --- adapter/outbound/sudoku.go | 4 +- transport/sudoku/features_test.go | 72 --------- transport/sudoku/handshake.go | 26 +--- transport/sudoku/httpmask_strategy.go | 214 -------------------------- 4 files changed, 3 insertions(+), 313 deletions(-) delete mode 100644 transport/sudoku/httpmask_strategy.go diff --git a/adapter/outbound/sudoku.go b/adapter/outbound/sudoku.go index 0f962e1c..cdf7647d 100644 --- a/adapter/outbound/sudoku.go +++ b/adapter/outbound/sudoku.go @@ -265,7 +265,7 @@ func (s *Sudoku) dialAndHandshake(ctx context.Context, cfg *sudoku.ProtocolConfi } upgrade := func(raw net.Conn) (net.Conn, error) { - return sudoku.ClientHandshakeWithOptions(raw, &handshakeCfg, sudoku.ClientHandshakeOptions{}) + return sudoku.ClientHandshake(raw, &handshakeCfg) } var ( @@ -303,7 +303,7 @@ func (s *Sudoku) dialAndHandshake(ctx context.Context, cfg *sudoku.ProtocolConfi } if !handshakeDone { - c, err = sudoku.ClientHandshakeWithOptions(c, &handshakeCfg, sudoku.ClientHandshakeOptions{}) + c, err = sudoku.ClientHandshake(c, &handshakeCfg) if err != nil { return nil, err } diff --git a/transport/sudoku/features_test.go b/transport/sudoku/features_test.go index 470598ce..39ec37c7 100644 --- a/transport/sudoku/features_test.go +++ b/transport/sudoku/features_test.go @@ -40,78 +40,6 @@ func TestSudokuObfsWriter_ReducesWriteAllocs(t *testing.T) { } } -func TestHTTPMaskStrategy_WebSocketAndPost(t *testing.T) { - key := "mask-test-key" - target := "1.1.1.1:80" - table := sudokuobfs.NewTable("mask-seed", "prefer_ascii") - - base := DefaultConfig() - base.Key = key - base.AEADMethod = "chacha20-poly1305" - base.Table = table - base.PaddingMin = 0 - base.PaddingMax = 0 - base.EnablePureDownlink = true - base.HandshakeTimeoutSeconds = 5 - base.DisableHTTPMask = false - base.ServerAddress = "example.com:443" - - cases := []string{"post", "websocket"} - for _, strategy := range cases { - t.Run(strategy, func(t *testing.T) { - serverConn, clientConn := net.Pipe() - defer serverConn.Close() - defer clientConn.Close() - - errCh := make(chan error, 1) - go func() { - defer close(errCh) - session, err := ServerHandshake(serverConn, base) - if err != nil { - errCh <- err - return - } - defer session.Conn.Close() - if session.Type != SessionTypeTCP { - errCh <- io.ErrUnexpectedEOF - return - } - if session.Target != target { - errCh <- io.ErrClosedPipe - return - } - _, _ = session.Conn.Write([]byte("ok")) - }() - - cConn, err := ClientHandshakeWithOptions(clientConn, base, ClientHandshakeOptions{HTTPMaskStrategy: strategy}) - if err != nil { - t.Fatalf("client handshake: %v", err) - } - defer cConn.Close() - - addrBuf, err := EncodeAddress(target) - if err != nil { - t.Fatalf("encode addr: %v", err) - } - if _, err := cConn.Write(addrBuf); err != nil { - t.Fatalf("write addr: %v", err) - } - - buf := make([]byte, 2) - if _, err := io.ReadFull(cConn, buf); err != nil { - t.Fatalf("read: %v", err) - } - if string(buf) != "ok" { - t.Fatalf("unexpected payload: %q", buf) - } - - if err := <-errCh; err != nil { - t.Fatalf("server: %v", err) - } - }) - } -} - func TestCustomTablesRotation_ProbedByServer(t *testing.T) { key := "rotate-test-key" target := "8.8.8.8:53" diff --git a/transport/sudoku/handshake.go b/transport/sudoku/handshake.go index 3e75ac3c..2b6c73dd 100644 --- a/transport/sudoku/handshake.go +++ b/transport/sudoku/handshake.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "net" - "strings" "time" "github.com/metacubex/mihomo/transport/sudoku/crypto" @@ -192,19 +191,8 @@ func ClientAEADSeed(key string) string { return key } -type ClientHandshakeOptions struct { - // HTTPMaskStrategy controls how the client generates the HTTP mask header when DisableHTTPMask=false. - // Supported: ""/"random" (default), "post", "websocket". - HTTPMaskStrategy string -} - // ClientHandshake performs the client-side Sudoku handshake (without sending target address). func ClientHandshake(rawConn net.Conn, cfg *ProtocolConfig) (net.Conn, error) { - return ClientHandshakeWithOptions(rawConn, cfg, ClientHandshakeOptions{}) -} - -// ClientHandshakeWithOptions performs the client-side Sudoku handshake (without sending target address). -func ClientHandshakeWithOptions(rawConn net.Conn, cfg *ProtocolConfig, opt ClientHandshakeOptions) (net.Conn, error) { if cfg == nil { return nil, fmt.Errorf("config is required") } @@ -213,7 +201,7 @@ func ClientHandshakeWithOptions(rawConn net.Conn, cfg *ProtocolConfig, opt Clien } if !cfg.DisableHTTPMask { - if err := WriteHTTPMaskHeader(rawConn, cfg.ServerAddress, cfg.HTTPMaskPathRoot, opt.HTTPMaskStrategy); err != nil { + if err := httpmask.WriteRandomRequestHeaderWithPathRoot(rawConn, cfg.ServerAddress, cfg.HTTPMaskPathRoot); err != nil { return nil, fmt.Errorf("write http mask failed: %w", err) } } @@ -363,18 +351,6 @@ func GenKeyPair() (privateKey, publicKey string, err error) { return } -func normalizeHTTPMaskStrategy(strategy string) string { - s := strings.TrimSpace(strings.ToLower(strategy)) - switch s { - case "", "random": - return "random" - case "ws": - return "websocket" - default: - return s - } -} - func userHashFromHandshake(handshakeBuf []byte) string { if len(handshakeBuf) < 16 { return "" diff --git a/transport/sudoku/httpmask_strategy.go b/transport/sudoku/httpmask_strategy.go deleted file mode 100644 index 5b98bbd0..00000000 --- a/transport/sudoku/httpmask_strategy.go +++ /dev/null @@ -1,214 +0,0 @@ -package sudoku - -import ( - "encoding/base64" - "fmt" - "io" - "math/rand" - "net" - "strconv" - "strings" - "sync" - "time" - - "github.com/metacubex/mihomo/transport/sudoku/obfs/httpmask" -) - -var ( - httpMaskUserAgents = []string{ - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", - } - httpMaskAccepts = []string{ - "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", - "application/json, text/plain, */*", - "application/octet-stream", - "*/*", - } - httpMaskAcceptLanguages = []string{ - "en-US,en;q=0.9", - "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", - } - httpMaskAcceptEncodings = []string{ - "gzip, deflate, br", - "gzip, deflate", - } - httpMaskPaths = []string{ - "/api/v1/upload", - "/data/sync", - "/v1/telemetry", - "/session", - "/ws", - } - httpMaskContentTypes = []string{ - "application/octet-stream", - "application/json", - } -) - -var ( - httpMaskRngPool = sync.Pool{ - New: func() any { return rand.New(rand.NewSource(time.Now().UnixNano())) }, - } - httpMaskBufPool = sync.Pool{ - New: func() any { - b := make([]byte, 0, 1024) - return &b - }, - } -) - -func trimPortForHost(host string) string { - if host == "" { - return host - } - h, _, err := net.SplitHostPort(host) - if err == nil && h != "" { - return h - } - return host -} - -func appendCommonHeaders(buf []byte, host string, r *rand.Rand) []byte { - ua := httpMaskUserAgents[r.Intn(len(httpMaskUserAgents))] - accept := httpMaskAccepts[r.Intn(len(httpMaskAccepts))] - lang := httpMaskAcceptLanguages[r.Intn(len(httpMaskAcceptLanguages))] - enc := httpMaskAcceptEncodings[r.Intn(len(httpMaskAcceptEncodings))] - - buf = append(buf, "Host: "...) - buf = append(buf, host...) - buf = append(buf, "\r\nUser-Agent: "...) - buf = append(buf, ua...) - buf = append(buf, "\r\nAccept: "...) - buf = append(buf, accept...) - buf = append(buf, "\r\nAccept-Language: "...) - buf = append(buf, lang...) - buf = append(buf, "\r\nAccept-Encoding: "...) - buf = append(buf, enc...) - buf = append(buf, "\r\nConnection: keep-alive\r\n"...) - buf = append(buf, "Cache-Control: no-cache\r\nPragma: no-cache\r\n"...) - return buf -} - -// WriteHTTPMaskHeader writes an HTTP/1.x request header as a mask, according to strategy. -// Supported strategies: ""/"random", "post", "websocket". -func WriteHTTPMaskHeader(w io.Writer, host string, pathRoot string, strategy string) error { - switch normalizeHTTPMaskStrategy(strategy) { - case "random": - return httpmask.WriteRandomRequestHeaderWithPathRoot(w, host, pathRoot) - case "post": - return writeHTTPMaskPOST(w, host, pathRoot) - case "websocket": - return writeHTTPMaskWebSocket(w, host, pathRoot) - default: - return fmt.Errorf("unsupported http-mask-strategy: %s", strategy) - } -} - -func writeHTTPMaskPOST(w io.Writer, host string, pathRoot string) error { - r := httpMaskRngPool.Get().(*rand.Rand) - defer httpMaskRngPool.Put(r) - - path := joinPathRoot(pathRoot, httpMaskPaths[r.Intn(len(httpMaskPaths))]) - ctype := httpMaskContentTypes[r.Intn(len(httpMaskContentTypes))] - - bufPtr := httpMaskBufPool.Get().(*[]byte) - buf := *bufPtr - buf = buf[:0] - defer func() { - if cap(buf) <= 4096 { - *bufPtr = buf - httpMaskBufPool.Put(bufPtr) - } - }() - - const minCL = int64(4 * 1024) - const maxCL = int64(10 * 1024 * 1024) - contentLength := minCL + r.Int63n(maxCL-minCL+1) - - buf = append(buf, "POST "...) - buf = append(buf, path...) - buf = append(buf, " HTTP/1.1\r\n"...) - buf = appendCommonHeaders(buf, host, r) - buf = append(buf, "Content-Type: "...) - buf = append(buf, ctype...) - buf = append(buf, "\r\nContent-Length: "...) - buf = strconv.AppendInt(buf, contentLength, 10) - buf = append(buf, "\r\n\r\n"...) - - _, err := w.Write(buf) - return err -} - -func writeHTTPMaskWebSocket(w io.Writer, host string, pathRoot string) error { - r := httpMaskRngPool.Get().(*rand.Rand) - defer httpMaskRngPool.Put(r) - - path := joinPathRoot(pathRoot, httpMaskPaths[r.Intn(len(httpMaskPaths))]) - - bufPtr := httpMaskBufPool.Get().(*[]byte) - buf := *bufPtr - buf = buf[:0] - defer func() { - if cap(buf) <= 4096 { - *bufPtr = buf - httpMaskBufPool.Put(bufPtr) - } - }() - - hostNoPort := trimPortForHost(host) - var keyBytes [16]byte - for i := 0; i < len(keyBytes); i++ { - keyBytes[i] = byte(r.Intn(256)) - } - var wsKey [24]byte - base64.StdEncoding.Encode(wsKey[:], keyBytes[:]) - - buf = append(buf, "GET "...) - buf = append(buf, path...) - buf = append(buf, " HTTP/1.1\r\n"...) - buf = appendCommonHeaders(buf, host, r) - buf = append(buf, "Upgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: "...) - buf = append(buf, wsKey[:]...) - buf = append(buf, "\r\nOrigin: https://"...) - buf = append(buf, hostNoPort...) - buf = append(buf, "\r\n\r\n"...) - - _, err := w.Write(buf) - return err -} - -func normalizePathRoot(root string) string { - root = strings.TrimSpace(root) - root = strings.Trim(root, "/") - if root == "" { - return "" - } - for i := 0; i < len(root); i++ { - c := root[i] - switch { - case c >= 'a' && c <= 'z': - case c >= 'A' && c <= 'Z': - case c >= '0' && c <= '9': - case c == '_' || c == '-': - default: - return "" - } - } - return "/" + root -} - -func joinPathRoot(root, path string) string { - root = normalizePathRoot(root) - if root == "" { - return path - } - if path == "" { - return root - } - if !strings.HasPrefix(path, "/") { - path = "/" + path - } - return root + path -}