Repository: envoyproxy/gateway
Pull request #3382 (2024-05-13 04:52:58)

build(deps): bump actions/checkout from 4.1.4 to 4.1.5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/checkout/releases">actions/checkout's releases</a>.</em></p>
<blockquote>
<h2>v4.1.5</h2>
<h2>What's Changed</h2>
<ul>
<li>Update NPM dependencies by <a href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1703">actions/checkout#1703</a></li>
<li>Bump github/codeql-action from 2 to 3 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1694">actions/checkout#1694</a></li>
<li>Bump actions/setup-node from 1 to 4 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1696">actions/checkout#1696</a></li>
<li>Bump actions/upload-artifact from 2 to 4 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1695">actions/checkout#1695</a></li>
<li>README: Suggest <code>user.email</code> to be <code>41898282+github-actions[bot]@users.noreply.github.com</code> by <a href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1707">actions/checkout#1707</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v4.1.4...v4.1.5">https://github.com/actions/checkout/compare/v4.1.4...v4.1.5</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/checkout/commit/44c2b7a8a4ea60a981eaca3cf939b5f4305c123b"><code>44c2b7a</code></a> README: Suggest <code>user.email</code> to be `41898282+github-actions[bot]<a href="https://github.com/users"><code>@​users</code></a>.norepl...</li>
<li><a href="https://github.com/actions/checkout/commit/8459bc0c7e3759cdf591f513d9f141a95fef0a8f"><code>8459bc0</code></a> Bump actions/upload-artifact from 2 to 4 (<a href="https://redirect.github.com/actions/checkout/issues/1695">#1695</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/3f603f6d5e9f40714f97b2f017aa0df2a443192a"><code>3f603f6</code></a> Bump actions/setup-node from 1 to 4 (<a href="https://redirect.github.com/actions/checkout/issues/1696">#1696</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/fd084cde189b7b76ec305d52e27be545a0172823"><code>fd084cd</code></a> Bump github/codeql-action from 2 to 3 (<a href="https://redirect.github.com/actions/checkout/issues/1694">#1694</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/9c1e94e0ad997d618b6113a2171b055037589028"><code>9c1e94e</code></a> Update NPM dependencies (<a href="https://redirect.github.com/actions/checkout/issues/1703">#1703</a>)</li>
<li>See full diff in <a href="https://github.com/actions/checkout/compare/0ad4b8fadaa221de15dcec353f45205ec38ea70b...44c2b7a8a4ea60a981eaca3cf939b5f4305c123b">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.4&new-version=4.1.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/cherrypick.yaml b/.github/workflows/cherrypick.yaml
index bda95da1b..797fe2241 100644
--- a/.github/workflows/cherrypick.yaml
+++ b/.github/workflows/cherrypick.yaml
@@ -18,7 +18,7 @@ jobs:
     if: ${{ contains(github.event.pull_request.labels.*.name, 'cherrypick/release-v1.0') && github.event.pull_request.merged == true }}
     steps:
       - name: Checkout
-        uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+        uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
         with:
           fetch-depth: 0
       - name: Cherry pick into release/v1.0
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index bc69c9130..e50f4b292 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -32,18 +32,18 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+      uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
 
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14  # v3.25.3
+      uses: github/codeql-action/init@ccf74c947955fd1cf117aef6a0e4e66191ef6f61  # v3.25.4
       with:
         languages: ${{ matrix.language }}
 
     - name: Autobuild
-      uses: github/codeql-action/autobuild@d39d31e687223d841ef683f52467bd88e9b21c14  # v3.25.3
+      uses: github/codeql-action/autobuild@ccf74c947955fd1cf117aef6a0e4e66191ef6f61  # v3.25.4
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14  # v3.25.3
+      uses: github/codeql-action/analyze@ccf74c947955fd1cf117aef6a0e4e66191ef6f61  # v3.25.4
       with:
         category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 28ec10e0b..bf07583b0 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -23,7 +23,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
     - name: Check out code
-      uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+      uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
       with:
         ref: ${{ github.event.pull_request.head.sha }}
 
@@ -48,7 +48,7 @@ jobs:
       contents: write
     steps:
     - name: Git checkout
-      uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+      uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
       with:
         submodules: true
         ref: ${{ github.event.pull_request.head.sha }}
diff --git a/.github/workflows/experimental_conformance.yaml b/.github/workflows/experimental_conformance.yaml
index c7f00139f..7bb4e92bc 100644
--- a/.github/workflows/experimental_conformance.yaml
+++ b/.github/workflows/experimental_conformance.yaml
@@ -20,7 +20,7 @@ jobs:
       matrix:
         version: [ v1.26.14, v1.27.11, v1.28.7, v1.29.2 ]
     steps:
-    - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+    - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
 
     # gateway api experimental conformance
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index f69ef4164..e8057ad6a 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -15,7 +15,7 @@ jobs:
     permissions:
       contents: write
     steps:
-      - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+      - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
 
       - name: Extract Release Tag and Commit SHA
         id: vars
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 77c1a3dcb..8347d4367 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -21,7 +21,7 @@ jobs:
 
     steps:
     - name: "Checkout code"
-      uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+      uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
       with:
         persist-credentials: false
 
@@ -40,6 +40,6 @@ jobs:
         retention-days: 5
 
     - name: "Upload to code-scanning"
-      uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14  # v3.25.3
+      uses: github/codeql-action/upload-sarif@ccf74c947955fd1cf117aef6a0e4e66191ef6f61  # v3.25.4
       with:
         sarif_file: results.sarif
diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml
index 87a7483f6..1529aaf24 100644
--- a/.github/workflows/trivy.yml
+++ b/.github/workflows/trivy.yml
@@ -18,7 +18,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
     - name: Checkout code
-      uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+      uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
 
     - name: Build an image from Dockerfile
       run: |
diff --git a/go.mod b/go.mod
index 2161f6966..30d1564ee 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,7 @@ require (
 	github.com/google/go-cmp v0.6.0
 	github.com/grafana/tempo v1.5.0
 	github.com/miekg/dns v1.1.59
-	github.com/prometheus/client_golang v1.19.0
+	github.com/prometheus/client_golang v1.19.1
 	github.com/prometheus/common v0.53.0
 	github.com/spf13/cobra v1.8.0
 	github.com/spf13/pflag v1.0.5
diff --git a/go.sum b/go.sum
index 58573d20d..2db575bcb 100644
--- a/go.sum
+++ b/go.sum
@@ -553,8 +553,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
-github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
-github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
+github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
+github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go
index 852cc201d..1a2df010d 100644
--- a/internal/gatewayapi/backendtrafficpolicy.go
+++ b/internal/gatewayapi/backendtrafficpolicy.go
@@ -339,8 +339,8 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
 		}
 	}
 
