diff --git a/connection/connection.go b/connection/connection.go index 24ae9b54c47..37267fe017e 100644 --- a/connection/connection.go +++ b/connection/connection.go @@ -24,7 +24,11 @@ const ( lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;" LogFieldConnIndex = "connIndex" MaxGracePeriod = time.Minute * 3 - MaxConcurrentStreams = math.MaxUint32 + // MaxConcurrentStreams is the HTTP/2 stream limit advertised to the edge. + // It must fit in an int32: http2.Server implementations backed by + // net/http (Go 1.27+) silently replace larger values with their default + // of 250 streams. + MaxConcurrentStreams = math.MaxInt32 contentTypeHeader = "content-type" contentLengthHeader = "content-length" diff --git a/connection/http2_test.go b/connection/http2_test.go index 9a4b3f9d756..1074a98c213 100644 --- a/connection/http2_test.go +++ b/connection/http2_test.go @@ -18,6 +18,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/net/http2" "github.com/cloudflare/cloudflared/client" "github.com/cloudflare/cloudflared/tracing" @@ -101,6 +102,42 @@ func TestHTTP2ConfigurationSet(t *testing.T) { wg.Wait() } +// TestHTTP2AdvertisedMaxConcurrentStreams checks the SETTINGS frame the +// HTTP/2 server actually sends to the edge, not just the configured value. +// Some http2.Server implementations silently replace limits they cannot +// represent with their default of 250 streams. +func TestHTTP2AdvertisedMaxConcurrentStreams(t *testing.T) { + http2Conn, edgeConn := newTestHTTP2Connection() + + ctx, cancel := context.WithCancel(t.Context()) + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + _ = http2Conn.Serve(ctx) + }() + defer func() { + cancel() + wg.Wait() + }() + + go func() { + _, _ = edgeConn.Write([]byte(http2.ClientPreface)) + }() + + require.NoError(t, edgeConn.SetReadDeadline(time.Now().Add(5*time.Second))) + framer := http2.NewFramer(nil, edgeConn) + frame, err := framer.ReadFrame() + require.NoError(t, err) + settings, ok := frame.(*http2.SettingsFrame) + require.True(t, ok, "expected the server to send SETTINGS first, got %T", frame) + require.False(t, settings.IsAck()) + + maxStreams, ok := settings.Value(http2.SettingMaxConcurrentStreams) + require.True(t, ok, "SETTINGS_MAX_CONCURRENT_STREAMS was not advertised") + assert.Equal(t, uint32(MaxConcurrentStreams), maxStreams) +} + func TestServeHTTP(t *testing.T) { tests := []testRequest{ {