diff --git a/protocol/cloudflare/connection_http2.go b/protocol/cloudflare/connection_http2.go index 50e07d40a..afe0699bb 100644 --- a/protocol/cloudflare/connection_http2.go +++ b/protocol/cloudflare/connection_http2.go @@ -40,8 +40,9 @@ type HTTP2Connection struct { gracePeriod time.Duration inbound *Inbound - registrationClient *RegistrationClient - registrationResult *RegistrationResult + numPreviousAttempts uint8 + registrationClient *RegistrationClient + registrationResult *RegistrationResult activeRequests sync.WaitGroup closeOnce sync.Once @@ -55,6 +56,7 @@ func NewHTTP2Connection( credentials Credentials, connectorID uuid.UUID, features []string, + numPreviousAttempts uint8, gracePeriod time.Duration, inbound *Inbound, logger log.ContextLogger, @@ -92,10 +94,11 @@ func NewHTTP2Connection( edgeAddr: edgeAddr, connIndex: connIndex, credentials: credentials, - connectorID: connectorID, - features: features, - gracePeriod: gracePeriod, - inbound: inbound, + connectorID: connectorID, + features: features, + numPreviousAttempts: numPreviousAttempts, + gracePeriod: gracePeriod, + inbound: inbound, }, nil } @@ -149,7 +152,9 @@ func (c *HTTP2Connection) handleControlStream(ctx context.Context, r *http.Reque c.registrationClient = NewRegistrationClient(ctx, stream) - options := BuildConnectionOptions(c.connectorID, c.features, 0) + host, _, _ := net.SplitHostPort(c.conn.LocalAddr().String()) + originLocalIP := net.ParseIP(host) + options := BuildConnectionOptions(c.connectorID, c.features, c.numPreviousAttempts, originLocalIP) result, err := c.registrationClient.RegisterConnection( ctx, c.credentials.Auth(), c.credentials.TunnelID, c.connIndex, options, ) diff --git a/protocol/cloudflare/connection_quic.go b/protocol/cloudflare/connection_quic.go index e7cfc073d..549a6eef4 100644 --- a/protocol/cloudflare/connection_quic.go +++ b/protocol/cloudflare/connection_quic.go @@ -6,6 +6,7 @@ import ( "context" "crypto/tls" "io" + "net" "sync" "time" @@ -25,18 +26,27 @@ const ( quicKeepAlivePeriod = 1 * time.Second ) +func quicInitialPacketSize(ipVersion int) uint16 { + initialPacketSize := uint16(1252) + if ipVersion == 4 { + initialPacketSize = 1232 + } + return initialPacketSize +} + // QUICConnection manages a single QUIC connection to the Cloudflare edge. type QUICConnection struct { - conn *quic.Conn - logger log.ContextLogger - edgeAddr *EdgeAddr - connIndex uint8 - credentials Credentials - connectorID uuid.UUID - features []string - gracePeriod time.Duration - registrationClient *RegistrationClient - registrationResult *RegistrationResult + conn *quic.Conn + logger log.ContextLogger + edgeAddr *EdgeAddr + connIndex uint8 + credentials Credentials + connectorID uuid.UUID + features []string + numPreviousAttempts uint8 + gracePeriod time.Duration + registrationClient *RegistrationClient + registrationResult *RegistrationResult closeOnce sync.Once } @@ -49,6 +59,7 @@ func NewQUICConnection( credentials Credentials, connectorID uuid.UUID, features []string, + numPreviousAttempts uint8, gracePeriod time.Duration, logger log.ContextLogger, ) (*QUICConnection, error) { @@ -65,10 +76,13 @@ func NewQUICConnection( } quicConfig := &quic.Config{ - HandshakeIdleTimeout: quicHandshakeIdleTimeout, - MaxIdleTimeout: quicMaxIdleTimeout, - KeepAlivePeriod: quicKeepAlivePeriod, - EnableDatagrams: true, + HandshakeIdleTimeout: quicHandshakeIdleTimeout, + MaxIdleTimeout: quicMaxIdleTimeout, + KeepAlivePeriod: quicKeepAlivePeriod, + MaxIncomingStreams: 1 << 60, + MaxIncomingUniStreams: 1 << 60, + EnableDatagrams: true, + InitialPacketSize: quicInitialPacketSize(edgeAddr.IPVersion), } conn, err := quic.DialAddr(ctx, edgeAddr.UDP.String(), tlsConfig, quicConfig) @@ -77,14 +91,15 @@ func NewQUICConnection( } return &QUICConnection{ - conn: conn, - logger: logger, - edgeAddr: edgeAddr, - connIndex: connIndex, - credentials: credentials, - connectorID: connectorID, - features: features, - gracePeriod: gracePeriod, + conn: conn, + logger: logger, + edgeAddr: edgeAddr, + connIndex: connIndex, + credentials: credentials, + connectorID: connectorID, + features: features, + numPreviousAttempts: numPreviousAttempts, + gracePeriod: gracePeriod, }, nil } @@ -128,7 +143,9 @@ func (q *QUICConnection) Serve(ctx context.Context, handler StreamHandler) error func (q *QUICConnection) register(ctx context.Context, stream *quic.Stream) error { q.registrationClient = NewRegistrationClient(ctx, newStreamReadWriteCloser(stream)) - options := BuildConnectionOptions(q.connectorID, q.features, 0) + host, _, _ := net.SplitHostPort(q.conn.LocalAddr().String()) + originLocalIP := net.ParseIP(host) + options := BuildConnectionOptions(q.connectorID, q.features, q.numPreviousAttempts, originLocalIP) result, err := q.registrationClient.RegisterConnection( ctx, q.credentials.Auth(), q.credentials.TunnelID, q.connIndex, options, ) diff --git a/protocol/cloudflare/connection_quic_test.go b/protocol/cloudflare/connection_quic_test.go new file mode 100644 index 000000000..7ea4a8690 --- /dev/null +++ b/protocol/cloudflare/connection_quic_test.go @@ -0,0 +1,25 @@ +//go:build with_cloudflare_tunnel + +package cloudflare + +import "testing" + +func TestQUICInitialPacketSize(t *testing.T) { + testCases := []struct { + name string + ipVersion int + expected uint16 + }{ + {name: "ipv4", ipVersion: 4, expected: 1232}, + {name: "ipv6", ipVersion: 6, expected: 1252}, + {name: "default", ipVersion: 0, expected: 1252}, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + if actual := quicInitialPacketSize(testCase.ipVersion); actual != testCase.expected { + t.Fatalf("quicInitialPacketSize(%d) = %d, want %d", testCase.ipVersion, actual, testCase.expected) + } + }) + } +} diff --git a/protocol/cloudflare/control.go b/protocol/cloudflare/control.go index 9f583f29e..a4130f096 100644 --- a/protocol/cloudflare/control.go +++ b/protocol/cloudflare/control.go @@ -5,9 +5,11 @@ package cloudflare import ( "context" "io" + "net" "runtime" "time" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/protocol/cloudflare/tunnelrpc" E "github.com/sagernet/sing/common/exceptions" @@ -18,9 +20,10 @@ import ( const ( registrationTimeout = 10 * time.Second - clientVersion = "sing-box" ) +var clientVersion = "sing-box " + C.Version + // RegistrationClient handles the Cap'n Proto RPC for tunnel registration. type RegistrationClient struct { client tunnelrpc.TunnelServer @@ -148,14 +151,15 @@ func (c *RegistrationClient) Close() error { } // BuildConnectionOptions creates the ConnectionOptions to send during registration. -func BuildConnectionOptions(connectorID uuid.UUID, features []string, numPreviousAttempts uint8) *RegistrationConnectionOptions { +func BuildConnectionOptions(connectorID uuid.UUID, features []string, numPreviousAttempts uint8, originLocalIP net.IP) *RegistrationConnectionOptions { return &RegistrationConnectionOptions{ Client: RegistrationClientInfo{ ClientID: connectorID[:], Features: features, Version: clientVersion, - Arch: runtime.GOARCH, + Arch: runtime.GOOS + "_" + runtime.GOARCH, }, + OriginLocalIP: originLocalIP, NumPreviousAttempts: numPreviousAttempts, } } diff --git a/protocol/cloudflare/inbound.go b/protocol/cloudflare/inbound.go index 36c04310e..dd0bd4398 100644 --- a/protocol/cloudflare/inbound.go +++ b/protocol/cloudflare/inbound.go @@ -267,7 +267,7 @@ func (i *Inbound) superviseConnection(connIndex uint8, edgeAddrs []*EdgeAddr, fe } edgeAddr := edgeAddrs[rand.Intn(len(edgeAddrs))] - err := i.serveConnection(connIndex, edgeAddr, features) + err := i.serveConnection(connIndex, edgeAddr, features, uint8(retries)) if err == nil || i.ctx.Err() != nil { return } @@ -284,7 +284,7 @@ func (i *Inbound) superviseConnection(connIndex uint8, edgeAddrs []*EdgeAddr, fe } } -func (i *Inbound) serveConnection(connIndex uint8, edgeAddr *EdgeAddr, features []string) error { +func (i *Inbound) serveConnection(connIndex uint8, edgeAddr *EdgeAddr, features []string, numPreviousAttempts uint8) error { protocol := i.protocol if protocol == "" { protocol = "quic" @@ -292,21 +292,21 @@ func (i *Inbound) serveConnection(connIndex uint8, edgeAddr *EdgeAddr, features switch protocol { case "quic": - return i.serveQUIC(connIndex, edgeAddr, features) + return i.serveQUIC(connIndex, edgeAddr, features, numPreviousAttempts) case "http2": - return i.serveHTTP2(connIndex, edgeAddr, features) + return i.serveHTTP2(connIndex, edgeAddr, features, numPreviousAttempts) default: return E.New("unsupported protocol: ", protocol) } } -func (i *Inbound) serveQUIC(connIndex uint8, edgeAddr *EdgeAddr, features []string) error { +func (i *Inbound) serveQUIC(connIndex uint8, edgeAddr *EdgeAddr, features []string, numPreviousAttempts uint8) error { i.logger.Info("connecting to edge via QUIC (connection ", connIndex, ")") connection, err := NewQUICConnection( i.ctx, edgeAddr, connIndex, i.credentials, i.connectorID, - features, i.gracePeriod, i.logger, + features, numPreviousAttempts, i.gracePeriod, i.logger, ) if err != nil { return E.Cause(err, "create QUIC connection") @@ -321,13 +321,13 @@ func (i *Inbound) serveQUIC(connIndex uint8, edgeAddr *EdgeAddr, features []stri return connection.Serve(i.ctx, i) } -func (i *Inbound) serveHTTP2(connIndex uint8, edgeAddr *EdgeAddr, features []string) error { +func (i *Inbound) serveHTTP2(connIndex uint8, edgeAddr *EdgeAddr, features []string, numPreviousAttempts uint8) error { i.logger.Info("connecting to edge via HTTP/2 (connection ", connIndex, ")") connection, err := NewHTTP2Connection( i.ctx, edgeAddr, connIndex, i.credentials, i.connectorID, - features, i.gracePeriod, i, i.logger, + features, numPreviousAttempts, i.gracePeriod, i, i.logger, ) if err != nil { return E.Cause(err, "create HTTP/2 connection")