mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-05 18:57:30 +03:00
Add Mieru inbound, refactor sudoku. Fixes
This commit is contained in:
293
transport/sudoku/client.go
Normal file
293
transport/sudoku/client.go
Normal file
@@ -0,0 +1,293 @@
|
||||
package sudoku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/transport/sudoku/obfs/httpmask"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type ClientOptions struct {
|
||||
Dialer N.Dialer
|
||||
TLSConfig tls.Config
|
||||
Config ProtocolConfig
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
dialer N.Dialer
|
||||
tlsConfig tls.Config
|
||||
baseConf ProtocolConfig
|
||||
|
||||
muxMu sync.Mutex
|
||||
muxClient *MultiplexClient
|
||||
|
||||
httpMaskMu sync.Mutex
|
||||
httpMaskClient *httpmask.TunnelClient
|
||||
httpMaskKey string
|
||||
}
|
||||
|
||||
func NewClient(options ClientOptions) *Client {
|
||||
return &Client{
|
||||
dialer: options.Dialer,
|
||||
tlsConfig: options.TLSConfig,
|
||||
baseConf: options.Config,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
cfg := c.baseConf
|
||||
cfg.TargetAddress = destination.String()
|
||||
|
||||
muxMode := normalizeHTTPMaskMultiplex(cfg.HTTPMaskMultiplex)
|
||||
if muxMode == "on" && !cfg.DisableHTTPMask && httpTunnelModeEnabled(cfg.HTTPMaskMode) {
|
||||
stream, err := c.dialMultiplex(ctx, cfg.TargetAddress)
|
||||
if err == nil {
|
||||
return stream, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := c.dialAndHandshake(ctx, &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkUDP:
|
||||
if err = WriteKIPMessage(conn, KIPTypeStartUoT, nil); err != nil {
|
||||
conn.Close()
|
||||
return nil, E.Cause(err, "start uot")
|
||||
}
|
||||
return conn, nil
|
||||
default:
|
||||
addrBuf, err := EncodeAddress(cfg.TargetAddress)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, E.Cause(err, "encode target address")
|
||||
}
|
||||
if err = WriteKIPMessage(conn, KIPTypeOpenTCP, addrBuf); err != nil {
|
||||
conn.Close()
|
||||
return nil, E.Cause(err, "send target address")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Close() {
|
||||
c.resetMuxClient()
|
||||
c.resetHTTPMaskClient()
|
||||
}
|
||||
|
||||
func (c *Client) dialAndHandshake(ctx context.Context, cfg *ProtocolConfig) (net.Conn, error) {
|
||||
handshakeCfg := *cfg
|
||||
if !handshakeCfg.DisableHTTPMask && httpTunnelModeEnabled(handshakeCfg.HTTPMaskMode) {
|
||||
handshakeCfg.DisableHTTPMask = true
|
||||
}
|
||||
|
||||
upgrade := func(raw net.Conn) (net.Conn, error) {
|
||||
return ClientHandshake(raw, &handshakeCfg)
|
||||
}
|
||||
|
||||
var conn net.Conn
|
||||
var err error
|
||||
var handshakeDone bool
|
||||
|
||||
if !cfg.DisableHTTPMask && httpTunnelModeEnabled(cfg.HTTPMaskMode) {
|
||||
muxMode := normalizeHTTPMaskMultiplex(cfg.HTTPMaskMultiplex)
|
||||
if muxMode == "auto" && strings.ToLower(strings.TrimSpace(cfg.HTTPMaskMode)) != "ws" {
|
||||
if client, cerr := c.getOrCreateHTTPMaskClient(cfg); cerr == nil && client != nil {
|
||||
conn, err = client.DialTunnel(ctx, httpmask.TunnelDialOptions{
|
||||
Mode: cfg.HTTPMaskMode,
|
||||
TLSConfig: c.httpMaskTLSConfig(),
|
||||
HostOverride: cfg.HTTPMaskHost,
|
||||
PathRoot: cfg.HTTPMaskPathRoot,
|
||||
AuthKey: ClientAEADSeed(cfg.Key),
|
||||
Upgrade: upgrade,
|
||||
Multiplex: cfg.HTTPMaskMultiplex,
|
||||
DialContext: c.dialRaw,
|
||||
})
|
||||
if err != nil {
|
||||
c.resetHTTPMaskClient()
|
||||
}
|
||||
}
|
||||
}
|
||||
if conn == nil && err == nil {
|
||||
conn, err = c.dialHTTPMaskTunnel(ctx, cfg, upgrade)
|
||||
}
|
||||
if err == nil && conn != nil {
|
||||
handshakeDone = true
|
||||
}
|
||||
}
|
||||
if conn == nil && err == nil {
|
||||
conn, err = c.dialer.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddr(cfg.ServerAddress))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "connect to ", cfg.ServerAddress)
|
||||
}
|
||||
|
||||
if !handshakeDone {
|
||||
conn, err = ClientHandshake(conn, &handshakeCfg)
|
||||
if err != nil {
|
||||
common.Close(conn)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) dialRaw(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return c.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
}
|
||||
|
||||
func (c *Client) dialHTTPMaskTunnel(ctx context.Context, cfg *ProtocolConfig, upgrade func(net.Conn) (net.Conn, error)) (net.Conn, error) {
|
||||
var earlyHandshake *httpmask.ClientEarlyHandshake
|
||||
var err error
|
||||
if upgrade != nil {
|
||||
earlyHandshake, err = newClientHTTPMaskEarlyHandshake(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return httpmask.DialTunnel(ctx, cfg.ServerAddress, httpmask.TunnelDialOptions{
|
||||
Mode: cfg.HTTPMaskMode,
|
||||
TLSConfig: c.httpMaskTLSConfig(),
|
||||
HostOverride: cfg.HTTPMaskHost,
|
||||
PathRoot: cfg.HTTPMaskPathRoot,
|
||||
AuthKey: ClientAEADSeed(cfg.Key),
|
||||
EarlyHandshake: earlyHandshake,
|
||||
Upgrade: upgrade,
|
||||
Multiplex: cfg.HTTPMaskMultiplex,
|
||||
DialContext: c.dialRaw,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) httpMaskTLSConfig() tls.Config {
|
||||
if c.tlsConfig == nil {
|
||||
return nil
|
||||
}
|
||||
return c.tlsConfig
|
||||
}
|
||||
|
||||
func (c *Client) dialMultiplex(ctx context.Context, targetAddress string) (net.Conn, error) {
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
client, err := c.getOrCreateMuxClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stream, err := client.Dial(ctx, targetAddress)
|
||||
if err != nil {
|
||||
c.resetMuxClient()
|
||||
continue
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
return nil, fmt.Errorf("multiplex open stream failed")
|
||||
}
|
||||
|
||||
func (c *Client) getOrCreateMuxClient(ctx context.Context) (*MultiplexClient, error) {
|
||||
c.muxMu.Lock()
|
||||
defer c.muxMu.Unlock()
|
||||
|
||||
if c.muxClient != nil && !c.muxClient.IsClosed() {
|
||||
return c.muxClient, nil
|
||||
}
|
||||
|
||||
baseCfg := c.baseConf
|
||||
baseConn, err := c.dialAndHandshake(ctx, &baseCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := StartMultiplexClient(baseConn)
|
||||
if err != nil {
|
||||
baseConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
c.muxClient = client
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) resetMuxClient() {
|
||||
c.muxMu.Lock()
|
||||
defer c.muxMu.Unlock()
|
||||
if c.muxClient != nil {
|
||||
c.muxClient.Close()
|
||||
c.muxClient = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) getOrCreateHTTPMaskClient(cfg *ProtocolConfig) (*httpmask.TunnelClient, error) {
|
||||
key := cfg.ServerAddress + "|" + fmt.Sprint(c.tlsConfig != nil) + "|" + strings.TrimSpace(cfg.HTTPMaskHost)
|
||||
|
||||
c.httpMaskMu.Lock()
|
||||
if c.httpMaskClient != nil && c.httpMaskKey == key {
|
||||
client := c.httpMaskClient
|
||||
c.httpMaskMu.Unlock()
|
||||
return client, nil
|
||||
}
|
||||
c.httpMaskMu.Unlock()
|
||||
|
||||
client, err := httpmask.NewTunnelClient(cfg.ServerAddress, httpmask.TunnelClientOptions{
|
||||
TLSConfig: c.httpMaskTLSConfig(),
|
||||
HostOverride: cfg.HTTPMaskHost,
|
||||
DialContext: c.dialRaw,
|
||||
MaxIdleConns: 32,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.httpMaskMu.Lock()
|
||||
defer c.httpMaskMu.Unlock()
|
||||
if c.httpMaskClient != nil && c.httpMaskKey == key {
|
||||
client.CloseIdleConnections()
|
||||
return c.httpMaskClient, nil
|
||||
}
|
||||
if c.httpMaskClient != nil {
|
||||
c.httpMaskClient.CloseIdleConnections()
|
||||
}
|
||||
c.httpMaskClient = client
|
||||
c.httpMaskKey = key
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) resetHTTPMaskClient() {
|
||||
c.httpMaskMu.Lock()
|
||||
defer c.httpMaskMu.Unlock()
|
||||
if c.httpMaskClient != nil {
|
||||
c.httpMaskClient.CloseIdleConnections()
|
||||
c.httpMaskClient = nil
|
||||
c.httpMaskKey = ""
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeHTTPMaskMultiplex(mode string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(mode)) {
|
||||
case "", "off":
|
||||
return "off"
|
||||
case "auto":
|
||||
return "auto"
|
||||
case "on":
|
||||
return "on"
|
||||
default:
|
||||
return "off"
|
||||
}
|
||||
}
|
||||
|
||||
func httpTunnelModeEnabled(mode string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(mode)) {
|
||||
case "stream", "poll", "auto", "ws":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,9 @@ import (
|
||||
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
)
|
||||
|
||||
type TLSClientConfig interface {
|
||||
Client(conn net.Conn) (net.Conn, error)
|
||||
}
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
)
|
||||
|
||||
type TunnelMode string
|
||||
|
||||
@@ -66,7 +64,7 @@ const (
|
||||
|
||||
type TunnelDialOptions struct {
|
||||
Mode string
|
||||
TLSConfig TLSClientConfig
|
||||
TLSConfig tls.Config
|
||||
HostOverride string
|
||||
// PathRoot is an optional first-level path prefix for all HTTP tunnel endpoints.
|
||||
// Example: "aabbcc" => "/aabbcc/session", "/aabbcc/api/v1/upload", ...
|
||||
@@ -91,7 +89,7 @@ type TunnelDialOptions struct {
|
||||
}
|
||||
|
||||
type TunnelClientOptions struct {
|
||||
TLSConfig TLSClientConfig
|
||||
TLSConfig tls.Config
|
||||
HostOverride string
|
||||
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
MaxIdleConns int
|
||||
@@ -244,7 +242,7 @@ type httpClientTarget struct {
|
||||
headerHost string
|
||||
}
|
||||
|
||||
func buildHTTPTransport(serverAddress string, tlsEnabled bool, tlsConfig TLSClientConfig, hostOverride string, dialContext func(ctx context.Context, network, addr string) (net.Conn, error), maxIdleConns int) (*http.Transport, httpClientTarget, error) {
|
||||
func buildHTTPTransport(serverAddress string, tlsEnabled bool, tlsConfig tls.Config, hostOverride string, dialContext func(ctx context.Context, network, addr string) (net.Conn, error), maxIdleConns int) (*http.Transport, httpClientTarget, error) {
|
||||
if dialContext == nil {
|
||||
panic("httpmask: DialContext is nil")
|
||||
}
|
||||
|
||||
@@ -114,6 +114,9 @@ func (s *QUICService) Start(ctx context.Context, udpConn net.PacketConn, tlsConf
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
if err := qtls.ConfigureHTTP3(tlsConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
quicListener, err := qtls.ListenEarly(udpConn, tlsConfig, &quic.Config{
|
||||
MaxIdleTimeout: DefaultSessionTimeout * 2,
|
||||
MaxIncomingStreams: 1 << 60,
|
||||
|
||||
Reference in New Issue
Block a user