From 14c4fb484d99ba79c58d053922e2ce54ce49f6e3 Mon Sep 17 00:00:00 2001 From: saba Date: Wed, 10 Dec 2025 17:27:45 +0800 Subject: [PATCH] update. add customized byte style --- adapter/outbound/sudoku.go | 7 ++++++- docs/config.yaml | 7 ++++--- go.mod | 2 +- go.sum | 4 ++-- listener/config/sudoku.go | 1 + listener/inbound/sudoku.go | 2 ++ listener/inbound/sudoku_test.go | 24 ++++++++++++++++++++++++ listener/sudoku/server.go | 8 +++++++- transport/sudoku/handshake.go | 17 ++++++++++++++--- transport/sudoku/handshake_test.go | 19 +++++++++++++++++++ 10 files changed, 80 insertions(+), 11 deletions(-) diff --git a/adapter/outbound/sudoku.go b/adapter/outbound/sudoku.go index 128dbc98..f9313ca3 100644 --- a/adapter/outbound/sudoku.go +++ b/adapter/outbound/sudoku.go @@ -30,6 +30,7 @@ type SudokuOption struct { TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" EnablePureDownlink *bool `proxy:"enable-pure-downlink,omitempty"` HTTPMask bool `proxy:"http-mask,omitempty"` + CustomTable string `proxy:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv } // DialContext implements C.ProxyAdapter @@ -178,13 +179,17 @@ func NewSudoku(option SudokuOption) (*Sudoku, error) { ServerAddress: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), Key: option.Key, AEADMethod: defaultConf.AEADMethod, - Table: sudoku.NewTable(sudoku.ClientAEADSeed(option.Key), tableType), PaddingMin: paddingMin, PaddingMax: paddingMax, EnablePureDownlink: enablePureDownlink, HandshakeTimeoutSeconds: defaultConf.HandshakeTimeoutSeconds, DisableHTTPMask: !option.HTTPMask, } + table, err := sudoku.NewTableWithCustom(sudoku.ClientAEADSeed(option.Key), tableType, option.CustomTable) + if err != nil { + return nil, fmt.Errorf("build table failed: %w", err) + } + baseConf.Table = table if option.AEADMethod != "" { baseConf.AEADMethod = option.AEADMethod } diff --git a/docs/config.yaml b/docs/config.yaml index c9a62fdd..cb76035f 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -1048,8 +1048,9 @@ proxies: # socks5 padding-min: 2 # 最小填充字节数 padding-max: 7 # 最大填充字节数 table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 + # custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则无需配置`table-type` http-mask: true # 是否启用http掩码 - enable-pure-downlink: false # 是否启用混淆下行,false的情况下能在保证数据安全的前提下极大提升下行速度 + enable-pure-downlink: false # 是否启用混淆下行,false的情况下能在保证数据安全的前提下极大提升下行速度,与服务端端保持相同(如果此处为false,则要求aead不可为none) # anytls - name: anytls @@ -1589,8 +1590,9 @@ listeners: padding-min: 1 # 填充最小长度 padding-max: 15 # 填充最大长度,均不建议过大 table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 + # custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v handshake-timeout: 5 # optional - enable-pure-downlink: false # 是否启用混淆下行,false的情况下能在保证数据安全的前提下极大提升下行速度,与客户端保持相同 + enable-pure-downlink: false # 是否启用混淆下行,false的情况下能在保证数据安全的前提下极大提升下行速度,与客户端保持相同(如果此处为false,则要求aead不可为none) @@ -1742,4 +1744,3 @@ listeners: # alpn: # - h3 # max-udp-relay-packet-size: 1500 - diff --git a/go.mod b/go.mod index 1f5159d2..8fb7e51f 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20 - github.com/saba-futai/sudoku v0.0.2-b + github.com/saba-futai/sudoku v0.0.2-c github.com/sagernet/cors v1.2.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/samber/lo v1.52.0 diff --git a/go.sum b/go.sum index b787f888..08d1d002 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/saba-futai/sudoku v0.0.2-b h1:IbBjgZe1IzzD4xjaCSAdAy8ZNrwOusT14AwCYm77NwI= -github.com/saba-futai/sudoku v0.0.2-b/go.mod h1:Rvggsoprp7HQM7bMIZUd1M27bPj8THRsZdY1dGbIAvo= +github.com/saba-futai/sudoku v0.0.2-c h1:0CaoCKx4Br8UL97fnIxn8Y7rnQpflBza7kfaIrdg2rI= +github.com/saba-futai/sudoku v0.0.2-c/go.mod h1:Rvggsoprp7HQM7bMIZUd1M27bPj8THRsZdY1dGbIAvo= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= diff --git a/listener/config/sudoku.go b/listener/config/sudoku.go index b1ceed08..b49e0255 100644 --- a/listener/config/sudoku.go +++ b/listener/config/sudoku.go @@ -14,6 +14,7 @@ type SudokuServer struct { TableType string `json:"table-type,omitempty"` HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"` EnablePureDownlink *bool `json:"enable-pure-downlink,omitempty"` + CustomTable string `json:"custom-table,omitempty"` } func (s SudokuServer) String() string { diff --git a/listener/inbound/sudoku.go b/listener/inbound/sudoku.go index e7cde308..be69dd39 100644 --- a/listener/inbound/sudoku.go +++ b/listener/inbound/sudoku.go @@ -20,6 +20,7 @@ type SudokuOption struct { TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"` EnablePureDownlink *bool `inbound:"enable-pure-downlink,omitempty"` + CustomTable string `inbound:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv } func (o SudokuOption) Equal(config C.InboundConfig) bool { @@ -52,6 +53,7 @@ func NewSudoku(options *SudokuOption) (*Sudoku, error) { TableType: options.TableType, HandshakeTimeoutSecond: options.HandshakeTimeoutSecond, EnablePureDownlink: options.EnablePureDownlink, + CustomTable: options.CustomTable, } return &Sudoku{ diff --git a/listener/inbound/sudoku_test.go b/listener/inbound/sudoku_test.go index f826e664..c3fbcc6f 100644 --- a/listener/inbound/sudoku_test.go +++ b/listener/inbound/sudoku_test.go @@ -138,3 +138,27 @@ func TestInboundSudoku_PackedDownlink(t *testing.T) { testInboundSudoku(t, inboundOptions, outboundOptions) }) } + +func TestInboundSudoku_CustomTable(t *testing.T) { + key := "test_key_custom" + custom := "xpxvvpvv" + inboundOptions := inbound.SudokuOption{ + Key: key, + TableType: "prefer_entropy", + CustomTable: custom, + } + outboundOptions := outbound.SudokuOption{ + Key: key, + TableType: "prefer_entropy", + CustomTable: custom, + } + testInboundSudoku(t, inboundOptions, outboundOptions) + + t.Run("ed25519key", func(t *testing.T) { + inboundOptions := inboundOptions + outboundOptions := outboundOptions + inboundOptions.Key = sudokuPublicKey + outboundOptions.Key = sudokuPrivateKey + testInboundSudoku(t, inboundOptions, outboundOptions) + }) +} diff --git a/listener/sudoku/server.go b/listener/sudoku/server.go index a1aa9fd2..072f372b 100644 --- a/listener/sudoku/server.go +++ b/listener/sudoku/server.go @@ -152,6 +152,12 @@ func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition) enablePureDownlink = *config.EnablePureDownlink } + table, err := sudoku.NewTableWithCustom(config.Key, tableType, config.CustomTable) + if err != nil { + _ = l.Close() + return nil, err + } + handshakeTimeout := defaultConf.HandshakeTimeoutSeconds if config.HandshakeTimeoutSecond != nil { handshakeTimeout = *config.HandshakeTimeoutSecond @@ -160,7 +166,7 @@ func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition) protoConf := sudoku.ProtocolConfig{ Key: config.Key, AEADMethod: defaultConf.AEADMethod, - Table: sudoku.NewTable(config.Key, tableType), + Table: table, PaddingMin: paddingMin, PaddingMax: paddingMax, EnablePureDownlink: enablePureDownlink, diff --git a/transport/sudoku/handshake.go b/transport/sudoku/handshake.go index a4f85f29..d34fceb4 100644 --- a/transport/sudoku/handshake.go +++ b/transport/sudoku/handshake.go @@ -146,12 +146,23 @@ func buildHandshakePayload(key string) [16]byte { } func NewTable(key string, tableType string) *sudoku.Table { - start := time.Now() - table := sudoku.NewTable(key, tableType) - log.Infoln("[Sudoku] Tables initialized (%s) in %v", tableType, time.Since(start)) + table, err := NewTableWithCustom(key, tableType, "") + if err != nil { + panic(fmt.Sprintf("[Sudoku] failed to init tables: %v", err)) + } return table } +func NewTableWithCustom(key string, tableType string, customTable string) (*sudoku.Table, error) { + start := time.Now() + table, err := sudoku.NewTableWithCustom(key, tableType, customTable) + if err != nil { + return nil, err + } + log.Infoln("[Sudoku] Tables initialized (%s, custom=%v) in %v", tableType, customTable != "", time.Since(start)) + return table, nil +} + func ClientAEADSeed(key string) string { if recovered, err := crypto.RecoverPublicKey(key); err == nil { return crypto.EncodePoint(recovered) diff --git a/transport/sudoku/handshake_test.go b/transport/sudoku/handshake_test.go index cfe5c75e..5d9443df 100644 --- a/transport/sudoku/handshake_test.go +++ b/transport/sudoku/handshake_test.go @@ -228,3 +228,22 @@ func runPackedUoTSession(id int, cfg *apis.ProtocolConfig, errCh chan<- error) { return } } + +func TestCustomTableHandshake(t *testing.T) { + table, err := sudokuobfs.NewTableWithCustom("custom-seed", "prefer_entropy", "xpxvvpvv") + if err != nil { + t.Fatalf("build custom table: %v", err) + } + cfg := newPackedConfig(table) + errCh := make(chan error, 2) + + runPackedTCPSession(42, cfg, errCh) + runPackedUoTSession(43, cfg, errCh) + + close(errCh) + for err := range errCh { + if err != nil { + t.Fatalf("custom table handshake failed: %v", err) + } + } +}