grpc/grpc-go · error
unsupported field 'use_original_dst' is present and set to…
Error message
unsupported field 'use_original_dst' is present and set to true
What it means
Returned by processServerSideListener when the LDS Listener has use_original_dst set to true. grpc-go's inbound listener handling does not implement Envoy's original-destination-based filter chain selection, so this field being true is treated as an unsupported configuration and the resource is rejected (NACK).
Solutions
- Remove or set use_original_dst=false on the LDS Listener resource consumed by grpc-go.
- If original-dst routing is genuinely required, terminate LDS with Envoy/proxy rather than the in-process grpc-go xDS server.
- Filter the listener at the control plane so grpc-go only receives listeners without use_original_dst.
Example fix
// before
// listener: { name: "inbound", use_original_dst: { value: true }, address: { ... } }
// after
// listener: { name: "inbound", address: { ... } } Defensive patterns
Strategy: validation
Validate before calling
// Reject listeners with use_original_dst before publishing to grpc-go.
func isGrpcCompatibleListener(lis *envoy_listener_pb.Listener) error {
if lis.GetUseOriginalDst().GetValue() {
return errors.New("use_original_dst unsupported by grpc-go xDS server; remove this field")
}
return nil
} Try / catch
Catch in the LDS watcher; log and alert. The fix is in the control plane (unset the field) or in deployment topology (use Envoy as the listener).
Prevention
- Do not reuse Envoy transparent-proxy Listener configs for grpc-go xDS.
- Maintain separate listener templates for gRPC vs Envoy server topologies.
- Add a policy rule on the xDS server that strips/rejects use_original_dst for gRPC-targeted listeners.
When it happens
Trigger: An LDS Listener destined for a gRPC server has use_original_dst=true. Typically comes from an Envoy-style config (e.g. transparent proxying / iptables redirect setups) being reused for a gRPC xDS server.
Common situations: Operator copied an Envoy Listener config (designed for transparent proxying with SO_ORIGINAL_DST) into a gRPC-managed LDS resource. Istio Pilot/ambient mesh emitted original-dst listener for a workload that grpc-go is consuming directly.
Related errors
- DownstreamTlsContext in LDS response does not contain a…
- filter missing name field
- multiple filter chains with overlapping matching rules are…
- security configuration on the server-side does not contain…
- security configuration on the client-side does not contain…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/3f46f004e37fe999.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/xdsclient/xdsresource/unmarshal_lds.go:277
}
var i int
for ; i < len(ret)-1; i++ {
if ret[i].Filter.IsTerminal() {
return nil, fmt.Errorf("http filter %q is a terminal filter but it is not last in the filter chain", ret[i].Name)
}
}
if !ret[i].Filter.IsTerminal() {
return nil, fmt.Errorf("http filter %q is not a terminal filter", ret[len(ret)-1].Name)
}
return ret, nil
}
func processServerSideListener(lis *v3listenerpb.Listener) (*ListenerUpdate, error) {
if n := len(lis.ListenerFilters); n != 0 {
return nil, fmt.Errorf("unsupported field 'listener_filters' contains %d entries", n)
}
if lis.GetUseOriginalDst().GetValue() {
return nil, errors.New("unsupported field 'use_original_dst' is present and set to true")
}
addr := lis.GetAddress()
if addr == nil {
return nil, fmt.Errorf("no address field in LDS response: %+v", lis)
}
sockAddr := addr.GetSocketAddress()
if sockAddr == nil {
return nil, fmt.Errorf("no socket_address field in LDS response: %+v", lis)
}
lu := &ListenerUpdate{
TCPListener: &InboundListenerConfig{
Address: sockAddr.GetAddress(),
Port: strconv.Itoa(int(sockAddr.GetPortValue())),
},
}
// Populate the default filter chain.
if dfc := lis.GetDefaultFilterChain(); dfc != nil {View on GitHub (pinned to 0c51461d27)