| name | eg-contrib-envoy-internals |
| description | Envoy Proxy internals for EG contributors — request lifecycle, xDS resource types, filter chain ordering, go-control-plane imports, and common xDS translation mistakes |
Envoy Proxy Internals for EG Development
Request Lifecycle Through Envoy
Understanding what Envoy does with the xDS configuration you generate is critical for writing correct translations.
Client Request
│
▼
1. Listener (binds to address:port)
│
▼
2. Listener Filters
│ TLS Inspector → extracts SNI for filter chain matching
│ HTTP Inspector → detects HTTP vs non-HTTP protocol
│
▼
3. Filter Chain Match
│ Selects the correct filter chain based on:
│ SNI, ALPN, destination port, source IP
│
▼
4. Transport Socket (TLS)
│ Handles TLS handshake, encryption/decryption
│
▼
5. Network Filter Chain
│ HTTP Connection Manager (HCM) is the primary network filter
│ For TCP/UDP routes: TCP Proxy or UDP Proxy filter instead
│
▼
6. HTTP Codec
│ Parses HTTP/1.1 or HTTP/2 frames
│ Demultiplexes HTTP/2 streams
│
▼
7. Downstream HTTP Filter Chain (per-stream)
│ Filters run in order: fault → CORS → auth → rate limit → ...
│ Each filter can: continue, stop iteration, or send local reply
│
▼
8. Router Filter (terminal — always last)
│ Matches the request to a route entry
│ Selects a cluster based on the route action
│
▼
9. Load Balancer
│ Picks an endpoint from the cluster
│ Strategies: round robin, least request, random, ring hash, maglev
│
▼
10. Upstream Connection
│ Connection pooling (HTTP/1.1 or HTTP/2)
│ Circuit breaking checks
│
▼
11. Upstream HTTP Filter Chain
│ Processes the outgoing request (rarely used in EG)
│
▼
12. Upstream Server → Response flows back through filter chain in reverse
Key Takeaways for EG Contributors
- Filters run in order — changing the order changes behavior
- The Router filter must always be last — it is terminal
- Filter chain matching happens on the listener level (SNI, port) — not HTTP level
- Virtual host matching happens inside HCM based on
:authority header
- Route matching happens inside a virtual host (path, headers, query params)
- A filter can short-circuit the chain by sending a local reply (e.g., 403 from ext_authz)
xDS Resource Types
EG generates five types of xDS resources served to Envoy via gRPC delta xDS:
| xDS Type | Short Name | What It Defines | Generated By |
|---|
Listener | LDS | Address:port bindings, filter chains, transport sockets | internal/xds/translator/listener.go |
RouteConfiguration | RDS | Virtual hosts and route entries (referenced by HCM) | internal/xds/translator/route.go |
Cluster | CDS | Upstream service definition (LB policy, health checks, circuit breakers) | internal/xds/translator/cluster.go |
ClusterLoadAssignment | EDS | Actual endpoints (IP:port) for a cluster | internal/xds/translator/cluster.go |
Secret | SDS | TLS certificates and private keys | internal/xds/translator/translator.go |
Resource Relationships
Listener
└── FilterChain
└── HttpConnectionManager (network filter)
├── HTTP Filter Chain [custom_response, fault, cors, jwt, ..., router]
└── RouteConfigName → RouteConfiguration
└── VirtualHost (matched by :authority)
└── Route (matched by path/headers)
└── ClusterName → Cluster
└── LoadAssignment (EDS)
└── Endpoints [ip:port]
HTTP Filter Chain Order
The order of HTTP filters in EG is defined in internal/xds/translator/httpfilters.go. When adding a new filter, you must set its order in the newOrderedHTTPFilter function.
| Order | Filter | EG Constant | Purpose |
|---|
| 0 | Custom Response | EnvoyFilterCustomResponse | Intercept local replies (error pages) |
| 1 | Health Check | EnvoyFilterHealthCheck | LB health probes (unaffected by other filters) |
| 2 | Fault Injection | EnvoyFilterFault | Inject delays/aborts (early = saves compute) |
| 3 | CORS | EnvoyFilterCORS | Cross-origin preflight (before auth) |
| 4 | Header Mutation | EnvoyFilterHeaderMutation | Modify headers before auth consumes them |
| 5 | External Authorization | EnvoyFilterExtAuthz | ExtAuth gRPC/HTTP service |
| 6 | API Key Auth | EnvoyFilterAPIKeyAuth | API key validation |
| 7 | Basic Auth | EnvoyFilterBasicAuth | HTTP basic auth |
| 8 | OAuth2 | EnvoyFilterOAuth2 | OAuth2/OIDC flow |
| 9 | JWT Authentication | EnvoyFilterJWTAuthn | JWT token validation |
| 10 | Session Persistence | EnvoyFilterSessionPersistence | Session affinity |
| 11 | Buffer | EnvoyFilterBuffer | Request buffering |
| 12+ | Lua | EnvoyFilterLua | Lua scripting (indexed per instance) |
| 100+ | ExtProc | EnvoyFilterExtProc | External processing (indexed per instance) |
| 200+ | Wasm | EnvoyFilterWasm | WebAssembly filters (indexed per instance) |
| 250+ | Dynamic Modules | EnvoyFilterDynamicModules | Dynamic module filters |
| 301 | RBAC | EnvoyFilterRBAC | Role-based access control |
| 302 | Local Rate Limit | EnvoyFilterLocalRateLimit | Per-instance rate limiting |
| 303 | Global Rate Limit | EnvoyFilterRateLimit | Shared rate limiting (Redis) |
| 304 | gRPC Web | EnvoyFilterGRPCWeb | gRPC-Web protocol translation |
| 305 | gRPC Stats | EnvoyFilterGRPCStats | gRPC metrics |
| 307 | Credential Injector | EnvoyFilterCredentialInjector | Inject credentials to upstream |
| 308 | Compressor | EnvoyFilterCompressor | Response compression |
| 309 | Dynamic Forward Proxy | EnvoyFilterDynamicForwardProxy | Dynamic upstream resolution |
| 310 | Router | EnvoyFilterRouter | Terminal — always last |
Critical: When adding a new filter type, you must:
- Add an order entry in
newOrderedHTTPFilter() in httpfilters.go
- Add the filter type constant in
api/v1alpha1/envoyfilter_types.go
- Update the validation rule for the
EnvoyFilter type in the API
go-control-plane Import Map
When writing xDS translation code, use the correct protobuf types from go-control-plane:
import (
listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
)
import (
hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
)
import (
corsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/cors/v3"
faultv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3"
jwtauthnv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/jwt_authn/v3"
extauthzv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3"
routerv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
ratelimitv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ratelimit/v3"
localrlv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/local_ratelimit/v3"
extprocv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3"
wasmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/wasm/v3"
luav3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/lua/v3"
rbacv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3"
bufferv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/buffer/v3"
)
import "google.golang.org/protobuf/types/known/anypb"
Wrapping Typed Config
All Envoy filter configs must be wrapped in Any for the typed_config field:
typedConfig, err := anypb.New(&corsv3.CorsPolicy{
AllowOriginStringMatch: []*matcherv3.StringMatcher{...},
})
if err != nil {
return err
}
httpFilter := &hcmv3.HttpFilter{
Name: "envoy.filters.http.cors",
ConfigType: &hcmv3.HttpFilter_TypedConfig{
TypedConfig: typedConfig,
},
}
Per-Route Filter Configuration
Envoy supports two patterns for per-route filter config. EG uses both:
Native Per-Route Config (preferred)
The filter has a built-in per-route config type. EG adds the filter once to HCM and sets per-route config on each route's TypedPerFilterConfig:
route.TypedPerFilterConfig["envoy.filters.http.cors"] = corsPerRouteConfig
Filters with native per-route support: CORS, JWT authn, rate limit, local rate limit, fault injection, RBAC.
Non-Native Per-Route Config (workaround)
The filter lacks built-in per-route config. EG adds a separate filter instance per route in the HCM chain, each disabled by default, then enables the correct one per route:
- Filter names are suffixed with the route name:
envoy.filters.http.ext_authz/route-name
- Each filter instance is disabled by default
- Routes enable their specific filter instance via
TypedPerFilterConfig
Filters using this pattern: OAuth2, ext_authz.
Extension Hook Points
Extensions can modify xDS resources at five points during translation. Hooks are defined in internal/extension/types/manager.go:
| Hook | When It Runs | What It Receives |
|---|
PostRouteModifyHook | After each route is translated | Route + HTTPRoute IR |
PostClusterModifyHook | After each cluster is translated | Cluster + backend refs |
PostVirtualHostModifyHook | After each virtual host | VirtualHost + all routes |
PostHTTPListenerModifyHook | After each HTTP listener | Listener + HCM |
PostTranslateModifyHook | After entire translation | All xDS resources |
Extensions run as external gRPC services configured in the EnvoyGateway config.
Common xDS Translation Mistakes
1. Filter Name Collisions
Filter names must be unique and stable across releases. Changing a filter name causes Envoy to drain and recreate filter chains, disrupting traffic.
2. Non-Deterministic Resource Names
Cluster and route names must be deterministic (typically hash-based). Non-deterministic names cause unnecessary xDS updates.
3. Missing @type in typed_config
Every typed_config field requires the @type URL. Always use anypb.New() which sets this automatically. Never manually construct Any protos.
4. Incorrect Filter Order
Adding a filter without setting its order in newOrderedHTTPFilter() gives it default order 50, which may be wrong. Auth filters before CORS breaks preflight. Rate limit after router never runs.
5. Not Handling Nil IR Fields
IR fields are often pointers (nil = not configured). Always check for nil before translating optional features.
6. Forgetting patchResources
Some filters need additional clusters (e.g., JWKS endpoint for JWT, token endpoint for OIDC). Implement the patchResources method on your httpFilter to add these.
7. Listener Drain on Config Change
Any change to a listener's filter chain causes Envoy to drain existing connections. Minimize unnecessary changes by using stable filter names and sorting filters deterministically.