mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-06 13:55:19 +03:00
Add OpenVPN, TrustTunnel, Sudoku, inbound managers. Fixes
This commit is contained in:
155
transport/sudoku/httpmask_tunnel.go
Normal file
155
transport/sudoku/httpmask_tunnel.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package sudoku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/transport/sudoku/obfs/httpmask"
|
||||
)
|
||||
|
||||
type HTTPMaskTunnelServer struct {
|
||||
cfg *ProtocolConfig
|
||||
ts *httpmask.TunnelServer
|
||||
}
|
||||
|
||||
func newHTTPMaskEarlyCodecConfig(cfg *ProtocolConfig, psk string) EarlyCodecConfig {
|
||||
return EarlyCodecConfig{
|
||||
PSK: psk,
|
||||
AEAD: cfg.AEADMethod,
|
||||
EnablePureDownlink: cfg.EnablePureDownlink,
|
||||
PaddingMin: cfg.PaddingMin,
|
||||
PaddingMax: cfg.PaddingMax,
|
||||
}
|
||||
}
|
||||
|
||||
func newClientHTTPMaskEarlyHandshake(cfg *ProtocolConfig) (*httpmask.ClientEarlyHandshake, error) {
|
||||
choice, err := pickClientTable(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewHTTPMaskClientEarlyHandshake(
|
||||
newHTTPMaskEarlyCodecConfig(cfg, ClientAEADSeed(cfg.Key)),
|
||||
choice.Table,
|
||||
choice.Hint,
|
||||
choice.HasHint,
|
||||
kipUserHashFromKey(cfg.Key),
|
||||
KIPFeatAll,
|
||||
)
|
||||
}
|
||||
|
||||
func NewHTTPMaskTunnelServer(cfg *ProtocolConfig) *HTTPMaskTunnelServer {
|
||||
return newHTTPMaskTunnelServer(cfg, false)
|
||||
}
|
||||
|
||||
func NewHTTPMaskTunnelServerWithFallback(cfg *ProtocolConfig) *HTTPMaskTunnelServer {
|
||||
return newHTTPMaskTunnelServer(cfg, true)
|
||||
}
|
||||
|
||||
func newHTTPMaskTunnelServer(cfg *ProtocolConfig, passThroughOnReject bool) *HTTPMaskTunnelServer {
|
||||
if cfg == nil {
|
||||
return &HTTPMaskTunnelServer{}
|
||||
}
|
||||
|
||||
var ts *httpmask.TunnelServer
|
||||
if !cfg.DisableHTTPMask {
|
||||
switch strings.ToLower(strings.TrimSpace(cfg.HTTPMaskMode)) {
|
||||
case "stream", "poll", "auto", "ws":
|
||||
ts = httpmask.NewTunnelServer(httpmask.TunnelServerOptions{
|
||||
Mode: cfg.HTTPMaskMode,
|
||||
PathRoot: cfg.HTTPMaskPathRoot,
|
||||
AuthKey: ServerAEADSeed(cfg.Key),
|
||||
EarlyHandshake: NewHTTPMaskServerEarlyHandshake(
|
||||
newHTTPMaskEarlyCodecConfig(cfg, ServerAEADSeed(cfg.Key)),
|
||||
cfg.tableCandidates(),
|
||||
globalHandshakeReplay.allow,
|
||||
),
|
||||
// When upstream fallback is enabled, preserve rejected HTTP requests for the caller.
|
||||
PassThroughOnReject: passThroughOnReject,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &HTTPMaskTunnelServer{cfg: cfg, ts: ts}
|
||||
}
|
||||
|
||||
// WrapConn inspects an accepted TCP connection and upgrades it to an HTTP tunnel stream when needed.
|
||||
//
|
||||
// Returns:
|
||||
// - done=true: this TCP connection has been fully handled (e.g., stream/poll control request), caller should return
|
||||
// - done=false: handshakeConn+cfg are ready for ServerHandshake
|
||||
func (s *HTTPMaskTunnelServer) WrapConn(rawConn net.Conn) (handshakeConn net.Conn, cfg *ProtocolConfig, done bool, err error) {
|
||||
if rawConn == nil {
|
||||
return nil, nil, true, fmt.Errorf("nil conn")
|
||||
}
|
||||
if s == nil {
|
||||
return rawConn, nil, false, nil
|
||||
}
|
||||
if s.ts == nil {
|
||||
return rawConn, s.cfg, false, nil
|
||||
}
|
||||
|
||||
res, c, err := s.ts.HandleConn(rawConn)
|
||||
if err != nil {
|
||||
return nil, nil, true, err
|
||||
}
|
||||
|
||||
switch res {
|
||||
case httpmask.HandleDone:
|
||||
return nil, nil, true, nil
|
||||
case httpmask.HandlePassThrough:
|
||||
return c, s.cfg, false, nil
|
||||
case httpmask.HandleStartTunnel:
|
||||
inner := *s.cfg
|
||||
inner.DisableHTTPMask = true
|
||||
// HTTPMask tunnel modes (stream/poll/auto/ws) add extra round trips before the first
|
||||
// handshake bytes can reach ServerHandshake, especially under high concurrency.
|
||||
// Bump the handshake timeout for tunneled conns to avoid flaky timeouts while keeping
|
||||
// the default strict for raw TCP handshakes.
|
||||
const minTunneledHandshakeTimeoutSeconds = 15
|
||||
if inner.HandshakeTimeoutSeconds <= 0 || inner.HandshakeTimeoutSeconds < minTunneledHandshakeTimeoutSeconds {
|
||||
inner.HandshakeTimeoutSeconds = minTunneledHandshakeTimeoutSeconds
|
||||
}
|
||||
return c, &inner, false, nil
|
||||
default:
|
||||
return nil, nil, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
type TunnelDialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// DialHTTPMaskTunnel dials a CDN-capable HTTP tunnel (stream/poll/auto/ws) and returns a stream carrying raw Sudoku bytes.
|
||||
func DialHTTPMaskTunnel(ctx context.Context, serverAddress string, cfg *ProtocolConfig, dial TunnelDialer, upgrade func(net.Conn) (net.Conn, error)) (net.Conn, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("config is required")
|
||||
}
|
||||
if cfg.DisableHTTPMask {
|
||||
return nil, fmt.Errorf("http mask is disabled")
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(cfg.HTTPMaskMode)) {
|
||||
case "stream", "poll", "auto", "ws":
|
||||
default:
|
||||
return nil, fmt.Errorf("http-mask-mode=%q does not use http tunnel", cfg.HTTPMaskMode)
|
||||
}
|
||||
var (
|
||||
earlyHandshake *httpmask.ClientEarlyHandshake
|
||||
err error
|
||||
)
|
||||
if upgrade != nil {
|
||||
earlyHandshake, err = newClientHTTPMaskEarlyHandshake(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return httpmask.DialTunnel(ctx, serverAddress, httpmask.TunnelDialOptions{
|
||||
Mode: cfg.HTTPMaskMode,
|
||||
HostOverride: cfg.HTTPMaskHost,
|
||||
PathRoot: cfg.HTTPMaskPathRoot,
|
||||
AuthKey: ClientAEADSeed(cfg.Key),
|
||||
EarlyHandshake: earlyHandshake,
|
||||
Upgrade: upgrade,
|
||||
Multiplex: cfg.HTTPMaskMultiplex,
|
||||
DialContext: dial,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user