From 3ba1a509b61063a15c23ad930e6647b2df1cea4f Mon Sep 17 00:00:00 2001 From: botengyao Date: Sat, 7 Nov 2026 22:59:08 -0500 Subject: [PATCH] atenet-router: limit HTTP/2 streams and windows on client HCMs Envoy's HTTP/2 defaults allow 2^31-1 streams and 256 MiB windows per connection on the client-facing HCMs. Use the atenet-egress values instead. Signed-off-by: botengyao --- cmd/atenet/internal/router/xds.go | 28 +++++++++++++---- cmd/atenet/internal/router/xds_test.go | 42 ++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/cmd/atenet/internal/router/xds.go b/cmd/atenet/internal/router/xds.go index 80c9aa6133..178ee8a7fe 100644 --- a/cmd/atenet/internal/router/xds.go +++ b/cmd/atenet/internal/router/xds.go @@ -228,6 +228,13 @@ const actorConnectCircuitBreakerLimit = 1048576 // tunnelIdleTimeout closes unused tunnels, which hold actor connections. const tunnelIdleTimeout = 60 * time.Second +// HTTP/2 limits on the client-facing HCMs. Envoy's defaults allow 2^31-1 +// streams and 256 MiB windows per connection. These match atenet-egress. +const ( + downstreamMaxConcurrentStreams = 100 + downstreamInitialWindowSize = 1 << 20 +) + // XdsServer implements an aggregated discovery service server for dynamic Envoy router nodes. type XdsServer struct { xdsPort int @@ -1316,14 +1323,22 @@ func (x *XdsServer) buildConnectTerminateHCM(statPrefix string) *anypb.Any { }, }, }, - Http2ProtocolOptions: &corev3.Http2ProtocolOptions{ - AllowConnect: true, - }, + Http2ProtocolOptions: downstreamHttp2Options(true), }) return hcm } +// downstreamHttp2Options returns the HTTP/2 limits for a client-facing HCM. +func downstreamHttp2Options(allowConnect bool) *corev3.Http2ProtocolOptions { + return &corev3.Http2ProtocolOptions{ + AllowConnect: allowConnect, + MaxConcurrentStreams: wrapperspb.UInt32(downstreamMaxConcurrentStreams), + InitialStreamWindowSize: wrapperspb.UInt32(downstreamInitialWindowSize), + InitialConnectionWindowSize: wrapperspb.UInt32(downstreamInitialWindowSize), + } +} + func buildConnectRoutes() *routev3.RouteConfiguration { return &routev3.RouteConfiguration{ Name: "default", @@ -1435,9 +1450,10 @@ func (x *XdsServer) buildHcm(statPrefix string, captureActorRouting bool) *anypb UpgradeConfigs: []*hcmv3.HttpConnectionManager_UpgradeConfig{ {UpgradeType: "websocket"}, }, - Tracing: x.buildTracing(), - AccessLog: stdoutAccessLog(accessLogFormat), - HttpFilters: httpFilters, + Tracing: x.buildTracing(), + AccessLog: stdoutAccessLog(accessLogFormat), + HttpFilters: httpFilters, + Http2ProtocolOptions: downstreamHttp2Options(false), RouteSpecifier: &hcmv3.HttpConnectionManager_Rds{ Rds: &hcmv3.Rds{ RouteConfigName: RouteName, diff --git a/cmd/atenet/internal/router/xds_test.go b/cmd/atenet/internal/router/xds_test.go index c7fcf8cdb1..40e0553271 100644 --- a/cmd/atenet/internal/router/xds_test.go +++ b/cmd/atenet/internal/router/xds_test.go @@ -522,6 +522,48 @@ func TestXdsServer_AccessLogsOmitQueryString(t *testing.T) { } } +// Every HCM serves client-controlled requests, either directly or from inside +// a client's CONNECT tunnel. +func TestXdsServer_ClientFacingHCMLimits(t *testing.T) { + server := NewXdsServer(18000) + server.SetConfig(50053, "127.0.0.1") + server.SetConnectTLSPort(8444) + setIngressTLS(server) + + var got []string + for name, l := range snapshotListeners(t, server) { + for _, fc := range l.GetFilterChains() { + for _, f := range fc.GetFilters() { + if f.GetName() != "envoy.filters.network.http_connection_manager" { + continue + } + got = append(got, name) + hcm := &hcmv3.HttpConnectionManager{} + if err := f.GetTypedConfig().UnmarshalTo(hcm); err != nil { + t.Fatalf("listener %s: unmarshal HCM: %v", name, err) + } + h2 := hcm.GetHttp2ProtocolOptions() + if want := name == "connect_terminate_tls"; h2.GetAllowConnect() != want { + t.Errorf("listener %s: allow_connect = %v, want %v", name, h2.GetAllowConnect(), want) + } + if v := h2.GetMaxConcurrentStreams().GetValue(); v != downstreamMaxConcurrentStreams { + t.Errorf("listener %s: max_concurrent_streams = %d, want %d", name, v, downstreamMaxConcurrentStreams) + } + if v := h2.GetInitialStreamWindowSize().GetValue(); v != downstreamInitialWindowSize { + t.Errorf("listener %s: initial_stream_window_size = %d, want %d", name, v, downstreamInitialWindowSize) + } + if v := h2.GetInitialConnectionWindowSize().GetValue(); v != downstreamInitialWindowSize { + t.Errorf("listener %s: initial_connection_window_size = %d, want %d", name, v, downstreamInitialWindowSize) + } + } + } + } + slices.Sort(got) + if want := []string{"connect_terminate_tls", IngressHTTPSListener, MainInternalName}; !slices.Equal(got, slices.Sorted(slices.Values(want))) { + t.Errorf("HCM listeners = %v, want %v", got, want) + } +} + func TestXdsServer_Serve_Shutdown(t *testing.T) { server := NewXdsServer(18000) server.SetConfig(50053, "127.0.0.1")