Repository: envoyproxy/gateway
Pull request #5725 (2025-04-14 04:21:46)

feat: impl dynamic resolver backend type
This PR implements the dynamic resolver Backend type. 

To keep the scope managed and make this easier to review, this PR only modifies the HTTPRoute.  Follow-up PRs will extend dynamic resolver for GRPCRoute, TLSRoute, TCPRoute, and UDPRoute.

Impl: #3560 
Release Notes: Yes


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
Attention: Patch coverage is `88.96104%` with `17 lines` in your changes missing coverage. Please review.
> Project coverage is 65.19%. Comparing base [(`0e49fa6`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/0e49fa6cc141788f83ff7673eba9889c7df6c527?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`a6e6862`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/a6e6862f10ed4e0ed80e98563e1d2a495e55a903?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).
> Report is 2 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) | Patch % | Lines |
|---|---|---|
| [internal/gatewayapi/route.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Froute.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS9yb3V0ZS5nbw==) | 96.03% | [4 Missing :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/xds/translator/cluster.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?src=pr&el=tree&filepath=internal%2Fxds%2Ftranslator%2Fcluster.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwveGRzL3RyYW5zbGF0b3IvY2x1c3Rlci5nbw==) | 82.60% | [3 Missing and 1 partial :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/gatewayapi/backend.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Fbackend.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS9iYWNrZW5kLmdv) | 40.00% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/gatewayapi/validate.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Fvalidate.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS92YWxpZGF0ZS5nbw==) | 78.57% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/xds/translator/translator.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?src=pr&el=tree&filepath=internal%2Fxds%2Ftranslator%2Ftranslator.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwveGRzL3RyYW5zbGF0b3IvdHJhbnNsYXRvci5nbw==) | 57.14% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5725?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |

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


```diff
@@            Coverage Diff             @@
##             main    #5725      +/-   ##
==========================================
- Coverage   65.34%   65.19%   -0.15%     
==========================================
  Files         213      214       +1     
  Lines       34121    34375     +254     
==========================================
+ Hits        22295    22411     +116     
- Misses      10489    10607     +118     
- Partials     1337     1357      +20     
```

</details>

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

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
arkodg: hey @zhaohuabing is it possible to split up the error condition improvements and the DFP feature into 2 PRs to its easier to review and isolate impact ?
zhaohuabing: > hey @zhaohuabing is it possible to split up the error condition improvements and the DFP feature into 2 PRs to its easier to review and isolate impact ?

@arkodg  https://github.com/envoyproxy/gateway/pull/5747

