Skip to content

HTTPRoute without hostname generates 500 directResponse instead of proper routes #7365

@pbabilas

Description

@pbabilas

Bug: HTTPRoute without hostname generates dummy 500 directResponse routes instead of proper backend routes

Description

When a Listener in a Gateway does NOT have a hostname specified, HTTPRoutes that would be attached to it are incorrectly translated to xDS config with directResponse: 500 instead of proper route: { cluster: ... } entries.

What is being seen:

  • All traffic to routes attached to hostname-less listeners returns 500 errors
  • xDS config inspection shows routes have "directResponse": { "status": 500 } instead of proper "route": { "cluster": "..." }

What should be happening:

  • When a Listener has NO hostname, it should act as a catch-all listener
  • HTTPRoutes matching that listener should be properly translated to xDS routes with valid cluster references
  • Requests should be routed to backend services, not rejected with 500 errors

Repro Steps

  1. Create a Gateway with HTTP/HTTPS listeners WITHOUT hostname field:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: test-gateway
spec:
  listeners:
  - name: http
    port: 80
    protocol: HTTP
    # NO hostname field
  - name: https
    port: 443
    protocol: HTTPS
    # NO hostname field
    tls:
      certificateRefs:
      - name: test-cert
        kind: Secret
  1. Create HTTPRoute(s) attached to this Gateway:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: test-route
spec:
  parentRefs:
  - name: test-gateway
    sectionName: http
  hostnames:
  - example.com
  rules:
  - backendRefs:
    - name: my-backend
      port: 8080
  1. Check the xDS route config:
kubectl exec -it <envoy-pod> -c envoy -- \
  curl -s localhost:15000/config_dump | jq '.configs[] | select(.type=="route")'
  1. Observe: The route has "directResponse": { "status": 500 } instead of proper cluster route

Root Cause Analysis

In internal/gatewayapi/route.go processHTTPRouteParentRefListener() (lines 609-680):

func (t *Translator) processHTTPRouteParentRefListener(route RouteContext, routeRoutes []*ir.HTTPRoute, parentRef *RouteParentContext, xdsIR XdsIRMap) bool {
	var hasHostnameIntersection bool

	for _, listener := range parentRef.listeners {
		hosts := computeHosts(GetHostnames(route), listener.Hostname)
		if len(hosts) == 0 {
			continue  // ← Routes are skipped
		}
		hasHostnameIntersection = true
		// ... routes are properly added to irListener.Routes ...
	}
	return hasHostnameIntersection
}

When len(hosts) == 0, the route is skipped (continue). However, somewhere in the translation pipeline, when a route is skipped but already has some state generated, the translator generates a dummy route with directResponse: 500 instead of either:

  • Properly attaching the route with hosts = []string{"*"} if listener has no hostname
  • Or completely skipping the route generation

The issue is in how computeHosts() in helpers.go handles the case when BOTH route and listener lack hostnames.

Environment

  • Envoy Gateway version: v1.5.4
  • Envoy Gateway Image: docker.io/envoyproxy/gateway:v1.5.4
  • Kubernetes version: [Please specify - e.g., 1.28+]
  • Gateway API version: v1
  • Envoy Proxy version: [Included in v1.5.4]

Logs

xDS Route Configuration Dumps

WITH hostname on Listener (WORKING):
See attached: route_with_hostname.log

Sample working route:

{
  "name": "httproute/newbalance-dev/admin/rule/0/match/5/newbalance-gw_prod_merce_com",
  "match": {
    "pathSeparatedPrefix": "/user/password_reset_email_sent"
  },
  "route": {
    "cluster": "httproute/newbalance-dev/admin/rule/0"
  }
}

WITHOUT hostname on Listener (BROKEN):
See attached: route_without_hostname.log

Sample broken route:

{
  "name": "httproute/newbalance-dev/admin/rule/0/match/5/newbalance-gw_prod_merce_com",
  "match": {
    "pathSeparatedPrefix": "/user/password_reset_email_sent"
  },
  "directResponse": {
    "status": 500
  }
}

Listener Configuration Dumps

  • listeners-with-hostnames.log - Shows proper filterChainMatch generation
  • listeners-no-hostnames.log - Shows missing filterChainMatch

listeners-no-hostnames.log
listeners-with-hostnames.log
route_with_hostname.log
route_without_hostname.log

Proposed Fix

The logic in computeHosts() should return []string{"*"} when BOTH conditions are true:

  • Route has no hostnames specified
  • Listener has no hostname specified

This would allow routes to properly attach to catch-all listeners.

Alternatively, the route translator should properly handle the case where len(hosts) == 0 by generating a catch-all route instead of skipping and leaving a dangling route that gets converted to a 500 error.

Additional Context

Per Gateway API specification, hostname field on Listener is OPTIONAL. When omitted, the listener should match requests for any hostname. The current implementation does not properly handle this case for HTTPRoute -> xDS translation, generating error routes instead of proper routing rules.

The Gateway API docs show examples with commented-out hostname fields (https://gateway.envoyproxy.io/docs/tasks/traffic/http-redirect/), suggesting that listeners without hostnames should be a valid and supported configuration.

Metadata

Metadata

Assignees

Labels

kind/bugSomething isn't working

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions