Fix cloudflared registration parameter inconsistencies

- Set QUIC InitialPacketSize per IP family (IPv4: 1252, IPv6: 1232)
- Set MaxIncomingStreams/MaxIncomingUniStreams to 1<<60
- Populate OriginLocalIP from local socket address in both QUIC and HTTP/2
- Pass NumPreviousAttempts from retry counter to registration
- Include version number in client version string
- Use OS_GOARCH format for Arch field
This commit is contained in:
世界
2026-03-24 10:10:32 +08:00
parent b68f4670b0
commit 87a2f4c336
5 changed files with 92 additions and 41 deletions

View File

@@ -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,
)

View File

@@ -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,
)

View File

@@ -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)
}
})
}
}

View File

@@ -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,
}
}

View File

@@ -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")