Patch:
diff --git a/internal/gatewayapi/backend.go b/internal/gatewayapi/backend.go
index ae184ee70..715504d49 100644
--- a/internal/gatewayapi/backend.go
+++ b/internal/gatewayapi/backend.go
@@ -40,6 +40,16 @@ func (t *Translator) ProcessBackends(backends []*egv1a1.Backend) []*egv1a1.Backe
 }
 
 func validateBackend(backend *egv1a1.Backend) status.Error {
+	if backend.Spec.Type != nil &&
+		*backend.Spec.Type == egv1a1.BackendTypeDynamicResolver {
+		if len(backend.Spec.Endpoints) > 0 || len(backend.Spec.AppProtocols) > 0 {
+			return status.NewRouteStatusError(
+				fmt.Errorf("DynamicResolver type cannot have endpoints or appProtocols specified"),
+				status.RouteReasonInvalidBackendRef,
+			)
+		}
+	}
+
 	for _, ep := range backend.Spec.Endpoints {
 		if ep.FQDN != nil {
 			hostname := ep.FQDN.Hostname
diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go
index 97c12bf35..05ec2772b 100644
--- a/internal/gatewayapi/filters.go
+++ b/internal/gatewayapi/filters.go
@@ -119,7 +119,7 @@ func (t *Translator) ProcessGRPCFilters(parentRef *RouteParentContext,
 	route RouteContext,
 	filters []gwapiv1.GRPCRouteFilter,
 	resources *resource.Resources,
-) (*HTTPFiltersContext, error) {
+) (*HTTPFiltersContext, status.Error) {
 	httpFiltersContext := &HTTPFiltersContext{
 		ParentRef: parentRef,
 		Route:     route,
@@ -901,11 +901,11 @@ func (t *Translator) processRequestMirrorFilter(
 	// This sets the status on the HTTPRoute, should the usage be changed so that the status message reflects that the backendRef is from the filter?
 	filterNs := filterContext.Route.GetNamespace()
 	serviceNamespace := NamespaceDerefOr(mirrorBackend.Namespace, filterNs)
-	err = t.validateBackendRef(mirrorBackendRef, filterContext.ParentRef, filterContext.Route,
+	err = t.validateBackendRef(mirrorBackendRef, filterContext.Route,
 		resources, serviceNamespace, resource.KindHTTPRoute)
 	if err != nil {
 		return status.NewRouteStatusError(
-			fmt.Errorf("failed to validate the RequestMirror filter: %w", err), err.Reason())
+			fmt.Errorf("failed to validate the RequestMirror filter: %w", err), err.Reason()).WithType(gwapiv1.RouteConditionResolvedRefs)
 	}
 
 	destName := fmt.Sprintf("%s-mirror-%d", irRouteDestinationName(filterContext.Route, filterContext.RuleIdx), filterIdx)
diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go
index 0c0cf7389..0d9d2a3ed 100644
--- a/internal/gatewayapi/route.go
+++ b/internal/gatewayapi/route.go
@@ -191,10 +191,17 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
 	for ruleIdx, rule := range httpRoute.Spec.Rules {
 		httpFiltersContext, err := t.ProcessHTTPFilters(parentRef, httpRoute, rule.Filters, ruleIdx, resources)
 		if err != nil {
-			errs.Add(status.NewRouteStatusError(
-				fmt.Errorf("failed to process route rule %d: %w", ruleIdx, err),
-				status.ConvertToAcceptedReason(err.Reason()),
-			).WithType(gwapiv1.RouteConditionAccepted))
+			// Some errors should be treated as ResolvedRefs condition type,
+			// e.g. Failed to resolve the BackendRef in the RequestMirror filter.
+			// Other errors should be treated as Accepted condition type.
+			if err.Type() != gwapiv1.RouteConditionResolvedRefs {
+				errs.Add(status.NewRouteStatusError(
+					fmt.Errorf("failed to process route rule %d: %w", ruleIdx, err),
+					status.ConvertToAcceptedReason(err.Reason()),
+				).WithType(gwapiv1.RouteConditionAccepted))
+			} else {
+				errs.Add(err)
+			}
 			continue
 		}
 
@@ -212,6 +219,7 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
 		destName := irRouteDestinationName(httpRoute, ruleIdx)
 		allDs := []*ir.DestinationSetting{}
 		failedProcessDestination := false
+		hasDynamicResolver := false
 
 		// process each backendRef, and calculate the destination settings for this rule
 		for i, backendRef := range rule.BackendRefs {
@@ -231,6 +239,11 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
 				continue
 			}
 			allDs = append(allDs, ds)
+
+			// check if there is a dynamic resolver in the backendRefs
+			if ds.IsDynamicResolver {
+				hasDynamicResolver = true
+			}
 		}
 
 		// process each ir route
@@ -254,6 +267,12 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
 				irRoute.DirectResponse = &ir.CustomResponse{
 					StatusCode: ptr.To(uint32(500)),
 				}
+				// A route can only have one destination if this destination is a dynamic resolver, because the behavior of
+				// multiple destinations with one being a dynamic resolver just doesn't make sense.
+			case hasDynamicResolver && len(rule.BackendRefs) > 1:
+				irRoute.DirectResponse = &ir.CustomResponse{
+					StatusCode: ptr.To(uint32(500)),
+				}
 			default:
 				destination.Name = destName
 				destination.Settings = allDs
@@ -261,6 +280,15 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
 			}
 		}
 
+		if hasDynamicResolver && len(rule.BackendRefs) > 1 {
+			errs.Add(status.NewRouteStatusError(
+				fmt.Errorf(
+					"failed to process route rule %d: dynamic resolver is not supported for multiple backendRefs",
+					ruleIdx),
+				status.RouteReasonInvalidBackendRef,
+			))
+		}
+
 		// TODO handle:
 		//	- sum of weights for valid backend refs is 0
 		//	- etc.
@@ -539,12 +567,11 @@ func (t *Translator) processGRPCRouteParentRefs(grpcRoute *GRPCRouteContext, res
 			status.SetRouteStatusCondition(routeStatus,
 				parentRef.routeParentStatusIdx,
 				grpcRoute.GetGeneration(),
-				gwapiv1.RouteConditionAccepted,
+				err.Type(),
 				metav1.ConditionFalse,
-				gwapiv1.RouteReasonUnsupportedValue, // TODO: better reason
+				err.Reason(),
 				status.Error2ConditionMsg(err),
 			)
-			continue
 		}
 
 		// If no negative condition has been set for ResolvedRefs, set "ResolvedRefs=True"
@@ -593,76 +620,99 @@ func (t *Translator) processGRPCRouteParentRefs(grpcRoute *GRPCRouteContext, res
 	}
 }
 
-func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRef *RouteParentContext, resources *resource.Resources) ([]*ir.HTTPRoute, error) {
-	var routeRoutes []*ir.HTTPRoute
+func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRef *RouteParentContext, resources *resource.Resources) ([]*ir.HTTPRoute, status.Error) {
+	var (
+		irRoutes []*ir.HTTPRoute
+		errs     = &status.MultiStatusError{}
+	)
 
 	// compute matches, filters, backends
 	for ruleIdx, rule := range grpcRoute.Spec.Rules {
 		httpFiltersContext, err := t.ProcessGRPCFilters(parentRef, grpcRoute, rule.Filters, resources)
 		if err != nil {
-			return nil, err
+			errs.Add(status.NewRouteStatusError(
+				fmt.Errorf("failed to process route rule %d: %w", ruleIdx, err),
+				status.ConvertToAcceptedReason(err.Reason()),
+			).WithType(gwapiv1.RouteConditionAccepted))
+			continue
 		}
 		// A rule is matched if any one of its matches
 		// is satisfied (i.e. a logical "OR"), so generate
 		// a unique Xds IR HTTPRoute per match.
 		ruleRoutes, err := t.processGRPCRouteRule(grpcRoute, ruleIdx, httpFiltersContext, rule)
 		if err != nil {
-			return nil, err
+			errs.Add(status.NewRouteStatusError(
+				fmt.Errorf("failed to process route rule %d: %w", ruleIdx, err),
+				status.ConvertToAcceptedReason(err.Reason()),
+			).WithType(gwapiv1.RouteConditionAccepted))
+			continue
 		}
 
 		destName := irRouteDestinationName(grpcRoute, ruleIdx)
+		allDs := []*ir.DestinationSetting{}
+		failedProcessDestination := false
+
 		for i, backendRef := range rule.BackendRefs {
 			settingName := irDestinationSettingName(destName, i)
 			ds, err := t.processDestination(settingName, backendRef, parentRef, grpcRoute, resources)
-			if ds == nil {
+			if err != nil {
+				errs.Add(status.NewRouteStatusError(
+					fmt.Errorf("failed to process route rule %d backendRef %d: %w", ruleIdx, i, err),
+					err.Reason(),
+				))
+				failedProcessDestination = true
 				continue
 			}
 
-			for _, route := range ruleRoutes {
-				// If the route already has a direct response or redirect configured, then it was from a filter so skip
-				// processing any destinations for this route.
-				if route.DirectResponse != nil || route.Redirect != nil {
-					continue
-				}
-
-				//  disable associated routes to a backend ref in case some of its config was invalid
-				if err != nil {
-					route.DirectResponse = &ir.CustomResponse{
-						StatusCode: ptr.To(uint32(500)),
-					}
-				}
-
-				if route.Destination == nil {
-					route.Destination = &ir.RouteDestination{
-						Name: destName,
-					}
-				}
-				route.Destination.Settings = append(route.Destination.Settings, ds)
+			if ds == nil {
+				continue
 			}
+			allDs = append(allDs, ds)
 		}
 
-		// If the route has no valid backends then just use a direct response and don't fuss with weighted responses
-		for _, ruleRoute := range ruleRoutes {
-			noValidBackends := ruleRoute.Destination == nil || ruleRoute.Destination.ToBackendWeights().Valid == 0
-			if noValidBackends && ruleRoute.Redirect == nil {
-				ruleRoute.DirectResponse = &ir.CustomResponse{
+		// process each ir route
+		for _, irRoute := range ruleRoutes {
+			irRoute.IsHTTP2 = true
+			destination := &ir.RouteDestination{
+				Settings: allDs,
+			}
+
+			switch {
+			// If the route already has a direct response or redirect configured, then it was from a filter so skip
+			// processing any destinations for this route.
+			case irRoute.DirectResponse != nil || irRoute.Redirect != nil:
+			// return 500 if any destination setting is invalid
+			// the error is already added to the error list when processing the destination
+			case failedProcessDestination:
+				irRoute.DirectResponse = &ir.CustomResponse{
+					StatusCode: ptr.To(uint32(500)),
+				}
+			// return 500 if the weight of all the valid destination settings(endpoints list is not empty) is 0
+			case destination.ToBackendWeights().Valid == 0:
+				irRoute.DirectResponse = &ir.CustomResponse{
 					StatusCode: ptr.To(uint32(500)),
 				}
+			default:
+				destination.Name = destName
+				destination.Settings = allDs
+				irRoute.Destination = destination
 			}
-			ruleRoute.IsHTTP2 = true
 		}
 
 		// TODO handle:
 		//	- sum of weights for valid backend refs is 0
 		//	- etc.
 
-		routeRoutes = append(routeRoutes, ruleRoutes...)
+		irRoutes = append(irRoutes, ruleRoutes...)
 	}
 
-	return routeRoutes, nil
+	if errs.Empty() {
+		return irRoutes, nil
+	}
+	return irRoutes, errs
 }
 
-func (t *Translator) processGRPCRouteRule(grpcRoute *GRPCRouteContext, ruleIdx int, httpFiltersContext *HTTPFiltersContext, rule gwapiv1.GRPCRouteRule) ([]*ir.HTTPRoute, error) {
+func (t *Translator) processGRPCRouteRule(grpcRoute *GRPCRouteContext, ruleIdx int, httpFiltersContext *HTTPFiltersContext, rule gwapiv1.GRPCRouteRule) ([]*ir.HTTPRoute, status.Error) {
 	var ruleRoutes []*ir.HTTPRoute
 
 	// If no matches are specified, the implementation MUST match every gRPC request.
@@ -692,7 +742,7 @@ func (t *Translator) processGRPCRouteRule(grpcRoute *GRPCRouteContext, ruleIdx i
 				})
 			case gwapiv1.GRPCHeaderMatchRegularExpression:
 				if err := regex.Validate(headerMatch.Value); err != nil {
-					return nil, err
+					return nil, status.NewRouteStatusError(err, gwapiv1.RouteReasonUnsupportedValue)
 				}
 				irRoute.HeaderMatches = append(irRoute.HeaderMatches, &ir.StringMatch{
 					Name:      string(headerMatch.Name),
@@ -709,12 +759,12 @@ func (t *Translator) processGRPCRouteRule(grpcRoute *GRPCRouteContext, ruleIdx i
 			case gwapiv1.GRPCMethodMatchRegularExpression:
 				if match.Method.Service != nil {
 					if err := regex.Validate(*match.Method.Service); err != nil {
-						return nil, err
+						return nil, status.NewRouteStatusError(err, gwapiv1.RouteReasonUnsupportedValue)
 					}
 				}
 				if match.Method.Method != nil {
 					if err := regex.Validate(*match.Method.Method); err != nil {
-						return nil, err
+						return nil, status.NewRouteStatusError(err, gwapiv1.RouteReasonUnsupportedValue)
 					}
 				}
 				t.processGRPCRouteMethodRegularExpression(match.Method, irRoute)
@@ -876,15 +926,21 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour
 		// Need to compute Route rules within the parentRef loop because
 		// any conditions that come out of it have to go on each RouteParentStatus,
 		// not on the Route as a whole.
-		var destSettings []*ir.DestinationSetting
+		var (
+			destSettings []*ir.DestinationSetting
+			resolveErrs  = &status.MultiStatusError{}
+			destName     = irRouteDestinationName(tlsRoute, -1 /*rule index*/)
+		)
 
-		destName := irRouteDestinationName(tlsRoute, -1 /*rule index*/)
 		// compute backends
 		for _, rule := range tlsRoute.Spec.Rules {
 			for i, backendRef := range rule.BackendRefs {
 				settingName := irDestinationSettingName(destName, i)
-				// not yet handled, requires to align with the conformance test - TLSRouteInvalidReferenceGrant.
-				ds, _ := t.processDestination(settingName, backendRef, parentRef, tlsRoute, resources)
+				ds, err := t.processDestination(settingName, backendRef, parentRef, tlsRoute, resources)
+				if err != nil {
+					resolveErrs.Add(err)
+					continue
+				}
 				if ds != nil {
 					destSettings = append(destSettings, ds)
 				}
@@ -897,9 +953,16 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour
 			//	- etc.
 		}
 
-		// If no negative condition has been set for ResolvedRefs, set "ResolvedRefs=True"
-		if !parentRef.HasCondition(tlsRoute, gwapiv1.RouteConditionResolvedRefs, metav1.ConditionFalse) {
-			routeStatus := GetRouteStatus(tlsRoute)
+		routeStatus := GetRouteStatus(tlsRoute)
+		if !resolveErrs.Empty() {
+			status.SetRouteStatusCondition(routeStatus,
+				parentRef.routeParentStatusIdx,
+				tlsRoute.GetGeneration(),
+				gwapiv1.RouteConditionResolvedRefs,
+				metav1.ConditionFalse,
+				resolveErrs.Reason(),
+				resolveErrs.Error())
+		} else {
 			status.SetRouteStatusCondition(routeStatus,
 				parentRef.routeParentStatusIdx,
 				tlsRoute.GetGeneration(),
@@ -1004,18 +1067,13 @@ func (t *Translator) ProcessUDPRoutes(udpRoutes []*gwapiv1a2.UDPRoute, gateways
 
 func (t *Translator) processUDPRouteParentRefs(udpRoute *UDPRouteContext, resources *resource.Resources, xdsIR resource.XdsIRMap) {
 	for _, parentRef := range udpRoute.ParentRefs {
-		// Need to compute Route rules within the parentRef loop because
-		// any conditions that come out of it have to go on each RouteParentStatus,
-		// not on the Route as a whole.
-		var destSettings []*ir.DestinationSetting
-
 		// compute backends
 		if len(udpRoute.Spec.Rules) != 1 {
 			routeStatus := GetRouteStatus(udpRoute)
 			status.SetRouteStatusCondition(routeStatus,
 				parentRef.routeParentStatusIdx,
 				udpRoute.GetGeneration(),
-				gwapiv1.RouteConditionResolvedRefs,
+				gwapiv1.RouteConditionAccepted,
 				metav1.ConditionFalse,
 				"InvalidRule",
 				"One and only one rule is supported",
@@ -1023,21 +1081,20 @@ func (t *Translator) processUDPRouteParentRefs(udpRoute *UDPRouteContext, resour
 			continue
 		}
 
-		destName := irRouteDestinationName(udpRoute, -1 /*rule index*/)
+		// Need to compute Route rules within the parentRef loop because
+		// any conditions that come out of it have to go on each RouteParentStatus,
+		// not on the Route as a whole.
+		var (
+			destSettings []*ir.DestinationSetting
+			resolveErrs  = &status.MultiStatusError{}
+			destName     = irRouteDestinationName(udpRoute, -1 /*rule index*/)
+		)
+
 		for i, backendRef := range udpRoute.Spec.Rules[0].BackendRefs {
 			settingName := irDestinationSettingName(destName, i)
 			ds, err := t.processDestination(settingName, backendRef, parentRef, udpRoute, resources)
-			// skip adding the route and provide the reason via route status.
 			if err != nil {
-				routeStatus := GetRouteStatus(udpRoute)
-				status.SetRouteStatusCondition(routeStatus,
-					parentRef.routeParentStatusIdx,
-					udpRoute.GetGeneration(),
-					gwapiv1.RouteConditionAccepted,
-					metav1.ConditionFalse,
-					"Failed to process the settings associated with the UDP route.",
-					err.Error(),
-				)
+				resolveErrs.Add(err)
 				continue
 			}
 
@@ -1047,9 +1104,26 @@ func (t *Translator) processUDPRouteParentRefs(udpRoute *UDPRouteContext, resour
 			}
 		}
 
-		// If no negative condition has been set for ResolvedRefs, set "ResolvedRefs=True"
-		if !parentRef.HasCondition(udpRoute, gwapiv1.RouteConditionResolvedRefs, metav1.ConditionFalse) {
-			routeStatus := GetRouteStatus(udpRoute)
+		routeStatus := GetRouteStatus(udpRoute)
+		if !resolveErrs.Empty() {
+			status.SetRouteStatusCondition(routeStatus,
+				parentRef.routeParentStatusIdx,
+				udpRoute.GetGeneration(),
+				gwapiv1.RouteConditionResolvedRefs,
+				metav1.ConditionFalse,
+				resolveErrs.Reason(),
+				resolveErrs.Error())
+
+			// TODO: zhaohuabing should we set the accepted condition to false here?
+			status.SetRouteStatusCondition(routeStatus,
+				parentRef.routeParentStatusIdx,
+				udpRoute.GetGeneration(),
+				gwapiv1.RouteConditionAccepted,
+				metav1.ConditionFalse,
+				"Failed to process the settings associated with the UDP route.",
+				resolveErrs.Error(),
+			)
+		} else {
 			status.SetRouteStatusCondition(routeStatus,
 				parentRef.routeParentStatusIdx,
 				udpRoute.GetGeneration(),
@@ -1152,18 +1226,13 @@ func (t *Translator) ProcessTCPRoutes(tcpRoutes []*gwapiv1a2.TCPRoute, gateways
 
 func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resources *resource.Resources, xdsIR resource.XdsIRMap) {
 	for _, parentRef := range tcpRoute.ParentRefs {
-		// Need to compute Route rules within the parentRef loop because
-		// any conditions that come out of it have to go on each RouteParentStatus,
-		// not on the Route as a whole.
-		var destSettings []*ir.DestinationSetting
-
 		// compute backends
 		if len(tcpRoute.Spec.Rules) != 1 {
 			routeStatus := GetRouteStatus(tcpRoute)
 			status.SetRouteStatusCondition(routeStatus,
 				parentRef.routeParentStatusIdx,
 				tcpRoute.GetGeneration(),
-				gwapiv1.RouteConditionResolvedRefs,
+				gwapiv1.RouteConditionAccepted,
 				metav1.ConditionFalse,
 				"InvalidRule",
 				"One and only one rule is supported",
@@ -1171,21 +1240,21 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour
 			continue
 		}
 
-		destName := irRouteDestinationName(tcpRoute, -1 /*rule index*/)
+		// Need to compute Route rules within the parentRef loop because
+		// any conditions that come out of it have to go on each RouteParentStatus,
+		// not on the Route as a whole.
+		var (
+			destSettings []*ir.DestinationSetting
+			resolveErrs  = &status.MultiStatusError{}
+			destName     = irRouteDestinationName(tcpRoute, -1 /*rule index*/)
+		)
+
 		for i, backendRef := range tcpRoute.Spec.Rules[0].BackendRefs {
 			settingName := irDestinationSettingName(destName, i)
 			ds, err := t.processDestination(settingName, backendRef, parentRef, tcpRoute, resources)
 			// skip adding the route and provide the reason via route status.
 			if err != nil {
-				routeStatus := GetRouteStatus(tcpRoute)
-				status.SetRouteStatusCondition(routeStatus,
-					parentRef.routeParentStatusIdx,
-					tcpRoute.GetGeneration(),
-					gwapiv1.RouteConditionAccepted,
-					metav1.ConditionFalse,
-					"Failed to process the settings associated with the TCP route.",
-					err.Error(),
-				)
+				resolveErrs.Add(err)
 				continue
 			}
 			// Skip nil destination settings
@@ -1194,9 +1263,16 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour
 			}
 		}
 
-		// If no negative condition has been set for ResolvedRefs, set "ResolvedRefs=True"
-		if !parentRef.HasCondition(tcpRoute, gwapiv1.RouteConditionResolvedRefs, metav1.ConditionFalse) {
-			routeStatus := GetRouteStatus(tcpRoute)
+		routeStatus := GetRouteStatus(tcpRoute)
+		if !resolveErrs.Empty() {
+			status.SetRouteStatusCondition(routeStatus,
+				parentRef.routeParentStatusIdx,
+				tcpRoute.GetGeneration(),
+				gwapiv1.RouteConditionResolvedRefs,
+				metav1.ConditionFalse,
+				resolveErrs.Reason(),
+				resolveErrs.Error())
+		} else {
 			status.SetRouteStatusCondition(routeStatus,
 				parentRef.routeParentStatusIdx,
 				tcpRoute.GetGeneration(),
@@ -1293,7 +1369,7 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe
 	}
 
 	backendNamespace := NamespaceDerefOr(backendRef.Namespace, route.GetNamespace())
-	err = t.validateBackendRef(backendRefContext, parentRef, route, resources, backendNamespace, routeType)
+	err = t.validateBackendRef(backendRefContext, route, resources, backendNamespace, routeType)
 	{
 		// return with empty endpoint means the backend is invalid and an error to fail the associated route.
 		if err != nil {
@@ -1354,14 +1430,6 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe
 	}
 
 	if err := validateDestinationSettings(ds, t.IsEnvoyServiceRouting(envoyProxy), backendRef.Kind); err != nil {
-		routeStatus := GetRouteStatus(route)
-		status.SetRouteStatusCondition(routeStatus,
-			parentRef.routeParentStatusIdx,
-			route.GetGeneration(),
-			gwapiv1.RouteConditionResolvedRefs,
-			metav1.ConditionFalse,
-			gwapiv1.RouteReasonResolvedRefs,
-			err.Error())
 		return nil, err
 	}
 
@@ -1821,6 +1889,12 @@ func (t *Translator) processBackendDestinationSetting(
 	backend := resources.GetBackend(backendNamespace, string(backendRef.Name))
 	ds := &ir.DestinationSetting{Name: name}
 
+	// There is only one backend if it is a dynamic resolver
+	if backend.Spec.Type != nil && *backend.Spec.Type == egv1a1.BackendTypeDynamicResolver {
+		ds.IsDynamicResolver = true
+		return ds
+	}
+
 	for _, bep := range backend.Spec.Endpoints {
 		var irde *ir.DestinationEndpoint
 		switch {
diff --git a/internal/gatewayapi/validate.go b/internal/gatewayapi/validate.go
index fc8b34aa2..0bbf0d145 100644
--- a/internal/gatewayapi/validate.go
+++ b/internal/gatewayapi/validate.go
@@ -24,24 +24,24 @@ import (
 	"github.com/envoyproxy/gateway/internal/gatewayapi/status"
 )
 
-func (t *Translator) validateBackendRef(backendRefContext BackendRefContext, parentRef *RouteParentContext, route RouteContext,
+func (t *Translator) validateBackendRef(backendRefContext BackendRefContext, route RouteContext,
 	resources *resource.Resources, backendNamespace string, routeKind gwapiv1.Kind,
 ) status.Error {
 	backendRef := GetBackendRef(backendRefContext)
 
-	if err := t.validateBackendRefFilters(backendRefContext, parentRef, route, routeKind); err != nil {
+	if err := t.validateBackendRefFilters(backendRefContext, routeKind); err != nil {
 		return err
 	}
-	if err := t.validateBackendRefGroup(backendRef, parentRef, route); err != nil {
+	if err := t.validateBackendRefGroup(backendRef); err != nil {
 		return err
 	}
-	if err := t.validateBackendRefKind(backendRef, parentRef, route); err != nil {
+	if err := t.validateBackendRefKind(backendRef); err != nil {
 		return err
 	}
-	if err := t.validateBackendNamespace(backendRef, parentRef, route, resources, routeKind); err != nil {
+	if err := t.validateBackendNamespace(backendRef, route, resources, routeKind); err != nil {
 		return err
 	}
-	if err := t.validateBackendPort(backendRef, parentRef, route); err != nil {
+	if err := t.validateBackendPort(backendRef); err != nil {
 		return err
 	}
 
@@ -50,73 +50,26 @@ func (t *Translator) validateBackendRef(backendRefContext BackendRefContext, par
 		protocol = corev1.ProtocolUDP
 	}
 
-	// TODO: zhaohuabing remove this and handle status in the out layer
 	backendRefKind := KindDerefOr(backendRef.Kind, resource.KindService)
 	switch backendRefKind {
 	case resource.KindService:
 		if err := validateBackendRefService(backendRef.BackendObjectReference, resources, backendNamespace, protocol); err != nil {
-			routeStatus := GetRouteStatus(route)
-			status.SetRouteStatusCondition(routeStatus,
-				parentRef.routeParentStatusIdx,
-				route.GetGeneration(),
-				gwapiv1.RouteConditionResolvedRefs,
-				metav1.ConditionFalse,
-				err.Reason(),
-				errorToMessage(err),
-			)
 			return err
 		}
 	case resource.KindServiceImport:
 		if err := t.validateBackendServiceImport(backendRef.BackendObjectReference, resources, backendNamespace, protocol); err != nil {
-			routeStatus := GetRouteStatus(route)
-			status.SetRouteStatusCondition(routeStatus,
-				parentRef.routeParentStatusIdx,
-				route.GetGeneration(),
-				gwapiv1.RouteConditionResolvedRefs,
-				metav1.ConditionFalse,
-				err.Reason(),
-				errorToMessage(err),
-			)
 			return err
 		}
 	case egv1a1.KindBackend:
 		if err := t.validateBackendRefBackend(backendRef.BackendObjectReference, resources, backendNamespace, false); err != nil {
-			routeStatus := GetRouteStatus(route)
-			status.SetRouteStatusCondition(routeStatus,
-				parentRef.routeParentStatusIdx,
-				route.GetGeneration(),
-				gwapiv1.RouteConditionResolvedRefs,
-				metav1.ConditionFalse,
-				err.Reason(),
-				errorToMessage(err),
-			)
 			return err
 		}
 	}
 	return nil
 }
 
-func errorToMessage(err error) string {
-	if err == nil {
-		return ""
-	}
-	msg := err.Error()
-
-	// Convert first character to upper
-	return strings.ToUpper(msg[0:1]) + msg[1:]
-}
-
-func (t *Translator) validateBackendRefGroup(backendRef *gwapiv1a2.BackendRef, parentRef *RouteParentContext, route RouteContext) status.Error {
+func (t *Translator) validateBackendRefGroup(backendRef *gwapiv1a2.BackendRef) status.Error {
 	if backendRef.Group != nil && *backendRef.Group != "" && *backendRef.Group != GroupMultiClusterService && *backendRef.Group != egv1a1.GroupName {
-		routeStatus := GetRouteStatus(route)
-		status.SetRouteStatusCondition(routeStatus, // TODO: zhaohuabing remove this and handle status in the out layer
-			parentRef.routeParentStatusIdx,
-			route.GetGeneration(),
-			gwapiv1.RouteConditionResolvedRefs,
-			metav1.ConditionFalse,
-			gwapiv1.RouteReasonInvalidKind,
-			fmt.Sprintf("Group is invalid, only the core API group (specified by omitting the group field or setting it to an empty string), %s and %s are supported", GroupMultiClusterService, egv1a1.GroupName),
-		)
 		return status.NewRouteStatusError(
 			fmt.Errorf("Group is invalid, only the core API group (specified by omitting the group field or setting it to an empty string), %s and %s are supported",
 				GroupMultiClusterService,
@@ -126,17 +79,8 @@ func (t *Translator) validateBackendRefGroup(backendRef *gwapiv1a2.BackendRef, p
 	return nil
 }
 
-func (t *Translator) validateBackendRefKind(backendRef *gwapiv1a2.BackendRef, parentRef *RouteParentContext, route RouteContext) status.Error {
+func (t *Translator) validateBackendRefKind(backendRef *gwapiv1a2.BackendRef) status.Error {
 	if backendRef.Kind != nil && *backendRef.Kind != resource.KindService && *backendRef.Kind != resource.KindServiceImport && *backendRef.Kind != egv1a1.KindBackend {
-		routeStatus := GetRouteStatus(route)
-		status.SetRouteStatusCondition(routeStatus, // TODO: zhaohuabing remove this and handle status in the out layer
-			parentRef.routeParentStatusIdx,
-			route.GetGeneration(),
-			gwapiv1.RouteConditionResolvedRefs,
-			metav1.ConditionFalse,
-			gwapiv1.RouteReasonInvalidKind,
-			"Kind is invalid, only Service, MCS ServiceImport and Envoy Gateway Backend are supported",
-		)
 		return status.NewRouteStatusError(
 			fmt.Errorf("Kind is invalid, only Service, MCS ServiceImport and Envoy Gateway Backend are supported"),
 			gwapiv1.RouteReasonInvalidKind)
@@ -144,7 +88,7 @@ func (t *Translator) validateBackendRefKind(backendRef *gwapiv1a2.BackendRef, pa
 	return nil
 }
 
-func (t *Translator) validateBackendRefFilters(backendRef BackendRefContext, parentRef *RouteParentContext, route RouteContext, routeKind gwapiv1.Kind) status.Error {
+func (t *Translator) validateBackendRefFilters(backendRef BackendRefContext, routeKind gwapiv1.Kind) status.Error {
 	filters := GetFilters(backendRef)
 	var unsupportedFilters bool
 
@@ -173,30 +117,20 @@ func (t *Translator) validateBackendRefFilters(backendRef BackendRefContext, par
 		return nil
 	}
 
-	// TODO: zhaohuabing remove this and handle status in the out layer
 	if unsupportedFilters {
 		message := "Specific filter is not supported within BackendRef, only RequestHeaderModifier, ResponseHeaderModifier and gateway.envoyproxy.io/HTTPRouteFilter are supported"
 		if routeKind == resource.KindGRPCRoute {
 			message = "Specific filter is not supported within BackendRef, only RequestHeaderModifier and ResponseHeaderModifier are supported"
 		}
-		routeStatus := GetRouteStatus(route)
-		status.SetRouteStatusCondition(routeStatus,
-			parentRef.routeParentStatusIdx,
-			route.GetGeneration(),
-			gwapiv1.RouteConditionResolvedRefs,
-			metav1.ConditionFalse,
-			status.RouteReasonUnsupportedRefValue,
-			message,
-		)
 		return status.NewRouteStatusError(
-			errors.New("specific filter is not supported within BackendRef, only RequestHeaderModifier and ResponseHeaderModifier are supported"),
+			errors.New(message),
 			status.RouteReasonUnsupportedRefValue)
 	}
 
 	return nil
 }
 
-func (t *Translator) validateBackendNamespace(backendRef *gwapiv1a2.BackendRef, parentRef *RouteParentContext, route RouteContext,
+func (t *Translator) validateBackendNamespace(backendRef *gwapiv1a2.BackendRef, route RouteContext,
 	resources *resource.Resources, routeKind gwapiv1.Kind,
 ) status.Error {
 	if backendRef.Namespace != nil && string(*backendRef.Namespace) != "" && string(*backendRef.Namespace) != route.GetNamespace() {
@@ -214,15 +148,6 @@ func (t *Translator) validateBackendNamespace(backendRef *gwapiv1a2.BackendRef,
 			},
 			resources.ReferenceGrants,
 		) {
-			routeStatus := GetRouteStatus(route)
-			status.SetRouteStatusCondition(routeStatus, // TODO: zhaohuabing remove this and handle status in the out layer
-				parentRef.routeParentStatusIdx,
-				route.GetGeneration(),
-				gwapiv1.RouteConditionResolvedRefs,
-				metav1.ConditionFalse,
-				gwapiv1.RouteReasonRefNotPermitted,
-				fmt.Sprintf("Backend ref to %s %s/%s not permitted by any ReferenceGrant.", KindDerefOr(backendRef.Kind, resource.KindService), *backendRef.Namespace, backendRef.Name),
-			)
 			return status.NewRouteStatusError(
 				fmt.Errorf("Backend ref to %s %s/%s not permitted by any ReferenceGrant.",
 					KindDerefOr(backendRef.Kind, resource.KindService),
@@ -234,7 +159,7 @@ func (t *Translator) validateBackendNamespace(backendRef *gwapiv1a2.BackendRef,
 	return nil
 }
 
-func (t *Translator) validateBackendPort(backendRef *gwapiv1a2.BackendRef, parentRef *RouteParentContext, route RouteContext) status.Error {
+func (t *Translator) validateBackendPort(backendRef *gwapiv1a2.BackendRef) status.Error {
 	if backendRef == nil {
 		return nil
 	}
@@ -244,16 +169,6 @@ func (t *Translator) validateBackendPort(backendRef *gwapiv1a2.BackendRef, paren
 	}
 
 	if backendRef.Port == nil {
-		// TODO: zhaohuabing remove this and handle status in the out layer
-		routeStatus := GetRouteStatus(route)
-		status.SetRouteStatusCondition(routeStatus,
-			parentRef.routeParentStatusIdx,
-			route.GetGeneration(),
-			gwapiv1.RouteConditionResolvedRefs,
-			metav1.ConditionFalse,
-			status.RouteReasonPortNotSpecified,
-			"A valid port number corresponding to a port on the Service must be specified",
-		)
 		return status.NewRouteStatusError(
 			errors.New("A valid port number corresponding to a port on the Service must be specified"),
 			status.RouteReasonPortNotSpecified)
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index 572fe56cd..74015695b 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -1533,9 +1533,12 @@ func (r *RouteDestination) ToBackendWeights() *BackendWeights {
 			continue
 		}
 
-		if len(s.Endpoints) > 0 {
+		switch {
+		case s.IsDynamicResolver: // Dynamic resolver has no endpoints
 			w.Valid += *s.Weight
-		} else {
+		case len(s.Endpoints) > 0:
+			w.Valid += *s.Weight
+		default:
 			w.Invalid += *s.Weight
 		}
 	}
@@ -1548,6 +1551,11 @@ func (r *RouteDestination) ToBackendWeights() *BackendWeights {
 type DestinationSetting struct {
 	// Name of the setting
 	Name string `json:"name" yaml:"name"`
+
+	// IsDynamicResolver specifies whether the destination is a dynamic resolver.
+	// A dynamic resolver is a destination that is resolved dynamically using the request's host header.
+	IsDynamicResolver bool `json:"isDynamicResolver,omitempty" yaml:"isDynamicResolver,omitempty"`
+
 	// Weight associated with this destination,
 	// invalid endpoints are represents with a
 	// non-zero weight with an empty endpoints list
diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go
index 81a395b16..92c726e1e 100644
--- a/internal/xds/translator/cluster.go
+++ b/internal/xds/translator/cluster.go
@@ -14,6 +14,8 @@ import (
 	clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
+	dfpv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3"
+	commondfpv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3"
 	codecv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/upstream_codec/v3"
 	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
 	preservecasev3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/header_formatters/preserve_case/v3"
@@ -73,6 +75,7 @@ type EndpointType int
 const (
 	EndpointTypeDNS EndpointType = iota
 	EndpointTypeStatic
+	EndpointTypeDynamicResolver
 )
 
 func buildEndpointType(settings []*ir.DestinationSetting) EndpointType {
@@ -82,6 +85,10 @@ func buildEndpointType(settings []*ir.DestinationSetting) EndpointType {
 		return EndpointTypeStatic
 	}
 
+	if settings[0].IsDynamicResolver {
+		return EndpointTypeDynamicResolver
+	}
+
 	addrType := settings[0].AddressType
 
 	if addrType != nil && *addrType == ir.FQDN {
@@ -163,33 +170,38 @@ func buildXdsCluster(args *xdsClusterArgs) (*buildClusterResult, error) {
 		}
 	}
 
-	if args.endpointType == EndpointTypeStatic {
-		cluster.ClusterDiscoveryType = &clusterv3.Cluster_Type{Type: clusterv3.Cluster_EDS}
-		cluster.EdsClusterConfig = &clusterv3.Cluster_EdsClusterConfig{
-			ServiceName: args.name,
-			EdsConfig: &corev3.ConfigSource{
-				ResourceApiVersion: resource.DefaultAPIVersion,
-				ConfigSourceSpecifier: &corev3.ConfigSource_Ads{
-					Ads: &corev3.AggregatedConfigSource{},
-				},
-			},
+	// Set Proxy Protocol
+	if args.proxyProtocol != nil {
+		cluster.TransportSocket = buildProxyProtocolSocket(args.proxyProtocol, args.tSocket)
+	} else if args.tSocket != nil {
+		cluster.TransportSocket = args.tSocket
+	}
+
+	for i, ds := range args.settings {
+		// If zone aware routing is enabled we update the cluster lb config
+		if ds.ZoneAwareRoutingEnabled {
+			cluster.CommonLbConfig.LocalityConfigSpecifier = &clusterv3.Cluster_CommonLbConfig_ZoneAwareLbConfig_{ZoneAwareLbConfig: &clusterv3.Cluster_CommonLbConfig_ZoneAwareLbConfig{}}
 		}
-		// Dont wait for a health check to determine health and remove these endpoints
-		// if the endpoint has been removed via EDS by the control plane
-		cluster.IgnoreHealthOnHostRemoval = true
-	} else {
-		cluster.ClusterDiscoveryType = &clusterv3.Cluster_Type{Type: clusterv3.Cluster_STRICT_DNS}
-		cluster.DnsRefreshRate = durationpb.New(30 * time.Second)
-		cluster.RespectDnsTtl = true
-		if args.dns != nil {
-			if args.dns.DNSRefreshRate != nil {
-				if args.dns.DNSRefreshRate.Duration > 0 {
-					cluster.DnsRefreshRate = durationpb.New(args.dns.DNSRefreshRate.Duration)
-				}
+
+		if ds.TLS != nil {
+			socket, err := buildXdsUpstreamTLSSocketWthCert(ds.TLS)
+			if err != nil {
+				// TODO: Log something here
+				return nil, err
 			}
-			if args.dns.RespectDNSTTL != nil {
-				cluster.RespectDnsTtl = ptr.Deref(args.dns.RespectDNSTTL, true)
+			if args.proxyProtocol != nil {
+				socket = buildProxyProtocolSocket(args.proxyProtocol, socket)
 			}
+			matchName := fmt.Sprintf("%s/tls/%d", args.name, i)
+			cluster.TransportSocketMatches = append(cluster.TransportSocketMatches, &clusterv3.Cluster_TransportSocketMatch{
+				Name: matchName,
+				Match: &structpb.Struct{
+					Fields: map[string]*structpb.Value{
+						"name": structpb.NewStringValue(matchName),
+					},
+				},
+				TransportSocket: socket,
+			})
 		}
 	}
 
@@ -204,9 +216,10 @@ func buildXdsCluster(args *xdsClusterArgs) (*buildClusterResult, error) {
 
 	// Set Load Balancer policy
 	//nolint:gocritic
-	if args.loadBalancer == nil {
+	switch {
+	case args.loadBalancer == nil:
 		cluster.LbPolicy = clusterv3.Cluster_LEAST_REQUEST
-	} else if args.loadBalancer.LeastRequest != nil {
+	case args.loadBalancer.LeastRequest != nil:
 		cluster.LbPolicy = clusterv3.Cluster_LEAST_REQUEST
 		if args.loadBalancer.LeastRequest.SlowStart != nil {
 			if args.loadBalancer.LeastRequest.SlowStart.Window != nil {
@@ -219,7 +232,7 @@ func buildXdsCluster(args *xdsClusterArgs) (*buildClusterResult, error) {
 				}
 			}
 		}
-	} else if args.loadBalancer.RoundRobin != nil {
+	case args.loadBalancer.RoundRobin != nil:
 		cluster.LbPolicy = clusterv3.Cluster_ROUND_ROBIN
 		if args.loadBalancer.RoundRobin.SlowStart != nil && args.loadBalancer.RoundRobin.SlowStart.Window != nil {
 			cluster.LbConfig = &clusterv3.Cluster_RoundRobinLbConfig_{
@@ -230,11 +243,10 @@ func buildXdsCluster(args *xdsClusterArgs) (*buildClusterResult, error) {
 				},
 			}
 		}
-	} else if args.loadBalancer.Random != nil {
+	case args.loadBalancer.Random != nil:
 		cluster.LbPolicy = clusterv3.Cluster_RANDOM
-	} else if args.loadBalancer.ConsistentHash != nil {
+	case args.loadBalancer.ConsistentHash != nil:
 		cluster.LbPolicy = clusterv3.Cluster_MAGLEV
-
 		if args.loadBalancer.ConsistentHash.TableSize != nil {
 			cluster.LbConfig = &clusterv3.Cluster_MaglevLbConfig_{
 				MaglevLbConfig: &clusterv3.Cluster_MaglevLbConfig{
@@ -258,35 +270,69 @@ func buildXdsCluster(args *xdsClusterArgs) (*buildClusterResult, error) {
 		cluster.UpstreamConnectionOptions = buildXdsClusterUpstreamOptions(args.tcpkeepalive)
 	}
 
-	// Set Proxy Protocol
-	if args.proxyProtocol != nil {
-		cluster.TransportSocket = buildProxyProtocolSocket(args.proxyProtocol, args.tSocket)
-	} else if args.tSocket != nil {
-		cluster.TransportSocket = args.tSocket
-	}
+	switch args.endpointType {
+	case EndpointTypeDynamicResolver:
+		dnsCacheConfig := &commondfpv3.DnsCacheConfig{
+			Name:            args.name,
+			DnsLookupFamily: dnsLookupFamily,
+		}
+		dnsCacheConfig.DnsRefreshRate = durationpb.New(30 * time.Second)
+		if args.dns != nil {
+			if args.dns.DNSRefreshRate != nil {
+				if args.dns.DNSRefreshRate.Duration > 0 {
+					dnsCacheConfig.DnsRefreshRate = durationpb.New(args.dns.DNSRefreshRate.Duration)
+				}
+			}
+		}
 
-	for i, ds := range args.settings {
-		if ds.TLS != nil {
-			socket, err := buildXdsUpstreamTLSSocketWthCert(ds.TLS)
-			if err != nil {
-				// TODO: Log something here
-				return nil, err
+		dfp := &dfpv3.ClusterConfig{
+			ClusterImplementationSpecifier: &dfpv3.ClusterConfig_DnsCacheConfig{
+				DnsCacheConfig: dnsCacheConfig,
+			},
+		}
+		dfpAny, err := proto.ToAnyWithValidation(dfp)
+		if err != nil {
+			return nil, err
+		}
+		cluster.ClusterDiscoveryType = &clusterv3.Cluster_ClusterType{ClusterType: &clusterv3.Cluster_CustomClusterType{
+			Name:        args.name,
+			TypedConfig: dfpAny,
+		}}
+		cluster.LbPolicy = clusterv3.Cluster_CLUSTER_PROVIDED
+	case EndpointTypeStatic:
+		cluster.ClusterDiscoveryType = &clusterv3.Cluster_Type{Type: clusterv3.Cluster_EDS}
+		cluster.EdsClusterConfig = &clusterv3.Cluster_EdsClusterConfig{
+			ServiceName: args.name,
+			EdsConfig: &corev3.ConfigSource{
+				ResourceApiVersion: resource.DefaultAPIVersion,
+				ConfigSourceSpecifier: &corev3.ConfigSource_Ads{
+					Ads: &corev3.AggregatedConfigSource{},
+				},
+			},
+		}
+		// Dont wait for a health check to determine health and remove these endpoints
+		// if the endpoint has been removed via EDS by the control plane
+		cluster.IgnoreHealthOnHostRemoval = true
+	default:
+		cluster.ClusterDiscoveryType = &clusterv3.Cluster_Type{Type: clusterv3.Cluster_STRICT_DNS}
+		cluster.DnsRefreshRate = durationpb.New(30 * time.Second)
+		cluster.RespectDnsTtl = true
+		if args.dns != nil {
+			if args.dns.DNSRefreshRate != nil {
+				if args.dns.DNSRefreshRate.Duration > 0 {
+					cluster.DnsRefreshRate = durationpb.New(args.dns.DNSRefreshRate.Duration)
+				}
 			}
-			if args.proxyProtocol != nil {
-				socket = buildProxyProtocolSocket(args.proxyProtocol, socket)
+			if args.dns.RespectDNSTTL != nil {
+				cluster.RespectDnsTtl = ptr.Deref(args.dns.RespectDNSTTL, true)
 			}
-			matchName := fmt.Sprintf("%s/tls/%d", args.name, i)
-			cluster.TransportSocketMatches = append(cluster.TransportSocketMatches, &clusterv3.Cluster_TransportSocketMatch{
-				Name: matchName,
-				Match: &structpb.Struct{
-					Fields: map[string]*structpb.Value{
-						"name": structpb.NewStringValue(matchName),
-					},
-				},
-				TransportSocket: socket,
-			})
 		}
-		buildZoneAwareRoutingCluster(ds.ZoneAwareRoutingEnabled, cluster, args.loadBalancer)
+	}
+
+	if args.endpointType != EndpointTypeDynamicResolver {
+		for _, ds := range args.settings {
+			buildZoneAwareRoutingCluster(ds.ZoneAwareRoutingEnabled, cluster, args.loadBalancer)
+		}
 	}
 
 	return &buildClusterResult{
diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go
index 3d6a441a0..b6df51dda 100644
--- a/internal/xds/translator/route.go
+++ b/internal/xds/translator/route.go
@@ -270,7 +270,7 @@ func buildXdsWeightedRouteAction(backendWeights *ir.BackendWeights, settings []*
 	}
 
 	for _, destinationSetting := range settings {
-		if len(destinationSetting.Endpoints) > 0 {
+		if len(destinationSetting.Endpoints) > 0 || destinationSetting.IsDynamicResolver { // Dynamic resolver has no endpoints
 			validCluster := &routev3.WeightedCluster_ClusterWeight{
 				Name:   destinationSetting.Name,
 				Weight: &wrapperspb.UInt32Value{Value: *destinationSetting.Weight},
diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go
index d3b54396c..aadde3f42 100644
--- a/internal/xds/translator/translator.go
+++ b/internal/xds/translator/translator.go
@@ -940,13 +940,19 @@ func addXdsCluster(tCtx *types.ResourceVersionTable, args *xdsClusterArgs) error
 		}
 	}
 	// Use EDS for static endpoints
-	if args.endpointType == EndpointTypeStatic {
+	switch args.endpointType {
+	case EndpointTypeStatic:
 		if err := tCtx.AddXdsResource(resourcev3.EndpointType, xdsEndpoints); err != nil {
 			return err
 		}
-	} else {
+	case EndpointTypeDNS:
 		xdsCluster.LoadAssignment = xdsEndpoints
+	case EndpointTypeDynamicResolver:
+		// Dynamic resolver has no endpoints
+		// This assignment is not necessary, but it is added for clarity.
+		xdsCluster.LoadAssignment = nil
 	}
+
 	if err := tCtx.AddXdsResource(resourcev3.ClusterType, xdsCluster); err != nil {
 		return err
 	}


Test patch:
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit-invalid-distinct-invert.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit-invalid-distinct-invert.out.yaml
index 82857ba43..a3b43a77f 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit-invalid-distinct-invert.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit-invalid-distinct-invert.out.yaml
@@ -103,7 +103,8 @@ grpcRoutes:
         status: "True"
         type: Accepted
       - lastTransitionTime: null
-        message: Service default/service not found
+        message: 'Failed to process route rule 0 backendRef 0: service default/service
+          not found.'
         reason: BackendNotFound
         status: "False"
         type: ResolvedRefs
diff --git a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml
index 507259c65..21e004407 100644
--- a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml
+++ b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml
@@ -210,17 +210,17 @@ tcpRoutes:
   status:
     parents:
     - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
       - lastTransitionTime: null
         message: 'ClientCertificateRef Secret is not located in the same namespace
           as Envoyproxy. Secret namespace: envoy-gateway-user-ns does not match Envoyproxy
           namespace: envoy-gateway-system'
-        reason: Failed to process the settings associated with the TCP route.
+        reason: InvalidBackendTLS
         status: "False"
-        type: Accepted
-      - lastTransitionTime: null
-        message: Resolved all the Object references for the Route
-        reason: ResolvedRefs
-        status: "True"
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
@@ -270,6 +270,17 @@ xdsIR:
     - address: 0.0.0.0
       name: envoy-gateway/gateway-tls/
       port: 10445
+      routes:
+      - destination:
+          name: tcproute/envoy-gateway/envoy-gateway/rule/-1
+        name: tcproute/envoy-gateway/envoy-gateway
+        tls:
+          terminate:
+            alpnProtocols: []
+            certificates:
+            - name: envoy-gateway/default-cert
+              privateKey: '[redacted]'
+              serverCertificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKRENDQWd5Z0F3SUJBZ0lVU3JTYktMZjBiTEVHb2dXeC9nQ3cyR0N0dnhFd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0V6RVJNQThHQTFVRUF3d0lWR1Z6ZENCSmJtTXdIaGNOTWpRd01qSTVNRGt6TURFd1doY05NelF3TWpJMgpNRGt6TURFd1dqQVRNUkV3RHdZRFZRUUREQWhVWlhOMElFbHVZekNDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFECmdnRVBBRENDQVFvQ2dnRUJBSzFKempQSWlXZzNxb0hTckFkZGtlSmphTVA5aXlNVGkvQlBvOWNKUG9SRThaaTcKV2FwVXJYTC85QTlyK2pITXlHSVpOWk5kY1o1Y1kyWHYwTFA4WnhWeTJsazArM3d0WXpIbnBHWUdWdHlxMnRldApEaEZzaVBsODJZUmpDMG16V2E0UU16NFNYekZITmdJRHBSZGhmcm92bXNldVdHUUU4cFY0VWQ5VUsvU0tpbE1PCnF0QjVKaXJMUDJWczVUMW9XaWNXTFF2ZmJHd3Y3c0ZEZHI5YkcwWHRTUXAxN0hTZ281MFNERTUrQmpTbXB0RncKMVZjS0xscWFoTVhCRERpb3Jnd2hJaEdHS3BFU2VNMFA3YkZoVm1rTTNhc2gyeFNUQnVGVUJEbEU0Sk9haHp3cwpEWHJ1cFVoRGRTMWhkYzJmUHJqaEZBbEpmV0VZWjZCbFpqeXNpVlVDQXdFQUFhTndNRzR3SFFZRFZSME9CQllFCkZCUXVmSzFMaWJ1Vm05VHMvVmpCeDhMM3VpTmVNQjhHQTFVZEl3UVlNQmFBRkJRdWZLMUxpYnVWbTlUcy9WakIKeDhMM3VpTmVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdHd1lEVlIwUkJCUXdFb0lCS29JTktpNWxlR0Z0Y0d4bApMbU52YlRBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWZQUzQxYWdldldNVjNaWHQwQ09GRzN1WWZQRlhuVnc2ClA0MXA5TzZHa2RZc3VxRnZQZVR5eUgyL2RBSUtLd1N6TS9wdGhnOEtuOExabG1KeUZObkExc3RKeG41WGRiVjEKcFBxajhVdllDQnp5ak1JcW1SeW9peUxpUWxib2hNYTBVZEVCS2NIL1BkTEU5SzhUR0pyWmdvR1hxcTFXbWl0RAozdmNQalNlUEtFaVVKVlM5bENoeVNzMEtZNUIraFVRRDBKajZucEZENFprMHhxZHhoMHJXdWVDcXE3dmpxRVl6CnBqNFB3cnVmbjFQQlRtZnhNdVYvVUpWNWViaWtldVpQMzVrV3pMUjdaV0FMN3d1RGRXcC82bzR5azNRTGFuRFEKQ3dnQ0ZjWCtzcyswVnl1TTNZZXJUT1VVOFFWSkp4NFVaQU5aeDYrNDNwZEpaT2NudFBaNENBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
       tls:
         alpnProtocols: []
         certificates:
diff --git a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml
index 259fa875a..d38001af0 100644
--- a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml
+++ b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml
@@ -209,16 +209,16 @@ tcpRoutes:
   status:
     parents:
     - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
       - lastTransitionTime: null
         message: 'failed to locate TLS secret for client auth: envoy-gateway-system/client-auth-not-found
           specified in EnvoyProxy envoy-gateway-system/test'
-        reason: Failed to process the settings associated with the TCP route.
+        reason: InvalidBackendTLS
         status: "False"
-        type: Accepted
-      - lastTransitionTime: null
-        message: Resolved all the Object references for the Route
-        reason: ResolvedRefs
-        status: "True"
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
@@ -268,6 +268,17 @@ xdsIR:
     - address: 0.0.0.0
       name: envoy-gateway/gateway-tls/
       port: 10445
+      routes:
+      - destination:
+          name: tcproute/envoy-gateway/envoy-gateway/rule/-1
+        name: tcproute/envoy-gateway/envoy-gateway
+        tls:
+          terminate:
+            alpnProtocols: []
+            certificates:
+            - name: envoy-gateway/default-cert
+              privateKey: '[redacted]'
+              serverCertificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKRENDQWd5Z0F3SUJBZ0lVU3JTYktMZjBiTEVHb2dXeC9nQ3cyR0N0dnhFd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0V6RVJNQThHQTFVRUF3d0lWR1Z6ZENCSmJtTXdIaGNOTWpRd01qSTVNRGt6TURFd1doY05NelF3TWpJMgpNRGt6TURFd1dqQVRNUkV3RHdZRFZRUUREQWhVWlhOMElFbHVZekNDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFECmdnRVBBRENDQVFvQ2dnRUJBSzFKempQSWlXZzNxb0hTckFkZGtlSmphTVA5aXlNVGkvQlBvOWNKUG9SRThaaTcKV2FwVXJYTC85QTlyK2pITXlHSVpOWk5kY1o1Y1kyWHYwTFA4WnhWeTJsazArM3d0WXpIbnBHWUdWdHlxMnRldApEaEZzaVBsODJZUmpDMG16V2E0UU16NFNYekZITmdJRHBSZGhmcm92bXNldVdHUUU4cFY0VWQ5VUsvU0tpbE1PCnF0QjVKaXJMUDJWczVUMW9XaWNXTFF2ZmJHd3Y3c0ZEZHI5YkcwWHRTUXAxN0hTZ281MFNERTUrQmpTbXB0RncKMVZjS0xscWFoTVhCRERpb3Jnd2hJaEdHS3BFU2VNMFA3YkZoVm1rTTNhc2gyeFNUQnVGVUJEbEU0Sk9haHp3cwpEWHJ1cFVoRGRTMWhkYzJmUHJqaEZBbEpmV0VZWjZCbFpqeXNpVlVDQXdFQUFhTndNRzR3SFFZRFZSME9CQllFCkZCUXVmSzFMaWJ1Vm05VHMvVmpCeDhMM3VpTmVNQjhHQTFVZEl3UVlNQmFBRkJRdWZLMUxpYnVWbTlUcy9WakIKeDhMM3VpTmVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdHd1lEVlIwUkJCUXdFb0lCS29JTktpNWxlR0Z0Y0d4bApMbU52YlRBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWZQUzQxYWdldldNVjNaWHQwQ09GRzN1WWZQRlhuVnc2ClA0MXA5TzZHa2RZc3VxRnZQZVR5eUgyL2RBSUtLd1N6TS9wdGhnOEtuOExabG1KeUZObkExc3RKeG41WGRiVjEKcFBxajhVdllDQnp5ak1JcW1SeW9peUxpUWxib2hNYTBVZEVCS2NIL1BkTEU5SzhUR0pyWmdvR1hxcTFXbWl0RAozdmNQalNlUEtFaVVKVlM5bENoeVNzMEtZNUIraFVRRDBKajZucEZENFprMHhxZHhoMHJXdWVDcXE3dmpxRVl6CnBqNFB3cnVmbjFQQlRtZnhNdVYvVUpWNWViaWtldVpQMzVrV3pMUjdaV0FMN3d1RGRXcC82bzR5azNRTGFuRFEKQ3dnQ0ZjWCtzcyswVnl1TTNZZXJUT1VVOFFWSkp4NFVaQU5aeDYrNDNwZEpaT2NudFBaNENBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
       tls:
         alpnProtocols: []
         certificates:
diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.out.yaml
index 7977e6050..8c016f7ef 100644
--- a/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.out.yaml
+++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.out.yaml
@@ -72,9 +72,9 @@ tcpRoutes:
     parents:
     - conditions:
       - lastTransitionTime: null
-        message: TCP Port 8081 not found on Service default/service-1
-        reason: Failed to process the settings associated with the TCP route.
-        status: "False"
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
         type: Accepted
       - lastTransitionTime: null
         message: TCP Port 8081 not found on Service default/service-1
@@ -99,3 +99,7 @@ xdsIR:
     - address: 0.0.0.0
       name: envoy-gateway/gateway-1/tcp
       port: 10162
+      routes:
+      - destination:
+          name: tcproute/default/tcproute-1/rule/-1
+        name: tcproute/default/tcproute-1
diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.out.yaml
index 998a5fa97..1630f34b4 100644
--- a/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.out.yaml
+++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.out.yaml
@@ -76,16 +76,11 @@ tcpRoutes:
   status:
     parents:
     - conditions:
-      - lastTransitionTime: null
-        message: Route is accepted
-        reason: Accepted
-        status: "True"
-        type: Accepted
       - lastTransitionTime: null
         message: One and only one rule is supported
         reason: InvalidRule
         status: "False"
-        type: ResolvedRefs
+        type: Accepted
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
         name: gateway-1
diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-udproute-with-multiple-rules.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-udproute-with-multiple-rules.out.yaml
index 225495e6a..578d12c6a 100644
--- a/internal/gatewayapi/testdata/gateway-with-listener-with-udproute-with-multiple-rules.out.yaml
+++ b/internal/gatewayapi/testdata/gateway-with-listener-with-udproute-with-multiple-rules.out.yaml
@@ -76,16 +76,11 @@ udpRoutes:
   status:
     parents:
     - conditions:
-      - lastTransitionTime: null
-        message: Route is accepted
-        reason: Accepted
-        status: "True"
-        type: Accepted
       - lastTransitionTime: null
         message: One and only one rule is supported
         reason: InvalidRule
         status: "False"
-        type: ResolvedRefs
+        type: Accepted
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
         name: gateway-1
diff --git a/internal/gatewayapi/testdata/httproute-dynamic-resolver-with-mutliple-backends.in.yaml b/internal/gatewayapi/testdata/httproute-dynamic-resolver-with-mutliple-backends.in.yaml
new file mode 100644
index 000000000..cbb462e0a
--- /dev/null
+++ b/internal/gatewayapi/testdata/httproute-dynamic-resolver-with-mutliple-backends.in.yaml
@@ -0,0 +1,54 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    namespace: envoy-gateway
+    name: gateway-1
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+      allowedRoutes:
+        namespaces:
+          from: All
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-1
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - backendRefs:
+      - group: gateway.envoyproxy.io
+        kind: Backend
+        name: backend-1
+      - group: gateway.envoyproxy.io
+        kind: Backend
+        name: backend-2
+backends:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: Backend
+  metadata:
+    name: backend-1
+    namespace: default
+  spec:
+    type: DynamicResolver
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: Backend
+  metadata:
+    name: backend-2
+    namespace: default
+  spec:
+    endpoints:
+    - ip:
+        address: 1.1.1.1
+        port: 3001
diff --git a/internal/gatewayapi/testdata/httproute-dynamic-resolver-with-mutliple-backends.out.yaml b/internal/gatewayapi/testdata/httproute-dynamic-resolver-with-mutliple-backends.out.yaml
new file mode 100644
index 000000000..3570d0b79
--- /dev/null
+++ b/internal/gatewayapi/testdata/httproute-dynamic-resolver-with-mutliple-backends.out.yaml
@@ -0,0 +1,167 @@
+backends:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: Backend
+  metadata:
+    creationTimestamp: null
+    name: backend-1
+    namespace: default
+  spec:
+    type: DynamicResolver
+  status:
+    conditions:
+    - lastTransitionTime: null
+      message: The Backend was accepted
+      reason: Accepted
+      status: "True"
+      type: Accepted
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: Backend
+  metadata:
+    creationTimestamp: null
+    name: backend-2
+    namespace: default
+  spec:
+    endpoints:
+    - ip:
+        address: 1.1.1.1
+        port: 3001
+  status:
+    conditions:
+    - lastTransitionTime: null
+      message: The Backend was accepted
+      reason: Accepted
+      status: "True"
+      type: Accepted
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - group: gateway.envoyproxy.io
+        kind: Backend
+        name: backend-1
+      - group: gateway.envoyproxy.io
+        kind: Backend
+        name: backend-2
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: 'Failed to process route rule 0: dynamic resolver is not supported
+          for multiple backendRefs.'
+        reason: InvalidBackendRef
+        status: "False"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-1/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-1
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      json:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - directResponse:
+          statusCode: 500
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/-1/gateway_envoyproxy_io
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml b/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml
new file mode 100644
index 000000000..3cd1bbb8e
--- /dev/null
+++ b/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml
@@ -0,0 +1,41 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    namespace: envoy-gateway
+    name: gateway-1
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+      allowedRoutes:
+        namespaces:
+          from: All
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-1
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - backendRefs:
+      - group: gateway.envoyproxy.io
+        kind: Backend
+        name: backend-1
+backends:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: Backend
+  metadata:
+    name: backend-1
+    namespace: default
+  spec:
+    type: DynamicResolver
diff --git a/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml b/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml
new file mode 100644
index 000000000..da6d59407
--- /dev/null
+++ b/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml
@@ -0,0 +1,149 @@
+backends:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: Backend
+  metadata:
+    creationTimestamp: null
+    name: backend-1
+    namespace: default
+  spec:
+    type: DynamicResolver
+  status:
+    conditions:
+    - lastTransitionTime: null
+      message: The Backend was accepted
+      reason: Accepted
+      status: "True"
+      type: Accepted
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - group: gateway.envoyproxy.io
+        kind: Backend
+        name: backend-1
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-1/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-1
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      json:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - isDynamicResolver: true
+            name: httproute/default/httproute-1/rule/0/backend/0
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/-1/gateway_envoyproxy_io
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-unsupported-filter.out.yaml b/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-unsupported-filter.out.yaml
index a688188f5..8d7fc7c53 100644
--- a/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-unsupported-filter.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-invalid-backend-ref-unsupported-filter.out.yaml
@@ -73,9 +73,9 @@ httpRoutes:
         status: "True"
         type: Accepted
       - lastTransitionTime: null
-        message: 'Failed to process route rule 0 backendRef 0: specific filter is
-          not supported within BackendRef, only RequestHeaderModifier and ResponseHeaderModifier
-          are supported.'
+        message: 'Failed to process route rule 0 backendRef 0: Specific filter is
+          not supported within BackendRef, only RequestHeaderModifier, ResponseHeaderModifier
+          and gateway.envoyproxy.io/HTTPRouteFilter are supported.'
         reason: UnsupportedRefValue
         status: "False"
         type: ResolvedRefs
diff --git a/internal/gatewayapi/testdata/httproute-with-mirror-filter-service-no-port.out.yaml b/internal/gatewayapi/testdata/httproute-with-mirror-filter-service-no-port.out.yaml
index d2605efc0..0ebb67ed5 100644
--- a/internal/gatewayapi/testdata/httproute-with-mirror-filter-service-no-port.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-mirror-filter-service-no-port.out.yaml
@@ -71,15 +71,13 @@ httpRoutes:
     parents:
     - conditions:
       - lastTransitionTime: null
-        message: 'Failed to process route rule 0: failed to validate the RequestMirror
-          filter: A valid port number corresponding to a port on the Service must
-          be specified.'
-        reason: UnsupportedValue
-        status: "False"
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
         type: Accepted
       - lastTransitionTime: null
-        message: A valid port number corresponding to a port on the Service must be
-          specified
+        message: 'Failed to validate the RequestMirror filter: A valid port number
+          corresponding to a port on the Service must be specified.'
         reason: PortNotSpecified
         status: "False"
         type: ResolvedRefs
diff --git a/internal/gatewayapi/testdata/httproute-with-mirror-filter-service-not-found.out.yaml b/internal/gatewayapi/testdata/httproute-with-mirror-filter-service-not-found.out.yaml
index 664ffd08f..6e11835b8 100644
--- a/internal/gatewayapi/testdata/httproute-with-mirror-filter-service-not-found.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-mirror-filter-service-not-found.out.yaml
@@ -72,13 +72,13 @@ httpRoutes:
     parents:
     - conditions:
       - lastTransitionTime: null
-        message: 'Failed to process route rule 0: failed to validate the RequestMirror
-          filter: service default/service-unknown not found.'
-        reason: UnsupportedValue
-        status: "False"
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
         type: Accepted
       - lastTransitionTime: null
-        message: Service default/service-unknown not found
+        message: 'Failed to validate the RequestMirror filter: service default/service-unknown
+          not found.'
         reason: BackendNotFound
         status: "False"
         type: ResolvedRefs
diff --git a/internal/gatewayapi/testdata/securitypolicy-status-conditions.out.yaml b/internal/gatewayapi/testdata/securitypolicy-status-conditions.out.yaml
index 2380d0ae7..748cd2c0e 100644
--- a/internal/gatewayapi/testdata/securitypolicy-status-conditions.out.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-status-conditions.out.yaml
@@ -159,7 +159,8 @@ grpcRoutes:
         status: "True"
         type: Accepted
       - lastTransitionTime: null
-        message: Service envoy-gateway/service-1 not found
+        message: 'Failed to process route rule 0 backendRef 0: service envoy-gateway/service-1
+          not found.'
         reason: BackendNotFound
         status: "False"
         type: ResolvedRefs
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-cors-targetrefs.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-cors-targetrefs.out.yaml
index 708a622f9..5868da939 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-cors-targetrefs.out.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-cors-targetrefs.out.yaml
@@ -144,7 +144,8 @@ grpcRoutes:
         status: "True"
         type: Accepted
       - lastTransitionTime: null
-        message: Service envoy-gateway/service-1 not found
+        message: 'Failed to process route rule 0 backendRef 0: service envoy-gateway/service-1
+          not found.'
         reason: BackendNotFound
         status: "False"
         type: ResolvedRefs
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml
new file mode 100644
index 000000000..5d7939120
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml
@@ -0,0 +1,34 @@
+http:
+- address: 0.0.0.0
+  hostnames:
+  - '*'
+  isHTTP2: false
+  metadata:
+    kind: Gateway
+    name: gateway-1
+    namespace: envoy-gateway
+    sectionName: http
+  name: envoy-gateway/gateway-1/http
+  path:
+    escapedSlashesAction: UnescapeAndRedirect
+    mergeSlashes: true
+  port: 10080
+  routes:
+  - destination:
+      name: httproute/default/httproute-1/rule/0
+      settings:
+      - isDynamicResolver: true
+        name: httproute/default/httproute-1/rule/0/backend/0
+        weight: 1
+    hostname: gateway.envoyproxy.io
+    isHTTP2: false
+    metadata:
+      kind: HTTPRoute
+      name: httproute-1
+      namespace: default
+    name: httproute/default/httproute-1/rule/0/match/-1/gateway_envoyproxy_io
+readyListener:
+  address: 0.0.0.0
+  ipFamily: IPv4
+  path: /ready
+  port: 19003
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml
new file mode 100644
index 000000000..29b8be2f4
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml
@@ -0,0 +1,18 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  clusterType:
+    name: httproute/default/httproute-1/rule/0
+    typedConfig:
+      '@type': type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig
+      dnsCacheConfig:
+        dnsLookupFamily: V4_PREFERRED
+        dnsRefreshRate: 30s
+        name: httproute/default/httproute-1/rule/0
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_PREFERRED
+  lbPolicy: CLUSTER_PROVIDED
+  name: httproute/default/httproute-1/rule/0
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.endpoints.yaml
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.endpoints.yaml
@@ -0,0 +1,1 @@
+[]
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.listeners.yaml
new file mode 100644
index 000000000..9677ab755
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.listeners.yaml
@@ -0,0 +1,70 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 19003
+  bypassOverloadManager: true
+  filterChains:
+  - filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        httpFilters:
+        - name: envoy.filters.http.health_check
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck
+            headers:
+            - name: :path
+              stringMatch:
+                exact: /ready
+            passThroughMode: false
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        routeConfig:
+          name: ready_route
+          virtualHosts:
+          - domains:
+            - '*'
+            name: ready_route
+            routes:
+            - directResponse:
+                status: 500
+              match:
+                prefix: /
+        statPrefix: eg-ready-http
+  name: envoy-gateway-proxy-ready-0.0.0.0-19003
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 10080
+  defaultFilterChain:
+    filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        commonHttpProtocolOptions:
+          headersWithUnderscoresAction: REJECT_REQUEST
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        mergeSlashes: true
+        normalizePath: true
+        pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: envoy-gateway/gateway-1/http
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http-10080
+        useRemoteAddress: true
+    name: envoy-gateway/gateway-1/http
+  name: envoy-gateway/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.routes.yaml
new file mode 100644
index 000000000..b4e7a9e65
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.routes.yaml
@@ -0,0 +1,29 @@
+- ignorePortInHostMatching: true
+  name: envoy-gateway/gateway-1/http
+  virtualHosts:
+  - domains:
+    - gateway.envoyproxy.io
+    metadata:
+      filterMetadata:
+        envoy-gateway:
+          resources:
+          - kind: Gateway
+            name: gateway-1
+            namespace: envoy-gateway
+            sectionName: http
+    name: envoy-gateway/gateway-1/http/gateway_envoyproxy_io
+    routes:
+    - match:
+        prefix: /
+      metadata:
+        filterMetadata:
+          envoy-gateway:
+            resources:
+            - kind: HTTPRoute
+              name: httproute-1
+              namespace: default
+      name: httproute/default/httproute-1/rule/0/match/-1/gateway_envoyproxy_io
+      route:
+        cluster: httproute/default/httproute-1/rule/0
+        upgradeConfigs:
+        - upgradeType: websocket
diff --git a/test/e2e/testdata/httproute-with-dynamic-resolver-backend.yaml b/test/e2e/testdata/httproute-with-dynamic-resolver-backend.yaml
new file mode 100644
index 000000000..abb97d608
--- /dev/null
+++ b/test/e2e/testdata/httproute-with-dynamic-resolver-backend.yaml
@@ -0,0 +1,49 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: httproute-with-dynamic-resolver-backend
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - backendRefs:
+    - group: gateway.envoyproxy.io
+      kind: Backend
+      name: backend-dynamic-resolver
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: Backend
+metadata:
+  name: backend-dynamic-resolver
+  namespace: gateway-conformance-infra
+spec:
+  type: DynamicResolver
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: test-service-foo
+  namespace: gateway-conformance-infra
+spec:
+  selector:
+    app: infra-backend-v1
+  ports:
+  - protocol: TCP
+    port: 80
+    name: http11
+    targetPort: 3000
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: test-service-bar
+  namespace: gateway-conformance-infra
+spec:
+  selector:
+    app: infra-backend-v1
+  ports:
+  - protocol: TCP
+    port: 80
+    name: http11
+    targetPort: 3000
diff --git a/test/e2e/tests/httproute_with_dynamic_resolver_backend.go b/test/e2e/tests/httproute_with_dynamic_resolver_backend.go
new file mode 100644
index 000000000..dfd1fe346
--- /dev/null
+++ b/test/e2e/tests/httproute_with_dynamic_resolver_backend.go
@@ -0,0 +1,65 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+//go:build e2e
+
+package tests
+
+import (
+	"testing"
+
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, EnvoyGatewayDynamicResolverBackendTest)
+}
+
+var EnvoyGatewayDynamicResolverBackendTest = suite.ConformanceTest{
+	ShortName:   "EnvoyGatewayDynamicResolverBackend",
+	Description: "Routes with a backend ref to a dynamic resolver backend",
+	Manifests: []string{
+		"testdata/httproute-with-dynamic-resolver-backend.yaml",
+	},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-infra"
+		routeNN := types.NamespacedName{Name: "httproute-with-dynamic-resolver-backend", Namespace: ns}
+		gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+		gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+		BackendMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "backend-dynamic-resolver", Namespace: ns})
+
+		t.Run("route to service foo", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Host: "test-service-foo.gateway-conformance-infra",
+					Path: "/",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+		})
+		t.Run("route to service bar", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Host: "test-service-bar.gateway-conformance-infra",
+					Path: "/",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+		})
+	},
+}