java-topology/defects/consul/patch/consul-0001-mesh-gateway-peername-hashset.md

2.8 KiB
Raw Permalink Blame History

UNDF: UNDF-2026-000000367

UNDF: (pending)

consul-0001: makeMeshGatewayPeerFilterChain — peerNames O(S×B×P) slice lookup

CWE-407 — Algorithmic Complexity

Field Value
ID consul-0001
Severity MEDIUM
Ecosystem consul
Package agent/xds
File agent/xds/listeners.go
Lines 22822286
Complexity O(S×B×P) — services × bundles × peers per service
Hot path Mesh gateway listener rebuild on every topology change

Defect

makeMeshGatewayPeerFilterChain receives peerNames []string (the set of peer names for this exported service) and iterates over all peering trust bundles, calling stringslice.Contains(peerNames, bundle.PeerName) for each bundle:

// agent/xds/listeners.go
for _, bundle := range cfgSnap.MeshGateway.PeeringTrustBundles {   // O(B)
    if stringslice.Contains(peerNames, bundle.PeerName) {           // O(P) scan
        peerBundles = append(peerBundles, bundle)
    }
}

stringslice.Contains is a linear scan:

// lib/stringslice/stringslice.go
func Contains(l []string, s string) bool {
    for _, v := range l {
        if v == s {
            return true
        }
    }
    return false
}

The outer caller iterates over all exported services:

for _, svc := range cfgSnap.MeshGatewayValidExportedServices() {   // O(S)
    peerNames := cfgSnap.MeshGateway.ExportedServicesWithPeers[svc]
    filterChain, err := s.makeMeshGatewayPeerFilterChain(cfgSnap, svc, peerNames, chain)

Total: O(S × B × P). In a production Consul cluster with S=50 services, B=20 trust bundles, P=20 peers per service, each listener rebuild costs 50×20×20 = 20,000 string comparisons instead of 50×20 = 1,000 map lookups.

Fix

Convert peerNames to a map[string]bool before the bundle loop:

// Before:
var peerBundles []*pbpeering.PeeringTrustBundle
for _, bundle := range cfgSnap.MeshGateway.PeeringTrustBundles {
    if stringslice.Contains(peerNames, bundle.PeerName) {
        peerBundles = append(peerBundles, bundle)
    }
}

// After:
peerNameSet := make(map[string]struct{}, len(peerNames))
for _, p := range peerNames {
    peerNameSet[p] = struct{}{}
}
var peerBundles []*pbpeering.PeeringTrustBundle
for _, bundle := range cfgSnap.MeshGateway.PeeringTrustBundles {
    if _, ok := peerNameSet[bundle.PeerName]; ok {
        peerBundles = append(peerBundles, bundle)
    }
}

Alternatively, ExportedServicesWithPeers could store map[string]struct{} instead of []string, propagating O(1) lookup to all callers.

Speedup

S (services) B (bundles) P (peers) Before (ops) After (ops) Speedup
10 10 10 1,000 200 5×
50 20 20 20,000 2,000 10×
100 50 50 250,000 10,000 25×