fix: Wait for trusttunnel client connection and add disable_path_mtu_discovery

This commit is contained in:
Shtorm
2026-10-19 17:09:48 +03:00
parent 60094a12b4
commit f269dee097
6 changed files with 82 additions and 47 deletions
+1
View File
@@ -52,6 +52,7 @@
"quic": true,
"congestion_controller": "bbr", // bbr, bbr_standard, bbr2, bbr2_variant, cubic, reno
"cwnd": 32,
"disable_path_mtu_discovery": false, // QUIC only
"multiplex": {
"enabled": true,
"max_connections": 8,
+1
View File
@@ -14,6 +14,7 @@
],
"congestion_controller": "bbr", // bbr, bbr_standard, bbr2, bbr2_variant, cubic, reno
"cwnd": 32,
"disable_path_mtu_discovery": false, // QUIC only
"tls": {
"enabled": true,
"alpn": ["h2", "h3"],
+14 -12
View File
@@ -3,10 +3,11 @@ package option
type TrustTunnelInboundOptions struct {
ListenOptions
InboundTLSOptionsContainer
Users []TrustTunnelUser `json:"users,omitempty"`
Network NetworkList `json:"network,omitempty"`
CongestionController string `json:"congestion_controller,omitempty"`
CWND int `json:"cwnd,omitempty"`
Users []TrustTunnelUser `json:"users,omitempty"`
Network NetworkList `json:"network,omitempty"`
CongestionController string `json:"congestion_controller,omitempty"`
CWND int `json:"cwnd,omitempty"`
DisablePathMTUDiscovery bool `json:"disable_path_mtu_discovery,omitempty"`
}
type TrustTunnelUser struct {
@@ -25,12 +26,13 @@ type TrustTunnelOutboundOptions struct {
DialerOptions
ServerOptions
OutboundTLSOptionsContainer
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Network NetworkList `json:"network,omitempty"`
HealthCheck bool `json:"health_check,omitempty"`
QUIC bool `json:"quic,omitempty"`
CongestionController string `json:"congestion_controller,omitempty"`
CWND int `json:"cwnd,omitempty"`
Multiplex *TrustTunnelMultiplexOptions `json:"multiplex,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Network NetworkList `json:"network,omitempty"`
HealthCheck bool `json:"health_check,omitempty"`
QUIC bool `json:"quic,omitempty"`
CongestionController string `json:"congestion_controller,omitempty"`
CWND int `json:"cwnd,omitempty"`
DisablePathMTUDiscovery bool `json:"disable_path_mtu_discovery,omitempty"`
Multiplex *TrustTunnelMultiplexOptions `json:"multiplex,omitempty"`
}
+4 -3
View File
@@ -157,9 +157,10 @@ func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error {
},
}
quicListener, err := qtls.ListenEarly(udpConn, h.http3TLSConfig, &quic.Config{
MaxIdleTimeout: trusttunnel.DefaultSessionTimeout * 2,
MaxIncomingStreams: 1 << 60,
Allow0RTT: true,
MaxIdleTimeout: trusttunnel.DefaultSessionTimeout * 2,
MaxIncomingStreams: 1 << 60,
Allow0RTT: true,
DisablePathMTUDiscovery: h.options.DisablePathMTUDiscovery,
})
if err != nil {
return err
+11 -10
View File
@@ -45,16 +45,17 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err
}
clientOpts := trusttunnel.ClientOptions{
Dialer: outboundDialer,
TLSConfig: tlsConfig,
Server: serverAddr,
Username: options.Username,
Password: options.Password,
QUIC: options.QUIC,
CongestionControl: options.CongestionController,
CWND: options.CWND,
Logger: logger,
HealthCheck: options.HealthCheck,
Dialer: outboundDialer,
TLSConfig: tlsConfig,
Server: serverAddr,
Username: options.Username,
Password: options.Password,
QUIC: options.QUIC,
DisablePathMTUDiscovery: options.DisablePathMTUDiscovery,
CongestionControl: options.CongestionController,
CWND: options.CWND,
Logger: logger,
HealthCheck: options.HealthCheck,
}
var client trusttunnel.Dialer
if options.Multiplex != nil && options.Multiplex.Enabled {
+51 -22
View File
@@ -8,6 +8,7 @@ import (
"io"
"net"
"net/http"
"net/http/httptrace"
"net/url"
"runtime"
"sync"
@@ -17,6 +18,7 @@ import (
"github.com/sagernet/sing-box/common/congestion"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -43,19 +45,20 @@ type Dialer interface {
}
type ClientOptions struct {
Dialer N.Dialer
TLSConfig tls.Config
Server M.Socksaddr
Username string
Password string
QUIC bool
CongestionControl string
CWND int
Logger logger.Logger
HealthCheck bool
MaxConnections int
MinStreams int
MaxStreams int
Dialer N.Dialer
TLSConfig tls.Config
Server M.Socksaddr
Username string
Password string
QUIC bool
DisablePathMTUDiscovery bool
CongestionControl string
CWND int
Logger logger.Logger
HealthCheck bool
MaxConnections int
MinStreams int
MaxStreams int
}
type Client struct {
@@ -92,8 +95,9 @@ func NewClient(ctx context.Context, options ClientOptions) (*Client, error) {
}
client.roundTripper = &http3.Transport{
QUICConfig: &quic.Config{
MaxIdleTimeout: DefaultSessionTimeout * 2,
KeepAlivePeriod: DefaultHealthCheckTimeout,
MaxIdleTimeout: DefaultSessionTimeout * 2,
KeepAlivePeriod: DefaultHealthCheckTimeout,
DisablePathMTUDiscovery: options.DisablePathMTUDiscovery,
},
Dial: func(ctx context.Context, addr string, tlsCfg *stdtls.Config, cfg *quic.Config) (*quic.Conn, error) {
udpConn, err := options.Dialer.DialContext(ctx, N.NetworkUDP, client.server)
@@ -151,27 +155,38 @@ func (c *Client) resetHealthCheckTimer() {
c.healthCheckTimer.Reset(DefaultHealthCheckTimeout)
}
func (c *Client) roundTrip(request *http.Request, conn *httpConn) {
func (c *Client) roundTrip(ctx context.Context, request *http.Request, conn *httpConn) error {
c.startOnce.Do(c.start)
pipeReader, pipeWriter := io.Pipe()
request.Body = pipeReader
*conn = httpConn{writer: pipeWriter, created: make(chan struct{})}
c.count.Add(1)
conn.closeFn = sync.OnceFunc(func() { c.count.Add(-1) })
ctx, cancel := context.WithCancel(c.ctx)
requestCtx, cancel := context.WithCancel(c.ctx)
conn.cancelFn = cancel
gotConn := make(chan bool, 1)
streamCtx := httptrace.WithClientTrace(requestCtx, &httptrace.ClientTrace{
GotConn: func(info httptrace.GotConnInfo) {
select {
case gotConn <- true:
default:
}
},
})
var requestErr common.TypedValue[error]
go func() {
timeout := time.AfterFunc(C.TCPTimeout, cancel)
defer timeout.Stop()
request = request.WithContext(ctx)
request = request.WithContext(streamCtx)
response, err := c.roundTripper.RoundTrip(request)
if err != nil {
requestErr.Store(err)
close(gotConn)
_ = pipeWriter.CloseWithError(err)
_ = pipeReader.CloseWithError(err)
conn.setup(nil, err)
} else if response.StatusCode != http.StatusOK {
_ = response.Body.Close()
err = fmt.Errorf("unexpected status code: %d", response.StatusCode)
requestErr.Store(err)
_ = pipeWriter.CloseWithError(err)
_ = pipeReader.CloseWithError(err)
conn.setup(nil, err)
@@ -180,6 +195,12 @@ func (c *Client) roundTrip(request *http.Request, conn *httpConn) {
conn.setup(response.Body, nil)
}
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-gotConn:
return requestErr.Load()
}
}
func (c *Client) newConnectRequest(host, userAgent string) *http.Request {
@@ -196,13 +217,21 @@ func (c *Client) newConnectRequest(host, userAgent string) *http.Request {
func (c *Client) Dial(ctx context.Context, host string) (net.Conn, error) {
conn := &tcpConn{}
c.roundTrip(c.newConnectRequest(host, tcpUserAgent), &conn.httpConn)
err := c.roundTrip(ctx, c.newConnectRequest(host, tcpUserAgent), &conn.httpConn)
if err != nil {
_ = conn.Close()
return nil, err
}
return conn, nil
}
func (c *Client) ListenPacket(ctx context.Context) (net.PacketConn, error) {
conn := &clientPacketConn{}
c.roundTrip(c.newConnectRequest(UDPMagicAddress, udpUserAgent), &conn.httpConn)
err := c.roundTrip(ctx, c.newConnectRequest(UDPMagicAddress, udpUserAgent), &conn.httpConn)
if err != nil {
_ = conn.Close()
return nil, err
}
return conn, nil
}