Repository: envoyproxy/gateway
Pull request #3437 (2024-05-20 02:56:27)

chore: enable gci in golangci-lint
we have gci settings in https://github.com/envoyproxy/gateway/blob/e847d68c2665d471912d66250990937b73787dc6/tools/linter/golangci-lint/.golangci.yml#L39,

but this setting is somehow not be enabled.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/3437?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 67.38%. Comparing base [(`e847d68`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/e847d68c2665d471912d66250990937b73787dc6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`54603d8`)](https://app.codecov.io/gh/envoyproxy/gateway/pull/3437?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #3437      +/-   ##
==========================================
- Coverage   67.41%   67.38%   -0.03%     
==========================================
  Files         166      166              
  Lines       19184    19184              
==========================================
- Hits        12932    12928       -4     
- Misses       5322     5324       +2     
- Partials      930      932       +2     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/3437?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).


Patch:
diff --git a/api/v1alpha1/clienttrafficpolicy_types.go b/api/v1alpha1/clienttrafficpolicy_types.go
index 440c01c6c..ea9d79018 100644
--- a/api/v1alpha1/clienttrafficpolicy_types.go
+++ b/api/v1alpha1/clienttrafficpolicy_types.go
@@ -108,6 +108,13 @@ type HeaderSettings struct {
 	// is encountered. The default action is to reject the request.
 	// +optional
 	WithUnderscoresAction *WithUnderscoresAction `json:"withUnderscoresAction,omitempty"`
+
+	// PreserveXRequestID configures Envoy to keep the X-Request-ID header if passed for a request that is edge
+	// (Edge request is the request from external clients to front Envoy) and not reset it, which is the current Envoy behaviour.
+	// It defaults to false.
+	//
+	// +optional
+	PreserveXRequestID *bool `json:"preserveXRequestID,omitempty"`
 }
 
 // WithUnderscoresAction configures the action to take when an HTTP header with underscores
diff --git a/api/v1alpha1/envoyproxy_helpers.go b/api/v1alpha1/envoyproxy_helpers.go
index 2bcc086cb..03f50d9c9 100644
--- a/api/v1alpha1/envoyproxy_helpers.go
+++ b/api/v1alpha1/envoyproxy_helpers.go
@@ -57,6 +57,23 @@ func DefaultEnvoyProxyHpaMetrics() []autoscalingv2.MetricSpec {
 	}
 }
 
+// NeedToSwitchPorts returns true if the EnvoyProxy needs to switch ports.
+func (e *EnvoyProxy) NeedToSwitchPorts() bool {
+	if e.Spec.Provider == nil {
+		return true
+	}
+
+	if e.Spec.Provider.Kubernetes == nil {
+		return true
+	}
+
+	if e.Spec.Provider.Kubernetes.UseListenerPortAsContainerPort == nil {
+		return true
+	}
+
+	return !*e.Spec.Provider.Kubernetes.UseListenerPortAsContainerPort
+}
+
 // GetEnvoyProxyKubeProvider returns the EnvoyProxyKubernetesProvider of EnvoyProxyProvider or
 // a default EnvoyProxyKubernetesProvider if unspecified. If EnvoyProxyProvider is not of
 // type "Kubernetes", a nil EnvoyProxyKubernetesProvider is returned.
diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go
index d8afe76ca..8ccf37460 100644
--- a/api/v1alpha1/envoyproxy_types.go
+++ b/api/v1alpha1/envoyproxy_types.go
@@ -264,6 +264,13 @@ type EnvoyProxyKubernetesProvider struct {
 	//
 	// +optional
 	EnvoyHpa *KubernetesHorizontalPodAutoscalerSpec `json:"envoyHpa,omitempty"`
+
+	// UseListenerPortAsContainerPort disables the port shifting feature in the Envoy Proxy.
+	// When set to false (default value), if the service port is a privileged port (1-1023), add a constant to the value converting it into an ephemeral port.
+	// This allows the container to bind to the port without needing a CAP_NET_BIND_SERVICE capability.
+	//
+	// +optional
+	UseListenerPortAsContainerPort *bool `json:"useListenerPortAsContainerPort,omitempty"`
 }
 
 // ProxyLogging defines logging parameters for managed proxies.
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index fd607c16a..1456fde63 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -1646,6 +1646,11 @@ func (in *EnvoyProxyKubernetesProvider) DeepCopyInto(out *EnvoyProxyKubernetesPr
 		*out = new(KubernetesHorizontalPodAutoscalerSpec)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.UseListenerPortAsContainerPort != nil {
+		in, out := &in.UseListenerPortAsContainerPort, &out.UseListenerPortAsContainerPort
+		*out = new(bool)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyProxyKubernetesProvider.
@@ -2473,6 +2478,11 @@ func (in *HeaderSettings) DeepCopyInto(out *HeaderSettings) {
 		*out = new(WithUnderscoresAction)
 		**out = **in
 	}
+	if in.PreserveXRequestID != nil {
+		in, out := &in.PreserveXRequestID, &out.PreserveXRequestID
+		*out = new(bool)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderSettings.
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 a9d3fb95f..6d4805ca0 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
@@ -147,6 +147,12 @@ spec:
                       EnableEnvoyHeaders configures Envoy Proxy to add the "X-Envoy-" headers to requests
                       and responses.
                     type: boolean
+                  preserveXRequestID:
+                    description: |-
+                      PreserveXRequestID configures Envoy to keep the X-Request-ID header if passed for a request that is edge
+                      (Edge request is the request from external clients to front Envoy) and not reset it, which is the current Envoy behaviour.
+                      It defaults to false.
+                    type: boolean
                   withUnderscoresAction:
                     description: |-
                       WithUnderscoresAction configures the action to take when an HTTP header with underscores
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
index 0282aed70..08042e02d 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
@@ -9980,6 +9980,12 @@ spec:
                         - message: loadBalancerIP can only be set for LoadBalancer
                             type
                           rule: '!has(self.loadBalancerIP) || self.type == ''LoadBalancer'''
+                      useListenerPortAsContainerPort:
+                        description: |-
+                          UseListenerPortAsContainerPort disables the port shifting feature in the Envoy Proxy.
+                          When set to false (default value), if the service port is a privileged port (1-1023), add a constant to the value converting it into an ephemeral port.
+                          This allows the container to bind to the port without needing a CAP_NET_BIND_SERVICE capability.
+                        type: boolean
                     type: object
                     x-kubernetes-validations:
                     - message: only one of envoyDeployment or envoyDaemonSet can be
diff --git a/internal/extension/registry/extension_manager.go b/internal/extension/registry/extension_manager.go
index dd2170c5b..d00e51ce4 100644
--- a/internal/extension/registry/extension_manager.go
+++ b/internal/extension/registry/extension_manager.go
@@ -14,7 +14,6 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/credentials/insecure"
-
 	corev1 "k8s.io/api/core/v1"
 	k8scli "sigs.k8s.io/controller-runtime/pkg/client"
 	k8sclicfg "sigs.k8s.io/controller-runtime/pkg/client/config"
diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go
index 8cc054dee..f5465fcd7 100644
--- a/internal/gatewayapi/clienttrafficpolicy.go
+++ b/internal/gatewayapi/clienttrafficpolicy.go
@@ -547,6 +547,7 @@ func translateListenerHeaderSettings(headerSettings *egv1a1.HeaderSettings, http
 	httpIR.Headers = &ir.HeaderSettings{
 		EnableEnvoyHeaders:    ptr.Deref(headerSettings.EnableEnvoyHeaders, false),
 		WithUnderscoresAction: ir.WithUnderscoresAction(ptr.Deref(headerSettings.WithUnderscoresAction, egv1a1.WithUnderscoresActionRejectRequest)),
+		PreserveXRequestID:    ptr.Deref(headerSettings.PreserveXRequestID, false),
 	}
 }
 
diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go
index 2dfc04a18..0e2b6b45a 100644
--- a/internal/gatewayapi/helpers.go
+++ b/internal/gatewayapi/helpers.go
@@ -17,6 +17,7 @@ import (
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 	"sigs.k8s.io/gateway-api/apis/v1alpha2"
 
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 	"github.com/envoyproxy/gateway/internal/ir"
 	"github.com/envoyproxy/gateway/internal/utils"
 )
@@ -228,7 +229,13 @@ func GatewayClassOwnerLabel(name string) map[string]string {
 
 // servicePortToContainerPort translates a service port into an ephemeral
 // container port.
-func servicePortToContainerPort(servicePort int32) int32 {
+func servicePortToContainerPort(servicePort int32, envoyProxy *egv1a1.EnvoyProxy) int32 {
+	if envoyProxy != nil {
+		if !envoyProxy.NeedToSwitchPorts() {
+			return servicePort
+		}
+	}
+
 	// If the service port is a privileged port (1-1023)
 	// add a constant to the value converting it into an ephemeral port.
 	// This allows the container to bind to the port without needing a
diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go
index 62d60b75f..0eb383c90 100644
--- a/internal/gatewayapi/listener.go
+++ b/internal/gatewayapi/listener.go
@@ -95,7 +95,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap
 			}
 			// Add the listener to the Xds IR
 			servicePort := &protocolPort{protocol: listener.Protocol, port: int32(listener.Port)}
-			containerPort := servicePortToContainerPort(servicePort.port)
+			containerPort := servicePortToContainerPort(int32(listener.Port), resources.EnvoyProxy)
 			switch listener.Protocol {
 			case gwapiv1.HTTPProtocolType, gwapiv1.HTTPSProtocolType:
 				irListener := &ir.HTTPListener{
@@ -136,7 +136,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap
 			// Add the listener to the Infra IR. Infra IR ports must have a unique port number per layer-4 protocol
 			// (TCP or UDP).
 			if !containsPort(foundPorts[irKey], servicePort) {
-				t.processInfraIRListener(listener, infraIR, irKey, servicePort)
+				t.processInfraIRListener(listener, infraIR, irKey, servicePort, containerPort)
 				foundPorts[irKey] = append(foundPorts[irKey], servicePort)
 			}
 		}
@@ -149,7 +149,7 @@ func (t *Translator) processProxyObservability(gw *gwapiv1.Gateway, xdsIR *ir.Xd
 	xdsIR.Metrics = processMetrics(envoyProxy)
 }
 
-func (t *Translator) processInfraIRListener(listener *ListenerContext, infraIR InfraIRMap, irKey string, servicePort *protocolPort) {
+func (t *Translator) processInfraIRListener(listener *ListenerContext, infraIR InfraIRMap, irKey string, servicePort *protocolPort, containerPort int32) {
 	var proto ir.ProtocolType
 	switch listener.Protocol {
 	case gwapiv1.HTTPProtocolType:
@@ -168,7 +168,7 @@ func (t *Translator) processInfraIRListener(listener *ListenerContext, infraIR I
 		Name:          irListenerPortName(proto, servicePort.port),
 		Protocol:      proto,
 		ServicePort:   servicePort.port,
-		ContainerPort: servicePortToContainerPort(servicePort.port),
+		ContainerPort: containerPort,
 	}
 
 	proxyListener := &ir.ProxyListener{
diff --git a/internal/ir/infra.go b/internal/ir/infra.go
index 60a26bddc..52672be64 100644
--- a/internal/ir/infra.go
+++ b/internal/ir/infra.go
@@ -222,8 +222,8 @@ func (p *ProxyInfra) Validate() error {
 				if listener.Ports[j].ServicePort < 1 || listener.Ports[j].ServicePort > 65353 {
 					errs = append(errs, errors.New("listener service port must be a valid port number"))
 				}
-				if listener.Ports[j].ContainerPort < 1024 || listener.Ports[j].ContainerPort > 65353 {
-					errs = append(errs, errors.New("listener container port must be a valid ephemeral port number"))
+				if listener.Ports[j].ContainerPort < 1 || listener.Ports[j].ContainerPort > 65353 {
+					errs = append(errs, errors.New("listener container port must be a valid port number"))
 				}
 			}
 		}
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index b77d26cf7..5b8af16c9 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -423,6 +423,11 @@ type HeaderSettings struct {
 	// is encountered. The default action is to reject the request.
 	// Refer to https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#envoy-v3-api-enum-config-core-v3-httpprotocoloptions-headerswithunderscoresaction
 	WithUnderscoresAction WithUnderscoresAction `json:"withUnderscoresAction,omitempty" yaml:"withUnderscoresAction,omitempty"`
+
+	// PreserveXRequestID configures whether Envoy will keep the x-request-id header if passed for a request that is edge
+	// (Edge request is the request from external clients to front Envoy) and not reset it, which is the current Envoy behaviour.
+	// It defaults to false.
+	PreserveXRequestID bool `json:"preserveXRequestID,omitempty" yaml:"preserveXRequestID,omitempty"`
 }
 
 // ClientTimeout sets the timeout configuration for downstream connections
diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go
index 1852384de..0c33873c1 100644
--- a/internal/provider/kubernetes/controller.go
+++ b/internal/provider/kubernetes/controller.go
@@ -129,9 +129,9 @@ type resourceMappings struct {
 	// Map for storing backendRefs' NamespaceNames referred by various Route objects.
 	allAssociatedBackendRefs map[gwapiv1.BackendObjectReference]struct{}
 	// extensionRefFilters is a map of filters managed by an extension.
-	// The key is the namespaced name of the filter and the value is the
+	// The key is the namespaced name, group and kind of the filter and the value is the
 	// unstructured form of the resource.
-	extensionRefFilters map[types.NamespacedName]unstructured.Unstructured
+	extensionRefFilters map[utils.NamespacedNameWithGroupKind]unstructured.Unstructured
 }
 
 func newResourceMapping() *resourceMappings {
@@ -143,7 +143,7 @@ func newResourceMapping() *resourceMappings {
 		allAssociatedTCPRoutes:   map[string]struct{}{},
 		allAssociatedUDPRoutes:   map[string]struct{}{},
 		allAssociatedBackendRefs: map[gwapiv1.BackendObjectReference]struct{}{},
-		extensionRefFilters:      map[types.NamespacedName]unstructured.Unstructured{},
+		extensionRefFilters:      map[utils.NamespacedNameWithGroupKind]unstructured.Unstructured{},
 	}
 }
 
diff --git a/internal/provider/kubernetes/routes.go b/internal/provider/kubernetes/routes.go
index f72d1b2cf..76991be99 100644
--- a/internal/provider/kubernetes/routes.go
+++ b/internal/provider/kubernetes/routes.go
@@ -185,10 +185,17 @@ func (r *gatewayAPIReconciler) processGRPCRoutes(ctx context.Context, gatewayNam
 				if filter.Type == gwapiv1.GRPCRouteFilterExtensionRef {
 					// NOTE: filters must be in the same namespace as the GRPCRoute
 					// Check if it's a Kind managed by an extension and add to resourceTree
-					key := types.NamespacedName{
-						Namespace: grpcRoute.Namespace,
-						Name:      string(filter.ExtensionRef.Name),
+					key := utils.NamespacedNameWithGroupKind{
+						NamespacedName: types.NamespacedName{
+							Namespace: grpcRoute.Namespace,
+							Name:      string(filter.ExtensionRef.Name),
+						},
+						GroupKind: schema.GroupKind{
+							Group: string(filter.ExtensionRef.Group),
+							Kind:  string(filter.ExtensionRef.Kind),
+						},
 					}
+
 					extRefFilter, ok := resourceMap.extensionRefFilters[key]
 					if !ok {
 						r.log.Error(
@@ -229,7 +236,7 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam
 	}
 	for i := range extensionRefFilters {
 		filter := extensionRefFilters[i]
-		resourceMap.extensionRefFilters[utils.NamespacedName(&filter)] = filter
+		resourceMap.extensionRefFilters[utils.GetNamespacedNameWithGroupKind(&filter)] = filter
 	}
 
 	if err := r.client.List(ctx, httpRouteList, &client.ListOptions{
@@ -367,9 +374,15 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam
 				} else if filter.Type == gwapiv1.HTTPRouteFilterExtensionRef {
 					// NOTE: filters must be in the same namespace as the HTTPRoute
 					// Check if it's a Kind managed by an extension and add to resourceTree
-					key := types.NamespacedName{
-						Namespace: httpRoute.Namespace,
-						Name:      string(filter.ExtensionRef.Name),
+					key := utils.NamespacedNameWithGroupKind{
+						NamespacedName: types.NamespacedName{
+							Namespace: httpRoute.Namespace,
+							Name:      string(filter.ExtensionRef.Name),
+						},
+						GroupKind: schema.GroupKind{
+							Group: string(filter.ExtensionRef.Group),
+							Kind:  string(filter.ExtensionRef.Kind),
+						},
 					}
 					extRefFilter, ok := resourceMap.extensionRefFilters[key]
 					if !ok {
diff --git a/internal/utils/misc.go b/internal/utils/misc.go
index 6e434660d..84043ecdc 100644
--- a/internal/utils/misc.go
+++ b/internal/utils/misc.go
@@ -11,10 +11,30 @@ import (
 	"hash/fnv"
 	"strings"
 
+	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
+type NamespacedNameWithGroupKind struct {
+	types.NamespacedName
+	schema.GroupKind
+}
+
+// GetNamespacedNameWithGroupKind creates and returns object's NamespacedNameWithGroupKind.
+func GetNamespacedNameWithGroupKind(obj client.Object) NamespacedNameWithGroupKind {
+	return NamespacedNameWithGroupKind{
+		NamespacedName: types.NamespacedName{
+			Namespace: obj.GetNamespace(),
+			Name:      obj.GetName(),
+		},
+		GroupKind: schema.GroupKind{
+			Group: obj.GetObjectKind().GroupVersionKind().GroupKind().Group,
+			Kind:  obj.GetObjectKind().GroupVersionKind().GroupKind().Kind,
+		},
+	}
+}
+
 // NamespacedName creates and returns object's NamespacedName.
 func NamespacedName(obj client.Object) types.NamespacedName {
 	return types.NamespacedName{
diff --git a/internal/xds/cache/metrics.go b/internal/xds/cache/metrics.go
new file mode 100644
index 000000000..3480b9c40
--- /dev/null
+++ b/internal/xds/cache/metrics.go
@@ -0,0 +1,23 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package cache
+
+import "github.com/envoyproxy/gateway/internal/metrics"
+
+var (
+	xdsSnapshotCreationTotal   = metrics.NewCounter("xds_snapshot_creation_total", "Total number of xds snapshot cache creation.")
+	xdsSnapshotCreationFailed  = metrics.NewCounter("xds_snapshot_creation_failed", "Number of xds snapshot cache creation that failed.")
+	xdsSnapshotCreationSuccess = metrics.NewCounter("xds_snapshot_creation_success", "Number of xds snapshot cache creation that succeeded.")
+
+	xdsSnapshotUpdateTotal   = metrics.NewCounter("xds_snapshot_update_total", "Total number of xds snapshot cache updates by node id.")
+	xdsSnapshotUpdateFailed  = metrics.NewCounter("xds_snapshot_update_failed", "Number of xds snapshot cache updates that failed by node id.")
+	xdsSnapshotUpdateSuccess = metrics.NewCounter("xds_snapshot_update_success", "Number of xds snapshot cache updates that succeeded by node id.")
+	nodeIDLabel              = metrics.NewLabel("nodeID")
+
+	xdsStreamDurationSeconds      = metrics.NewHistogram("xds_stream_duration_seconds", "How long a xds stream takes to finish.", []float64{0.1, 10, 50, 100, 1000, 10000})
+	xdsDeltaStreamDurationSeconds = metrics.NewHistogram("xds_delta_stream_duration_seconds", "How long a xds delta stream takes to finish.", []float64{0.1, 10, 50, 100, 1000, 10000})
+	streamIDLabel                 = metrics.NewLabel("streamID")
+)
diff --git a/internal/xds/cache/snapshotcache.go b/internal/xds/cache/snapshotcache.go
index bd3b22fb2..383aad836 100644
--- a/internal/xds/cache/snapshotcache.go
+++ b/internal/xds/cache/snapshotcache.go
@@ -19,6 +19,7 @@ import (
 	"math"
 	"strconv"
 	"sync"
+	"time"
 
 	corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	discoveryv3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
@@ -51,13 +52,17 @@ type snapshotMap map[string]*cachev3.Snapshot
 
 type nodeInfoMap map[int64]*corev3.Node
 
+type streamDurationMap map[int64]time.Time
+
 type snapshotCache struct {
 	cachev3.SnapshotCache
-	streamIDNodeInfo nodeInfoMap
-	snapshotVersion  int64
-	lastSnapshot     snapshotMap
-	log              *zap.SugaredLogger
-	mu               sync.Mutex
+	streamIDNodeInfo    nodeInfoMap
+	streamDuration      streamDurationMap
+	deltaStreamDuration streamDurationMap
+	snapshotVersion     int64
+	lastSnapshot        snapshotMap
+	log                 *zap.SugaredLogger
+	mu                  sync.Mutex
 }
 
 // GenerateNewSnapshot takes a table of resources (the output from the IR->xDS
@@ -73,17 +78,26 @@ func (s *snapshotCache) GenerateNewSnapshot(irKey string, resources types.XdsRes
 		version,
 		resources,
 	)
+
+	xdsSnapshotCreationTotal.Increment()
 	if err != nil {
+		xdsSnapshotCreationFailed.Increment()
 		return err
+	} else {
+		xdsSnapshotCreationSuccess.Increment()
 	}
 
 	s.lastSnapshot[irKey] = snapshot
 
 	for _, node := range s.getNodeIDs(irKey) {
 		s.log.Debugf("Generating a snapshot with Node %s", node)
-		err := s.SetSnapshot(context.TODO(), node, snapshot)
-		if err != nil {
+		xdsSnapshotUpdateTotal.With(nodeIDLabel.Value(node)).Increment()
+
+		if err = s.SetSnapshot(context.TODO(), node, snapshot); err != nil {
+			xdsSnapshotUpdateFailed.With(nodeIDLabel.Value(node)).Increment()
 			return err
+		} else {
+			xdsSnapshotUpdateSuccess.With(nodeIDLabel.Value(node)).Increment()
 		}
 	}
 
@@ -110,10 +124,12 @@ func NewSnapshotCache(ads bool, logger logging.Logger) SnapshotCacheWithCallback
 	// Set up the nasty wrapper hack.
 	wrappedLogger := logger.Sugar()
 	return &snapshotCache{
-		SnapshotCache:    cachev3.NewSnapshotCache(ads, &Hash, wrappedLogger),
-		log:              wrappedLogger,
-		lastSnapshot:     make(snapshotMap),
-		streamIDNodeInfo: make(nodeInfoMap),
+		SnapshotCache:       cachev3.NewSnapshotCache(ads, &Hash, wrappedLogger),
+		log:                 wrappedLogger,
+		lastSnapshot:        make(snapshotMap),
+		streamIDNodeInfo:    make(nodeInfoMap),
+		streamDuration:      make(streamDurationMap),
+		deltaStreamDuration: make(streamDurationMap),
 	}
 }
 
@@ -137,16 +153,26 @@ func (s *snapshotCache) OnStreamOpen(_ context.Context, streamID int64, _ string
 	defer s.mu.Unlock()
 
 	s.streamIDNodeInfo[streamID] = nil
+	s.streamDuration[streamID] = time.Now()
 
 	return nil
 }
 
-func (s *snapshotCache) OnStreamClosed(streamID int64, _ *corev3.Node) {
+func (s *snapshotCache) OnStreamClosed(streamID int64, node *corev3.Node) {
 	// TODO: something with the node?
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
+	if startTime, ok := s.streamDuration[streamID]; ok {
+		streamDuration := time.Since(startTime)
+		xdsStreamDurationSeconds.With(
+			streamIDLabel.Value(strconv.FormatInt(streamID, 10)),
+			nodeIDLabel.Value(node.Id),
+		).Record(streamDuration.Seconds())
+	}
+
 	delete(s.streamIDNodeInfo, streamID)
+	delete(s.streamDuration, streamID)
 }
 
 func (s *snapshotCache) OnStreamRequest(streamID int64, req *discoveryv3.DiscoveryRequest) error {
@@ -229,16 +255,26 @@ func (s *snapshotCache) OnDeltaStreamOpen(_ context.Context, streamID int64, _ s
 
 	// Ensure that we're adding the streamID to the Node ID list.
 	s.streamIDNodeInfo[streamID] = nil
+	s.deltaStreamDuration[streamID] = time.Now()
 
 	return nil
 }
 
-func (s *snapshotCache) OnDeltaStreamClosed(streamID int64, _ *corev3.Node) {
+func (s *snapshotCache) OnDeltaStreamClosed(streamID int64, node *corev3.Node) {
 	// TODO: something with the node?
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
+	if startTime, ok := s.deltaStreamDuration[streamID]; ok {
+		deltaStreamDuration := time.Since(startTime)
+		xdsDeltaStreamDurationSeconds.With(
+			streamIDLabel.Value(strconv.FormatInt(streamID, 10)),
+			nodeIDLabel.Value(node.Id),
+		).Record(deltaStreamDuration.Seconds())
+	}
+
 	delete(s.streamIDNodeInfo, streamID)
+	delete(s.deltaStreamDuration, streamID)
 }
 
 func (s *snapshotCache) OnStreamDeltaRequest(streamID int64, req *discoveryv3.DeltaDiscoveryRequest) error {
diff --git a/internal/xds/server/runner/runner.go b/internal/xds/server/runner/runner.go
index 28c203a86..f08127525 100644
--- a/internal/xds/server/runner/runner.go
+++ b/internal/xds/server/runner/runner.go
@@ -16,8 +16,6 @@ import (
 	"strconv"
 	"time"
 
-	"google.golang.org/grpc/keepalive"
-
 	clusterv3 "github.com/envoyproxy/go-control-plane/envoy/service/cluster/v3"
 	discoveryv3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
 	endpointv3 "github.com/envoyproxy/go-control-plane/envoy/service/endpoint/v3"
@@ -28,6 +26,7 @@ import (
 	serverv3 "github.com/envoyproxy/go-control-plane/pkg/server/v3"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/keepalive"
 
 	"github.com/envoyproxy/gateway/api/v1alpha1"
 	"github.com/envoyproxy/gateway/internal/envoygateway/config"
diff --git a/internal/xds/translator/extauth.go b/internal/xds/translator/extauth.go
index 0d8edfce2..46ce5df80 100644
--- a/internal/xds/translator/extauth.go
+++ b/internal/xds/translator/extauth.go
@@ -113,6 +113,7 @@ func extAuthConfig(extAuth *ir.ExtAuth) *extauthv3.ExtAuthz {
 			MatchPattern: &matcherv3.StringMatcher_Exact{
 				Exact: header,
 			},
+			IgnoreCase: true,
 		})
 	}
 
@@ -178,6 +179,7 @@ func httpService(http *ir.HTTPExtAuthService) *extauthv3.HttpService {
 			MatchPattern: &matcherv3.StringMatcher_Exact{
 				Exact: header,
 			},
+			IgnoreCase: true,
 		})
 	}
 
diff --git a/internal/xds/translator/extproc.go b/internal/xds/translator/extproc.go
index 9a397484e..9d4779b41 100644
--- a/internal/xds/translator/extproc.go
+++ b/internal/xds/translator/extproc.go
@@ -12,12 +12,11 @@ import (
 	routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	extprocv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3"
 	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+	"google.golang.org/protobuf/types/known/anypb"
+	"google.golang.org/protobuf/types/known/durationpb"
 
 	"github.com/envoyproxy/gateway/internal/ir"
 	"github.com/envoyproxy/gateway/internal/xds/types"
-
-	"google.golang.org/protobuf/types/known/anypb"
-	"google.golang.org/protobuf/types/known/durationpb"
 )
 
 const (
diff --git a/internal/xds/translator/httpfilters.go b/internal/xds/translator/httpfilters.go
index 5738d621e..22c351f81 100644
--- a/internal/xds/translator/httpfilters.go
+++ b/internal/xds/translator/httpfilters.go
@@ -18,10 +18,9 @@ import (
 	"k8s.io/utils/ptr"
 
 	"github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/ir"
 	"github.com/envoyproxy/gateway/internal/xds/filters"
 	"github.com/envoyproxy/gateway/internal/xds/types"
-
-	"github.com/envoyproxy/gateway/internal/ir"
 )
 
 var httpFilters []httpFilter
diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go
index a86bff57f..ada6ee454 100644
--- a/internal/xds/translator/listener.go
+++ b/internal/xds/translator/listener.go
@@ -260,7 +260,8 @@ func (t *Translator) addHCMToXDSListener(xdsListener *listenerv3.Listener, irLis
 		CommonHttpProtocolOptions: &corev3.HttpProtocolOptions{
 			HeadersWithUnderscoresAction: buildHeadersWithUnderscoresAction(irListener.Headers),
 		},
-		Tracing: hcmTracing,
+		Tracing:                   hcmTracing,
+		PreserveExternalRequestId: ptr.Deref(irListener.Headers, ir.HeaderSettings{}).PreserveXRequestID,
 	}
 
 	if irListener.Timeout != nil && irListener.Timeout.HTTP != nil {
diff --git a/tools/linter/golangci-lint/.golangci.yml b/tools/linter/golangci-lint/.golangci.yml
index 267fbc46c..10c9da6ce 100644
--- a/tools/linter/golangci-lint/.golangci.yml
+++ b/tools/linter/golangci-lint/.golangci.yml
@@ -7,6 +7,7 @@ linters:
     - depguard
     - errorlint
     - exportloopref
+    - gci
     - gofmt
     - gofumpt
     - goimports


Test patch:
diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml
index 4f53f7f30..d5714c531 100644
--- a/.github/workflows/build_and_test.yaml
+++ b/.github/workflows/build_and_test.yaml
@@ -61,10 +61,8 @@ jobs:
         files: ./coverage.xml
         name: codecov-envoy-gateway
         verbose: true
-        # only use oidc for push events or PRs from the same repo, since PRs from forks cannot fetch an OIDC token
-        # Codecov supports Token-less Uploads for PRs from forks:
-        # https://docs.codecov.com/docs/codecov-uploader#supporting-token-less-uploads-for-forks-of-open-source-repos-using-codecov
-        use_oidc: ${{ github.event_name == 'push' }}
+        # TODO change to `use_oidc: true` once https://github.com/codecov/codecov-action/issues/1435 is resolved.
+        use_oidc: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }}
 
   build:
     runs-on: ubuntu-latest
diff --git a/internal/gatewayapi/status/gatewayclass_test.go b/internal/gatewayapi/status/gatewayclass_test.go
index 0ccce4263..8e66d166f 100644
--- a/internal/gatewayapi/status/gatewayclass_test.go
+++ b/internal/gatewayapi/status/gatewayclass_test.go
@@ -11,7 +11,6 @@ import (
 	"github.com/stretchr/testify/assert"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/sets"
-
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 	"sigs.k8s.io/gateway-api/conformance/utils/suite"
 	"sigs.k8s.io/gateway-api/pkg/features"
diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-headers.in.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-headers.in.yaml
index c21285612..6d73bee1a 100644
--- a/internal/gatewayapi/testdata/clienttrafficpolicy-headers.in.yaml
+++ b/internal/gatewayapi/testdata/clienttrafficpolicy-headers.in.yaml
@@ -8,6 +8,7 @@ clientTrafficPolicies:
     headers:
       enableEnvoyHeaders: true
       withUnderscoresAction: Allow
+      preserveXRequestID: true
     targetRef:
       group: gateway.networking.k8s.io
       kind: Gateway
diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-headers.out.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-headers.out.yaml
index 8fa939ac3..8b184cbba 100644
--- a/internal/gatewayapi/testdata/clienttrafficpolicy-headers.out.yaml
+++ b/internal/gatewayapi/testdata/clienttrafficpolicy-headers.out.yaml
@@ -8,6 +8,7 @@ clientTrafficPolicies:
   spec:
     headers:
       enableEnvoyHeaders: true
+      preserveXRequestID: true
       withUnderscoresAction: Allow
     targetRef:
       group: gateway.networking.k8s.io
@@ -129,6 +130,7 @@ xdsIR:
     - address: 0.0.0.0
       headers:
         enableEnvoyHeaders: true
+        preserveXRequestID: true
         withUnderscoresAction: Allow
       hostnames:
       - '*'
@@ -141,6 +143,7 @@ xdsIR:
     - address: 0.0.0.0
       headers:
         enableEnvoyHeaders: true
+        preserveXRequestID: true
         withUnderscoresAction: Allow
       hostnames:
       - '*'
diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go
index 23ade13da..1a200555b 100644
--- a/internal/gatewayapi/translator_test.go
+++ b/internal/gatewayapi/translator_test.go
@@ -679,27 +679,71 @@ func TestServicePortToContainerPort(t *testing.T) {
 	testCases := []struct {
 		servicePort   int32
 		containerPort int32
+		envoyProxy    *egv1a1.EnvoyProxy
 	}{
 		{
 			servicePort:   99,
 			containerPort: 10099,
+			envoyProxy:    nil,
 		},
 		{
 			servicePort:   1023,
 			containerPort: 11023,
+			envoyProxy:    nil,
 		},
 		{
 			servicePort:   1024,
 			containerPort: 1024,
+			envoyProxy:    nil,
 		},
 		{
 			servicePort:   8080,
 			containerPort: 8080,
+			envoyProxy:    nil,
+		},
+		{
+			servicePort:   99,
+			containerPort: 10099,
+			envoyProxy: &egv1a1.EnvoyProxy{
+				Spec: egv1a1.EnvoyProxySpec{
+					Provider: &egv1a1.EnvoyProxyProvider{
+						Type: egv1a1.ProviderTypeKubernetes,
+					},
+				},
+			},
+		},
+		{
+			servicePort:   99,
+			containerPort: 10099,
+			envoyProxy: &egv1a1.EnvoyProxy{
+				Spec: egv1a1.EnvoyProxySpec{
+					Provider: &egv1a1.EnvoyProxyProvider{
+						Type: egv1a1.ProviderTypeKubernetes,
+						Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+							UseListenerPortAsContainerPort: ptr.To(false),
+						},
+					},
+				},
+			},
+		},
+		{
+			servicePort:   99,
+			containerPort: 99,
+			envoyProxy: &egv1a1.EnvoyProxy{
+				Spec: egv1a1.EnvoyProxySpec{
+					Provider: &egv1a1.EnvoyProxyProvider{
+						Type: egv1a1.ProviderTypeKubernetes,
+						Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+							UseListenerPortAsContainerPort: ptr.To(true),
+						},
+					},
+				},
+			},
 		},
 	}
 
 	for _, tc := range testCases {
-		got := servicePortToContainerPort(tc.servicePort)
+		got := servicePortToContainerPort(tc.servicePort, tc.envoyProxy)
 		assert.Equal(t, tc.containerPort, got)
 	}
 }
diff --git a/internal/provider/kubernetes/routes_test.go b/internal/provider/kubernetes/routes_test.go
index 9a230e682..be6769589 100644
--- a/internal/provider/kubernetes/routes_test.go
+++ b/internal/provider/kubernetes/routes_test.go
@@ -120,6 +120,102 @@ func TestProcessHTTPRoutes(t *testing.T) {
 			},
 			expected: true,
 		},
+		{
+			name: "httproute with extension filter multiple types same name",
+			routes: []*gwapiv1.HTTPRoute{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: httpRouteNS,
+						Name:      "test",
+					},
+					Spec: gwapiv1.HTTPRouteSpec{
+						CommonRouteSpec: gwapiv1.CommonRouteSpec{
+							ParentRefs: []gwapiv1.ParentReference{
+								{
+									Name: "test",
+								},
+							},
+						},
+						Rules: []gwapiv1.HTTPRouteRule{
+							{
+								Matches: []gwapiv1.HTTPRouteMatch{
+									{
+										Path: &gwapiv1.HTTPPathMatch{
+											Type:  ptr.To(gwapiv1.PathMatchPathPrefix),
+											Value: ptr.To("/"),
+										},
+									},
+								},
+								Filters: []gwapiv1.HTTPRouteFilter{
+									{
+										Type: gwapiv1.HTTPRouteFilterExtensionRef,
+										ExtensionRef: &gwapiv1.LocalObjectReference{
+											Group: gwapiv1.Group("gateway.example.io"),
+											Kind:  gwapiv1.Kind("Bar"),
+											Name:  gwapiv1.ObjectName("test"),
+										},
+									},
+									{
+										Type: gwapiv1.HTTPRouteFilterExtensionRef,
+										ExtensionRef: &gwapiv1.LocalObjectReference{
+											Group: gwapiv1.Group("gateway.example.io"),
+											Kind:  gwapiv1.Kind("Foo"),
+											Name:  gwapiv1.ObjectName("test"),
+										},
+									},
+								},
+								BackendRefs: []gwapiv1.HTTPBackendRef{
+									{
+										BackendRef: gwapiv1.BackendRef{
+											BackendObjectReference: gwapiv1.BackendObjectReference{
+												Group: gatewayapi.GroupPtr(corev1.GroupName),
+												Kind:  gatewayapi.KindPtr(gatewayapi.KindService),
+												Name:  "test",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			extensionFilters: []*unstructured.Unstructured{
+				{
+					Object: map[string]interface{}{
+						"apiVersion": "gateway.example.io/v1alpha1",
+						"kind":       "Bar",
+						"metadata": map[string]interface{}{
+							"name":      "test",
+							"namespace": httpRouteNS,
+						},
+					},
+				},
+				{
+					Object: map[string]interface{}{
+						"apiVersion": "gateway.example.io/v1alpha1",
+						"kind":       "Foo",
+						"metadata": map[string]interface{}{
+							"name":      "test",
+							"namespace": httpRouteNS,
+						},
+					},
+				},
+			},
+			extensionAPIGroups: []schema.GroupVersionKind{
+				{
+					Group:   "gateway.example.io",
+					Version: "v1alpha1",
+					Kind:    "Bar",
+				},
+				{
+					Group:   "gateway.example.io",
+					Version: "v1alpha1",
+					Kind:    "Foo",
+				},
+			},
+			expected: true,
+		},
 		{
 			name: "httproute with one filter_from_extension",
 			routes: []*gwapiv1.HTTPRoute{
@@ -294,10 +390,16 @@ func TestProcessHTTPRoutes(t *testing.T) {
 				// Ensure the resource tree and map are as expected.
 				require.Equal(t, tc.routes, resourceTree.HTTPRoutes)
 				if tc.extensionFilters != nil {
-					for i, filter := range tc.extensionFilters {
-						key := types.NamespacedName{
-							Namespace: tc.routes[i].Namespace,
-							Name:      filter.GetName(),
+					for _, filter := range tc.extensionFilters {
+						key := utils.NamespacedNameWithGroupKind{
+							NamespacedName: types.NamespacedName{
+								Namespace: tc.routes[0].Namespace,
+								Name:      filter.GetName(),
+							},
+							GroupKind: schema.GroupKind{
+								Group: filter.GroupVersionKind().Group,
+								Kind:  filter.GroupVersionKind().Kind,
+							},
 						}
 						require.Equal(t, *filter, resourceMap.extensionRefFilters[key])
 					}
diff --git a/internal/xds/translator/runner/runner_test.go b/internal/xds/translator/runner/runner_test.go
index a7be53236..43bd2164e 100644
--- a/internal/xds/translator/runner/runner_test.go
+++ b/internal/xds/translator/runner/runner_test.go
@@ -13,7 +13,6 @@ import (
 
 	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
-
 	"github.com/stretchr/testify/require"
 
 	"github.com/envoyproxy/gateway/api/v1alpha1"
diff --git a/internal/xds/translator/testdata/in/xds-ir/headers-with-preserve-x-request-id.yaml b/internal/xds/translator/testdata/in/xds-ir/headers-with-preserve-x-request-id.yaml
new file mode 100644
index 000000000..1376be42e
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/headers-with-preserve-x-request-id.yaml
@@ -0,0 +1,32 @@
+http:
+- name: "first-listener"
+  address: "0.0.0.0"
+  port: 8081
+  hostnames:
+  - "*"
+  routes:
+  - name: "first-route"
+    hostname: "*"
+    destination:
+      name: "first-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.1.1.1"
+          port: 8081
+  headers:
+    preserveXRequestID: true
+- name: "second-listener"
+  address: "0.0.0.0"
+  port: 8082
+  hostnames:
+  - "*"
+  routes:
+  - name: "second-route"
+    hostname: "*"
+    destination:
+      name: "second-route-dest"
+      settings:
+      - endpoints:
+        - host: "2.2.2.2"
+          port: 8082
+
diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-auth.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-auth.listeners.yaml
index 083a4a6f3..1c2ed9d00 100644
--- a/internal/xds/translator/testdata/out/xds-ir/ext-auth.listeners.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/ext-auth.listeners.yaml
@@ -21,7 +21,9 @@
             allowedHeaders:
               patterns:
               - exact: header1
+                ignoreCase: true
               - exact: header2
+                ignoreCase: true
             grpcService:
               envoyGrpc:
                 authority: grpc-backend.default:9000
@@ -38,7 +40,9 @@
                 allowedUpstreamHeaders:
                   patterns:
                   - exact: header1
+                    ignoreCase: true
                   - exact: header2
+                    ignoreCase: true
               pathPrefix: /auth
               serverUri:
                 cluster: securitypolicy/default/policy-for-gateway-1/envoy-gateway/http-backend
diff --git a/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.clusters.yaml
new file mode 100755
index 000000000..d65e267ad
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.clusters.yaml
@@ -0,0 +1,34 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: first-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: first-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: second-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: second-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.endpoints.yaml
new file mode 100755
index 000000000..9ad09dd91
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.endpoints.yaml
@@ -0,0 +1,24 @@
+- clusterName: first-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.1.1.1
+            portValue: 8081
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: first-route-dest/backend/0
+- clusterName: second-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 2.2.2.2
+            portValue: 8082
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: second-route-dest/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.listeners.yaml
new file mode 100644
index 000000000..880644d46
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.listeners.yaml
@@ -0,0 +1,65 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 8081
+  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:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        normalizePath: true
+        preserveExternalRequestId: true
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: first-listener
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http
+        useRemoteAddress: true
+  drainType: MODIFY_ONLY
+  name: first-listener
+  perConnectionBufferLimitBytes: 32768
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 8082
+  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:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        normalizePath: true
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: second-listener
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http
+        useRemoteAddress: true
+  drainType: MODIFY_ONLY
+  name: second-listener
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.routes.yaml
new file mode 100755
index 000000000..ff93cfff3
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/headers-with-preserve-x-request-id.routes.yaml
@@ -0,0 +1,28 @@
+- ignorePortInHostMatching: true
+  name: first-listener
+  virtualHosts:
+  - domains:
+    - '*'
+    name: first-listener/*
+    routes:
+    - match:
+        prefix: /
+      name: first-route
+      route:
+        cluster: first-route-dest
+        upgradeConfigs:
+        - upgradeType: websocket
+- ignorePortInHostMatching: true
+  name: second-listener
+  virtualHosts:
+  - domains:
+    - '*'
+    name: second-listener/*
+    routes:
+    - match:
+        prefix: /
+      name: second-route
+      route:
+        cluster: second-route-dest
+        upgradeConfigs:
+        - upgradeType: websocket
diff --git a/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.listeners.yaml
index cec7fd427..1365c290c 100755
--- a/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.listeners.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/multiple-listeners-same-port-with-different-filters.listeners.yaml
@@ -27,7 +27,9 @@
                 allowedUpstreamHeaders:
                   patterns:
                   - exact: header1
+                    ignoreCase: true
                   - exact: header2
+                    ignoreCase: true
               pathPrefix: /auth
               serverUri:
                 cluster: securitypolicy/default/policy-for-http-route-2/envoy-gateway/http-backend
@@ -86,7 +88,9 @@
                 allowedUpstreamHeaders:
                   patterns:
                   - exact: header1
+                    ignoreCase: true
                   - exact: header2
+                    ignoreCase: true
               pathPrefix: /auth
               serverUri:
                 cluster: securitypolicy/default/policy-for-http-route-2/envoy-gateway/http-backend
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index 6ac4fe6b1..1e3391760 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -1233,6 +1233,7 @@ _Appears in:_
 | `envoyDaemonSet` | _[KubernetesDaemonSetSpec](#kubernetesdaemonsetspec)_ |  false  | EnvoyDaemonSet defines the desired state of the Envoy daemonset resource.<br />Disabled by default, a deployment resource is used instead to provision the Envoy Proxy fleet |
 | `envoyService` | _[KubernetesServiceSpec](#kubernetesservicespec)_ |  false  | EnvoyService defines the desired state of the Envoy service resource.<br />If unspecified, default settings for the managed Envoy service resource<br />are applied. |
 | `envoyHpa` | _[KubernetesHorizontalPodAutoscalerSpec](#kuberneteshorizontalpodautoscalerspec)_ |  false  | EnvoyHpa defines the Horizontal Pod Autoscaler settings for Envoy Proxy Deployment.<br />Once the HPA is being set, Replicas field from EnvoyDeployment will be ignored. |
+| `useListenerPortAsContainerPort` | _boolean_ |  false  | UseListenerPortAsContainerPort disables the port shifting feature in the Envoy Proxy.<br />When set to false (default value), if the service port is a privileged port (1-1023), add a constant to the value converting it into an ephemeral port.<br />This allows the container to bind to the port without needing a CAP_NET_BIND_SERVICE capability. |
 
 
 #### EnvoyProxyProvider
@@ -1797,6 +1798,7 @@ _Appears in:_
 | ---   | ---  | ---      | ---         |
 | `enableEnvoyHeaders` | _boolean_ |  false  | EnableEnvoyHeaders configures Envoy Proxy to add the "X-Envoy-" headers to requests<br />and responses. |
 | `withUnderscoresAction` | _[WithUnderscoresAction](#withunderscoresaction)_ |  false  | WithUnderscoresAction configures the action to take when an HTTP header with underscores<br />is encountered. The default action is to reject the request. |
+| `preserveXRequestID` | _boolean_ |  false  | PreserveXRequestID configures Envoy to keep the X-Request-ID header if passed for a request that is edge<br />(Edge request is the request from external clients to front Envoy) and not reset it, which is the current Envoy behaviour.<br />It defaults to false. |
 
 
 #### HealthCheck
diff --git a/test/cel-validation/envoyextensionpolicy_test.go b/test/cel-validation/envoyextensionpolicy_test.go
index 53d55412f..773ce8640 100644
--- a/test/cel-validation/envoyextensionpolicy_test.go
+++ b/test/cel-validation/envoyextensionpolicy_test.go
@@ -15,13 +15,12 @@ import (
 	"testing"
 	"time"
 
-	"k8s.io/utils/ptr"
-
-	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
-
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 )
 
 func TestEnvoyExtensionPolicyTarget(t *testing.T) {
diff --git a/test/cel-validation/securitypolicy_test.go b/test/cel-validation/securitypolicy_test.go
index 12511c6d4..984903cf7 100644
--- a/test/cel-validation/securitypolicy_test.go
+++ b/test/cel-validation/securitypolicy_test.go
@@ -18,7 +18,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/ptr"
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
-
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
 	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
diff --git a/test/e2e/tests/backend_upgrade.go b/test/e2e/tests/backend_upgrade.go
index 04aa1cc8a..261ee9acb 100644
--- a/test/e2e/tests/backend_upgrade.go
+++ b/test/e2e/tests/backend_upgrade.go
@@ -15,13 +15,11 @@ import (
 	"time"
 
 	"fortio.org/fortio/periodic"
-
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
-
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/gateway-api/conformance/utils/config"
 	"sigs.k8s.io/gateway-api/conformance/utils/http"
diff --git a/test/e2e/tests/tcp_route.go b/test/e2e/tests/tcp_route.go
index f09521db0..bb4d80ff6 100644
--- a/test/e2e/tests/tcp_route.go
+++ b/test/e2e/tests/tcp_route.go
@@ -19,17 +19,16 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/require"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+	"sigs.k8s.io/gateway-api/apis/v1alpha2"
 	"sigs.k8s.io/gateway-api/conformance/utils/config"
 	"sigs.k8s.io/gateway-api/conformance/utils/http"
 	"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
 	"sigs.k8s.io/gateway-api/conformance/utils/suite"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
-	"sigs.k8s.io/gateway-api/apis/v1alpha2"
 )
 
 func init() {
diff --git a/test/e2e/tests/utils.go b/test/e2e/tests/utils.go
index a07cf6cb4..c33cc9e3f 100644
--- a/test/e2e/tests/utils.go
+++ b/test/e2e/tests/utils.go
@@ -15,16 +15,13 @@ import (
 	"fortio.org/fortio/fhttp"
 	"fortio.org/fortio/periodic"
 	flog "fortio.org/log"
-
 	"github.com/google/go-cmp/cmp"
 	"github.com/stretchr/testify/require"
-
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
-
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 	"sigs.k8s.io/gateway-api/conformance/utils/config"