From e25323c79ba5019093e04587e3fada7bfa95e802 Mon Sep 17 00:00:00 2001 From: Rajat Vig Date: Sun, 8 Feb 2026 20:42:56 +0000 Subject: [PATCH 1/5] feat(api): add HTTP/2 connection keepalive to ClientTrafficPolicy Signed-off-by: Rajat Vig --- api/v1alpha1/clienttrafficpolicy_types.go | 59 ++++++- api/v1alpha1/zz_generated.deepcopy.go | 72 ++++++++- ...y.envoyproxy.io_clienttrafficpolicies.yaml | 24 +++ ...y.envoyproxy.io_clienttrafficpolicies.yaml | 24 +++ internal/gatewayapi/clienttrafficpolicy.go | 2 +- internal/gatewayapi/http.go | 86 +++++++++++ ...lienttrafficpolicy-http2-keepalive.in.yaml | 32 ++++ ...ienttrafficpolicy-http2-keepalive.out.yaml | 144 ++++++++++++++++++ internal/ir/xds.go | 13 ++ internal/ir/zz_generated.deepcopy.go | 35 +++++ internal/xds/translator/listener.go | 15 ++ .../testdata/in/xds-ir/http2-keepalive.yaml | 27 ++++ .../out/xds-ir/http2-keepalive.clusters.yaml | 24 +++ .../out/xds-ir/http2-keepalive.endpoints.yaml | 12 ++ .../out/xds-ir/http2-keepalive.listeners.yaml | 39 +++++ .../out/xds-ir/http2-keepalive.routes.yaml | 14 ++ release-notes/current.yaml | 1 + site/content/en/latest/api/extension_types.md | 38 ++++- 18 files changed, 656 insertions(+), 5 deletions(-) create mode 100644 internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.in.yaml create mode 100644 internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.out.yaml create mode 100644 internal/xds/translator/testdata/in/xds-ir/http2-keepalive.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/http2-keepalive.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/http2-keepalive.endpoints.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/http2-keepalive.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/http2-keepalive.routes.yaml diff --git a/api/v1alpha1/clienttrafficpolicy_types.go b/api/v1alpha1/clienttrafficpolicy_types.go index d87f568b00..4a01cdf637 100644 --- a/api/v1alpha1/clienttrafficpolicy_types.go +++ b/api/v1alpha1/clienttrafficpolicy_types.go @@ -6,6 +6,7 @@ package v1alpha1 import ( + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" ) @@ -98,7 +99,7 @@ type ClientTrafficPolicySpec struct { // HTTP2 provides HTTP/2 configuration on the listener. // // +optional - HTTP2 *HTTP2Settings `json:"http2,omitempty"` + HTTP2 *HTTP2ClientSettings `json:"http2,omitempty"` // HTTP3 provides HTTP/3 configuration on the listener. // // +optional @@ -440,6 +441,62 @@ const ( SchemeHeaderTransformMatchBackend SchemeHeaderTransform = "MatchBackend" ) +// HTTP2ClientSettings provides HTTP/2 configuration for client connections to the listener. +type HTTP2ClientSettings struct { + // InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + // If not set, the default value is 64 KiB(64*1024). + // + // +kubebuilder:validation:XIntOrString + // +kubebuilder:validation:Pattern="^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$" + // +optional + InitialStreamWindowSize *resource.Quantity `json:"initialStreamWindowSize,omitempty"` + + // InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + // If not set, the default value is 1 MiB. + // + // +kubebuilder:validation:XIntOrString + // +kubebuilder:validation:Pattern="^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$" + // +optional + InitialConnectionWindowSize *resource.Quantity `json:"initialConnectionWindowSize,omitempty"` + + // MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + // If not set, the default value is 100. + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=2147483647 + // +optional + MaxConcurrentStreams *uint32 `json:"maxConcurrentStreams,omitempty"` + + // OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + // It's recommended for L2 Envoy deployments to set this value to TerminateStream. + // https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + // Default: TerminateConnection + // +optional + OnInvalidMessage *InvalidMessageAction `json:"onInvalidMessage,omitempty"` + + // ConnectionKeepalive configures HTTP/2 connection keepalive using PING frames. + // +optional + ConnectionKeepalive *HTTP2ConnectionKeepalive `json:"connectionKeepalive,omitempty"` +} + +// HTTP2ConnectionKeepalive configures HTTP/2 PING-based keepalive settings. +type HTTP2ConnectionKeepalive struct { + // Interval specifies how often to send HTTP/2 PING frames to keep the connection alive. + // If not set, PING frames will not be sent periodically. + // +optional + Interval *gwapiv1.Duration `json:"interval,omitempty"` + + // Timeout specifies how long to wait for a PING response before considering the connection dead. + // If not set, a default timeout is used. + // +optional + Timeout *gwapiv1.Duration `json:"timeout,omitempty"` + + // ConnectionIdleInterval specifies how long a connection must be idle before a PING is sent + // to check if the connection is still alive. + // If not set, idle connection checks are not performed. + // +optional + ConnectionIdleInterval *gwapiv1.Duration `json:"connectionIdleInterval,omitempty"` +} + func init() { localSchemeBuilder.Register(&ClientTrafficPolicy{}, &ClientTrafficPolicyList{}) } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fb1eedf645..ec2f7d3871 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1196,7 +1196,7 @@ func (in *ClientTrafficPolicySpec) DeepCopyInto(out *ClientTrafficPolicySpec) { } if in.HTTP2 != nil { in, out := &in.HTTP2, &out.HTTP2 - *out = new(HTTP2Settings) + *out = new(HTTP2ClientSettings) (*in).DeepCopyInto(*out) } if in.HTTP3 != nil { @@ -3491,6 +3491,76 @@ func (in *HTTP1Settings) DeepCopy() *HTTP1Settings { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTP2ClientSettings) DeepCopyInto(out *HTTP2ClientSettings) { + *out = *in + if in.InitialStreamWindowSize != nil { + in, out := &in.InitialStreamWindowSize, &out.InitialStreamWindowSize + x := (*in).DeepCopy() + *out = &x + } + if in.InitialConnectionWindowSize != nil { + in, out := &in.InitialConnectionWindowSize, &out.InitialConnectionWindowSize + x := (*in).DeepCopy() + *out = &x + } + if in.MaxConcurrentStreams != nil { + in, out := &in.MaxConcurrentStreams, &out.MaxConcurrentStreams + *out = new(uint32) + **out = **in + } + if in.OnInvalidMessage != nil { + in, out := &in.OnInvalidMessage, &out.OnInvalidMessage + *out = new(InvalidMessageAction) + **out = **in + } + if in.ConnectionKeepalive != nil { + in, out := &in.ConnectionKeepalive, &out.ConnectionKeepalive + *out = new(HTTP2ConnectionKeepalive) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP2ClientSettings. +func (in *HTTP2ClientSettings) DeepCopy() *HTTP2ClientSettings { + if in == nil { + return nil + } + out := new(HTTP2ClientSettings) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTP2ConnectionKeepalive) DeepCopyInto(out *HTTP2ConnectionKeepalive) { + *out = *in + if in.Interval != nil { + in, out := &in.Interval, &out.Interval + *out = new(v1.Duration) + **out = **in + } + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.ConnectionIdleInterval != nil { + in, out := &in.ConnectionIdleInterval, &out.ConnectionIdleInterval + *out = new(v1.Duration) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP2ConnectionKeepalive. +func (in *HTTP2ConnectionKeepalive) DeepCopy() *HTTP2ConnectionKeepalive { + if in == nil { + return nil + } + out := new(HTTP2ConnectionKeepalive) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTP2Settings) DeepCopyInto(out *HTTP2Settings) { *out = *in diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml index f86e2c2eca..f419ca457b 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml @@ -799,6 +799,30 @@ spec: http2: description: HTTP2 provides HTTP/2 configuration on the listener. properties: + connectionKeepalive: + description: ConnectionKeepalive configures HTTP/2 connection + keepalive using PING frames. + properties: + connectionIdleInterval: + description: |- + ConnectionIdleInterval specifies how long a connection must be idle before a PING is sent + to check if the connection is still alive. + If not set, idle connection checks are not performed. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + Interval specifies how often to send HTTP/2 PING frames to keep the connection alive. + If not set, PING frames will not be sent periodically. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + timeout: + description: |- + Timeout specifies how long to wait for a PING response before considering the connection dead. + If not set, a default timeout is used. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object initialConnectionWindowSize: allOf: - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml index 0a83915dbe..46b8a4b3f9 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml @@ -798,6 +798,30 @@ spec: http2: description: HTTP2 provides HTTP/2 configuration on the listener. properties: + connectionKeepalive: + description: ConnectionKeepalive configures HTTP/2 connection + keepalive using PING frames. + properties: + connectionIdleInterval: + description: |- + ConnectionIdleInterval specifies how long a connection must be idle before a PING is sent + to check if the connection is still alive. + If not set, idle connection checks are not performed. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + Interval specifies how often to send HTTP/2 PING frames to keep the connection alive. + If not set, PING frames will not be sent periodically. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + timeout: + description: |- + Timeout specifies how long to wait for a PING response before considering the connection dead. + If not set, a default timeout is used. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object initialConnectionWindowSize: allOf: - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go index 076f4e779e..54cb71e25f 100644 --- a/internal/gatewayapi/clienttrafficpolicy.go +++ b/internal/gatewayapi/clienttrafficpolicy.go @@ -470,7 +470,7 @@ func (t *Translator) translateClientTrafficPolicyForListener( } // Translate HTTP2 Settings - if h2, err := buildIRHTTP2Settings(policy.Spec.HTTP2); err != nil { + if h2, err := buildIRHTTP2ClientSettings(policy.Spec.HTTP2); err != nil { err = perr.WithMessage(err, "HTTP2") errs = errors.Join(errs, err) } else { diff --git a/internal/gatewayapi/http.go b/internal/gatewayapi/http.go index e54b3f761d..8a09d5ad2a 100644 --- a/internal/gatewayapi/http.go +++ b/internal/gatewayapi/http.go @@ -8,6 +8,7 @@ package gatewayapi import ( "errors" "fmt" + "time" "k8s.io/utils/ptr" @@ -74,3 +75,88 @@ func buildIRHTTP2Settings(http2Settings *egv1a1.HTTP2Settings) (*ir.HTTP2Setting return http2, errs } + +func buildIRHTTP2ClientSettings(http2Settings *egv1a1.HTTP2ClientSettings) (*ir.HTTP2Settings, error) { + if http2Settings == nil { + return nil, nil + } + var ( + http2 = &ir.HTTP2Settings{} + errs error + ) + + if http2Settings.InitialStreamWindowSize != nil { + initialStreamWindowSize, ok := http2Settings.InitialStreamWindowSize.AsInt64() + switch { + case !ok: + errs = errors.Join(errs, fmt.Errorf("invalid InitialStreamWindowSize value %s", http2Settings.InitialStreamWindowSize.String())) + case initialStreamWindowSize < MinHTTP2InitialStreamWindowSize || initialStreamWindowSize > MaxHTTP2InitialStreamWindowSize: + errs = errors.Join(errs, fmt.Errorf("InitialStreamWindowSize value %s is out of range, must be between %d and %d", + http2Settings.InitialStreamWindowSize.String(), + MinHTTP2InitialStreamWindowSize, + MaxHTTP2InitialStreamWindowSize)) + default: + http2.InitialStreamWindowSize = ptr.To(uint32(initialStreamWindowSize)) + } + } + + if http2Settings.InitialConnectionWindowSize != nil { + initialConnectionWindowSize, ok := http2Settings.InitialConnectionWindowSize.AsInt64() + switch { + case !ok: + errs = errors.Join(errs, fmt.Errorf("invalid InitialConnectionWindowSize value %s", http2Settings.InitialConnectionWindowSize.String())) + case initialConnectionWindowSize < MinHTTP2InitialConnectionWindowSize || initialConnectionWindowSize > MaxHTTP2InitialConnectionWindowSize: + errs = errors.Join(errs, fmt.Errorf("InitialConnectionWindowSize value %s is out of range, must be between %d and %d", + http2Settings.InitialConnectionWindowSize.String(), + MinHTTP2InitialConnectionWindowSize, + MaxHTTP2InitialConnectionWindowSize)) + default: + http2.InitialConnectionWindowSize = ptr.To(uint32(initialConnectionWindowSize)) + } + } + + http2.MaxConcurrentStreams = http2Settings.MaxConcurrentStreams + + if http2Settings.OnInvalidMessage != nil { + switch *http2Settings.OnInvalidMessage { + case egv1a1.InvalidMessageActionTerminateStream: + http2.ResetStreamOnError = ptr.To(true) + case egv1a1.InvalidMessageActionTerminateConnection: + http2.ResetStreamOnError = ptr.To(false) + } + } + + if http2Settings.ConnectionKeepalive != nil { + keepalive := &ir.HTTP2ConnectionKeepalive{} + if http2Settings.ConnectionKeepalive.Interval != nil { + d, err := time.ParseDuration(string(*http2Settings.ConnectionKeepalive.Interval)) + if err != nil { + errs = errors.Join(errs, fmt.Errorf("invalid ConnectionKeepalive.Interval value %s: %w", + *http2Settings.ConnectionKeepalive.Interval, err)) + } else { + keepalive.Interval = ptr.To(uint32(d.Seconds())) + } + } + if http2Settings.ConnectionKeepalive.Timeout != nil { + d, err := time.ParseDuration(string(*http2Settings.ConnectionKeepalive.Timeout)) + if err != nil { + errs = errors.Join(errs, fmt.Errorf("invalid ConnectionKeepalive.Timeout value %s: %w", + *http2Settings.ConnectionKeepalive.Timeout, err)) + } else { + keepalive.Timeout = ptr.To(uint32(d.Seconds())) + } + } + if http2Settings.ConnectionKeepalive.ConnectionIdleInterval != nil { + d, err := time.ParseDuration(string(*http2Settings.ConnectionKeepalive.ConnectionIdleInterval)) + if err != nil { + errs = errors.Join(errs, fmt.Errorf("invalid ConnectionKeepalive.ConnectionIdleInterval value %s: %w", + *http2Settings.ConnectionKeepalive.ConnectionIdleInterval, err)) + } else { + keepalive.ConnectionIdleInterval = ptr.To(uint32(d.Seconds())) + } + } + http2.ConnectionKeepalive = keepalive + } + + return http2, errs +} diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.in.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.in.yaml new file mode 100644 index 0000000000..81ae627b56 --- /dev/null +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.in.yaml @@ -0,0 +1,32 @@ +clientTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: ClientTrafficPolicy + metadata: + namespace: envoy-gateway + name: target-gateway-1-section-http-1 + spec: + http2: + connectionKeepalive: + interval: 60s + timeout: 10s + connectionIdleInterval: 30s + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: http-1 +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-1 + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.out.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.out.yaml new file mode 100644 index 0000000000..3767fd456e --- /dev/null +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.out.yaml @@ -0,0 +1,144 @@ +clientTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: ClientTrafficPolicy + metadata: + name: target-gateway-1-section-http-1 + namespace: envoy-gateway + spec: + http2: + connectionKeepalive: + connectionIdleInterval: 30s + interval: 60s + timeout: 10s + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: http-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-1 + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: Same + name: http-1 + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 0 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http-1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http-1 + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + http2: + connectionKeepalive: + connectionIdleInterval: 30 + interval: 60 + timeout: 10 + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-1 + name: envoy-gateway/gateway-1/http-1 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 7b8f10eb1f..587bb2900d 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -616,6 +616,19 @@ type HTTP2Settings struct { MaxConcurrentStreams *uint32 `json:"maxConcurrentStreams,omitempty" yaml:"maxConcurrentStreams,omitempty"` // ResetStreamOnError determines if a stream or connection is reset on messaging error. ResetStreamOnError *bool `json:"resetStreamOnError,omitempty" yaml:"resetStreamOnError,omitempty"` + // ConnectionKeepalive configures HTTP/2 PING-based keepalive settings. + ConnectionKeepalive *HTTP2ConnectionKeepalive `json:"connectionKeepalive,omitempty" yaml:"connectionKeepalive,omitempty"` +} + +// HTTP2ConnectionKeepalive configures HTTP/2 PING-based keepalive settings. +// +k8s:deepcopy-gen=true +type HTTP2ConnectionKeepalive struct { + // Interval specifies how often to send HTTP/2 PING frames (in seconds). + Interval *uint32 `json:"interval,omitempty" yaml:"interval,omitempty"` + // Timeout specifies how long to wait for a PING response (in seconds). + Timeout *uint32 `json:"timeout,omitempty" yaml:"timeout,omitempty"` + // ConnectionIdleInterval specifies idle time before sending a PING (in seconds). + ConnectionIdleInterval *uint32 `json:"connectionIdleInterval,omitempty" yaml:"connectionIdleInterval,omitempty"` } // ResponseOverride defines the configuration to override specific responses with a custom one. diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index dc2b458f0c..ca2bfd0eda 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -1702,6 +1702,36 @@ func (in *HTTP1Settings) DeepCopy() *HTTP1Settings { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTP2ConnectionKeepalive) DeepCopyInto(out *HTTP2ConnectionKeepalive) { + *out = *in + if in.Interval != nil { + in, out := &in.Interval, &out.Interval + *out = new(uint32) + **out = **in + } + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(uint32) + **out = **in + } + if in.ConnectionIdleInterval != nil { + in, out := &in.ConnectionIdleInterval, &out.ConnectionIdleInterval + *out = new(uint32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP2ConnectionKeepalive. +func (in *HTTP2ConnectionKeepalive) DeepCopy() *HTTP2ConnectionKeepalive { + if in == nil { + return nil + } + out := new(HTTP2ConnectionKeepalive) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTP2Settings) DeepCopyInto(out *HTTP2Settings) { *out = *in @@ -1725,6 +1755,11 @@ func (in *HTTP2Settings) DeepCopyInto(out *HTTP2Settings) { *out = new(bool) **out = **in } + if in.ConnectionKeepalive != nil { + in, out := &in.ConnectionKeepalive, &out.ConnectionKeepalive + *out = new(HTTP2ConnectionKeepalive) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP2Settings. diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index 8e4be35e29..fa3dea7f66 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -11,6 +11,7 @@ import ( "net" "strconv" "strings" + "time" xdscore "github.com/cncf/xds/go/xds/core/v3" matcher "github.com/cncf/xds/go/xds/type/matcher/v3" @@ -110,6 +111,20 @@ func http2ProtocolOptions(opts *ir.HTTP2Settings) *corev3.Http2ProtocolOptions { } } + if opts.ConnectionKeepalive != nil { + keepalive := &corev3.KeepaliveSettings{} + if opts.ConnectionKeepalive.Interval != nil { + keepalive.Interval = durationpb.New(time.Duration(*opts.ConnectionKeepalive.Interval) * time.Second) + } + if opts.ConnectionKeepalive.Timeout != nil { + keepalive.Timeout = durationpb.New(time.Duration(*opts.ConnectionKeepalive.Timeout) * time.Second) + } + if opts.ConnectionKeepalive.ConnectionIdleInterval != nil { + keepalive.ConnectionIdleInterval = durationpb.New(time.Duration(*opts.ConnectionKeepalive.ConnectionIdleInterval) * time.Second) + } + out.ConnectionKeepalive = keepalive + } + return out } diff --git a/internal/xds/translator/testdata/in/xds-ir/http2-keepalive.yaml b/internal/xds/translator/testdata/in/xds-ir/http2-keepalive.yaml new file mode 100644 index 0000000000..b8a1d99182 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/http2-keepalive.yaml @@ -0,0 +1,27 @@ +http: +- name: "first-listener" + address: "::" + port: 10080 + hostnames: + - "foo.com" + path: + mergeSlashes: true + escapedSlashesAction: UnescapeAndRedirect + http2: + initialConnectionWindowSize: 65536 + initialStreamWindowSize: 33554432 + maxConcurrentStreams: 200 + connectionKeepalive: + interval: 60 + timeout: 10 + connectionIdleInterval: 30 + routes: + - name: "first-route" + hostname: "*" + destination: + name: "first-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + name: "first-route-dest/backend/0" diff --git a/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.clusters.yaml new file mode 100644 index 0000000000..054f90bb71 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.clusters.yaml @@ -0,0 +1,24 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: first-route-dest + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: first-route-dest + perConnectionBufferLimitBytes: 32768 + type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.endpoints.yaml new file mode 100644 index 0000000000..3b3f2d0907 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.endpoints.yaml @@ -0,0 +1,12 @@ +- clusterName: first-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: first-route-dest/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.listeners.yaml new file mode 100644 index 0000000000..1970b887b1 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.listeners.yaml @@ -0,0 +1,39 @@ +- address: + socketAddress: + address: '::' + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + connectionKeepalive: + connectionIdleInterval: 30s + interval: 60s + timeout: 10s + initialConnectionWindowSize: 33554432 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 200 + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: first-listener + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10080 + useRemoteAddress: true + name: first-listener + maxConnectionsToAcceptPerSocketEvent: 1 + name: first-listener + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.routes.yaml new file mode 100644 index 0000000000..0b5b4bee7b --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.routes.yaml @@ -0,0 +1,14 @@ +- ignorePortInHostMatching: true + name: first-listener + virtualHosts: + - domains: + - '*' + name: first-listener/* + routes: + - match: + prefix: / + name: first-route + route: + cluster: first-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 1faf4c4c4c..368d776c6c 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -9,6 +9,7 @@ security updates: | # New features or capabilities added in this release. new features: | Added support for configuring optional health check configuration. + Added HTTP/2 connection keepalive support to ClientTrafficPolicy. bug fixes: | Rejected ClientTrafficPolicy if invalid TLS cipher suites are configured. diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index b4e5822d70..913dc9e622 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -800,7 +800,7 @@ _Appears in:_ | `timeout` | _[ClientTimeout](#clienttimeout)_ | false | | Timeout settings for the client connections. | | `connection` | _[ClientConnection](#clientconnection)_ | false | | Connection includes client connection settings. | | `http1` | _[HTTP1Settings](#http1settings)_ | false | | HTTP1 provides HTTP/1 configuration on the listener. | -| `http2` | _[HTTP2Settings](#http2settings)_ | false | | HTTP2 provides HTTP/2 configuration on the listener. | +| `http2` | _[HTTP2ClientSettings](#http2clientsettings)_ | false | | HTTP2 provides HTTP/2 configuration on the listener. | | `http3` | _[HTTP3Settings](#http3settings)_ | false | | HTTP3 provides HTTP/3 configuration on the listener. | | `healthCheck` | _[HealthCheckSettings](#healthchecksettings)_ | false | | HealthCheck provides configuration for determining whether the HTTP/HTTPS listener is healthy. | | `scheme` | _[SchemeHeaderTransform](#schemeheadertransform)_ | false | | Scheme configures how the :scheme pseudo-header is set for requests forwarded to backends.
- Preserve (default): Preserves the :scheme from the original client request.
Use this when backends need to know the original client scheme for URL generation or redirects.
- MatchBackend: Sets the :scheme to match the backend transport protocol.
If the backend uses TLS, the scheme is "https", otherwise "http".
Use this when backends require the scheme to match the actual transport protocol,
such as strictly HTTPS services that validate the :scheme header. | @@ -2386,6 +2386,40 @@ _Appears in:_ | `disableSafeMaxConnectionDuration` | _boolean_ | false | | DisableSafeMaxConnectionDuration controls the close behavior for HTTP/1 connections.
By default, connection closure is delayed until the next request arrives after maxConnectionDuration is exceeded.
It then adds a Connection: close header and gracefully closes the connection after the response completes.
When set to true (disabled), Envoy uses its default drain behavior, closing the connection shortly after maxConnectionDuration elapses.
Has no effect unless maxConnectionDuration is set. | +#### HTTP2ClientSettings + + + +HTTP2ClientSettings provides HTTP/2 configuration for client connections to the listener. + +_Appears in:_ +- [ClientTrafficPolicySpec](#clienttrafficpolicyspec) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `initialStreamWindowSize` | _[Quantity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#quantity-resource-api)_ | false | | InitialStreamWindowSize sets the initial window size for HTTP/2 streams.
If not set, the default value is 64 KiB(64*1024). | +| `initialConnectionWindowSize` | _[Quantity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#quantity-resource-api)_ | false | | InitialConnectionWindowSize sets the initial window size for HTTP/2 connections.
If not set, the default value is 1 MiB. | +| `maxConcurrentStreams` | _integer_ | false | | MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection.
If not set, the default value is 100. | +| `onInvalidMessage` | _[InvalidMessageAction](#invalidmessageaction)_ | false | | OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error
It's recommended for L2 Envoy deployments to set this value to TerminateStream.
https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two
Default: TerminateConnection | +| `connectionKeepalive` | _[HTTP2ConnectionKeepalive](#http2connectionkeepalive)_ | false | | ConnectionKeepalive configures HTTP/2 connection keepalive using PING frames. | + + +#### HTTP2ConnectionKeepalive + + + +HTTP2ConnectionKeepalive configures HTTP/2 PING-based keepalive settings. + +_Appears in:_ +- [HTTP2ClientSettings](#http2clientsettings) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `interval` | _[Duration](https://gateway-api.sigs.k8s.io/reference/1.4/spec/#duration)_ | false | | Interval specifies how often to send HTTP/2 PING frames to keep the connection alive.
If not set, PING frames will not be sent periodically. | +| `timeout` | _[Duration](https://gateway-api.sigs.k8s.io/reference/1.4/spec/#duration)_ | false | | Timeout specifies how long to wait for a PING response before considering the connection dead.
If not set, a default timeout is used. | +| `connectionIdleInterval` | _[Duration](https://gateway-api.sigs.k8s.io/reference/1.4/spec/#duration)_ | false | | ConnectionIdleInterval specifies how long a connection must be idle before a PING is sent
to check if the connection is still alive.
If not set, idle connection checks are not performed. | + + #### HTTP2Settings @@ -2394,7 +2428,6 @@ HTTP2Settings provides HTTP/2 configuration for listeners and backends. _Appears in:_ - [BackendTrafficPolicySpec](#backendtrafficpolicyspec) -- [ClientTrafficPolicySpec](#clienttrafficpolicyspec) - [ClusterSettings](#clustersettings) | Field | Type | Required | Default | Description | @@ -2925,6 +2958,7 @@ _Underlying type:_ _string_ _Appears in:_ +- [HTTP2ClientSettings](#http2clientsettings) - [HTTP2Settings](#http2settings) | Value | Description | From e8624cad0edbfcb20e657689a27b11ee5ea95c31 Mon Sep 17 00:00:00 2001 From: Rajat Vig Date: Sun, 8 Feb 2026 22:56:08 +0000 Subject: [PATCH 2/5] fix: fix compile issues, reduce duplication, add generated code Signed-off-by: Rajat Vig --- api/v1alpha1/clienttrafficpolicy_types.go | 32 +---------- api/v1alpha1/zz_generated.deepcopy.go | 21 +------ internal/gatewayapi/http.go | 57 +++---------------- site/content/en/latest/api/extension_types.md | 1 + .../clienttrafficpolicy_test.go | 4 +- test/helm/gateway-crds-helm/all.out.yaml | 24 ++++++++ .../envoy-gateway-crds.out.yaml | 24 ++++++++ 7 files changed, 62 insertions(+), 101 deletions(-) diff --git a/api/v1alpha1/clienttrafficpolicy_types.go b/api/v1alpha1/clienttrafficpolicy_types.go index 4a01cdf637..4d551c6e74 100644 --- a/api/v1alpha1/clienttrafficpolicy_types.go +++ b/api/v1alpha1/clienttrafficpolicy_types.go @@ -6,7 +6,6 @@ package v1alpha1 import ( - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" ) @@ -443,35 +442,8 @@ const ( // HTTP2ClientSettings provides HTTP/2 configuration for client connections to the listener. type HTTP2ClientSettings struct { - // InitialStreamWindowSize sets the initial window size for HTTP/2 streams. - // If not set, the default value is 64 KiB(64*1024). - // - // +kubebuilder:validation:XIntOrString - // +kubebuilder:validation:Pattern="^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$" - // +optional - InitialStreamWindowSize *resource.Quantity `json:"initialStreamWindowSize,omitempty"` - - // InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. - // If not set, the default value is 1 MiB. - // - // +kubebuilder:validation:XIntOrString - // +kubebuilder:validation:Pattern="^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$" - // +optional - InitialConnectionWindowSize *resource.Quantity `json:"initialConnectionWindowSize,omitempty"` - - // MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. - // If not set, the default value is 100. - // +kubebuilder:validation:Minimum=1 - // +kubebuilder:validation:Maximum=2147483647 - // +optional - MaxConcurrentStreams *uint32 `json:"maxConcurrentStreams,omitempty"` - - // OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error - // It's recommended for L2 Envoy deployments to set this value to TerminateStream. - // https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two - // Default: TerminateConnection - // +optional - OnInvalidMessage *InvalidMessageAction `json:"onInvalidMessage,omitempty"` + // Embed shared HTTP/2 settings. + HTTP2Settings `json:",inline"` // ConnectionKeepalive configures HTTP/2 connection keepalive using PING frames. // +optional diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ec2f7d3871..38ba279256 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3494,26 +3494,7 @@ func (in *HTTP1Settings) DeepCopy() *HTTP1Settings { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTP2ClientSettings) DeepCopyInto(out *HTTP2ClientSettings) { *out = *in - if in.InitialStreamWindowSize != nil { - in, out := &in.InitialStreamWindowSize, &out.InitialStreamWindowSize - x := (*in).DeepCopy() - *out = &x - } - if in.InitialConnectionWindowSize != nil { - in, out := &in.InitialConnectionWindowSize, &out.InitialConnectionWindowSize - x := (*in).DeepCopy() - *out = &x - } - if in.MaxConcurrentStreams != nil { - in, out := &in.MaxConcurrentStreams, &out.MaxConcurrentStreams - *out = new(uint32) - **out = **in - } - if in.OnInvalidMessage != nil { - in, out := &in.OnInvalidMessage, &out.OnInvalidMessage - *out = new(InvalidMessageAction) - **out = **in - } + in.HTTP2Settings.DeepCopyInto(&out.HTTP2Settings) if in.ConnectionKeepalive != nil { in, out := &in.ConnectionKeepalive, &out.ConnectionKeepalive *out = new(HTTP2ConnectionKeepalive) diff --git a/internal/gatewayapi/http.go b/internal/gatewayapi/http.go index 8a09d5ad2a..c74d37d4d8 100644 --- a/internal/gatewayapi/http.go +++ b/internal/gatewayapi/http.go @@ -80,59 +80,20 @@ func buildIRHTTP2ClientSettings(http2Settings *egv1a1.HTTP2ClientSettings) (*ir. if http2Settings == nil { return nil, nil } - var ( - http2 = &ir.HTTP2Settings{} - errs error - ) - - if http2Settings.InitialStreamWindowSize != nil { - initialStreamWindowSize, ok := http2Settings.InitialStreamWindowSize.AsInt64() - switch { - case !ok: - errs = errors.Join(errs, fmt.Errorf("invalid InitialStreamWindowSize value %s", http2Settings.InitialStreamWindowSize.String())) - case initialStreamWindowSize < MinHTTP2InitialStreamWindowSize || initialStreamWindowSize > MaxHTTP2InitialStreamWindowSize: - errs = errors.Join(errs, fmt.Errorf("InitialStreamWindowSize value %s is out of range, must be between %d and %d", - http2Settings.InitialStreamWindowSize.String(), - MinHTTP2InitialStreamWindowSize, - MaxHTTP2InitialStreamWindowSize)) - default: - http2.InitialStreamWindowSize = ptr.To(uint32(initialStreamWindowSize)) - } - } - - if http2Settings.InitialConnectionWindowSize != nil { - initialConnectionWindowSize, ok := http2Settings.InitialConnectionWindowSize.AsInt64() - switch { - case !ok: - errs = errors.Join(errs, fmt.Errorf("invalid InitialConnectionWindowSize value %s", http2Settings.InitialConnectionWindowSize.String())) - case initialConnectionWindowSize < MinHTTP2InitialConnectionWindowSize || initialConnectionWindowSize > MaxHTTP2InitialConnectionWindowSize: - errs = errors.Join(errs, fmt.Errorf("InitialConnectionWindowSize value %s is out of range, must be between %d and %d", - http2Settings.InitialConnectionWindowSize.String(), - MinHTTP2InitialConnectionWindowSize, - MaxHTTP2InitialConnectionWindowSize)) - default: - http2.InitialConnectionWindowSize = ptr.To(uint32(initialConnectionWindowSize)) - } - } - - http2.MaxConcurrentStreams = http2Settings.MaxConcurrentStreams - if http2Settings.OnInvalidMessage != nil { - switch *http2Settings.OnInvalidMessage { - case egv1a1.InvalidMessageActionTerminateStream: - http2.ResetStreamOnError = ptr.To(true) - case egv1a1.InvalidMessageActionTerminateConnection: - http2.ResetStreamOnError = ptr.To(false) - } + // Reuse shared builder for common fields + http2, errs := buildIRHTTP2Settings(&http2Settings.HTTP2Settings) + if http2 == nil { + http2 = &ir.HTTP2Settings{} } + // Handle keepalive (ClientTrafficPolicy-specific) if http2Settings.ConnectionKeepalive != nil { keepalive := &ir.HTTP2ConnectionKeepalive{} if http2Settings.ConnectionKeepalive.Interval != nil { d, err := time.ParseDuration(string(*http2Settings.ConnectionKeepalive.Interval)) if err != nil { - errs = errors.Join(errs, fmt.Errorf("invalid ConnectionKeepalive.Interval value %s: %w", - *http2Settings.ConnectionKeepalive.Interval, err)) + errs = errors.Join(errs, fmt.Errorf("invalid ConnectionKeepalive.Interval: %w", err)) } else { keepalive.Interval = ptr.To(uint32(d.Seconds())) } @@ -140,8 +101,7 @@ func buildIRHTTP2ClientSettings(http2Settings *egv1a1.HTTP2ClientSettings) (*ir. if http2Settings.ConnectionKeepalive.Timeout != nil { d, err := time.ParseDuration(string(*http2Settings.ConnectionKeepalive.Timeout)) if err != nil { - errs = errors.Join(errs, fmt.Errorf("invalid ConnectionKeepalive.Timeout value %s: %w", - *http2Settings.ConnectionKeepalive.Timeout, err)) + errs = errors.Join(errs, fmt.Errorf("invalid ConnectionKeepalive.Timeout: %w", err)) } else { keepalive.Timeout = ptr.To(uint32(d.Seconds())) } @@ -149,8 +109,7 @@ func buildIRHTTP2ClientSettings(http2Settings *egv1a1.HTTP2ClientSettings) (*ir. if http2Settings.ConnectionKeepalive.ConnectionIdleInterval != nil { d, err := time.ParseDuration(string(*http2Settings.ConnectionKeepalive.ConnectionIdleInterval)) if err != nil { - errs = errors.Join(errs, fmt.Errorf("invalid ConnectionKeepalive.ConnectionIdleInterval value %s: %w", - *http2Settings.ConnectionKeepalive.ConnectionIdleInterval, err)) + errs = errors.Join(errs, fmt.Errorf("invalid ConnectionKeepalive.ConnectionIdleInterval: %w", err)) } else { keepalive.ConnectionIdleInterval = ptr.To(uint32(d.Seconds())) } diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 913dc9e622..b3cd0d8ef0 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -2429,6 +2429,7 @@ HTTP2Settings provides HTTP/2 configuration for listeners and backends. _Appears in:_ - [BackendTrafficPolicySpec](#backendtrafficpolicyspec) - [ClusterSettings](#clustersettings) +- [HTTP2ClientSettings](#http2clientsettings) | Field | Type | Required | Default | Description | | --- | --- | --- | --- | --- | diff --git a/test/cel-validation/clienttrafficpolicy_test.go b/test/cel-validation/clienttrafficpolicy_test.go index ab28d5142c..4ed061675d 100644 --- a/test/cel-validation/clienttrafficpolicy_test.go +++ b/test/cel-validation/clienttrafficpolicy_test.go @@ -465,7 +465,7 @@ func TestClientTrafficPolicyTarget(t *testing.T) { }, }, }, - HTTP2: &egv1a1.HTTP2Settings{ + HTTP2: &egv1a1.HTTP2ClientSettings{ InitialStreamWindowSize: ptr.To(resource.MustParse("15m")), }, } @@ -487,7 +487,7 @@ func TestClientTrafficPolicyTarget(t *testing.T) { }, }, }, - HTTP2: &egv1a1.HTTP2Settings{ + HTTP2: &egv1a1.HTTP2ClientSettings{ InitialConnectionWindowSize: ptr.To(resource.MustParse("15m")), }, } diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 8ad08a02bb..a3fa36fcce 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -25095,6 +25095,30 @@ spec: http2: description: HTTP2 provides HTTP/2 configuration on the listener. properties: + connectionKeepalive: + description: ConnectionKeepalive configures HTTP/2 connection + keepalive using PING frames. + properties: + connectionIdleInterval: + description: |- + ConnectionIdleInterval specifies how long a connection must be idle before a PING is sent + to check if the connection is still alive. + If not set, idle connection checks are not performed. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + Interval specifies how often to send HTTP/2 PING frames to keep the connection alive. + If not set, PING frames will not be sent periodically. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + timeout: + description: |- + Timeout specifies how long to wait for a PING response before considering the connection dead. + If not set, a default timeout is used. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object initialConnectionWindowSize: allOf: - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index ebb016d68c..2d75297d71 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -4275,6 +4275,30 @@ spec: http2: description: HTTP2 provides HTTP/2 configuration on the listener. properties: + connectionKeepalive: + description: ConnectionKeepalive configures HTTP/2 connection + keepalive using PING frames. + properties: + connectionIdleInterval: + description: |- + ConnectionIdleInterval specifies how long a connection must be idle before a PING is sent + to check if the connection is still alive. + If not set, idle connection checks are not performed. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + Interval specifies how often to send HTTP/2 PING frames to keep the connection alive. + If not set, PING frames will not be sent periodically. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + timeout: + description: |- + Timeout specifies how long to wait for a PING response before considering the connection dead. + If not set, a default timeout is used. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object initialConnectionWindowSize: allOf: - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ From 7f00be1c64f7fe4e82364fa05451111778ea09da Mon Sep 17 00:00:00 2001 From: Rajat Vig Date: Sun, 8 Feb 2026 23:04:02 +0000 Subject: [PATCH 3/5] fix: embed is a different include Signed-off-by: Rajat Vig --- test/cel-validation/clienttrafficpolicy_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/cel-validation/clienttrafficpolicy_test.go b/test/cel-validation/clienttrafficpolicy_test.go index 4ed061675d..97e2fab549 100644 --- a/test/cel-validation/clienttrafficpolicy_test.go +++ b/test/cel-validation/clienttrafficpolicy_test.go @@ -466,7 +466,9 @@ func TestClientTrafficPolicyTarget(t *testing.T) { }, }, HTTP2: &egv1a1.HTTP2ClientSettings{ - InitialStreamWindowSize: ptr.To(resource.MustParse("15m")), + HTTP2Settings: egv1a1.HTTP2Settings{ + InitialStreamWindowSize: ptr.To(resource.MustParse("15m")), + }, }, } }, @@ -488,7 +490,9 @@ func TestClientTrafficPolicyTarget(t *testing.T) { }, }, HTTP2: &egv1a1.HTTP2ClientSettings{ - InitialConnectionWindowSize: ptr.To(resource.MustParse("15m")), + HTTP2Settings: egv1a1.HTTP2Settings{ + InitialConnectionWindowSize: ptr.To(resource.MustParse("15m")), + }, }, } }, From 3ec7a3c7634173398228ee1a464bb8a830e29307 Mon Sep 17 00:00:00 2001 From: Rajat Vig Date: Mon, 9 Feb 2026 01:01:21 +0000 Subject: [PATCH 4/5] address feedback Signed-off-by: Rajat Vig --- api/v1alpha1/clienttrafficpolicy_types.go | 7 + api/v1alpha1/zz_generated.deepcopy.go | 5 + ...y.envoyproxy.io_clienttrafficpolicies.yaml | 8 + ...y.envoyproxy.io_clienttrafficpolicies.yaml | 8 + internal/gatewayapi/http.go | 7 +- ...cy-http2-keepalive-invalid-timeout.in.yaml | 31 ++++ ...y-http2-keepalive-invalid-timeout.out.yaml | 138 ++++++++++++++++++ ...lienttrafficpolicy-http2-keepalive.in.yaml | 1 + ...ienttrafficpolicy-http2-keepalive.out.yaml | 2 + internal/ir/xds.go | 2 + internal/ir/zz_generated.deepcopy.go | 5 + internal/xds/translator/listener.go | 3 + .../testdata/in/xds-ir/http2-keepalive.yaml | 1 + .../out/xds-ir/http2-keepalive.listeners.yaml | 2 + site/content/en/latest/api/extension_types.md | 1 + 15 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive-invalid-timeout.in.yaml create mode 100644 internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive-invalid-timeout.out.yaml diff --git a/api/v1alpha1/clienttrafficpolicy_types.go b/api/v1alpha1/clienttrafficpolicy_types.go index 4d551c6e74..5c9a31cf5e 100644 --- a/api/v1alpha1/clienttrafficpolicy_types.go +++ b/api/v1alpha1/clienttrafficpolicy_types.go @@ -462,6 +462,13 @@ type HTTP2ConnectionKeepalive struct { // +optional Timeout *gwapiv1.Duration `json:"timeout,omitempty"` + // IntervalJitter specifies a random jitter percentage added to each interval. + // Defaults to 15% if not specified. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=100 + // +optional + IntervalJitter *uint32 `json:"intervalJitter,omitempty"` + // ConnectionIdleInterval specifies how long a connection must be idle before a PING is sent // to check if the connection is still alive. // If not set, idle connection checks are not performed. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 38ba279256..740c1fea1f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3525,6 +3525,11 @@ func (in *HTTP2ConnectionKeepalive) DeepCopyInto(out *HTTP2ConnectionKeepalive) *out = new(v1.Duration) **out = **in } + if in.IntervalJitter != nil { + in, out := &in.IntervalJitter, &out.IntervalJitter + *out = new(uint32) + **out = **in + } if in.ConnectionIdleInterval != nil { in, out := &in.ConnectionIdleInterval, &out.ConnectionIdleInterval *out = new(v1.Duration) diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml index f419ca457b..6d2a254080 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml @@ -816,6 +816,14 @@ spec: If not set, PING frames will not be sent periodically. pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ type: string + intervalJitter: + description: |- + IntervalJitter specifies a random jitter percentage added to each interval. + Defaults to 15% if not specified. + format: int32 + maximum: 100 + minimum: 0 + type: integer timeout: description: |- Timeout specifies how long to wait for a PING response before considering the connection dead. diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml index 46b8a4b3f9..d32d718008 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml @@ -815,6 +815,14 @@ spec: If not set, PING frames will not be sent periodically. pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ type: string + intervalJitter: + description: |- + IntervalJitter specifies a random jitter percentage added to each interval. + Defaults to 15% if not specified. + format: int32 + maximum: 100 + minimum: 0 + type: integer timeout: description: |- Timeout specifies how long to wait for a PING response before considering the connection dead. diff --git a/internal/gatewayapi/http.go b/internal/gatewayapi/http.go index c74d37d4d8..f8316c7522 100644 --- a/internal/gatewayapi/http.go +++ b/internal/gatewayapi/http.go @@ -87,7 +87,6 @@ func buildIRHTTP2ClientSettings(http2Settings *egv1a1.HTTP2ClientSettings) (*ir. http2 = &ir.HTTP2Settings{} } - // Handle keepalive (ClientTrafficPolicy-specific) if http2Settings.ConnectionKeepalive != nil { keepalive := &ir.HTTP2ConnectionKeepalive{} if http2Settings.ConnectionKeepalive.Interval != nil { @@ -106,6 +105,12 @@ func buildIRHTTP2ClientSettings(http2Settings *egv1a1.HTTP2ClientSettings) (*ir. keepalive.Timeout = ptr.To(uint32(d.Seconds())) } } + if keepalive.Interval != nil && keepalive.Timeout != nil && *keepalive.Timeout >= *keepalive.Interval { + errs = errors.Join(errs, fmt.Errorf("ConnectionKeepalive.Timeout must be less than Interval")) + } + if http2Settings.ConnectionKeepalive.IntervalJitter != nil { + keepalive.IntervalJitter = http2Settings.ConnectionKeepalive.IntervalJitter + } if http2Settings.ConnectionKeepalive.ConnectionIdleInterval != nil { d, err := time.ParseDuration(string(*http2Settings.ConnectionKeepalive.ConnectionIdleInterval)) if err != nil { diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive-invalid-timeout.in.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive-invalid-timeout.in.yaml new file mode 100644 index 0000000000..af370c367c --- /dev/null +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive-invalid-timeout.in.yaml @@ -0,0 +1,31 @@ +clientTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: ClientTrafficPolicy + metadata: + namespace: envoy-gateway + name: target-gateway-1-section-http-1 + spec: + http2: + connectionKeepalive: + interval: 10s + timeout: 60s + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: http-1 +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-1 + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive-invalid-timeout.out.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive-invalid-timeout.out.yaml new file mode 100644 index 0000000000..0d3a5ea81d --- /dev/null +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive-invalid-timeout.out.yaml @@ -0,0 +1,138 @@ +clientTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: ClientTrafficPolicy + metadata: + name: target-gateway-1-section-http-1 + namespace: envoy-gateway + spec: + http2: + connectionKeepalive: + interval: 10s + timeout: 60s + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: http-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-1 + conditions: + - lastTransitionTime: null + message: 'HTTP2: ConnectionKeepalive.Timeout must be less than Interval.' + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: Same + name: http-1 + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 0 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http-1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http-1 + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-1 + name: envoy-gateway/gateway-1/http-1 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.in.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.in.yaml index 81ae627b56..1fe936bce7 100644 --- a/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.in.yaml +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.in.yaml @@ -9,6 +9,7 @@ clientTrafficPolicies: connectionKeepalive: interval: 60s timeout: 10s + intervalJitter: 20 connectionIdleInterval: 30s targetRef: group: gateway.networking.k8s.io diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.out.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.out.yaml index 3767fd456e..4a7902d104 100644 --- a/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.out.yaml +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-http2-keepalive.out.yaml @@ -9,6 +9,7 @@ clientTrafficPolicies: connectionKeepalive: connectionIdleInterval: 30s interval: 60s + intervalJitter: 20 timeout: 10s targetRef: group: gateway.networking.k8s.io @@ -125,6 +126,7 @@ xdsIR: connectionKeepalive: connectionIdleInterval: 30 interval: 60 + intervalJitter: 20 timeout: 10 isHTTP2: false metadata: diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 587bb2900d..a902416923 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -627,6 +627,8 @@ type HTTP2ConnectionKeepalive struct { Interval *uint32 `json:"interval,omitempty" yaml:"interval,omitempty"` // Timeout specifies how long to wait for a PING response (in seconds). Timeout *uint32 `json:"timeout,omitempty" yaml:"timeout,omitempty"` + // IntervalJitter specifies a random jitter percentage added to each interval (0-100). + IntervalJitter *uint32 `json:"intervalJitter,omitempty" yaml:"intervalJitter,omitempty"` // ConnectionIdleInterval specifies idle time before sending a PING (in seconds). ConnectionIdleInterval *uint32 `json:"connectionIdleInterval,omitempty" yaml:"connectionIdleInterval,omitempty"` } diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index ca2bfd0eda..130ac7b8c2 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -1715,6 +1715,11 @@ func (in *HTTP2ConnectionKeepalive) DeepCopyInto(out *HTTP2ConnectionKeepalive) *out = new(uint32) **out = **in } + if in.IntervalJitter != nil { + in, out := &in.IntervalJitter, &out.IntervalJitter + *out = new(uint32) + **out = **in + } if in.ConnectionIdleInterval != nil { in, out := &in.ConnectionIdleInterval, &out.ConnectionIdleInterval *out = new(uint32) diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index fa3dea7f66..f866caf0ed 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -119,6 +119,9 @@ func http2ProtocolOptions(opts *ir.HTTP2Settings) *corev3.Http2ProtocolOptions { if opts.ConnectionKeepalive.Timeout != nil { keepalive.Timeout = durationpb.New(time.Duration(*opts.ConnectionKeepalive.Timeout) * time.Second) } + if opts.ConnectionKeepalive.IntervalJitter != nil { + keepalive.IntervalJitter = &typev3.Percent{Value: float64(*opts.ConnectionKeepalive.IntervalJitter)} + } if opts.ConnectionKeepalive.ConnectionIdleInterval != nil { keepalive.ConnectionIdleInterval = durationpb.New(time.Duration(*opts.ConnectionKeepalive.ConnectionIdleInterval) * time.Second) } diff --git a/internal/xds/translator/testdata/in/xds-ir/http2-keepalive.yaml b/internal/xds/translator/testdata/in/xds-ir/http2-keepalive.yaml index b8a1d99182..b539ec8d5b 100644 --- a/internal/xds/translator/testdata/in/xds-ir/http2-keepalive.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/http2-keepalive.yaml @@ -14,6 +14,7 @@ http: connectionKeepalive: interval: 60 timeout: 10 + intervalJitter: 20 connectionIdleInterval: 30 routes: - name: "first-route" diff --git a/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.listeners.yaml index 1970b887b1..00964415dd 100644 --- a/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/http2-keepalive.listeners.yaml @@ -13,6 +13,8 @@ connectionKeepalive: connectionIdleInterval: 30s interval: 60s + intervalJitter: + value: 20 timeout: 10s initialConnectionWindowSize: 33554432 initialStreamWindowSize: 65536 diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index b3cd0d8ef0..29c6e848db 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -2417,6 +2417,7 @@ _Appears in:_ | --- | --- | --- | --- | --- | | `interval` | _[Duration](https://gateway-api.sigs.k8s.io/reference/1.4/spec/#duration)_ | false | | Interval specifies how often to send HTTP/2 PING frames to keep the connection alive.
If not set, PING frames will not be sent periodically. | | `timeout` | _[Duration](https://gateway-api.sigs.k8s.io/reference/1.4/spec/#duration)_ | false | | Timeout specifies how long to wait for a PING response before considering the connection dead.
If not set, a default timeout is used. | +| `intervalJitter` | _integer_ | false | | IntervalJitter specifies a random jitter percentage added to each interval.
Defaults to 15% if not specified. | | `connectionIdleInterval` | _[Duration](https://gateway-api.sigs.k8s.io/reference/1.4/spec/#duration)_ | false | | ConnectionIdleInterval specifies how long a connection must be idle before a PING is sent
to check if the connection is still alive.
If not set, idle connection checks are not performed. | From 311a277e0ad3a36fe78880391acfccefea73cb70 Mon Sep 17 00:00:00 2001 From: Rajat Vig Date: Mon, 9 Feb 2026 01:02:20 +0000 Subject: [PATCH 5/5] add generated code Signed-off-by: Rajat Vig --- test/helm/gateway-crds-helm/all.out.yaml | 8 ++++++++ test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index a3fa36fcce..ecf0ffcc49 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -25112,6 +25112,14 @@ spec: If not set, PING frames will not be sent periodically. pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ type: string + intervalJitter: + description: |- + IntervalJitter specifies a random jitter percentage added to each interval. + Defaults to 15% if not specified. + format: int32 + maximum: 100 + minimum: 0 + type: integer timeout: description: |- Timeout specifies how long to wait for a PING response before considering the connection dead. diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index 2d75297d71..951cd52181 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -4292,6 +4292,14 @@ spec: If not set, PING frames will not be sent periodically. pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ type: string + intervalJitter: + description: |- + IntervalJitter specifies a random jitter percentage added to each interval. + Defaults to 15% if not specified. + format: int32 + maximum: 100 + minimum: 0 + type: integer timeout: description: |- Timeout specifies how long to wait for a PING response before considering the connection dead.