{"record":{"id":"2f4cfcad57e621ee","repo":"grpc/grpc-go","slug":"multiple-filter-chains-with-overlapping-matching-r","errorCode":null,"errorMessage":"multiple filter chains with overlapping matching rules are defined","messagePattern":"multiple filter chains with overlapping matching rules are defined","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/xdsclient/xdsresource/unmarshal_lds.go","lineNumber":598,"sourceCode":"\n\t// Not found, create a new entry.\n\tsrcPrefixes.Entries = append(srcPrefixes.Entries, SourcePrefixEntry{\n\t\tPrefix:  prefix,\n\t\tPortMap: make(map[int]NetworkFilterChainConfig),\n\t})\n\treturn addFilterChainsForSourcePorts(&srcPrefixes.Entries[len(srcPrefixes.Entries)-1], fc)\n}\n\nfunc addFilterChainsForSourcePorts(entry *SourcePrefixEntry, fc *v3listenerpb.FilterChain) error {\n\tports := fc.GetFilterChainMatch().GetSourcePorts()\n\tsrcPorts := make([]int, 0, len(ports))\n\tfor _, port := range ports {\n\t\tsrcPorts = append(srcPorts, int(port))\n\t}\n\n\tif len(srcPorts) == 0 {\n\t\tif !entry.PortMap[0].IsEmpty() {\n\t\t\treturn errors.New(\"multiple filter chains with overlapping matching rules are defined\")\n\t\t}\n\t\tfcc, err := filterChainFromProto(fc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tentry.PortMap[0] = fcc\n\t\treturn nil\n\t}\n\tfor _, port := range srcPorts {\n\t\tif !entry.PortMap[port].IsEmpty() {\n\t\t\treturn errors.New(\"multiple filter chains with overlapping matching rules are defined\")\n\t\t}\n\t\tfcc, err := filterChainFromProto(fc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tentry.PortMap[port] = fcc\n\t}","sourceCodeStart":580,"sourceCodeEnd":616,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/xdsclient/xdsresource/unmarshal_lds.go#L580-L616","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","If using Istio, run `istioctl proxy-config listeners <pod> --port <port> -o json` and diff the filterChainMatch blocks to locate the colliding pair.","Validate the Listener config with an Envoy config-drop tool (envoy --mode validate) before pushing, since gRPC mirrors Envoy's non-overlap semantics."],"exampleFix":"// before (xDS / Istio EnvoyFilter or LDS):\n// filterChain A: { sourcePrefixRange: { addressPrefix: \"10.0.0.0/8\" } }   // no ports\n// filterChain B: { sourcePrefixRange: { addressPrefix: \"10.0.0.0/8\" } }   // no ports -> overlap\n\n// after:\n// filterChain A: { sourcePrefixRange: { addressPrefix: \"10.0.0.0/8\" }, sourcePorts: [443] }\n// filterChain B: { sourcePrefixRange: { addressPrefix: \"10.0.0.0/8\" } }   // catch-all, no longer collides","handlingStrategy":"validation","validationCode":"// Before pushing an LDS resource, assert no two filter chains collide on\n// (source_prefix_range, source_ports). Pseudo-check over the decoded proto:\nfunc checkChainOverlap(chains []*listenerpb.FilterChain) error {\n    type key struct{ prefix, port string }\n    seen := map[key][]int{}\n    for i, fc := range chains {\n        m := fc.GetFilterChainMatch()\n        prefix := m.GetSourcePrefixRanges()[0].GetAddressPrefix() // simplify\n        ports := m.GetSourcePorts()\n        if len(ports) == 0 {\n            k := key{prefix, \"\"}\n            if _, dup := seen[k]; dup {\n                return fmt.Errorf(\"filter chain %d overlaps catch-all slot for prefix %q\", i, prefix)\n            }\n            seen[k] = append(seen[k], i)\n            continue\n        }\n        for _, p := range ports {\n            k := key{prefix, strconv.Itoa(int(p))}\n            if _, dup := seen[k]; dup {\n                return fmt.Errorf(\"filter chain %d overlaps port %d for prefix %q\", i, p, prefix)\n            }\n            seen[k] = append(seen[k], i)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat filter-chain match rules as a unique key set; lint for uniqueness in CI.","Run the control plane's config validator (envoy --mode validate, istioctl analyze) before apply.","Keep one catch-all chain per listener and use distinct source_ports for the rest.","Diff LDS resources in code review to catch duplicated matchers."],"tags":["xds","lds","filter-chain","config","control-plane","envoy"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}