chore: remove unreachable code in sudoku

This commit is contained in:
wwqgtxx
2026-01-26 09:08:14 +08:00
parent 98b3060558
commit 65c3d3e4e2
4 changed files with 3 additions and 313 deletions

View File

@@ -265,7 +265,7 @@ func (s *Sudoku) dialAndHandshake(ctx context.Context, cfg *sudoku.ProtocolConfi
} }
upgrade := func(raw net.Conn) (net.Conn, error) { upgrade := func(raw net.Conn) (net.Conn, error) {
return sudoku.ClientHandshakeWithOptions(raw, &handshakeCfg, sudoku.ClientHandshakeOptions{}) return sudoku.ClientHandshake(raw, &handshakeCfg)
} }
var ( var (
@@ -303,7 +303,7 @@ func (s *Sudoku) dialAndHandshake(ctx context.Context, cfg *sudoku.ProtocolConfi
} }
if !handshakeDone { if !handshakeDone {
c, err = sudoku.ClientHandshakeWithOptions(c, &handshakeCfg, sudoku.ClientHandshakeOptions{}) c, err = sudoku.ClientHandshake(c, &handshakeCfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -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) { func TestCustomTablesRotation_ProbedByServer(t *testing.T) {
key := "rotate-test-key" key := "rotate-test-key"
target := "8.8.8.8:53" target := "8.8.8.8:53"

View File

@@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strings"
"time" "time"
"github.com/metacubex/mihomo/transport/sudoku/crypto" "github.com/metacubex/mihomo/transport/sudoku/crypto"
@@ -192,19 +191,8 @@ func ClientAEADSeed(key string) string {
return key 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). // ClientHandshake performs the client-side Sudoku handshake (without sending target address).
func ClientHandshake(rawConn net.Conn, cfg *ProtocolConfig) (net.Conn, error) { 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 { if cfg == nil {
return nil, fmt.Errorf("config is required") return nil, fmt.Errorf("config is required")
} }
@@ -213,7 +201,7 @@ func ClientHandshakeWithOptions(rawConn net.Conn, cfg *ProtocolConfig, opt Clien
} }
if !cfg.DisableHTTPMask { 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) return nil, fmt.Errorf("write http mask failed: %w", err)
} }
} }
@@ -363,18 +351,6 @@ func GenKeyPair() (privateKey, publicKey string, err error) {
return 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 { func userHashFromHandshake(handshakeBuf []byte) string {
if len(handshakeBuf) < 16 { if len(handshakeBuf) < 16 {
return "" return ""

View File

@@ -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
}