diff --git a/adapter/outbound/tuic.go b/adapter/outbound/tuic.go index 58c79fa1..10fbfd2e 100644 --- a/adapter/outbound/tuic.go +++ b/adapter/outbound/tuic.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "encoding/pem" "fmt" + "math" "net" "os" "strconv" @@ -42,6 +43,7 @@ type TuicOption struct { MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"` FastOpen bool `proxy:"fast-open,omitempty"` + MaxOpenStreams int `proxy:"max-open-streams,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"` CustomCA string `proxy:"ca,omitempty"` @@ -148,11 +150,20 @@ func NewTuic(option TuicOption) (*Tuic, error) { option.MaxUdpRelayPacketSize = 1500 } + if option.MaxOpenStreams == 0 { + option.MaxOpenStreams = 100 + } + + // ensure server's incoming stream can handle correctly, increase to 1.1x + quicMaxOpenStreams := int64(option.MaxOpenStreams) + quicMaxOpenStreams = quicMaxOpenStreams + int64(math.Ceil(float64(quicMaxOpenStreams)/10.0)) quicConfig := &quic.Config{ InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn), MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn), InitialConnectionReceiveWindow: uint64(option.ReceiveWindow), MaxConnectionReceiveWindow: uint64(option.ReceiveWindow), + MaxIncomingStreams: quicMaxOpenStreams, + MaxIncomingUniStreams: quicMaxOpenStreams, KeepAlivePeriod: time.Duration(option.HeartbeatInterval) * time.Millisecond, DisablePathMTUDiscovery: option.DisableMTUDiscovery, EnableDatagrams: true, @@ -186,6 +197,12 @@ func NewTuic(option TuicOption) (*Tuic, error) { prefer: C.NewDNSPrefer(option.IPVersion), }, } + // to avoid tuic's "too many open streams", decrease to 0.9x + clientMaxOpenStreams := int64(option.MaxOpenStreams) + clientMaxOpenStreams = clientMaxOpenStreams - int64(math.Ceil(float64(clientMaxOpenStreams)/10.0)) + if clientMaxOpenStreams < 1 { + clientMaxOpenStreams = 1 + } clientOption := &tuic.ClientOption{ DialFn: t.dial, TlsConfig: tlsConfig, @@ -198,6 +215,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { RequestTimeout: option.RequestTimeout, MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize, FastOpen: option.FastOpen, + MaxOpenStreams: clientMaxOpenStreams, } t.client = tuic.NewClientPool(clientOption) diff --git a/transport/tuic/client.go b/transport/tuic/client.go index f4eaf913..ca0e78a1 100644 --- a/transport/tuic/client.go +++ b/transport/tuic/client.go @@ -28,8 +28,6 @@ var ( TooManyOpenStreams = errors.New("tuic: too many open streams") ) -const MaxOpenStreams = 100 - 90 - type ClientOption struct { DialFn func(ctx context.Context, opts ...dialer.Option) (pc net.PacketConn, addr net.Addr, err error) @@ -43,6 +41,7 @@ type ClientOption struct { RequestTimeout int MaxUdpRelayPacketSize int FastOpen bool + MaxOpenStreams int64 } type Client struct { @@ -52,7 +51,7 @@ type Client struct { quicConn quic.Connection connMutex sync.Mutex - openStreams atomic.Int32 + openStreams atomic.Int64 udpInputMap sync.Map @@ -255,7 +254,7 @@ func (t *Client) DialContext(ctx context.Context, metadata *C.Metadata) (net.Con return nil, err } openStreams := t.openStreams.Add(1) - if openStreams >= MaxOpenStreams { + if openStreams >= t.MaxOpenStreams { t.openStreams.Add(-1) return nil, TooManyOpenStreams } @@ -396,7 +395,7 @@ func (t *Client) ListenPacketContext(ctx context.Context, metadata *C.Metadata) return nil, err } openStreams := t.openStreams.Add(1) - if openStreams >= MaxOpenStreams { + if openStreams >= t.MaxOpenStreams { t.openStreams.Add(-1) return nil, TooManyOpenStreams }