grpc/grpc-go · error

multiple filter chains with overlapping matching rules are d

Error message

multiple filter chains with overlapping matching rules are defined

What it means

Raised by addFilterChainsForSourcePorts in the xDS LDS unmarshaller when two server-side filter chains declare the same source-prefix AND no source-port constraint (the catch-all port slot, PortMap[0], is already occupied). gRPC's xDS listener model forbids ambiguous chain matching, so the second chain is rejected rather than silently shadowing the first. The error propagates up through LDS resource parsing and causes the whole Listener resource to be NACKed.

Source

Thrown at internal/xds/xdsclient/xdsresource/unmarshal_lds.go:598

	// Not found, create a new entry.
	srcPrefixes.Entries = append(srcPrefixes.Entries, SourcePrefixEntry{
		Prefix:  prefix,
		PortMap: make(map[int]NetworkFilterChainConfig),
	})
	return addFilterChainsForSourcePorts(&srcPrefixes.Entries[len(srcPrefixes.Entries)-1], fc)
}

func addFilterChainsForSourcePorts(entry *SourcePrefixEntry, fc *v3listenerpb.FilterChain) error {
	ports := fc.GetFilterChainMatch().GetSourcePorts()
	srcPorts := make([]int, 0, len(ports))
	for _, port := range ports {
		srcPorts = append(srcPorts, int(port))
	}

	if len(srcPorts) == 0 {
		if !entry.PortMap[0].IsEmpty() {
			return errors.New("multiple filter chains with overlapping matching rules are defined")
		}
		fcc, err := filterChainFromProto(fc)
		if err != nil {
			return err
		}
		entry.PortMap[0] = fcc
		return nil
	}
	for _, port := range srcPorts {
		if !entry.PortMap[port].IsEmpty() {
			return errors.New("multiple filter chains with overlapping matching rules are defined")
		}
		fcc, err := filterChainFromProto(fc)
		if err != nil {
			return err
		}
		entry.PortMap[port] = fcc
	}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Inspect the LDS resource and find the two filter chains whose FilterChainMatch.source_prefix_range resolve to the same masked prefix and both omit source_ports; delete or disambiguate one.
  2. Add a distinct source_ports list (or destination_port / prefix_range / application_protocols matcher) to one chain so the match rules no longer fully overlap.
  3. If using Istio, run `istioctl proxy-config listeners <pod> --port <port> -o json` and diff the filterChainMatch blocks to locate the colliding pair.
  4. Validate the Listener config with an Envoy config-drop tool (envoy --mode validate) before pushing, since gRPC mirrors Envoy's non-overlap semantics.

Example fix

// before (xDS / Istio EnvoyFilter or LDS):
// filterChain A: { sourcePrefixRange: { addressPrefix: "10.0.0.0/8" } }   // no ports
// filterChain B: { sourcePrefixRange: { addressPrefix: "10.0.0.0/8" } }   // no ports -> overlap

// after:
// filterChain A: { sourcePrefixRange: { addressPrefix: "10.0.0.0/8" }, sourcePorts: [443] }
// filterChain B: { sourcePrefixRange: { addressPrefix: "10.0.0.0/8" } }   // catch-all, no longer collides
Defensive patterns

Strategy: validation

Validate before calling

// Before pushing an LDS resource, assert no two filter chains collide on
// (source_prefix_range, source_ports). Pseudo-check over the decoded proto:
func checkChainOverlap(chains []*listenerpb.FilterChain) error {
    type key struct{ prefix, port string }
    seen := map[key][]int{}
    for i, fc := range chains {
        m := fc.GetFilterChainMatch()
        prefix := m.GetSourcePrefixRanges()[0].GetAddressPrefix() // simplify
        ports := m.GetSourcePorts()
        if len(ports) == 0 {
            k := key{prefix, ""}
            if _, dup := seen[k]; dup {
                return fmt.Errorf("filter chain %d overlaps catch-all slot for prefix %q", i, prefix)
            }
            seen[k] = append(seen[k], i)
            continue
        }
        for _, p := range ports {
            k := key{prefix, strconv.Itoa(int(p))}
            if _, dup := seen[k]; dup {
                return fmt.Errorf("filter chain %d overlaps port %d for prefix %q", i, p, prefix)
            }
            seen[k] = append(seen[k], i)
        }
    }
    return nil
}

Prevention

When it happens

Trigger: An xDS control plane (Istio, Envoy xDS server, etc.) sends an LDS response containing >=2 filter chains whose FilterChainMatch specifies the same source_prefix_range (or an overlapping CIDR collapsing to the same netip.Prefix) while neither chain specifies source_ports. The duplicate is detected when getOrCreateSourcePrefixEntry routes the second chain into the existing SourcePrefixEntry and PortMap[0] is non-empty.

Common situations: Migrating from port-specific routing to catch-all routing and leaving a stale chain behind; duplicating a filter chain block in YAML and forgetting to differentiate its match; Istio/Envoy filter chain templates that fan out to multiple chains but share the same source prefix; control-plane version drift where an older config generator emits chains the newer gRPC client considers overlapping.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/2f4cfcad57e621ee. Report an issue: GitHub.