-	for _, ir := range xdsIR {
-		for _, tcp := range ir.TCP {
+	for _, x := range xdsIR {
+		for _, tcp := range x.TCP {
 			for _, r := range tcp.Routes {
 				if strings.HasPrefix(r.Destination.Name, prefix) {
 					r.LoadBalancer = lb
@@ -353,34 +353,37 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
 			}
 		}
 
-		for _, udp := range ir.UDP {
+		for _, udp := range x.UDP {
 			if strings.HasPrefix(udp.Destination.Name, prefix) {
 				udp.LoadBalancer = lb
 				udp.Timeout = to
 			}
 		}
 
-		for _, http := range ir.HTTP {
+		for _, http := range x.HTTP {
 			for _, r := range http.Routes {
 				// Apply if there is a match
 				if strings.HasPrefix(r.Name, prefix) {
-					r.RateLimit = rl
-					r.LoadBalancer = lb
-					r.ProxyProtocol = pp
-					r.HealthCheck = hc
+					r.Traffic = &ir.TrafficFeatures{
+						RateLimit:      rl,
+						LoadBalancer:   lb,
+						ProxyProtocol:  pp,
+						HealthCheck:    hc,
+						CircuitBreaker: cb,
+						FaultInjection: fi,
+						TCPKeepalive:   ka,
+						Retry:          rt,
+					}
+
 					// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
-					r.HealthCheck.SetHTTPHostIfAbsent(r.Hostname)
-					r.CircuitBreaker = cb
-					r.FaultInjection = fi
-					r.TCPKeepalive = ka
-					r.Retry = rt
+					r.Traffic.HealthCheck.SetHTTPHostIfAbsent(r.Hostname)
 
-					// some timeout setting originate from the route
+					// Some timeout setting originate from the route.
 					if policy.Spec.Timeout != nil {
 						if to, err = t.buildTimeout(policy, r); err != nil {
 							return errors.Wrap(err, "Timeout")
 						}
-						r.Timeout = to
+						r.Traffic.Timeout = to
 					}
 
 					if policy.Spec.UseClientProtocol != nil {
@@ -445,7 +448,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 	// set by a policy attaching to the route
 	irKey := t.getIRKey(gateway.Gateway)
 	// Should exist since we've validated this
-	ir := xdsIR[irKey]
+	x := xdsIR[irKey]
 
 	policyTarget := irStringKey(policy.Namespace, string(policy.Spec.TargetRef.Name))
 
@@ -455,7 +458,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 		}
 	}
 
-	for _, tcp := range ir.TCP {
+	for _, tcp := range x.TCP {
 		gatewayName := tcp.Name[0:strings.LastIndex(tcp.Name, "/")]
 		if t.MergeGateways && gatewayName != policyTarget {
 			continue
@@ -481,7 +484,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 		}
 	}
 
-	for _, udp := range ir.UDP {
+	for _, udp := range x.UDP {
 		gatewayName := udp.Name[0:strings.LastIndex(udp.Name, "/")]
 		if t.MergeGateways && gatewayName != policyTarget {
 			continue
@@ -498,7 +501,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 		}
 	}
 
-	for _, http := range ir.HTTP {
+	for _, http := range x.HTTP {
 		gatewayName := http.Name[0:strings.LastIndex(http.Name, "/")]
 		if t.MergeGateways && gatewayName != policyTarget {
 			continue
@@ -509,52 +512,29 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 		for _, r := range http.Routes {
 			// If any of the features are already set, it means that a more specific
 			// policy(targeting xRoute) has already set it, so we skip it.
-			// TODO: zhaohuabing group the features into a struct and check if all of them are set
-			if r.RateLimit != nil || r.LoadBalancer != nil ||
-				r.ProxyProtocol != nil || r.HealthCheck != nil ||
-				r.CircuitBreaker != nil || r.FaultInjection != nil ||
-				r.TCPKeepalive != nil || r.Retry != nil ||
-				r.Timeout != nil {
+			if r.Traffic != nil {
 				continue
 			}
 
-			// Apply if not already set
-			if r.RateLimit == nil {
-				r.RateLimit = rl
-			}
-			if r.LoadBalancer == nil {
-				r.LoadBalancer = lb
-			}
-			if r.ProxyProtocol == nil {
-				r.ProxyProtocol = pp
-			}
-			if r.HealthCheck == nil {
-				r.HealthCheck = hc
-				// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
-				r.HealthCheck.SetHTTPHostIfAbsent(r.Hostname)
+			r.Traffic = &ir.TrafficFeatures{
+				RateLimit:      rl,
+				LoadBalancer:   lb,
+				ProxyProtocol:  pp,
+				HealthCheck:    hc,
+				CircuitBreaker: cb,
+				FaultInjection: fi,
+				TCPKeepalive:   ka,
+				Retry:          rt,
 			}
 
-			if r.CircuitBreaker == nil {
-				r.CircuitBreaker = cb
-			}
-			if r.FaultInjection == nil {
-				r.FaultInjection = fi
-			}
-			if r.TCPKeepalive == nil {
-				r.TCPKeepalive = ka
-			}
-			if r.Retry == nil {
-				r.Retry = rt
-			}
+			// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
+			r.Traffic.HealthCheck.SetHTTPHostIfAbsent(r.Hostname)
 
 			if policy.Spec.Timeout != nil {
 				if ct, err = t.buildTimeout(policy, r); err != nil {
 					return errors.Wrap(err, "Timeout")
 				}
-
-				if r.Timeout == nil {
-					r.Timeout = ct
-				}
+				r.Traffic.Timeout = ct
 			}
 
 			if policy.Spec.UseClientProtocol != nil {
@@ -1066,13 +1046,17 @@ func (t *Translator) buildTimeout(policy *egv1a1.BackendTrafficPolicy, r *ir.HTT
 
 	// http request timeout is translated during the gateway-api route resource translation
 	// merge route timeout setting with backendtrafficpolicy timeout settings
-	if r != nil && r.Timeout != nil && r.Timeout.HTTP != nil && r.Timeout.HTTP.RequestTimeout != nil {
+	if r != nil &&
+		r.Traffic != nil &&
+		r.Traffic.Timeout != nil &&
+		r.Traffic.Timeout.HTTP != nil &&
+		r.Traffic.Timeout.HTTP.RequestTimeout != nil {
 		if hto == nil {
 			hto = &ir.HTTPTimeout{
-				RequestTimeout: r.Timeout.HTTP.RequestTimeout,
+				RequestTimeout: r.Traffic.Timeout.HTTP.RequestTimeout,
 			}
 		} else {
-			hto.RequestTimeout = r.Timeout.HTTP.RequestTimeout
+			hto.RequestTimeout = r.Traffic.Timeout.HTTP.RequestTimeout
 		}
 	}
 
diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go
index d10331ca2..8abbac6c8 100644
--- a/internal/gatewayapi/route.go
+++ b/internal/gatewayapi/route.go
@@ -248,8 +248,8 @@ func processTimeout(irRoute *ir.HTTPRoute, rule gwapiv1.HTTPRouteRule) {
 		var rto *ir.Timeout
 
 		// Timeout is translated from multiple resources and may already be partially set
-		if irRoute.Timeout != nil {
-			rto = irRoute.Timeout.DeepCopy()
+		if irRoute.Traffic != nil && irRoute.Traffic.Timeout != nil {
+			rto = irRoute.Traffic.Timeout.DeepCopy()
 		} else {
 			rto = &ir.Timeout{}
 		}
@@ -272,7 +272,9 @@ func processTimeout(irRoute *ir.HTTPRoute, rule gwapiv1.HTTPRouteRule) {
 			setRequestTimeout(rto, metav1.Duration{Duration: d})
 		}
 
-		irRoute.Timeout = rto
+		irRoute.Traffic = &ir.TrafficFeatures{
+			Timeout: rto,
+		}
 	}
 }
 
@@ -681,14 +683,18 @@ func (t *Translator) processHTTPRouteParentRefListener(route RouteContext, route
 					URLRewrite:            routeRoute.URLRewrite,
 					Mirrors:               routeRoute.Mirrors,
 					ExtensionRefs:         routeRoute.ExtensionRefs,
-					Timeout:               routeRoute.Timeout,
-					Retry:                 routeRoute.Retry,
 					IsHTTP2:               routeRoute.IsHTTP2,
 				}
 				// Don't bother copying over the weights unless the route has invalid backends.
 				if routeRoute.BackendWeights.Invalid > 0 {
 					hostRoute.BackendWeights = routeRoute.BackendWeights
 				}
+				if routeRoute.Traffic != nil {
+					hostRoute.Traffic = &ir.TrafficFeatures{
+						Timeout: routeRoute.Traffic.Timeout,
+						Retry:   routeRoute.Traffic.Retry,
+					}
+				}
 				perHostRoutes = append(perHostRoutes, hostRoute)
 			}
 		}
diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go
index c7862d3cc..e68cf3fab 100644
--- a/internal/gatewayapi/securitypolicy.go
+++ b/internal/gatewayapi/securitypolicy.go
@@ -465,7 +465,7 @@ func (t *Translator) translateSecurityPolicyForGateway(
 		for _, r := range h.Routes {
 			// If any of the features are already set, it means that a more specific
 			// policy(targeting xRoute) has already set it, so we skip it.
-			if !r.Security.Empty() {
+			if r.Security != nil {
 				continue
 			}
 
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index 126e8f8fb..25e3f5834 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -476,6 +476,24 @@ type HTTPRoute struct {
 	Destination *RouteDestination `json:"destination,omitempty" yaml:"destination,omitempty"`
 	// Rewrite to be changed for this route.
 	URLRewrite *URLRewrite `json:"urlRewrite,omitempty" yaml:"urlRewrite,omitempty"`
+	// ExtensionRefs holds unstructured resources that were introduced by an extension and used on the HTTPRoute as extensionRef filters
+	ExtensionRefs []*UnstructuredRef `json:"extensionRefs,omitempty" yaml:"extensionRefs,omitempty"`
+	// External Processing extensions
+	ExtProcs []ExtProc `json:"extProc,omitempty" yaml:"extProc,omitempty"`
+	// Wasm extensions
+	Wasms []Wasm `json:"wasm,omitempty" yaml:"wasm,omitempty"`
+
+	// Traffic holds the features associated with BackendTrafficPolicy
+	Traffic *TrafficFeatures `json:"traffic,omitempty" yaml:"traffic,omitempty"`
+	// Security holds the features associated with SecurityPolicy
+	Security *SecurityFeatures `json:"security,omitempty" yaml:"security,omitempty"`
+	// UseClientProtocol enables using the same protocol upstream that was used downstream
+	UseClientProtocol *bool `json:"useClientProtocol,omitempty" yaml:"useClientProtocol,omitempty"`
+}
+
+// TrafficFeatures holds the information associated with the Backend Traffic Policy.
+// +k8s:deepcopy-gen=true
+type TrafficFeatures struct {
 	// RateLimit defines the more specific match conditions as well as limits for ratelimiting
 	// the requests on this route.
 	RateLimit *RateLimit `json:"rateLimit,omitempty" yaml:"rateLimit,omitempty"`
@@ -487,8 +505,6 @@ type HTTPRoute struct {
 	HealthCheck *HealthCheck `json:"healthCheck,omitempty" yaml:"healthCheck,omitempty"`
 	// FaultInjection defines the schema for injecting faults into HTTP requests.
 	FaultInjection *FaultInjection `json:"faultInjection,omitempty" yaml:"faultInjection,omitempty"`
-	// ExtensionRefs holds unstructured resources that were introduced by an extension and used on the HTTPRoute as extensionRef filters
-	ExtensionRefs []*UnstructuredRef `json:"extensionRefs,omitempty" yaml:"extensionRefs,omitempty"`
 	// Circuit Breaker Settings
 	CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty" yaml:"circuitBreaker,omitempty"`
 	// Request and connection timeout settings
@@ -497,15 +513,23 @@ type HTTPRoute struct {
 	TCPKeepalive *TCPKeepalive `json:"tcpKeepalive,omitempty" yaml:"tcpKeepalive,omitempty"`
 	// Retry settings
 	Retry *Retry `json:"retry,omitempty" yaml:"retry,omitempty"`
-	// External Processing extensions
-	ExtProcs []ExtProc `json:"extProc,omitempty" yaml:"extProc,omitempty"`
-	// Wasm extensions
-	Wasms []Wasm `json:"wasm,omitempty" yaml:"wasm,omitempty"`
+}
 
-	// Security holds the features associated with SecurityPolicy
-	Security *SecurityFeatures `json:"security,omitempty" yaml:"security,omitempty"`
-	// UseClientProtocol enables using the same protocol upstream that was used downstream
-	UseClientProtocol *bool `json:"useClientProtocol,omitempty" yaml:"useClientProtocol,omitempty"`
+func (b *TrafficFeatures) Validate() error {
+	var errs error
+
+	if b.LoadBalancer != nil {
+		if err := b.LoadBalancer.Validate(); err != nil {
+			errs = errors.Join(errs, err)
+		}
+	}
+	if b.HealthCheck != nil {
+		if err := b.HealthCheck.Validate(); err != nil {
+			errs = errors.Join(errs, err)
+		}
+	}
+
+	return errs
 }
 
 // SecurityFeatures holds the information associated with the Security Policy.
@@ -523,19 +547,6 @@ type SecurityFeatures struct {
 	ExtAuth *ExtAuth `json:"extAuth,omitempty" yaml:"extAuth,omitempty"`
 }
 
-// Empty returns true if all the features are not set.
-func (s *SecurityFeatures) Empty() bool {
-	if s == nil {
-		return true
-	}
-
-	return s.CORS == nil &&
-		s.JWT == nil &&
-		s.OIDC == nil &&
-		s.BasicAuth == nil &&
-		s.ExtAuth == nil
-}
-
 func (s *SecurityFeatures) Printable() *SecurityFeatures {
 	out := s.DeepCopy()
 	if out.OIDC != nil {
@@ -872,8 +883,8 @@ func (h HTTPRoute) Validate() error {
 			occurred.Insert(header)
 		}
 	}
-	if h.LoadBalancer != nil {
-		if err := h.LoadBalancer.Validate(); err != nil {
+	if h.Traffic != nil {
+		if err := h.Traffic.Validate(); err != nil {
 			errs = errors.Join(errs, err)
 		}
 	}
@@ -882,11 +893,6 @@ func (h HTTPRoute) Validate() error {
 			errs = errors.Join(errs, err)
 		}
 	}
-	if h.HealthCheck != nil {
-		if err := h.HealthCheck.Validate(); err != nil {
-			errs = errors.Join(errs, err)
-		}
-	}
 
 	return errs
 }
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index bd0ce421a..514543c80 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -1021,31 +1021,6 @@ func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) {
 		*out = new(URLRewrite)
 		(*in).DeepCopyInto(*out)
 	}
-	if in.RateLimit != nil {
-		in, out := &in.RateLimit, &out.RateLimit
-		*out = new(RateLimit)
-		(*in).DeepCopyInto(*out)
-	}
-	if in.LoadBalancer != nil {
-		in, out := &in.LoadBalancer, &out.LoadBalancer
-		*out = new(LoadBalancer)
-		(*in).DeepCopyInto(*out)
-	}
-	if in.ProxyProtocol != nil {
-		in, out := &in.ProxyProtocol, &out.ProxyProtocol
-		*out = new(ProxyProtocol)
-		**out = **in
-	}
-	if in.HealthCheck != nil {
-		in, out := &in.HealthCheck, &out.HealthCheck
-		*out = new(HealthCheck)
-		(*in).DeepCopyInto(*out)
-	}
-	if in.FaultInjection != nil {
-		in, out := &in.FaultInjection, &out.FaultInjection
-		*out = new(FaultInjection)
-		(*in).DeepCopyInto(*out)
-	}
 	if in.ExtensionRefs != nil {
 		in, out := &in.ExtensionRefs, &out.ExtensionRefs
 		*out = make([]*UnstructuredRef, len(*in))
@@ -1057,26 +1032,6 @@ func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) {
 			}
 		}
 	}
-	if in.CircuitBreaker != nil {
-		in, out := &in.CircuitBreaker, &out.CircuitBreaker
-		*out = new(CircuitBreaker)
-		(*in).DeepCopyInto(*out)
-	}
-	if in.Timeout != nil {
-		in, out := &in.Timeout, &out.Timeout
-		*out = new(Timeout)
-		(*in).DeepCopyInto(*out)
-	}
-	if in.TCPKeepalive != nil {
-		in, out := &in.TCPKeepalive, &out.TCPKeepalive
-		*out = new(TCPKeepalive)
-		(*in).DeepCopyInto(*out)
-	}
-	if in.Retry != nil {
-		in, out := &in.Retry, &out.Retry
-		*out = new(Retry)
-		(*in).DeepCopyInto(*out)
-	}
 	if in.ExtProcs != nil {
 		in, out := &in.ExtProcs, &out.ExtProcs
 		*out = make([]ExtProc, len(*in))
@@ -1091,6 +1046,11 @@ func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.Traffic != nil {
+		in, out := &in.Traffic, &out.Traffic
+		*out = new(TrafficFeatures)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.Security != nil {
 		in, out := &in.Security, &out.Security
 		*out = new(SecurityFeatures)
@@ -2424,6 +2384,66 @@ func (in *Tracing) DeepCopy() *Tracing {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TrafficFeatures) DeepCopyInto(out *TrafficFeatures) {
+	*out = *in
+	if in.RateLimit != nil {
+		in, out := &in.RateLimit, &out.RateLimit
+		*out = new(RateLimit)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.LoadBalancer != nil {
+		in, out := &in.LoadBalancer, &out.LoadBalancer
+		*out = new(LoadBalancer)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ProxyProtocol != nil {
+		in, out := &in.ProxyProtocol, &out.ProxyProtocol
+		*out = new(ProxyProtocol)
+		**out = **in
+	}
+	if in.HealthCheck != nil {
+		in, out := &in.HealthCheck, &out.HealthCheck
+		*out = new(HealthCheck)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.FaultInjection != nil {
+		in, out := &in.FaultInjection, &out.FaultInjection
+		*out = new(FaultInjection)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.CircuitBreaker != nil {
+		in, out := &in.CircuitBreaker, &out.CircuitBreaker
+		*out = new(CircuitBreaker)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Timeout != nil {
+		in, out := &in.Timeout, &out.Timeout
+		*out = new(Timeout)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.TCPKeepalive != nil {
+		in, out := &in.TCPKeepalive, &out.TCPKeepalive
+		*out = new(TCPKeepalive)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Retry != nil {
+		in, out := &in.Retry, &out.Retry
+		*out = new(Retry)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficFeatures.
+func (in *TrafficFeatures) DeepCopy() *TrafficFeatures {
+	if in == nil {
+		return nil
+	}
+	out := new(TrafficFeatures)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *UDPListener) DeepCopyInto(out *UDPListener) {
 	*out = *in
diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go
index 8e23f656a..e7329d49a 100644
--- a/internal/provider/kubernetes/controller.go
+++ b/internal/provider/kubernetes/controller.go
@@ -441,7 +441,7 @@ func (r *gatewayAPIReconciler) processSecurityPolicyObjectRefs(
 
 			if backendNamespace != policy.Namespace {
 				from := ObjectKindNamespacedName{
-					kind:      gatewayapi.KindHTTPRoute,
+					kind:      gatewayapi.KindSecurityPolicy,
 					namespace: policy.Namespace,
 					name:      policy.Name,
 				}
diff --git a/internal/xds/translator/fault.go b/internal/xds/translator/fault.go
index 46642934c..43fae199c 100644
--- a/internal/xds/translator/fault.go
+++ b/internal/xds/translator/fault.go
@@ -36,7 +36,7 @@ var _ httpFilter = &fault{}
 
 // patchHCM builds and appends the fault Filters to the HTTP Connection Manager
 // if applicable, and it does not already exist.
-// Note: this method creates an fault filter for each route that contains an Fault config.
+// Note: this method creates a fault filter for each route that contains an Fault config.
 func (*fault) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error {
 	if mgr == nil {
 		return errors.New("hcm is nil")
@@ -99,7 +99,9 @@ func listenerContainsFault(irListener *ir.HTTPListener) bool {
 
 // routeContainsFault returns true if Fault exists for the provided route.
 func routeContainsFault(irRoute *ir.HTTPRoute) bool {
-	if irRoute != nil && irRoute.FaultInjection != nil {
+	if irRoute != nil &&
+		irRoute.Traffic != nil &&
+		irRoute.Traffic.FaultInjection != nil {
 		return true
 	}
 	return false
@@ -118,7 +120,7 @@ func (*fault) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
 	if irRoute == nil {
 		return errors.New("ir route is nil")
 	}
-	if irRoute.FaultInjection == nil {
+	if irRoute.Traffic == nil || irRoute.Traffic.FaultInjection == nil {
 		return nil
 	}
 
@@ -131,34 +133,35 @@ func (*fault) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
 
 	routeCfgProto := &xdshttpfaultv3.HTTPFault{}
 
-	if irRoute.FaultInjection.Delay != nil {
+	delay := irRoute.Traffic.FaultInjection.Delay
+	if delay != nil {
 		routeCfgProto.Delay = &xdsfault.FaultDelay{}
-		if irRoute.FaultInjection.Delay.Percentage != nil {
-			routeCfgProto.Delay.Percentage = translatePercentToFractionalPercent(irRoute.FaultInjection.Delay.Percentage)
+		if delay.Percentage != nil {
+			routeCfgProto.Delay.Percentage = translatePercentToFractionalPercent(delay.Percentage)
 		}
-		if irRoute.FaultInjection.Delay.FixedDelay != nil {
+		if delay.FixedDelay != nil {
 			routeCfgProto.Delay.FaultDelaySecifier = &xdsfault.FaultDelay_FixedDelay{
-				FixedDelay: durationpb.New(irRoute.FaultInjection.Delay.FixedDelay.Duration),
+				FixedDelay: durationpb.New(delay.FixedDelay.Duration),
 			}
 		}
 	}
 
-	if irRoute.FaultInjection.Abort != nil {
+	abort := irRoute.Traffic.FaultInjection.Abort
+	if abort != nil {
 		routeCfgProto.Abort = &xdshttpfaultv3.FaultAbort{}
-		if irRoute.FaultInjection.Abort.Percentage != nil {
-			routeCfgProto.Abort.Percentage = translatePercentToFractionalPercent(irRoute.FaultInjection.Abort.Percentage)
+		if abort.Percentage != nil {
+			routeCfgProto.Abort.Percentage = translatePercentToFractionalPercent(abort.Percentage)
 		}
-		if irRoute.FaultInjection.Abort.HTTPStatus != nil {
+		if abort.HTTPStatus != nil {
 			routeCfgProto.Abort.ErrorType = &xdshttpfaultv3.FaultAbort_HttpStatus{
-				HttpStatus: uint32(*irRoute.FaultInjection.Abort.HTTPStatus),
+				HttpStatus: uint32(*abort.HTTPStatus),
 			}
 		}
-		if irRoute.FaultInjection.Abort.GrpcStatus != nil {
+		if abort.GrpcStatus != nil {
 			routeCfgProto.Abort.ErrorType = &xdshttpfaultv3.FaultAbort_GrpcStatus{
-				GrpcStatus: uint32(*irRoute.FaultInjection.Abort.GrpcStatus),
+				GrpcStatus: uint32(*abort.GrpcStatus),
 			}
 		}
-
 	}
 
 	if routeCfgProto.Delay == nil && routeCfgProto.Abort == nil {
@@ -179,7 +182,7 @@ func (*fault) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
 	return nil
 }
 
-// translatePercentToFractionalPercent translates an v1alpha3 Percent instance
+// translatePercentToFractionalPercent translates a v1alpha3 Percent instance
 // to an envoy.type.FractionalPercent instance.
 func translatePercentToFractionalPercent(p *float32) *xdstype.FractionalPercent {
 	return &xdstype.FractionalPercent{
diff --git a/internal/xds/translator/local_ratelimit.go b/internal/xds/translator/local_ratelimit.go
index 688461e9f..1a0d88d5d 100644
--- a/internal/xds/translator/local_ratelimit.go
+++ b/internal/xds/translator/local_ratelimit.go
@@ -96,7 +96,10 @@ func listenerContainsLocalRateLimit(irListener *ir.HTTPListener) bool {
 }
 
 func routeContainsLocalRateLimit(irRoute *ir.HTTPRoute) bool {
-	if irRoute == nil || irRoute.RateLimit == nil || irRoute.RateLimit.Local == nil {
+	if irRoute == nil ||
+		irRoute.Traffic == nil ||
+		irRoute.Traffic.RateLimit == nil ||
+		irRoute.Traffic.RateLimit.Local == nil {
 		return false
 	}
 
@@ -113,7 +116,7 @@ func (*localRateLimit) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) e
 	routeAction := route.GetRoute()
 
 	// Return early if no rate limit config exists.
-	if irRoute.RateLimit == nil || irRoute.RateLimit.Local == nil || routeAction == nil {
+	if !routeContainsLocalRateLimit(irRoute) || routeAction == nil {
 		return nil
 	}
 
@@ -125,7 +128,7 @@ func (*localRateLimit) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) e
 			route.Name)
 	}
 
-	local := irRoute.RateLimit.Local
+	local := irRoute.Traffic.RateLimit.Local
 
 	rateLimits, descriptors, err := buildRouteLocalRateLimits(local)
 	if err != nil {
diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go
index 02b1c3d60..4534f303f 100644
--- a/internal/xds/translator/ratelimit.go
+++ b/internal/xds/translator/ratelimit.go
@@ -73,6 +73,17 @@ func (t *Translator) patchHCMWithRateLimit(mgr *hcmv3.HttpConnectionManager, irL
 	mgr.HttpFilters = append([]*hcmv3.HttpFilter{rateLimitFilter}, mgr.HttpFilters...)
 }
 
+func routeContainsGlobalRateLimit(irRoute *ir.HTTPRoute) bool {
+	if irRoute == nil ||
+		irRoute.Traffic == nil ||
+		irRoute.Traffic.RateLimit == nil ||
+		irRoute.Traffic.RateLimit.Global == nil {
+		return false
+	}
+
+	return true
+}
+
 // isRateLimitPresent returns true if rate limit config exists for the listener.
 func (t *Translator) isRateLimitPresent(irListener *ir.HTTPListener) bool {
 	// Return false if global ratelimiting is disabled.
@@ -81,7 +92,7 @@ func (t *Translator) isRateLimitPresent(irListener *ir.HTTPListener) bool {
 	}
 	// Return true if rate limit config exists.
 	for _, route := range irListener.Routes {
-		if route.RateLimit != nil && route.RateLimit.Global != nil {
+		if routeContainsGlobalRateLimit(route) {
 			return true
 		}
 	}
@@ -128,11 +139,11 @@ func (t *Translator) buildRateLimitFilter(irListener *ir.HTTPListener) *hcmv3.Ht
 // patchRouteWithRateLimit builds rate limit actions and appends to the route.
 func patchRouteWithRateLimit(xdsRouteAction *routev3.RouteAction, irRoute *ir.HTTPRoute) error { //nolint:unparam
 	// Return early if no rate limit config exists.
-	if irRoute.RateLimit == nil || irRoute.RateLimit.Global == nil || xdsRouteAction == nil {
+	if !routeContainsGlobalRateLimit(irRoute) || xdsRouteAction == nil {
 		return nil
 	}
 
-	rateLimits := buildRouteRateLimits(irRoute.Name, irRoute.RateLimit.Global)
+	rateLimits := buildRouteRateLimits(irRoute.Name, irRoute.Traffic.RateLimit.Global)
 	xdsRouteAction.RateLimits = rateLimits
 	return nil
 }
@@ -287,8 +298,8 @@ func BuildRateLimitServiceConfig(irListener *ir.HTTPListener) *rlsconfv3.RateLim
 	pbDescriptors := make([]*rlsconfv3.RateLimitDescriptor, 0, len(irListener.Routes))
 
 	for _, route := range irListener.Routes {
-		if route.RateLimit != nil && route.RateLimit.Global != nil {
-			serviceDescriptors := buildRateLimitServiceDescriptors(route.RateLimit.Global)
+		if routeContainsGlobalRateLimit(route) {
+			serviceDescriptors := buildRateLimitServiceDescriptors(route.Traffic.RateLimit.Global)
 
 			// Get route rule descriptors within each route.
 			//
diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go
index a52371103..bf68ac7df 100644
--- a/internal/xds/translator/route.go
+++ b/internal/xds/translator/route.go
@@ -10,8 +10,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/envoyproxy/gateway/internal/utils/protocov"
-
 	corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	previoushost "github.com/envoyproxy/go-control-plane/envoy/extensions/retry/host/previous_hosts/v3"
@@ -20,6 +18,7 @@ import (
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
 	"github.com/envoyproxy/gateway/internal/ir"
+	"github.com/envoyproxy/gateway/internal/utils/protocov"
 )
 
 const (
@@ -99,13 +98,18 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute) (*routev3.Route, error) {
 	}
 
 	// Timeouts
-	if router.GetRoute() != nil && httpRoute.Timeout != nil && httpRoute.Timeout.HTTP != nil &&
-		httpRoute.Timeout.HTTP.RequestTimeout != nil {
-		router.GetRoute().Timeout = durationpb.New(httpRoute.Timeout.HTTP.RequestTimeout.Duration)
+	if router.GetRoute() != nil &&
+		httpRoute.Traffic != nil &&
+		httpRoute.Traffic.Timeout != nil &&
+		httpRoute.Traffic.Timeout.HTTP != nil &&
+		httpRoute.Traffic.Timeout.HTTP.RequestTimeout != nil {
+		router.GetRoute().Timeout = durationpb.New(httpRoute.Traffic.Timeout.HTTP.RequestTimeout.Duration)
 	}
 
 	// Retries
-	if router.GetRoute() != nil && httpRoute.Retry != nil {
+	if router.GetRoute() != nil &&
+		httpRoute.Traffic != nil &&
+		httpRoute.Traffic.Retry != nil {
 		if rp, err := buildRetryPolicy(httpRoute); err == nil {
 			router.GetRoute().RetryPolicy = rp
 		} else {
@@ -259,22 +263,24 @@ func buildXdsWeightedRouteAction(httpRoute *ir.HTTPRoute) *routev3.RouteAction {
 }
 
 func idleTimeout(httpRoute *ir.HTTPRoute) *durationpb.Duration {
-	if httpRoute.Timeout != nil && httpRoute.Timeout.HTTP != nil {
-		if httpRoute.Timeout.HTTP.RequestTimeout != nil {
-			timeout := time.Hour // Default to 1 hour
+	if httpRoute.Traffic != nil &&
+		httpRoute.Traffic.Timeout != nil &&
+		httpRoute.Traffic.Timeout.HTTP != nil &&
+		httpRoute.Traffic.Timeout.HTTP.RequestTimeout != nil {
+		rt := httpRoute.Traffic.Timeout.HTTP.RequestTimeout
+		timeout := time.Hour // Default to 1 hour
 
-			// Ensure is not less than the request timeout
-			if timeout < httpRoute.Timeout.HTTP.RequestTimeout.Duration {
-				timeout = httpRoute.Timeout.HTTP.RequestTimeout.Duration
-			}
-
-			// Disable idle timeout when request timeout is disabled
-			if httpRoute.Timeout.HTTP.RequestTimeout.Duration == 0 {
-				timeout = 0
-			}
+		// Ensure is not less than the request timeout
+		if timeout < rt.Duration {
+			timeout = rt.Duration
+		}
 
-			return durationpb.New(timeout)
+		// Disable idle timeout when request timeout is disabled
+		if rt.Duration == 0 {
+			timeout = 0
 		}
+
+		return durationpb.New(timeout)
 	}
 	return nil
 }
@@ -436,22 +442,27 @@ func buildXdsAddedHeaders(headersToAdd []ir.AddHeader) []*corev3.HeaderValueOpti
 
 func buildHashPolicy(httpRoute *ir.HTTPRoute) []*routev3.RouteAction_HashPolicy {
 	// Return early
-	if httpRoute == nil || httpRoute.LoadBalancer == nil || httpRoute.LoadBalancer.ConsistentHash == nil {
+	if httpRoute == nil ||
+		httpRoute.Traffic == nil ||
+		httpRoute.Traffic.LoadBalancer == nil ||
+		httpRoute.Traffic.LoadBalancer.ConsistentHash == nil {
 		return nil
 	}
 
+	ch := httpRoute.Traffic.LoadBalancer.ConsistentHash
+
 	switch {
-	case httpRoute.LoadBalancer.ConsistentHash.Header != nil:
+	case ch.Header != nil:
 		hashPolicy := &routev3.RouteAction_HashPolicy{
 			PolicySpecifier: &routev3.RouteAction_HashPolicy_Header_{
 				Header: &routev3.RouteAction_HashPolicy_Header{
-					HeaderName: httpRoute.LoadBalancer.ConsistentHash.Header.Name,
+					HeaderName: ch.Header.Name,
 				},
 			},
 		}
 		return []*routev3.RouteAction_HashPolicy{hashPolicy}
-	case httpRoute.LoadBalancer.ConsistentHash.SourceIP != nil:
-		if !*httpRoute.LoadBalancer.ConsistentHash.SourceIP {
+	case ch.SourceIP != nil:
+		if !*ch.SourceIP {
 			return nil
 		}
 		hashPolicy := &routev3.RouteAction_HashPolicy{
@@ -468,68 +479,64 @@ func buildHashPolicy(httpRoute *ir.HTTPRoute) []*routev3.RouteAction_HashPolicy
 }
 
 func buildRetryPolicy(route *ir.HTTPRoute) (*routev3.RetryPolicy, error) {
-	if route.Retry != nil {
-		rr := route.Retry
-		rp := &routev3.RetryPolicy{
-			RetryOn:              retryDefaultRetryOn,
-			RetriableStatusCodes: []uint32{retryDefaultRetriableStatusCode},
-			NumRetries:           &wrapperspb.UInt32Value{Value: retryDefaultNumRetries},
-			RetryHostPredicate: []*routev3.RetryPolicy_RetryHostPredicate{
-				{
-					Name: "envoy.retry_host_predicates.previous_hosts",
-					ConfigType: &routev3.RetryPolicy_RetryHostPredicate_TypedConfig{
-						TypedConfig: protocov.ToAny(&previoushost.PreviousHostsPredicate{}),
-					},
+	rr := route.Traffic.Retry
+	rp := &routev3.RetryPolicy{
+		RetryOn:              retryDefaultRetryOn,
+		RetriableStatusCodes: []uint32{retryDefaultRetriableStatusCode},
+		NumRetries:           &wrapperspb.UInt32Value{Value: retryDefaultNumRetries},
+		RetryHostPredicate: []*routev3.RetryPolicy_RetryHostPredicate{
+			{
+				Name: "envoy.retry_host_predicates.previous_hosts",
+				ConfigType: &routev3.RetryPolicy_RetryHostPredicate_TypedConfig{
+					TypedConfig: protocov.ToAny(&previoushost.PreviousHostsPredicate{}),
 				},
 			},
-			HostSelectionRetryMaxAttempts: 5,
-		}
+		},
+		HostSelectionRetryMaxAttempts: 5,
+	}
 
-		if rr.NumRetries != nil {
-			rp.NumRetries = &wrapperspb.UInt32Value{Value: *rr.NumRetries}
-		}
+	if rr.NumRetries != nil {
+		rp.NumRetries = &wrapperspb.UInt32Value{Value: *rr.NumRetries}
+	}
 
-		if rr.RetryOn != nil {
-			if rr.RetryOn.Triggers != nil && len(rr.RetryOn.Triggers) > 0 {
-				if ro, err := buildRetryOn(rr.RetryOn.Triggers); err == nil {
-					rp.RetryOn = ro
-				} else {
-					return nil, err
-				}
+	if rr.RetryOn != nil {
+		if rr.RetryOn.Triggers != nil && len(rr.RetryOn.Triggers) > 0 {
+			if ro, err := buildRetryOn(rr.RetryOn.Triggers); err == nil {
+				rp.RetryOn = ro
+			} else {
+				return nil, err
 			}
+		}
 
-			if rr.RetryOn.HTTPStatusCodes != nil && len(rr.RetryOn.HTTPStatusCodes) > 0 {
-				rp.RetriableStatusCodes = buildRetryStatusCodes(rr.RetryOn.HTTPStatusCodes)
-			}
+		if rr.RetryOn.HTTPStatusCodes != nil && len(rr.RetryOn.HTTPStatusCodes) > 0 {
+			rp.RetriableStatusCodes = buildRetryStatusCodes(rr.RetryOn.HTTPStatusCodes)
 		}
+	}
 
-		if rr.PerRetry != nil {
-			if rr.PerRetry.Timeout != nil {
-				rp.PerTryTimeout = durationpb.New(rr.PerRetry.Timeout.Duration)
-			}
+	if rr.PerRetry != nil {
+		if rr.PerRetry.Timeout != nil {
+			rp.PerTryTimeout = durationpb.New(rr.PerRetry.Timeout.Duration)
+		}
 
-			if rr.PerRetry.BackOff != nil {
-				bbo := false
-				rbo := &routev3.RetryPolicy_RetryBackOff{}
-				if rr.PerRetry.BackOff.BaseInterval != nil {
-					rbo.BaseInterval = durationpb.New(rr.PerRetry.BackOff.BaseInterval.Duration)
-					bbo = true
-				}
+		if rr.PerRetry.BackOff != nil {
+			bbo := false
+			rbo := &routev3.RetryPolicy_RetryBackOff{}
+			if rr.PerRetry.BackOff.BaseInterval != nil {
+				rbo.BaseInterval = durationpb.New(rr.PerRetry.BackOff.BaseInterval.Duration)
+				bbo = true
+			}
 
-				if rr.PerRetry.BackOff.MaxInterval != nil {
-					rbo.MaxInterval = durationpb.New(rr.PerRetry.BackOff.MaxInterval.Duration)
-					bbo = true
-				}
+			if rr.PerRetry.BackOff.MaxInterval != nil {
+				rbo.MaxInterval = durationpb.New(rr.PerRetry.BackOff.MaxInterval.Duration)
+				bbo = true
+			}
 
-				if bbo {
-					rp.RetryBackOff = rbo
-				}
+			if bbo {
+				rp.RetryBackOff = rbo
 			}
 		}
-		return rp, nil
 	}
-
-	return nil, nil
+	return rp, nil
 }
 
 func buildRetryStatusCodes(codes []ir.HTTPStatus) []uint32 {
diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go
index a88f4e3fc..cab26d1f9 100644
--- a/internal/xds/translator/translator.go
+++ b/internal/xds/translator/translator.go
@@ -643,21 +643,28 @@ func findXdsEndpoint(tCtx *types.ResourceVersionTable, name string) *endpointv3.
 
 // processXdsCluster processes a xds cluster by its endpoint address type.
 func processXdsCluster(tCtx *types.ResourceVersionTable, httpRoute *ir.HTTPRoute, http1Settings *ir.HTTP1Settings, metrics *ir.Metrics) error {
-	if err := addXdsCluster(tCtx, &xdsClusterArgs{
+	clusterArgs := &xdsClusterArgs{
 		name:              httpRoute.Destination.Name,
 		settings:          httpRoute.Destination.Settings,
 		tSocket:           nil,
 		endpointType:      buildEndpointType(httpRoute.Destination.Settings),
-		loadBalancer:      httpRoute.LoadBalancer,
-		proxyProtocol:     httpRoute.ProxyProtocol,
-		circuitBreaker:    httpRoute.CircuitBreaker,
-		healthCheck:       httpRoute.HealthCheck,
 		http1Settings:     http1Settings,
-		timeout:           httpRoute.Timeout,
-		tcpkeepalive:      httpRoute.TCPKeepalive,
 		metrics:           metrics,
 		useClientProtocol: ptr.Deref(httpRoute.UseClientProtocol, false),
-	}); err != nil && !errors.Is(err, ErrXdsClusterExists) {
+	}
+
+	// Populate traffic features.
+	bt := httpRoute.Traffic
+	if bt != nil {
+		clusterArgs.loadBalancer = bt.LoadBalancer
+		clusterArgs.proxyProtocol = bt.ProxyProtocol
+		clusterArgs.circuitBreaker = bt.CircuitBreaker
+		clusterArgs.healthCheck = bt.HealthCheck
+		clusterArgs.timeout = bt.Timeout
+		clusterArgs.tcpkeepalive = bt.TCPKeepalive
+	}
+
+	if err := addXdsCluster(tCtx, clusterArgs); err != nil && !errors.Is(err, ErrXdsClusterExists) {
 		return err
 	}
 


Test patch:
diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml
index 66bf96ebd..e812062f3 100644
--- a/.github/workflows/build_and_test.yaml
+++ b/.github/workflows/build_and_test.yaml
@@ -20,7 +20,7 @@ jobs:
   lint:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+    - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
     # Generate the installation manifests first, so it can check
     # for errors while running `make -k lint`
@@ -31,21 +31,21 @@ jobs:
   gen-check:
     runs-on: ubuntu-22.04
     steps:
-    - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+    - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
     - run: make -k gen-check
 
   license-check:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+    - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
     - run: make -k licensecheck
 
   coverage-test:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+    - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
 
     # test
@@ -63,7 +63,7 @@ jobs:
     runs-on: ubuntu-latest
     needs: [lint, gen-check, license-check, coverage-test]
     steps:
-    - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+    - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
 
     - name: Build EG Multiarch Binaries
@@ -82,7 +82,7 @@ jobs:
       matrix:
         version: [ v1.26.14, v1.27.11, v1.28.7, v1.29.2 ]
     steps:
-    - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+    - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
 
     - name: Download EG Binaries
@@ -110,7 +110,7 @@ jobs:
       matrix:
         version: [ v1.26.14, v1.27.11, v1.28.7, v1.29.2 ]
     steps:
-    - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+    - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
 
     - name: Download EG Binaries
@@ -135,7 +135,7 @@ jobs:
     runs-on: ubuntu-latest
     needs: [conformance-test, e2e-test]
     steps:
-    - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+    - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
 
     - name: Download EG Binaries
diff --git a/.github/workflows/latest_release.yaml b/.github/workflows/latest_release.yaml
index 9a3f8b468..5560014ba 100644
--- a/.github/workflows/latest_release.yaml
+++ b/.github/workflows/latest_release.yaml
@@ -22,7 +22,7 @@ jobs:
     permissions:
       contents: write
     steps:
-    - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b  # v4.1.4
+    - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b  # v4.1.5
     - uses: ./tools/github-actions/setup-deps
 
     - name: Generate Release Manifests
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-override-replace.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-override-replace.out.yaml
index b6cf93163..c5ad9495b 100755
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-override-replace.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-override-replace.out.yaml
@@ -232,14 +232,15 @@ xdsIR:
             weight: 1
         hostname: gateway.envoyproxy.io
         isHTTP2: false
-        loadBalancer:
-          consistentHash:
-            sourceIP: true
         name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
         pathMatch:
           distinct: false
           name: ""
           prefix: /foo
+        traffic:
+          loadBalancer:
+            consistentHash:
+              sourceIP: true
       - backendWeights:
           invalid: 0
           valid: 0
@@ -254,16 +255,17 @@ xdsIR:
             weight: 1
         hostname: gateway.envoyproxy.io
         isHTTP2: false
-        loadBalancer:
-          random: {}
         name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io
         pathMatch:
           distinct: false
           name: ""
           prefix: /bar
-        timeout:
-          http:
-            connectionIdleTimeout: 21s
-            maxConnectionDuration: 22s
-          tcp:
-            connectTimeout: 20s
+        traffic:
+          loadBalancer:
+            random: {}
+          timeout:
+            http:
+              connectionIdleTimeout: 21s
+              maxConnectionDuration: 22s
+            tcp:
+              connectTimeout: 20s
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-status-conditions.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-status-conditions.out.yaml
index 41088add2..edbf09d03 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-status-conditions.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-status-conditions.out.yaml
@@ -557,6 +557,7 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
+        traffic: {}
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -584,6 +585,7 @@ xdsIR:
         hostname: '*'
         isHTTP2: true
         name: grpcroute/envoy-gateway/grpcroute-1/rule/0/match/0/*
+        traffic: {}
     tcp:
     - address: 0.0.0.0
       name: envoy-gateway/gateway-2/tcp
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-status-fault-injection.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-status-fault-injection.out.yaml
index 7d7867841..31566cafd 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-status-fault-injection.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-status-fault-injection.out.yaml
@@ -351,16 +351,17 @@ xdsIR:
               port: 8080
             protocol: GRPC
             weight: 1
-        faultInjection:
-          abort:
-            grpcStatus: 14
-            percentage: 100
-          delay:
-            fixedDelay: 5.4s
-            percentage: 80
         hostname: '*'
         isHTTP2: true
         name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
+        traffic:
+          faultInjection:
+            abort:
+              grpcStatus: 14
+              percentage: 100
+            delay:
+              fixedDelay: 5.4s
+              percentage: 80
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -388,10 +389,6 @@ xdsIR:
               port: 8080
             protocol: HTTP
             weight: 1
-        faultInjection:
-          abort:
-            httpStatus: 14
-            percentage: 0.01
         hostname: gateway.envoyproxy.io
         isHTTP2: false
         name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io
@@ -399,6 +396,11 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /route2
+        traffic:
+          faultInjection:
+            abort:
+              httpStatus: 14
+              percentage: 0.01
       - backendWeights:
           invalid: 0
           valid: 0
@@ -411,13 +413,6 @@ xdsIR:
               port: 8080
             protocol: HTTP
             weight: 1
-        faultInjection:
-          abort:
-            httpStatus: 500
-            percentage: 100
-          delay:
-            fixedDelay: 5.4s
-            percentage: 80
         hostname: gateway.envoyproxy.io
         isHTTP2: false
         name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
@@ -425,3 +420,11 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
+        traffic:
+          faultInjection:
+            abort:
+              httpStatus: 500
+              percentage: 100
+            delay:
+              fixedDelay: 5.4s
+              percentage: 80
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-use-client-protocol.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-use-client-protocol.out.yaml
index 5711eea32..2f43d16b4 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-use-client-protocol.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-use-client-protocol.out.yaml
@@ -156,4 +156,5 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
+        traffic: {}
         useClientProtocol: true
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-circuitbreakers.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-circuitbreakers.out.yaml
index aa471f547..b3f265b9b 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-circuitbreakers.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-circuitbreakers.out.yaml
@@ -266,12 +266,6 @@ xdsIR:
       - backendWeights:
           invalid: 0
           valid: 0
-        circuitBreaker:
-          maxConnections: 2048
-          maxParallelRequests: 4294967295
-          maxParallelRetries: 1024
-          maxPendingRequests: 1
-          maxRequestsPerConnection: 1
         destination:
           name: grpcroute/default/grpcroute-1/rule/0
           settings:
@@ -284,6 +278,13 @@ xdsIR:
         hostname: '*'
         isHTTP2: true
         name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
+        traffic:
+          circuitBreaker:
+            maxConnections: 2048
+            maxParallelRequests: 4294967295
+            maxParallelRetries: 1024
+            maxPendingRequests: 1
+            maxRequestsPerConnection: 1
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -302,12 +303,6 @@ xdsIR:
       - backendWeights:
           invalid: 0
           valid: 0
-        circuitBreaker:
-          maxConnections: 42
-          maxParallelRequests: 42
-          maxParallelRetries: 24
-          maxPendingRequests: 42
-          maxRequestsPerConnection: 42
         destination:
           name: httproute/default/httproute-1/rule/0
           settings:
@@ -324,3 +319,10 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
+        traffic:
+          circuitBreaker:
+            maxConnections: 42
+            maxParallelRequests: 42
+            maxParallelRetries: 24
+            maxPendingRequests: 42
+            maxRequestsPerConnection: 42
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml
index b49ec2daf..ee23bed19 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml
@@ -483,32 +483,33 @@ xdsIR:
               port: 8080
             protocol: GRPC
             weight: 1
-        healthCheck:
-          active:
-            healthyThreshold: 1
-            http:
-              expectedResponse:
-                binary: RXZlcnl0aGluZyBPSw==
-              expectedStatuses:
-              - 200
-              - 300
-              host: '*'
-              method: GET
-              path: /healthz
-            interval: 3s
-            timeout: 500ms
-            unhealthyThreshold: 3
-          passive:
-            baseEjectionTime: 2m40s
-            consecutive5XxErrors: 5
-            consecutiveGatewayErrors: 0
-            consecutiveLocalOriginFailures: 5
-            interval: 2s
-            maxEjectionPercent: 100
-            splitExternalLocalOriginErrors: false
         hostname: '*'
         isHTTP2: true
         name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
+        traffic:
+          healthCheck:
+            active:
+              healthyThreshold: 1
+              http:
+                expectedResponse:
+                  binary: RXZlcnl0aGluZyBPSw==
+                expectedStatuses:
+                - 200
+                - 300
+                host: '*'
+                method: GET
+                path: /healthz
+              interval: 3s
+              timeout: 500ms
+              unhealthyThreshold: 3
+            passive:
+              baseEjectionTime: 2m40s
+              consecutive5XxErrors: 5
+              consecutiveGatewayErrors: 0
+              consecutiveLocalOriginFailures: 5
+              interval: 2s
+              maxEjectionPercent: 100
+              splitExternalLocalOriginErrors: false
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -536,25 +537,6 @@ xdsIR:
               port: 8080
             protocol: HTTP
             weight: 1
-        healthCheck:
-          active:
-            healthyThreshold: 3
-            interval: 5s
-            tcp:
-              receive:
-                text: pong
-              send:
-                text: ping
-            timeout: 1s
-            unhealthyThreshold: 3
-          passive:
-            baseEjectionTime: 10s
-            consecutive5XxErrors: 5
-            consecutiveGatewayErrors: 0
-            consecutiveLocalOriginFailures: 5
-            interval: 10s
-            maxEjectionPercent: 10
-            splitExternalLocalOriginErrors: false
         hostname: gateway.envoyproxy.io
         isHTTP2: false
         name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io
@@ -562,6 +544,26 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /v2
+        traffic:
+          healthCheck:
+            active:
+              healthyThreshold: 3
+              interval: 5s
+              tcp:
+                receive:
+                  text: pong
+                send:
+                  text: ping
+              timeout: 1s
+              unhealthyThreshold: 3
+            passive:
+              baseEjectionTime: 10s
+              consecutive5XxErrors: 5
+              consecutiveGatewayErrors: 0
+              consecutiveLocalOriginFailures: 5
+              interval: 10s
+              maxEjectionPercent: 10
+              splitExternalLocalOriginErrors: false
       - backendWeights:
           invalid: 0
           valid: 0
@@ -574,25 +576,6 @@ xdsIR:
               port: 8080
             protocol: HTTP
             weight: 1
-        healthCheck:
-          active:
-            healthyThreshold: 1
-            interval: 3s
-            tcp:
-              receive:
-                binary: RXZlcnl0aGluZyBPSw==
-              send:
-                binary: cGluZw==
-            timeout: 1s
-            unhealthyThreshold: 3
-          passive:
-            baseEjectionTime: 2m40s
-            consecutive5XxErrors: 5
-            consecutiveGatewayErrors: 0
-            consecutiveLocalOriginFailures: 5
-            interval: 8ms
-            maxEjectionPercent: 11
-            splitExternalLocalOriginErrors: false
         hostname: gateway.envoyproxy.io
         isHTTP2: false
         name: httproute/default/httproute-3/rule/0/match/0/gateway_envoyproxy_io
@@ -600,6 +583,26 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /v3
+        traffic:
+          healthCheck:
+            active:
+              healthyThreshold: 1
+              interval: 3s
+              tcp:
+                receive:
+                  binary: RXZlcnl0aGluZyBPSw==
+                send:
+                  binary: cGluZw==
+              timeout: 1s
+              unhealthyThreshold: 3
+            passive:
+              baseEjectionTime: 2m40s
+              consecutive5XxErrors: 5
+              consecutiveGatewayErrors: 0
+              consecutiveLocalOriginFailures: 5
+              interval: 8ms
+              maxEjectionPercent: 11
+              splitExternalLocalOriginErrors: false
       - backendWeights:
           invalid: 0
           valid: 0
@@ -612,29 +615,6 @@ xdsIR:
               port: 8080
             protocol: HTTP
             weight: 1
-        healthCheck:
-          active:
-            healthyThreshold: 3
-            http:
-              expectedResponse:
-                text: pong
-              expectedStatuses:
-              - 200
-              - 201
-              host: gateway.envoyproxy.io
-              method: GET
-              path: /healthz
-            interval: 5s
-            timeout: 1s
-            unhealthyThreshold: 3
-          passive:
-            baseEjectionTime: 2m30s
-            consecutive5XxErrors: 5
-            consecutiveGatewayErrors: 0
-            consecutiveLocalOriginFailures: 5
-            interval: 1s
-            maxEjectionPercent: 100
-            splitExternalLocalOriginErrors: false
         hostname: gateway.envoyproxy.io
         isHTTP2: false
         name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
@@ -642,3 +622,27 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
+        traffic:
+          healthCheck:
+            active:
+              healthyThreshold: 3
+              http:
+                expectedResponse:
+                  text: pong
+                expectedStatuses:
+                - 200
+                - 201
+                host: gateway.envoyproxy.io
+                method: GET
+                path: /healthz
+              interval: 5s
+              timeout: 1s
+              unhealthyThreshold: 3
+            passive:
+              baseEjectionTime: 2m30s
+              consecutive5XxErrors: 5
+              consecutiveGatewayErrors: 0
+              consecutiveLocalOriginFailures: 5
+              interval: 1s
+              maxEjectionPercent: 100
+              splitExternalLocalOriginErrors: false
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml
index d308794c0..6114cd008 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml
@@ -374,9 +374,10 @@ xdsIR:
             weight: 1
         hostname: '*'
         isHTTP2: true
-        loadBalancer:
-          random: {}
         name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
+        traffic:
+          loadBalancer:
+            random: {}
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -406,15 +407,16 @@ xdsIR:
             weight: 1
         hostname: gateway.envoyproxy.io
         isHTTP2: false
-        loadBalancer:
-          leastRequest:
-            slowStart:
-              window: 5m0s
         name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io
         pathMatch:
           distinct: false
           name: ""
           prefix: /test2
+        traffic:
+          loadBalancer:
+            leastRequest:
+              slowStart:
+                window: 5m0s
       - backendWeights:
           invalid: 0
           valid: 0
@@ -429,11 +431,12 @@ xdsIR:
             weight: 1
         hostname: gateway.envoyproxy.io
         isHTTP2: false
-        loadBalancer:
-          consistentHash:
-            sourceIP: true
         name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
         pathMatch:
           distinct: false
           name: ""
           prefix: /
+        traffic:
+          loadBalancer:
+            consistentHash:
+              sourceIP: true
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml
index dcd818488..c55cf6333 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml
@@ -179,34 +179,35 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        rateLimit:
-          local:
-            default:
-              requests: 4294967295
-              unit: Second
-            rules:
-            - headerMatches:
-              - distinct: false
-                exact: one
-                name: x-user-id
-              - distinct: false
-                exact: foo
-                name: x-org-id
-              limit:
-                requests: 10
-                unit: Hour
-            - cidrMatch:
-                cidr: 192.168.0.0/16
-                distinct: false
-                ipv6: false
-                maskLen: 16
-              headerMatches:
-              - distinct: false
-                exact: two
-                name: x-user-id
-              - distinct: false
-                exact: bar
-                name: x-org-id
-              limit:
-                requests: 10
-                unit: Minute
+        traffic:
+          rateLimit:
+            local:
+              default:
+                requests: 4294967295
+                unit: Second
+              rules:
+              - headerMatches:
+                - distinct: false
+                  exact: one
+                  name: x-user-id
+                - distinct: false
+                  exact: foo
+                  name: x-org-id
+                limit:
+                  requests: 10
+                  unit: Hour
+              - cidrMatch:
+                  cidr: 192.168.0.0/16
+                  distinct: false
+                  ipv6: false
+                  maskLen: 16
+                headerMatches:
+                - distinct: false
+                  exact: two
+                  name: x-user-id
+                - distinct: false
+                  exact: bar
+                  name: x-org-id
+                limit:
+                  requests: 10
+                  unit: Minute
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml
index b331cd6fa..9c9d73f35 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml
@@ -182,34 +182,35 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        rateLimit:
-          local:
-            default:
-              requests: 10
-              unit: Minute
-            rules:
-            - headerMatches:
-              - distinct: false
-                exact: one
-                name: x-user-id
-              - distinct: false
-                exact: foo
-                name: x-org-id
-              limit:
-                requests: 10
-                unit: Hour
-            - cidrMatch:
-                cidr: 192.168.0.0/16
-                distinct: false
-                ipv6: false
-                maskLen: 16
-              headerMatches:
-              - distinct: false
-                exact: two
-                name: x-user-id
-              - distinct: false
-                exact: bar
-                name: x-org-id
-              limit:
+        traffic:
+          rateLimit:
+            local:
+              default:
                 requests: 10
                 unit: Minute
+              rules:
+              - headerMatches:
+                - distinct: false
+                  exact: one
+                  name: x-user-id
+                - distinct: false
+                  exact: foo
+                  name: x-org-id
+                limit:
+                  requests: 10
+                  unit: Hour
+              - cidrMatch:
+                  cidr: 192.168.0.0/16
+                  distinct: false
+                  ipv6: false
+                  maskLen: 16
+                headerMatches:
+                - distinct: false
+                  exact: two
+                  name: x-user-id
+                - distinct: false
+                  exact: bar
+                  name: x-org-id
+                limit:
+                  requests: 10
+                  unit: Minute
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-proxyprotocol.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-proxyprotocol.out.yaml
index 8e6332a01..92245edd5 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-proxyprotocol.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-proxyprotocol.out.yaml
@@ -270,8 +270,9 @@ xdsIR:
         hostname: '*'
         isHTTP2: true
         name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
-        proxyProtocol:
-          version: V1
+        traffic:
+          proxyProtocol:
+            version: V1
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -306,5 +307,6 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        proxyProtocol:
-          version: V2
+        traffic:
+          proxyProtocol:
+            version: V2
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml
index d3d6c8eb5..7a8ea390f 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml
@@ -290,18 +290,19 @@ xdsIR:
         hostname: '*'
         isHTTP2: true
         name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
-        rateLimit:
-          global:
-            rules:
-            - headerMatches:
-              - distinct: false
-                exact: one
-                name: x-user-id
-              - distinct: true
-                name: x-org-id
-              limit:
-                requests: 10
-                unit: Hour
+        traffic:
+          rateLimit:
+            global:
+              rules:
+              - headerMatches:
+                - distinct: false
+                  exact: one
+                  name: x-user-id
+                - distinct: true
+                  name: x-org-id
+                limit:
+                  requests: 10
+                  unit: Hour
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -336,15 +337,16 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        rateLimit:
-          global:
-            rules:
-            - cidrMatch:
-                cidr: 192.168.0.0/16
-                distinct: true
-                ipv6: false
-                maskLen: 16
-              headerMatches: []
-              limit:
-                requests: 20
-                unit: Hour
+        traffic:
+          rateLimit:
+            global:
+              rules:
+              - cidrMatch:
+                  cidr: 192.168.0.0/16
+                  distinct: true
+                  ipv6: false
+                  maskLen: 16
+                headerMatches: []
+                limit:
+                  requests: 20
+                  unit: Hour
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-retries.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-retries.out.yaml
index ae83655a6..e2db512f8 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-retries.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-retries.out.yaml
@@ -289,15 +289,16 @@ xdsIR:
         hostname: '*'
         isHTTP2: true
         name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
-        retry:
-          perRetry:
-            backOff:
-              baseInterval: 100ms
-              maxInterval: 10s
-            timeout: 250ms
-          retryOn:
-            triggers:
-            - cancelled
+        traffic:
+          retry:
+            perRetry:
+              backOff:
+                baseInterval: 100ms
+                maxInterval: 10s
+              timeout: 250ms
+            retryOn:
+              triggers:
+              - cancelled
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -332,17 +333,18 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        retry:
-          numRetries: 5
-          perRetry:
-            backOff:
-              baseInterval: 100ms
-              maxInterval: 10s
-            timeout: 250ms
-          retryOn:
-            httpStatusCodes:
-            - 429
-            - 503
-            triggers:
-            - connect-failure
-            - retriable-status-codes
+        traffic:
+          retry:
+            numRetries: 5
+            perRetry:
+              backOff:
+                baseInterval: 100ms
+                maxInterval: 10s
+              timeout: 250ms
+            retryOn:
+              httpStatusCodes:
+              - 429
+              - 503
+              triggers:
+              - connect-failure
+              - retriable-status-codes
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-same-prefix-httproutes.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-same-prefix-httproutes.out.yaml
index c9a22a833..ad6bbe269 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-same-prefix-httproutes.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-same-prefix-httproutes.out.yaml
@@ -182,10 +182,6 @@ xdsIR:
       - backendWeights:
           invalid: 0
           valid: 0
-        circuitBreaker:
-          maxConnections: 2048
-          maxParallelRequests: 4294967295
-          maxPendingRequests: 1
         destination:
           name: httproute/default/httproute-1/rule/0
           settings:
@@ -202,6 +198,11 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
+        traffic:
+          circuitBreaker:
+            maxConnections: 2048
+            maxParallelRequests: 4294967295
+            maxPendingRequests: 1
       - backendWeights:
           invalid: 0
           valid: 0
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-tcpkeepalive.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-tcpkeepalive.out.yaml
index eb796b42f..902cdd313 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-tcpkeepalive.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-tcpkeepalive.out.yaml
@@ -274,10 +274,11 @@ xdsIR:
         hostname: '*'
         isHTTP2: true
         name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
-        tcpKeepalive:
-          idleTime: 1200
-          interval: 60
-          probes: 3
+        traffic:
+          tcpKeepalive:
+            idleTime: 1200
+            interval: 60
+            probes: 3
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -312,7 +313,8 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        tcpKeepalive:
-          idleTime: 10
-          interval: 1800
-          probes: 6
+        traffic:
+          tcpKeepalive:
+            idleTime: 10
+            interval: 1800
+            probes: 6
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout.out.yaml
index 27ed1f540..b7dc2f6a0 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout.out.yaml
@@ -278,12 +278,13 @@ xdsIR:
         hostname: '*'
         isHTTP2: true
         name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
-        timeout:
-          http:
-            connectionIdleTimeout: 16s
-            maxConnectionDuration: 17s
-          tcp:
-            connectTimeout: 15s
+        traffic:
+          timeout:
+            http:
+              connectionIdleTimeout: 16s
+              maxConnectionDuration: 17s
+            tcp:
+              connectTimeout: 15s
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -318,9 +319,10 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        timeout:
-          http:
-            connectionIdleTimeout: 21s
-            maxConnectionDuration: 22s
-          tcp:
-            connectTimeout: 20s
+        traffic:
+          timeout:
+            http:
+              connectionIdleTimeout: 21s
+              maxConnectionDuration: 22s
+            tcp:
+              connectTimeout: 20s
diff --git a/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout-error.out.yaml b/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout-error.out.yaml
index b6093872e..3a85ecb7c 100644
--- a/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout-error.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout-error.out.yaml
@@ -163,6 +163,7 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        timeout:
-          http:
-            requestTimeout: 1s
+        traffic:
+          timeout:
+            http:
+              requestTimeout: 1s
diff --git a/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout.out.yaml b/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout.out.yaml
index 2f060c2db..097b37b51 100644
--- a/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout.out.yaml
@@ -279,12 +279,13 @@ xdsIR:
         hostname: '*'
         isHTTP2: true
         name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
-        timeout:
-          http:
-            connectionIdleTimeout: 16s
-            maxConnectionDuration: 17s
-          tcp:
-            connectTimeout: 15s
+        traffic:
+          timeout:
+            http:
+              connectionIdleTimeout: 16s
+              maxConnectionDuration: 17s
+            tcp:
+              connectTimeout: 15s
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -319,9 +320,9 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        timeout:
-          http:
-            maxConnectionDuration: 22s
-            requestTimeout: 1s
-          tcp:
-            connectTimeout: 20s
+        traffic:
+          timeout:
+            http:
+              maxConnectionDuration: 22s
+            tcp:
+              connectTimeout: 20s
diff --git a/internal/gatewayapi/testdata/httproute-backend-request-timeout.out.yaml b/internal/gatewayapi/testdata/httproute-backend-request-timeout.out.yaml
index f56835ccb..33076734d 100644
--- a/internal/gatewayapi/testdata/httproute-backend-request-timeout.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-backend-request-timeout.out.yaml
@@ -129,6 +129,7 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        timeout:
-          http:
-            requestTimeout: 1s
+        traffic:
+          timeout:
+            http:
+              requestTimeout: 1s
diff --git a/internal/gatewayapi/testdata/httproute-request-timeout.out.yaml b/internal/gatewayapi/testdata/httproute-request-timeout.out.yaml
index 3d3f73076..49c64cfe6 100644
--- a/internal/gatewayapi/testdata/httproute-request-timeout.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-request-timeout.out.yaml
@@ -129,6 +129,7 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        timeout:
-          http:
-            requestTimeout: 5s
+        traffic:
+          timeout:
+            http:
+              requestTimeout: 5s
diff --git a/internal/gatewayapi/testdata/merge-with-isolated-policies-2.out.yaml b/internal/gatewayapi/testdata/merge-with-isolated-policies-2.out.yaml
index 674289ecd..62df76b1b 100755
--- a/internal/gatewayapi/testdata/merge-with-isolated-policies-2.out.yaml
+++ b/internal/gatewayapi/testdata/merge-with-isolated-policies-2.out.yaml
@@ -546,10 +546,11 @@ xdsIR:
             - x-header-7
             - x-header-8
             maxAge: 33m20s
-        tcpKeepalive:
-          idleTime: 1200
-          interval: 60
-          probes: 3
+        traffic:
+          tcpKeepalive:
+            idleTime: 1200
+            interval: 60
+            probes: 3
       timeout:
         http:
           requestReceivedTimeout: 5s
@@ -598,10 +599,11 @@ xdsIR:
             - x-header-7
             - x-header-8
             maxAge: 33m20s
-        tcpKeepalive:
-          idleTime: 1200
-          interval: 60
-          probes: 3
+        traffic:
+          tcpKeepalive:
+            idleTime: 1200
+            interval: 60
+            probes: 3
       timeout:
         http:
           requestReceivedTimeout: 5s
diff --git a/internal/gatewayapi/testdata/merge-with-isolated-policies.out.yaml b/internal/gatewayapi/testdata/merge-with-isolated-policies.out.yaml
index d2bc2693c..9c3438bff 100644
--- a/internal/gatewayapi/testdata/merge-with-isolated-policies.out.yaml
+++ b/internal/gatewayapi/testdata/merge-with-isolated-policies.out.yaml
@@ -338,10 +338,11 @@ xdsIR:
             - x-header-7
             - x-header-8
             maxAge: 33m20s
-        tcpKeepalive:
-          idleTime: 1200
-          interval: 60
-          probes: 3
+        traffic:
+          tcpKeepalive:
+            idleTime: 1200
+            interval: 60
+            probes: 3
     - address: 0.0.0.0
       hostnames:
       - '*'
diff --git a/internal/xds/translator/route_test.go b/internal/xds/translator/route_test.go
index ed9198b6c..3d7d6a972 100644
--- a/internal/xds/translator/route_test.go
+++ b/internal/xds/translator/route_test.go
@@ -32,23 +32,39 @@ func Test_buildHashPolicy(t *testing.T) {
 			want:      nil,
 		},
 		{
-			name:      "Nil ConsistentHash in LoadBalancer",
-			httpRoute: &ir.HTTPRoute{LoadBalancer: &ir.LoadBalancer{}},
-			want:      nil,
+			name: "Nil ConsistentHash in LoadBalancer",
+			httpRoute: &ir.HTTPRoute{
+				Traffic: &ir.TrafficFeatures{
+					LoadBalancer: &ir.LoadBalancer{},
+				},
+			},
+			want: nil,
 		},
 		{
-			name:      "ConsistentHash with nil SourceIP and Header",
-			httpRoute: &ir.HTTPRoute{LoadBalancer: &ir.LoadBalancer{ConsistentHash: &ir.ConsistentHash{}}},
-			want:      nil,
+			name: "ConsistentHash with nil SourceIP and Header",
+			httpRoute: &ir.HTTPRoute{
+				Traffic: &ir.TrafficFeatures{
+					LoadBalancer: &ir.LoadBalancer{ConsistentHash: &ir.ConsistentHash{}},
+				},
+			},
+			want: nil,
 		},
 		{
-			name:      "ConsistentHash with SourceIP set to false",
-			httpRoute: &ir.HTTPRoute{LoadBalancer: &ir.LoadBalancer{ConsistentHash: &ir.ConsistentHash{SourceIP: ptr.To(false)}}},
-			want:      nil,
+			name: "ConsistentHash with SourceIP set to false",
+			httpRoute: &ir.HTTPRoute{
+				Traffic: &ir.TrafficFeatures{
+					LoadBalancer: &ir.LoadBalancer{ConsistentHash: &ir.ConsistentHash{SourceIP: ptr.To(false)}},
+				},
+			},
+			want: nil,
 		},
 		{
-			name:      "ConsistentHash with SourceIP set to true",
-			httpRoute: &ir.HTTPRoute{LoadBalancer: &ir.LoadBalancer{ConsistentHash: &ir.ConsistentHash{SourceIP: ptr.To(true)}}},
+			name: "ConsistentHash with SourceIP set to true",
+			httpRoute: &ir.HTTPRoute{
+				Traffic: &ir.TrafficFeatures{
+					LoadBalancer: &ir.LoadBalancer{ConsistentHash: &ir.ConsistentHash{SourceIP: ptr.To(true)}},
+				},
+			},
 			want: []*routev3.RouteAction_HashPolicy{
 				{
 					PolicySpecifier: &routev3.RouteAction_HashPolicy_ConnectionProperties_{
@@ -60,8 +76,12 @@ func Test_buildHashPolicy(t *testing.T) {
 			},
 		},
 		{
-			name:      "ConsistentHash with Header",
-			httpRoute: &ir.HTTPRoute{LoadBalancer: &ir.LoadBalancer{ConsistentHash: &ir.ConsistentHash{Header: &ir.Header{Name: "name"}}}},
+			name: "ConsistentHash with Header",
+			httpRoute: &ir.HTTPRoute{
+				Traffic: &ir.TrafficFeatures{
+					LoadBalancer: &ir.LoadBalancer{ConsistentHash: &ir.ConsistentHash{Header: &ir.Header{Name: "name"}}},
+				},
+			},
 			want: []*routev3.RouteAction_HashPolicy{
 				{
 					PolicySpecifier: &routev3.RouteAction_HashPolicy_Header_{
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/distinct-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/distinct-match.yaml
index 0f3f97496..742805ac2 100644
--- a/internal/xds/translator/testdata/in/ratelimit-config/distinct-match.yaml
+++ b/internal/xds/translator/testdata/in/ratelimit-config/distinct-match.yaml
@@ -8,15 +8,16 @@ path:
   escapedSlashesAction: UnescapeAndRedirect
 routes:
 - name: "first-route"
-  rateLimit:
-    global:
-      rules:
-      - headerMatches:
-        - name: "x-user-id"
-          distinct: true
-        limit:
-          requests: 5
-          unit: second
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - headerMatches:
+          - name: "x-user-id"
+            distinct: true
+          limit:
+            requests: 5
+            unit: second
   pathMatch:
     exact: "foo/bar"
   destination:
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/distinct-remote-address-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/distinct-remote-address-match.yaml
index 7ff820e7b..4fbb48de3 100644
--- a/internal/xds/translator/testdata/in/ratelimit-config/distinct-remote-address-match.yaml
+++ b/internal/xds/translator/testdata/in/ratelimit-config/distinct-remote-address-match.yaml
@@ -8,17 +8,18 @@ path:
   escapedSlashesAction: UnescapeAndRedirect
 routes:
 - name: "first-route"
-  rateLimit:
-    global:
-      rules:
-      - cidrMatch:
-          cidr: "192.168.0.0/16"
-          ipv6: false
-          maskLen: 16
-          distinct: true
-        limit:
-          requests: 5
-          unit: second
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - cidrMatch:
+            cidr: "192.168.0.0/16"
+            ipv6: false
+            maskLen: 16
+            distinct: true
+          limit:
+            requests: 5
+            unit: second
   pathMatch:
     exact: "foo/bar"
   destination:
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/empty-header-matches.yaml b/internal/xds/translator/testdata/in/ratelimit-config/empty-header-matches.yaml
index ca13b2132..0de76f96e 100644
--- a/internal/xds/translator/testdata/in/ratelimit-config/empty-header-matches.yaml
+++ b/internal/xds/translator/testdata/in/ratelimit-config/empty-header-matches.yaml
@@ -8,12 +8,13 @@ path:
   escapedSlashesAction: UnescapeAndRedirect
 routes:
 - name: "first-route"
-  rateLimit:
-    global:
-      rules:
-      - limit:
-          requests: 5
-          unit: second
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - limit:
+            requests: 5
+            unit: second
   pathMatch:
     exact: "foo/bar"
   destination:
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/masked-remote-address-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/masked-remote-address-match.yaml
index 70531d48a..4e21ae73c 100644
--- a/internal/xds/translator/testdata/in/ratelimit-config/masked-remote-address-match.yaml
+++ b/internal/xds/translator/testdata/in/ratelimit-config/masked-remote-address-match.yaml
@@ -8,16 +8,17 @@ path:
   escapedSlashesAction: UnescapeAndRedirect
 routes:
 - name: "first-route"
-  rateLimit:
-    global:
-      rules:
-      - cidrMatch:
-          cidr: "192.168.0.0/16"
-          ipv6: false
-          maskLen: 16
-        limit:
-          requests: 5
-          unit: second
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - cidrMatch:
+            cidr: "192.168.0.0/16"
+            ipv6: false
+            maskLen: 16
+          limit:
+            requests: 5
+            unit: second
   pathMatch:
     exact: "foo/bar"
   destination:
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.yaml
index 32792f005..3f5dba6a9 100644
--- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.yaml
+++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.yaml
@@ -8,16 +8,17 @@ path:
   escapedSlashesAction: UnescapeAndRedirect
 routes:
 - name: "first-route"
-  rateLimit:
-    global:
-      rules:
-      - cidrMatch:
-          cidr: "192.168.0.10/32"
-          ipv6: false
-          maskLen: 32
-        limit:
-          requests: 15
-          unit: Hour
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - cidrMatch:
+            cidr: "192.168.0.10/32"
+            ipv6: false
+            maskLen: 32
+          limit:
+            requests: 15
+            unit: Hour
   pathMatch:
     exact: "foo/bar"
   destination:
@@ -27,16 +28,17 @@ routes:
       - host: "1.2.3.4"
         port: 50000
 - name: "second-route"
-  rateLimit:
-    global:
-      rules:
-      - cidrMatch:
-          cidr: "192.168.0.10/32"
-          ipv6: false
-          maskLen: 32
-        limit:
-          requests: 300
-          unit: Hour
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - cidrMatch:
+            cidr: "192.168.0.10/32"
+            ipv6: false
+            maskLen: 32
+          limit:
+            requests: 300
+            unit: Hour
   pathMatch:
     exact: "foo/bar"
   destination:
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-matches.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-matches.yaml
index 67de62abb..bebb4cb14 100644
--- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-matches.yaml
+++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-matches.yaml
@@ -8,17 +8,18 @@ path:
   escapedSlashesAction: UnescapeAndRedirect
 routes:
 - name: "first-route"
-  rateLimit:
-    global:
-      rules:
-      - headerMatches:
-        - name: "x-user-id"
-          exact: "one"
-        - name: "x-user-id"
-          exact: "two"
-        limit:
-          requests: 5
-          unit: second
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - headerMatches:
+          - name: "x-user-id"
+            exact: "one"
+          - name: "x-user-id"
+            exact: "two"
+          limit:
+            requests: 5
+            unit: second
   pathMatch:
     exact: "foo/bar"
   destination:
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-routes.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-routes.yaml
index c5a768c9b..6db5c96e8 100644
--- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-routes.yaml
+++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-routes.yaml
@@ -8,27 +8,29 @@ path:
   escapedSlashesAction: UnescapeAndRedirect
 routes:
 - name: "first-route"
-  rateLimit:
-    global:
-      rules:
-      - headerMatches:
-        - name: "x-user-id"
-          exact: "one"
-        limit:
-          requests: 5
-          unit: second
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - headerMatches:
+          - name: "x-user-id"
+            exact: "one"
+          limit:
+            requests: 5
+            unit: second
   pathMatch:
     exact: "foo/baz"
 - name: "second-route"
-  rateLimit:
-    global:
-      rules:
-      - headerMatches:
-        - name: "x-user-id"
-          exact: "two"
-        limit:
-          requests: 10
-          unit: second
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - headerMatches:
+          - name: "x-user-id"
+            exact: "two"
+          limit:
+            requests: 10
+            unit: second
   pathMatch:
     exact: "foo/bar"
   destination:
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-rules.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-rules.yaml
index ef9b8d228..3267a695b 100644
--- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-rules.yaml
+++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-rules.yaml
@@ -8,21 +8,22 @@ path:
   escapedSlashesAction: UnescapeAndRedirect
 routes:
 - name: "first-route"
-  rateLimit:
-    global:
-      rules:
-      - headerMatches:
-        - name: "x-user-id"
-          exact: "one"
-        limit:
-          requests: 5
-          unit: second
-      - headerMatches:
-        - name: "x-user-id"
-          exact: "two"
-        limit:
-          requests: 10
-          unit: second
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - headerMatches:
+          - name: "x-user-id"
+            exact: "one"
+          limit:
+            requests: 5
+            unit: second
+        - headerMatches:
+          - name: "x-user-id"
+            exact: "two"
+          limit:
+            requests: 10
+            unit: second
   pathMatch:
     exact: "foo/bar"
   destination:
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/value-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/value-match.yaml
index 8fc933e92..a265cab5c 100644
--- a/internal/xds/translator/testdata/in/ratelimit-config/value-match.yaml
+++ b/internal/xds/translator/testdata/in/ratelimit-config/value-match.yaml
@@ -8,15 +8,16 @@ path:
   escapedSlashesAction: UnescapeAndRedirect
 routes:
 - name: "first-route"
-  rateLimit:
-    global:
-      rules:
-      - headerMatches:
-        - name: "x-user-id"
-          exact: "one"
-        limit:
-          requests: 5
-          unit: second
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - headerMatches:
+          - name: "x-user-id"
+            exact: "one"
+          limit:
+            requests: 5
+            unit: second
   pathMatch:
     exact: "foo/bar"
   destination:
diff --git a/internal/xds/translator/testdata/in/xds-ir/circuit-breaker.yaml b/internal/xds/translator/testdata/in/xds-ir/circuit-breaker.yaml
index a63297e26..f4dd3bbaa 100644
--- a/internal/xds/translator/testdata/in/xds-ir/circuit-breaker.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/circuit-breaker.yaml
@@ -10,12 +10,13 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    circuitBreaker:
-      maxConnections: 1
-      maxPendingRequests: 1
-      maxParallelRequests: 1
-      maxParallelRetries: 2
-      maxRequestsPerConnection: 10
+    traffic:
+      circuitBreaker:
+        maxConnections: 1
+        maxPendingRequests: 1
+        maxParallelRequests: 1
+        maxParallelRetries: 2
+        maxRequestsPerConnection: 10
     destination:
       name: "first-route-dest"
       settings:
diff --git a/internal/xds/translator/testdata/in/xds-ir/client-timeout.yaml b/internal/xds/translator/testdata/in/xds-ir/client-timeout.yaml
index 06c44b051..6c3cfa3aa 100644
--- a/internal/xds/translator/testdata/in/xds-ir/client-timeout.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/client-timeout.yaml
@@ -20,4 +20,3 @@ http:
       http:
         requestReceivedTimeout: "5s"
         idleTimeout: "10s"
-
diff --git a/internal/xds/translator/testdata/in/xds-ir/fault-injection.yaml b/internal/xds/translator/testdata/in/xds-ir/fault-injection.yaml
index a99b77d5e..39b351eb6 100644
--- a/internal/xds/translator/testdata/in/xds-ir/fault-injection.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/fault-injection.yaml
@@ -10,10 +10,11 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    faultInjection:
-      abort:
-        httpStatus: 500
-        percentage: 0.01
+    traffic:
+      faultInjection:
+        abort:
+          httpStatus: 500
+          percentage: 0.01
     pathMatch:
       exact: "foo/bar"
     destination:
@@ -24,10 +25,11 @@ http:
           port: 50000
   - name: "second-route"
     hostname: "*"
-    faultInjection:
-      delay:
-        fixedDelay: 5.4s
-        percentage: 80
+    traffic:
+      faultInjection:
+        delay:
+          fixedDelay: 5.4s
+          percentage: 80
     pathMatch:
       exact: "example"
     destination:
@@ -38,13 +40,14 @@ http:
           port: 50000
   - name: "third-route"
     hostname: "*"
-    faultInjection:
-      abort:
-        httpStatus: 500
-        percentage: 100
-      delay:
-        fixedDelay: 5.005s
-        percentage: 100
+    traffic:
+      faultInjection:
+        abort:
+          httpStatus: 500
+          percentage: 100
+        delay:
+          fixedDelay: 5.005s
+          percentage: 100
     pathMatch:
       exact: "test"
     destination:
@@ -55,13 +58,14 @@ http:
           port: 50000
   - name: "fourth-route"
     hostname: "*"
-    faultInjection:
-      abort:
-        grpcStatus: 14
-        percentage: 100
-      delay:
-        fixedDelay: 5.005s
-        percentage: 100
+    traffic:
+      faultInjection:
+        abort:
+          grpcStatus: 14
+          percentage: 100
+        delay:
+          fixedDelay: 5.005s
+          percentage: 100
     pathMatch:
       exact: "test"
     destination:
@@ -72,7 +76,8 @@ http:
           port: 50000
   - name: "fifth-route"
     hostname: "*"
-    faultInjection:
+    traffic:
+      faultInjection: {}
     pathMatch:
       exact: "test"
     destination:
diff --git a/internal/xds/translator/testdata/in/xds-ir/health-check.yaml b/internal/xds/translator/testdata/in/xds-ir/health-check.yaml
index a767bdab2..30ecd2da7 100644
--- a/internal/xds/translator/testdata/in/xds-ir/health-check.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/health-check.yaml
@@ -10,28 +10,29 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    healthCheck:
-      active:
-        timeout: "500ms"
-        interval: "3s"
-        unhealthyThreshold: 3
-        healthyThreshold: 1
-        http:
-          host: "*"
-          path: "/healthz"
-          expectedResponse:
-            text: "ok"
-          expectedStatuses:
-          - 200
-          - 300
-      passive:
-        baseEjectionTime: 180s
-        interval: 2s
-        maxEjectionPercent: 100
-        consecutive5XxErrors: 5
-        consecutiveGatewayErrors: 0
-        consecutiveLocalOriginFailures: 5
-        splitExternalLocalOriginErrors: false
+    traffic:
+      healthCheck:
+        active:
+          timeout: "500ms"
+          interval: "3s"
+          unhealthyThreshold: 3
+          healthyThreshold: 1
+          http:
+            host: "*"
+            path: "/healthz"
+            expectedResponse:
+              text: "ok"
+            expectedStatuses:
+            - 200
+            - 300
+        passive:
+          baseEjectionTime: 180s
+          interval: 2s
+          maxEjectionPercent: 100
+          consecutive5XxErrors: 5
+          consecutiveGatewayErrors: 0
+          consecutiveLocalOriginFailures: 5
+          splitExternalLocalOriginErrors: false
     destination:
       name: "first-route-dest"
       settings:
@@ -40,28 +41,29 @@ http:
           port: 50000
   - name: "second-route"
     hostname: "*"
-    healthCheck:
-      active:
-        timeout: "1s"
-        interval: "5s"
-        unhealthyThreshold: 3
-        healthyThreshold: 3
-        http:
-          host: "*"
-          path: "/healthz"
-          expectedResponse:
-            binary: "cG9uZw=="
-          expectedStatuses:
-          - 200
-          - 201
-      passive:
-        baseEjectionTime: 180s
-        interval: 1s
-        maxEjectionPercent: 100
-        consecutive5XxErrors: 5
-        consecutiveGatewayErrors: 0
-        consecutiveLocalOriginFailures: 5
-        splitExternalLocalOriginErrors: false
+    traffic:
+      healthCheck:
+        active:
+          timeout: "1s"
+          interval: "5s"
+          unhealthyThreshold: 3
+          healthyThreshold: 3
+          http:
+            host: "*"
+            path: "/healthz"
+            expectedResponse:
+              binary: "cG9uZw=="
+            expectedStatuses:
+            - 200
+            - 201
+        passive:
+          baseEjectionTime: 180s
+          interval: 1s
+          maxEjectionPercent: 100
+          consecutive5XxErrors: 5
+          consecutiveGatewayErrors: 0
+          consecutiveLocalOriginFailures: 5
+          splitExternalLocalOriginErrors: false
     destination:
       name: "second-route-dest"
       settings:
@@ -70,25 +72,26 @@ http:
           port: 50000
   - name: "third-route"
     hostname: "*"
-    healthCheck:
-      active:
-        timeout: "1s"
-        interval: "5s"
-        unhealthyThreshold: 3
-        healthyThreshold: 3
-        tcp:
-          send:
-            text: "ping"
-          receive:
-            text: "pong"
-      passive:
-        baseEjectionTime: 160s
-        interval: 1s
-        maxEjectionPercent: 100
-        consecutive5XxErrors: 5
-        consecutiveGatewayErrors: 0
-        consecutiveLocalOriginFailures: 5
-        splitExternalLocalOriginErrors: false
+    traffic:
+      healthCheck:
+        active:
+          timeout: "1s"
+          interval: "5s"
+          unhealthyThreshold: 3
+          healthyThreshold: 3
+          tcp:
+            send:
+              text: "ping"
+            receive:
+              text: "pong"
+        passive:
+          baseEjectionTime: 160s
+          interval: 1s
+          maxEjectionPercent: 100
+          consecutive5XxErrors: 5
+          consecutiveGatewayErrors: 0
+          consecutiveLocalOriginFailures: 5
+          splitExternalLocalOriginErrors: false
     destination:
       name: "third-route-dest"
       settings:
@@ -97,25 +100,26 @@ http:
           port: 50000
   - name: "fourth-route"
     hostname: "*"
-    healthCheck:
-      active:
-        timeout: "1s"
-        interval: "5s"
-        unhealthyThreshold: 3
-        healthyThreshold: 3
-        tcp:
-          send:
-            binary: "cGluZw=="
-          receive:
-            binary: "cG9uZw=="
-      passive:
-        baseEjectionTime: 180s
-        interval: 1s
-        maxEjectionPercent: 90
-        consecutive5XxErrors: 5
-        consecutiveGatewayErrors: 0
-        consecutiveLocalOriginFailures: 5
-        splitExternalLocalOriginErrors: true
+    traffic:
+      healthCheck:
+        active:
+          timeout: "1s"
+          interval: "5s"
+          unhealthyThreshold: 3
+          healthyThreshold: 3
+          tcp:
+            send:
+              binary: "cGluZw=="
+            receive:
+              binary: "cG9uZw=="
+        passive:
+          baseEjectionTime: 180s
+          interval: 1s
+          maxEjectionPercent: 90
+          consecutive5XxErrors: 5
+          consecutiveGatewayErrors: 0
+          consecutiveLocalOriginFailures: 5
+          splitExternalLocalOriginErrors: true
     destination:
       name: "fourth-route-dest"
       settings:
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-timeout.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-timeout.yaml
index fdd2b84ef..746d49225 100644
--- a/internal/xds/translator/testdata/in/xds-ir/http-route-timeout.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/http-route-timeout.yaml
@@ -10,9 +10,10 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    timeout:
-      http:
-        requestTimeout: 5s
+    traffic:
+      timeout:
+        http:
+          requestTimeout: 5s
     headerMatches:
     - name: user
       stringMatch:
@@ -25,9 +26,10 @@ http:
           port: 50000
   - name: "second-route"
     hostname: "*"
-    timeout:
-      http:
-        requestTimeout: 4000s
+    traffic:
+      timeout:
+        http:
+          requestTimeout: 4000s
     destination:
       name: "second-route-dest"
       settings:
@@ -36,9 +38,10 @@ http:
           port: 50001
   - name: "third-route"
     hostname: "*"
-    timeout:
-      http:
-        requestTimeout: 0s
+    traffic:
+      timeout:
+        http:
+          requestTimeout: 0s
     destination:
       name: "third-route-dest"
       settings:
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-with-tlsbundle-multiple-certs.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-with-tlsbundle-multiple-certs.yaml
index 7dd532ab4..a706244db 100644
--- a/internal/xds/translator/testdata/in/xds-ir/http-route-with-tlsbundle-multiple-certs.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/http-route-with-tlsbundle-multiple-certs.yaml
@@ -56,8 +56,9 @@ http:
       - backendWeights:
           invalid: 0
           valid: 0
-        proxyProtocol:
-          version: "V2"
+        traffic:
+          proxyProtocol:
+            version: "V2"
         destination:
           name: httproute/envoy-gateway/httproute-btls-2/rule/0
           settings:
diff --git a/internal/xds/translator/testdata/in/xds-ir/jwt-ratelimit.yaml b/internal/xds/translator/testdata/in/xds-ir/jwt-ratelimit.yaml
index a4682250a..008b5b9bd 100644
--- a/internal/xds/translator/testdata/in/xds-ir/jwt-ratelimit.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/jwt-ratelimit.yaml
@@ -10,15 +10,16 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - headerMatches:
-          - name: "x-user-id"
-            exact: "one"
-          limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - headerMatches:
+            - name: "x-user-id"
+              exact: "one"
+            limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "foo/bar"
     destination:
@@ -38,15 +39,16 @@ http:
             uri: https://192.168.1.250/jwt/public-key/jwks.json
   - name: "second-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - headerMatches:
-          - name: "x-user-id"
-            distinct: true
-          limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - headerMatches:
+            - name: "x-user-id"
+              distinct: true
+            limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "example"
     destination:
@@ -57,12 +59,13 @@ http:
           port: 50000
   - name: "third-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "test"
     destination:
diff --git a/internal/xds/translator/testdata/in/xds-ir/load-balancer.yaml b/internal/xds/translator/testdata/in/xds-ir/load-balancer.yaml
index 9c76ca46b..2d8ef97ef 100644
--- a/internal/xds/translator/testdata/in/xds-ir/load-balancer.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/load-balancer.yaml
@@ -10,8 +10,9 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    loadBalancer:
-      roundRobin: {}
+    traffic:
+      loadBalancer:
+        roundRobin: {}
     destination:
       name: "first-route-dest"
       settings:
@@ -20,8 +21,9 @@ http:
           port: 50000
   - name: "second-route"
     hostname: "*"
-    loadBalancer:
-      random: {}
+    traffic:
+      loadBalancer:
+        random: {}
     destination:
       name: "second-route-dest"
       settings:
@@ -30,8 +32,9 @@ http:
           port: 50000
   - name: "third-route"
     hostname: "*"
-    loadBalancer:
-      leastRequest: {}
+    traffic:
+      loadBalancer:
+        leastRequest: {}
     destination:
       name: "third-route-dest"
       settings:
@@ -40,9 +43,10 @@ http:
           port: 50000
   - name: "fourth-route"
     hostname: "*"
-    loadBalancer:
-      consistentHash:
-        sourceIP: true
+    traffic:
+      loadBalancer:
+        consistentHash:
+          sourceIP: true
     destination:
       name: "fourth-route-dest"
       settings:
@@ -51,10 +55,11 @@ http:
           port: 50000
   - name: "fifth-route"
     hostname: "*"
-    loadBalancer:
-      leastRequest:
-        slowStart:
-          window: 60s
+    traffic:
+      loadBalancer:
+        leastRequest:
+          slowStart:
+            window: 60s
     destination:
       name: "fifth-route-dest"
       settings:
@@ -63,10 +68,11 @@ http:
           port: 50000
   - name: "sixth-route"
     hostname: "*"
-    loadBalancer:
-      roundRobin:
-        slowStart:
-          window: 300s
+    traffic:
+      loadBalancer:
+        roundRobin:
+          slowStart:
+            window: 300s
     destination:
       name: "sixth-route-dest"
       settings:
@@ -75,10 +81,11 @@ http:
           port: 50000
   - name: "seventh-route"
     hostname: "*"
-    loadBalancer:
-      consistentHash:
-        header:
-          name: name
+    traffic:
+      loadBalancer:
+        consistentHash:
+          header:
+            name: name
     destination:
       name: "seventh-route-dest"
       settings:
diff --git a/internal/xds/translator/testdata/in/xds-ir/local-ratelimit.yaml b/internal/xds/translator/testdata/in/xds-ir/local-ratelimit.yaml
index 9e818b4d1..fb7baf05c 100644
--- a/internal/xds/translator/testdata/in/xds-ir/local-ratelimit.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/local-ratelimit.yaml
@@ -10,20 +10,21 @@ http:
   routes:
   - name: "first-route-ratelimit-single-rule"
     hostname: "*"
-    rateLimit:
-      local:
-        default:
-          requests: 10
-          unit: Minute
-        rules:
-        - headerMatches:
-          - name: x-user-id
-            exact: one
-          - name: x-org-id
-            exact: foo
-          limit:
+    traffic:
+      rateLimit:
+        local:
+          default:
             requests: 10
-            unit: Hour
+            unit: Minute
+          rules:
+          - headerMatches:
+            - name: x-user-id
+              exact: one
+            - name: x-org-id
+              exact: foo
+            limit:
+              requests: 10
+              unit: Hour
     pathMatch:
       exact: "foo/bar"
     destination:
@@ -34,31 +35,32 @@ http:
           port: 50000
   - name: "second-route-ratelimit-multiple-rules"
     hostname: "*"
-    rateLimit:
-      local:
-        default:
-          requests: 10
-          unit: Minute
-        rules:
-        - headerMatches:
-          - name: x-user-id
-            exact: one
-          - name: x-org-id
-            exact: foo
-          limit:
-            requests: 10
-            unit: Hour
-        - cidrMatch:
-            cidr: 192.168.0.0/16
-            maskLen: 16
-          headerMatches:
-          - name: x-user-id
-            exact: two
-          - name: x-org-id
-            exact: bar
-          limit:
+    traffic:
+      rateLimit:
+        local:
+          default:
             requests: 10
             unit: Minute
+          rules:
+          - headerMatches:
+            - name: x-user-id
+              exact: one
+            - name: x-org-id
+              exact: foo
+            limit:
+              requests: 10
+              unit: Hour
+          - cidrMatch:
+              cidr: 192.168.0.0/16
+              maskLen: 16
+            headerMatches:
+            - name: x-user-id
+              exact: two
+            - name: x-org-id
+              exact: bar
+            limit:
+              requests: 10
+              unit: Minute
     pathMatch:
       exact: "example"
     destination:
@@ -69,11 +71,12 @@ http:
           port: 50000
   - name: "third-route-ratelimit-no-rule"
     hostname: "*"
-    rateLimit:
-      local:
-        default:
-          requests: 10
-          unit: Minute
+    traffic:
+      rateLimit:
+        local:
+          default:
+            requests: 10
+            unit: Minute
     pathMatch:
       exact: "test"
     destination:
diff --git a/internal/xds/translator/testdata/in/xds-ir/proxy-protocol-upstream.yaml b/internal/xds/translator/testdata/in/xds-ir/proxy-protocol-upstream.yaml
index adf21ca8f..47df0026b 100644
--- a/internal/xds/translator/testdata/in/xds-ir/proxy-protocol-upstream.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/proxy-protocol-upstream.yaml
@@ -10,8 +10,9 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    proxyProtocol:
-      version: "V2"
+    traffic:
+      proxyProtocol:
+        version: "V2"
     destination:
       name: "first-route-dest"
       settings:
diff --git a/internal/xds/translator/testdata/in/xds-ir/ratelimit-custom-domain.yaml b/internal/xds/translator/testdata/in/xds-ir/ratelimit-custom-domain.yaml
index 8d9c2f5b4..271d39cfd 100644
--- a/internal/xds/translator/testdata/in/xds-ir/ratelimit-custom-domain.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/ratelimit-custom-domain.yaml
@@ -10,15 +10,16 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - headerMatches:
-          - name: "x-user-id"
-            exact: "one"
-          limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - headerMatches:
+            - name: "x-user-id"
+              exact: "one"
+            limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "foo/bar"
     destination:
@@ -29,15 +30,16 @@ http:
           port: 50000
   - name: "second-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - headerMatches:
-          - name: "x-user-id"
-            distinct: true
-          limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - headerMatches:
+            - name: "x-user-id"
+              distinct: true
+            limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "example"
     destination:
@@ -48,12 +50,13 @@ http:
           port: 50000
   - name: "third-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "test"
     destination:
diff --git a/internal/xds/translator/testdata/in/xds-ir/ratelimit-endpoint-stats.yaml b/internal/xds/translator/testdata/in/xds-ir/ratelimit-endpoint-stats.yaml
index 56a4b8d48..32f951172 100644
--- a/internal/xds/translator/testdata/in/xds-ir/ratelimit-endpoint-stats.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/ratelimit-endpoint-stats.yaml
@@ -12,15 +12,16 @@ http:
     routes:
       - name: "first-route"
         hostname: "*"
-        rateLimit:
-          global:
-            rules:
-              - headerMatches:
-                  - name: "x-user-id"
-                    exact: "one"
-                limit:
-                  requests: 5
-                  unit: second
+        traffic:
+          rateLimit:
+            global:
+              rules:
+                - headerMatches:
+                    - name: "x-user-id"
+                      exact: "one"
+                  limit:
+                    requests: 5
+                    unit: second
         pathMatch:
           exact: "foo/bar"
         destination:
@@ -31,15 +32,16 @@ http:
                   port: 50000
       - name: "second-route"
         hostname: "*"
-        rateLimit:
-          global:
-            rules:
-              - headerMatches:
-                  - name: "x-user-id"
-                    distinct: true
-                limit:
-                  requests: 5
-                  unit: second
+        traffic:
+          rateLimit:
+            global:
+              rules:
+                - headerMatches:
+                    - name: "x-user-id"
+                      distinct: true
+                  limit:
+                    requests: 5
+                    unit: second
         pathMatch:
           exact: "example"
         destination:
@@ -50,12 +52,13 @@ http:
                   port: 50000
       - name: "third-route"
         hostname: "*"
-        rateLimit:
-          global:
-            rules:
-              - limit:
-                  requests: 5
-                  unit: second
+        traffic:
+          rateLimit:
+            global:
+              rules:
+                - limit:
+                    requests: 5
+                    unit: second
         pathMatch:
           exact: "test"
         destination:
diff --git a/internal/xds/translator/testdata/in/xds-ir/ratelimit-sourceip.yaml b/internal/xds/translator/testdata/in/xds-ir/ratelimit-sourceip.yaml
index 69b3480fc..495fa9b7a 100644
--- a/internal/xds/translator/testdata/in/xds-ir/ratelimit-sourceip.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/ratelimit-sourceip.yaml
@@ -10,15 +10,16 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - cidrMatch:
-            cidr: 192.168.0.0/16
-            maskLen: 16
-          limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - cidrMatch:
+              cidr: 192.168.0.0/16
+              maskLen: 16
+            limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "foo/bar"
     destination:
@@ -29,15 +30,16 @@ http:
           port: 50000
   - name: "second-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - cidrMatch:
-            cidr: 192.168.0.0/24
-            maskLen: 24
-          limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - cidrMatch:
+              cidr: 192.168.0.0/24
+              maskLen: 24
+            limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "example"
     destination:
@@ -48,12 +50,13 @@ http:
           port: 50000
   - name: "third-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "test"
     destination:
@@ -64,16 +67,17 @@ http:
           port: 50000
   - name: "fourth-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - cidrMatch:
-            cidr: 192.168.0.0/16
-            maskLen: 16
-            distinct: true
-          limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - cidrMatch:
+              cidr: 192.168.0.0/16
+              maskLen: 16
+              distinct: true
+            limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "distinct"
     destination:
diff --git a/internal/xds/translator/testdata/in/xds-ir/ratelimit.yaml b/internal/xds/translator/testdata/in/xds-ir/ratelimit.yaml
index 8d9c2f5b4..271d39cfd 100644
--- a/internal/xds/translator/testdata/in/xds-ir/ratelimit.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/ratelimit.yaml
@@ -10,15 +10,16 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - headerMatches:
-          - name: "x-user-id"
-            exact: "one"
-          limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - headerMatches:
+            - name: "x-user-id"
+              exact: "one"
+            limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "foo/bar"
     destination:
@@ -29,15 +30,16 @@ http:
           port: 50000
   - name: "second-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - headerMatches:
-          - name: "x-user-id"
-            distinct: true
-          limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - headerMatches:
+            - name: "x-user-id"
+              distinct: true
+            limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "example"
     destination:
@@ -48,12 +50,13 @@ http:
           port: 50000
   - name: "third-route"
     hostname: "*"
-    rateLimit:
-      global:
-        rules:
-        - limit:
-            requests: 5
-            unit: second
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - limit:
+              requests: 5
+              unit: second
     pathMatch:
       exact: "test"
     destination:
diff --git a/internal/xds/translator/testdata/in/xds-ir/retry-partial-invalid.yaml b/internal/xds/translator/testdata/in/xds-ir/retry-partial-invalid.yaml
index 7f5938fd6..748335672 100644
--- a/internal/xds/translator/testdata/in/xds-ir/retry-partial-invalid.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/retry-partial-invalid.yaml
@@ -10,21 +10,22 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    retry:
-      numRetries: 5
-      retryOn:
-        httpStatusCodes:
-        - 429
-        - 503
-        triggers:
-        - reset
-        - connect-failure
-        - retriable-status-codes
-      perRetry:
-        timeout: 250ms
-        backoff:
-          baseInterval: 100ms
-          maxInterval: 10s
+    traffic:
+      retry:
+        numRetries: 5
+        retryOn:
+          httpStatusCodes:
+          - 429
+          - 503
+          triggers:
+          - reset
+          - connect-failure
+          - retriable-status-codes
+        perRetry:
+          timeout: 250ms
+          backoff:
+            baseInterval: 100ms
+            maxInterval: 10s
     destination:
       name: "first-route-dest"
       settings:
@@ -33,7 +34,8 @@ http:
           port: 50000
   - name: "second-route-defaults"
     hostname: "foo"
-    retry: {}
+    traffic:
+      retry: {}
     destination:
       name: "first-route-dest"
       settings:
@@ -42,10 +44,11 @@ http:
           port: 50000
   - name: "third-route-error"
     hostname: "bar"
-    retry:
-      retryOn:
-        triggers:
-        - this-is-not-a-trigger
+    traffic:
+      retry:
+        retryOn:
+          triggers:
+          - this-is-not-a-trigger
     destination:
       name: "first-route-dest"
       settings:
diff --git a/internal/xds/translator/testdata/in/xds-ir/timeout.yaml b/internal/xds/translator/testdata/in/xds-ir/timeout.yaml
index daee154f0..8abc0af3c 100644
--- a/internal/xds/translator/testdata/in/xds-ir/timeout.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/timeout.yaml
@@ -10,12 +10,13 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    timeout:
-      tcp:
-        connectTimeout: "31s"
-      http:
-        connectionIdleTimeout: "32s"
-        maxConnectionDuration: "33s"
+    traffic:
+      timeout:
+        tcp:
+          connectTimeout: "31s"
+        http:
+          connectionIdleTimeout: "32s"
+          maxConnectionDuration: "33s"
     destination:
       name: "first-route-dest"
       settings:
diff --git a/internal/xds/translator/testdata/in/xds-ir/upstream-tcpkeepalive.yaml b/internal/xds/translator/testdata/in/xds-ir/upstream-tcpkeepalive.yaml
index bb7febec8..b00f5e55a 100644
--- a/internal/xds/translator/testdata/in/xds-ir/upstream-tcpkeepalive.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/upstream-tcpkeepalive.yaml
@@ -10,10 +10,11 @@ http:
   routes:
   - name: "first-route"
     hostname: "*"
-    tcpKeepalive:
-      idleTime: 1200
-      interval: 60
-      probes: 3
+    traffic:
+      tcpKeepalive:
+        idleTime: 1200
+        interval: 60
+        probes: 3
     destination:
       name: "first-route-dest"
       settings: