mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-03-04 04:47:30 +00:00
158 lines
4.4 KiB
Go
158 lines
4.4 KiB
Go
package sudoku
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/metacubex/mihomo/transport/sudoku/obfs/httpmask"
|
|
)
|
|
|
|
type HTTPMaskTunnelServer struct {
|
|
cfg *ProtocolConfig
|
|
ts *httpmask.TunnelServer
|
|
}
|
|
|
|
func NewHTTPMaskTunnelServer(cfg *ProtocolConfig) *HTTPMaskTunnelServer {
|
|
if cfg == nil {
|
|
return &HTTPMaskTunnelServer{}
|
|
}
|
|
|
|
var ts *httpmask.TunnelServer
|
|
if !cfg.DisableHTTPMask {
|
|
switch strings.ToLower(strings.TrimSpace(cfg.HTTPMaskMode)) {
|
|
case "stream", "poll", "auto":
|
|
ts = httpmask.NewTunnelServer(httpmask.TunnelServerOptions{
|
|
Mode: cfg.HTTPMaskMode,
|
|
PathRoot: cfg.HTTPMaskPathRoot,
|
|
AuthKey: ClientAEADSeed(cfg.Key),
|
|
})
|
|
}
|
|
}
|
|
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
|
|
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) 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":
|
|
default:
|
|
return nil, fmt.Errorf("http-mask-mode=%q does not use http tunnel", cfg.HTTPMaskMode)
|
|
}
|
|
return httpmask.DialTunnel(ctx, serverAddress, httpmask.TunnelDialOptions{
|
|
Mode: cfg.HTTPMaskMode,
|
|
TLSEnabled: cfg.HTTPMaskTLSEnabled,
|
|
HostOverride: cfg.HTTPMaskHost,
|
|
PathRoot: cfg.HTTPMaskPathRoot,
|
|
AuthKey: ClientAEADSeed(cfg.Key),
|
|
Upgrade: upgrade,
|
|
Multiplex: cfg.HTTPMaskMultiplex,
|
|
DialContext: dial,
|
|
})
|
|
}
|
|
|
|
type HTTPMaskTunnelClient struct {
|
|
mode string
|
|
pathRoot string
|
|
authKey string
|
|
client *httpmask.TunnelClient
|
|
}
|
|
|
|
func NewHTTPMaskTunnelClient(serverAddress string, cfg *ProtocolConfig, dial TunnelDialer) (*HTTPMaskTunnelClient, 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":
|
|
default:
|
|
return nil, fmt.Errorf("http-mask-mode=%q does not use http tunnel", cfg.HTTPMaskMode)
|
|
}
|
|
switch strings.ToLower(strings.TrimSpace(cfg.HTTPMaskMultiplex)) {
|
|
case "auto", "on":
|
|
default:
|
|
return nil, fmt.Errorf("http-mask-multiplex=%q does not enable reuse", cfg.HTTPMaskMultiplex)
|
|
}
|
|
|
|
c, err := httpmask.NewTunnelClient(serverAddress, httpmask.TunnelClientOptions{
|
|
TLSEnabled: cfg.HTTPMaskTLSEnabled,
|
|
HostOverride: cfg.HTTPMaskHost,
|
|
DialContext: dial,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &HTTPMaskTunnelClient{
|
|
mode: cfg.HTTPMaskMode,
|
|
pathRoot: cfg.HTTPMaskPathRoot,
|
|
authKey: ClientAEADSeed(cfg.Key),
|
|
client: c,
|
|
}, nil
|
|
}
|
|
|
|
func (c *HTTPMaskTunnelClient) Dial(ctx context.Context, upgrade func(net.Conn) (net.Conn, error)) (net.Conn, error) {
|
|
if c == nil || c.client == nil {
|
|
return nil, fmt.Errorf("nil httpmask tunnel client")
|
|
}
|
|
return c.client.DialTunnel(ctx, httpmask.TunnelDialOptions{
|
|
Mode: c.mode,
|
|
PathRoot: c.pathRoot,
|
|
AuthKey: c.authKey,
|
|
Upgrade: upgrade,
|
|
})
|
|
}
|
|
|
|
func (c *HTTPMaskTunnelClient) CloseIdleConnections() {
|
|
if c == nil || c.client == nil {
|
|
return
|
|
}
|
|
c.client.CloseIdleConnections()
|
|
}
|