mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-05 13:25:17 +03:00
Add OpenVPN, TrustTunnel, Sudoku, inbound managers. Fixes
This commit is contained in:
164
transport/sudoku/table_probe.go
Normal file
164
transport/sudoku/table_probe.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package sudoku
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/transport/sudoku/crypto"
|
||||
"github.com/sagernet/sing-box/transport/sudoku/obfs/sudoku"
|
||||
)
|
||||
|
||||
type clientTableChoice struct {
|
||||
Table *sudoku.Table
|
||||
Hint uint32
|
||||
HasHint bool
|
||||
}
|
||||
|
||||
func pickClientTable(cfg *ProtocolConfig) (clientTableChoice, error) {
|
||||
candidates := cfg.tableCandidates()
|
||||
if len(candidates) == 0 {
|
||||
return clientTableChoice{}, fmt.Errorf("no table configured")
|
||||
}
|
||||
if len(candidates) == 1 {
|
||||
return clientTableChoice{Table: candidates[0], Hint: candidates[0].Hint()}, nil
|
||||
}
|
||||
var b [1]byte
|
||||
if _, err := crand.Read(b[:]); err != nil {
|
||||
return clientTableChoice{}, fmt.Errorf("random table pick failed: %w", err)
|
||||
}
|
||||
idx := int(b[0]) % len(candidates)
|
||||
return clientTableChoice{Table: candidates[idx], Hint: candidates[idx].Hint(), HasHint: true}, nil
|
||||
}
|
||||
|
||||
type readOnlyConn struct {
|
||||
*bytes.Reader
|
||||
}
|
||||
|
||||
func (c *readOnlyConn) Write([]byte) (int, error) { return 0, io.ErrClosedPipe }
|
||||
func (c *readOnlyConn) Close() error { return nil }
|
||||
func (c *readOnlyConn) LocalAddr() net.Addr { return nil }
|
||||
func (c *readOnlyConn) RemoteAddr() net.Addr { return nil }
|
||||
func (c *readOnlyConn) SetDeadline(time.Time) error { return nil }
|
||||
func (c *readOnlyConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (c *readOnlyConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
|
||||
func drainBuffered(r *bufio.Reader) ([]byte, error) {
|
||||
n := r.Buffered()
|
||||
if n <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
out := make([]byte, n)
|
||||
_, err := io.ReadFull(r, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func probeHandshakeBytes(probe []byte, cfg *ProtocolConfig, table *sudoku.Table) error {
|
||||
rc := &readOnlyConn{Reader: bytes.NewReader(probe)}
|
||||
_, obfsConn := buildServerObfsConn(rc, cfg, table, false)
|
||||
seed := ServerAEADSeed(cfg.Key)
|
||||
pskC2S, pskS2C := derivePSKDirectionalBases(seed)
|
||||
// Server side: recv is client->server, send is server->client.
|
||||
cConn, err := crypto.NewRecordConn(obfsConn, cfg.AEADMethod, pskS2C, pskC2S)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg, err := ReadKIPMessage(cConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.Type != KIPTypeClientHello {
|
||||
return fmt.Errorf("unexpected handshake message: %d", msg.Type)
|
||||
}
|
||||
ch, err := DecodeKIPClientHelloPayload(msg.Payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if absInt64(time.Now().Unix()-ch.Timestamp.Unix()) > int64(kipHandshakeSkew.Seconds()) {
|
||||
return fmt.Errorf("time skew/replay")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func selectTableByProbe(r *bufio.Reader, cfg *ProtocolConfig, tables []*sudoku.Table) (*sudoku.Table, []byte, error) {
|
||||
const (
|
||||
maxProbeBytes = 64 * 1024
|
||||
readChunk = 4 * 1024
|
||||
)
|
||||
if len(tables) == 0 {
|
||||
return nil, nil, fmt.Errorf("no table candidates")
|
||||
}
|
||||
if len(tables) > 255 {
|
||||
return nil, nil, fmt.Errorf("too many table candidates: %d", len(tables))
|
||||
}
|
||||
|
||||
// Copy so we can prune candidates without mutating the caller slice.
|
||||
candidates := make([]*sudoku.Table, 0, len(tables))
|
||||
for _, t := range tables {
|
||||
if t != nil {
|
||||
candidates = append(candidates, t)
|
||||
}
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return nil, nil, fmt.Errorf("no table candidates")
|
||||
}
|
||||
|
||||
probe, err := drainBuffered(r)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err)
|
||||
}
|
||||
|
||||
tmp := make([]byte, readChunk)
|
||||
for {
|
||||
if len(candidates) == 1 {
|
||||
tail, err := drainBuffered(r)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err)
|
||||
}
|
||||
probe = append(probe, tail...)
|
||||
return candidates[0], probe, nil
|
||||
}
|
||||
|
||||
needMore := false
|
||||
next := candidates[:0]
|
||||
for _, table := range candidates {
|
||||
err := probeHandshakeBytes(probe, cfg, table)
|
||||
if err == nil {
|
||||
tail, err := drainBuffered(r)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err)
|
||||
}
|
||||
probe = append(probe, tail...)
|
||||
return table, probe, nil
|
||||
}
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
needMore = true
|
||||
next = append(next, table)
|
||||
}
|
||||
// Definitive mismatch: drop table.
|
||||
}
|
||||
candidates = next
|
||||
|
||||
if len(candidates) == 0 || !needMore {
|
||||
return nil, probe, fmt.Errorf("handshake table selection failed")
|
||||
}
|
||||
if len(probe) >= maxProbeBytes {
|
||||
return nil, probe, fmt.Errorf("handshake probe exceeded %d bytes", maxProbeBytes)
|
||||
}
|
||||
|
||||
n, err := r.Read(tmp)
|
||||
if n > 0 {
|
||||
probe = append(probe, tmp[:n]...)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, probe, fmt.Errorf("handshake probe read failed: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user