Hello BIRD maintainers, I would like to ask for clarification about BIRD's BGP `merge paths` equivalence rules. BIRD 2.19.0+ appears to use an AS_PATH-length-based equivalence check for BGP routes when kernel `merge paths` is enabled. In the reproduced cases, BIRD exported one ECMP route from two BGP paths even though: 1. The two eBGP paths came from different neighboring ASes, and their AS_PATH contents were different. 2. The two iBGP paths had different AS_PATH contents. The common condition was that both paths had the same AS_PATH length and otherwise equal attributes. This looks similar to AS-path multipath-relax behavior in implementations such as FRR, but in FRR this relaxed behavior is enabled by an explicit `bgp bestpath as-path multipath-relax` option. FRR's default multipath behavior is stricter: eBGP multipath requires the same remote AS, and iBGP multipath requires matching AS_PATH contents. I am reporting this as a route-merging behavior / correctness concern and documentation clarification request, not as a security vulnerability or a strict RFC 4271 violation. ## Version Tested I reproduced this on: ```text BIRD version 2.19.0+branch.master.880200b1f94c ``` The PoC intentionally refuses to run against versions older than 2.19.0. ## Background BIRD's documentation for the kernel protocol says that `merge paths` merges best routes and "equivalent non-best routes" to generate one ECMP route: ```text With path merging enabled, both best routes and equivalent non-best routes are merged during export to generate one ECMP (equal-cost multipath) route for each network. ``` The question is what counts as BGP-equivalent for this purpose. The current documentation does not appear to make clear that equivalence can mean equal AS_PATH length rather than equal AS_PATH contents or same eBGP neighboring AS. For comparison, FRR's default behavior is: - iBGP paths are multipath-equivalent only when AS_PATH contents match. - eBGP paths are multipath-equivalent only when the remote AS is the same. - Different AS_PATHs of equal length are allowed only when the explicit `bgp bestpath as-path multipath-relax` knob is enabled. ## Expected Behavior / Documentation I expected BIRD either to avoid merging such paths by default, or to document that `merge paths` intentionally uses an AS_PATH-length-based relaxed equivalence rule. For example, I did not expect these two eBGP routes to be treated as ECMP-equivalent unless this relaxed rule is intentional and documented: ```text Route A: remote AS 65100, AS_PATH 65100 65200 Route B: remote AS 65300, AS_PATH 65300 65400 ``` Likewise, I did not expect these two iBGP routes to be treated as ECMP-equivalent unless AS_PATH-length-based equivalence is the intended behavior: ```text Route A: AS_PATH 65010 65020 Route B: AS_PATH 65030 65040 ``` If this relaxed behavior is intentional, it may be helpful to document it explicitly or expose a configuration knob similar to FRR's AS-path multipath relax option. ## Actual Behavior With `merge paths yes` enabled, BIRD exported a merged ECMP route in both cases. ### Case 1: eBGP paths from different neighboring ASes The PoC announced the same prefix from two synthetic eBGP peers: ```text Peer A: source: 127.0.0.11 remote AS: 65100 AS_PATH: 65100 65200 next-hop: 10.10.0.11 Peer B: source: 127.0.0.12 remote AS: 65300 AS_PATH: 65300 65400 next-hop: 10.10.0.12 ``` BIRD showed both BGP routes: ```text 203.0.113.0/24 unicast [poc_peer1 ... from 127.0.0.11] ! (100/?) [AS65200i] via 192.0.2.11 on lo onlink BGP.origin: IGP BGP.as_path: 65100 65200 BGP.next_hop: 10.10.0.11 BGP.local_pref: 100 unicast [poc_peer2 ... from 127.0.0.12] (100/?) [AS65400i] via 192.0.2.12 on lo onlink BGP.origin: IGP BGP.as_path: 65300 65400 BGP.next_hop: 10.10.0.12 BGP.local_pref: 100 ``` The merged export view for the kernel protocol contained both nexthops: ```text 203.0.113.0/24 unicast [poc_peer1 ... from 127.0.0.11] ! (100/?) [AS65200i] via 192.0.2.11 on lo onlink weight 1 via 192.0.2.12 on lo onlink weight 1 BGP.origin: IGP BGP.as_path: 65100 65200 BGP.next_hop: 10.10.0.11 BGP.local_pref: 100 ``` ### Case 2: iBGP paths with different AS_PATH contents The PoC also reproduced the behavior with two iBGP peers: ```text Peer A: source: 127.0.0.11 AS_PATH: 65010 65020 next-hop: 10.10.0.11 Peer B: source: 127.0.0.12 AS_PATH: 65030 65040 next-hop: 10.10.0.12 ``` BIRD again exported one merged route: ```text 203.0.113.0/24 unicast [poc_peer1 ... from 127.0.0.11] ! (100/?) [AS65020i] via 192.0.2.11 on lo onlink weight 1 via 192.0.2.12 on lo onlink weight 1 BGP.origin: IGP BGP.as_path: 65010 65020 BGP.next_hop: 10.10.0.11 BGP.local_pref: 100 ``` ## Steps to Reproduce The attached PoC starts a private BIRD instance, creates two synthetic BGP peers, advertises the same IPv4 prefix twice, and enables kernel `merge paths`. Run the eBGP case: ```sh python3 poc_bird_bgp_multipath_aspath.py --keep-workdir ``` Run the iBGP case: ```sh python3 poc_bird_bgp_multipath_aspath.py --case ibgp --keep-workdir ``` Expected reproduced signal: ```text RESULT: BIRD treated the routes as merge-paths equivalent and produced ECMP export view. ``` The script exits with status `2` when the behavior is reproduced. On an unprivileged account, BIRD may log netlink permission errors and the Linux kernel table may remain empty. The important evidence is the `birdc show route export krt_poc 203.0.113.0/24 all` output, because BIRD's CLI uses the same `rt_export_merged()` path to construct the merged export route. ## Source-Level Analysis BIRD's BGP merge predicate checks AS_PATH length but not full AS_PATH equality or same eBGP neighboring AS. In `proto/bgp/attrs.c`, `bgp_rte_mergable()` checks local preference, resolvability, LLGR stale state, AS_PATH length, origin, MED, peer type, and IGP metric: ```c /* RFC 4271 9.1.2.2. a) Use AS path lengths */ if (pri_bgp->cf->compare_path_lengths || sec_bgp->cf->compare_path_lengths) { x = ea_find(pri->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_AS_PATH)); y = ea_find(sec->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_AS_PATH)); p = x ? as_path_getlen(x->u.ptr) : AS_PATH_MAXLEN; s = y ? as_path_getlen(y->u.ptr) : AS_PATH_MAXLEN; if (p != s) return 0; } ``` It prevents mixing iBGP and eBGP paths: ```c /* RFC 4271 9.1.2.2. d) Prefer external peers */ if (pri_bgp->is_interior != sec_bgp->is_interior) return 0; ``` But after checking IGP metric, the remaining criteria are ignored: ```c /* Remaining criteria are ignored */ return 1; ``` I did not find a check equivalent to: - for iBGP: full AS_PATH contents must match, or - for eBGP: neighboring AS must match. ## Comparison with FRR FRR explicitly distinguishes relaxed and non-relaxed multipath behavior. In its default mode: ```c } else if (peer_new->sort == BGP_PEER_IBGP) { if (aspath_cmp(new->attr->aspath, exist->attr->aspath)) { *paths_eq = 1; } } else if (peer_new->as == peer_exist->as) { *paths_eq = 1; } ``` FRR only allows different AS_PATH contents of the same length when the explicit AS-path multipath relax flag is configured: ```c } else if (CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_MULTIPATH_RELAX)) { *paths_eq = 1; } ``` This makes the relaxed behavior visible to operators as an intentional configuration choice. ## Impact When `merge paths` is enabled, BIRD may export ECMP routes whose contributing BGP paths would not be considered multipath-equivalent by implementations such as FRR unless an explicit AS-path multipath-relax option is enabled. Potential consequences include: 1. Traffic sharing across different upstream ASes when the operator expected same-AS eBGP multipath only. 2. Traffic sharing across iBGP paths that have different AS_PATH contents. 3. Policy surprises when one AS_PATH would normally be considered non-equivalent to another even though their lengths match. 4. Interoperability differences compared with implementations such as FRR that require explicit AS-path multipath relaxation. This impact is configuration-dependent. It requires `merge paths` or equivalent multipath export behavior to be enabled. It is not a session-reset issue and is not being reported as a security vulnerability. ## Suggested Fix or Clarification If the current behavior is unintentional, BIRD could make `bgp_rte_mergable()` stricter by default: 1. For iBGP routes, require full AS_PATH equality before merging. 2. For eBGP routes, require the same neighboring AS before merging. 3. Optionally add a configuration knob to allow the current relaxed behavior explicitly, similar to FRR's `bgp bestpath as-path multipath-relax`. If the current behavior is intentional, documenting that `merge paths` may merge BGP routes with different AS_PATH contents or different eBGP neighboring ASes when AS_PATH lengths are equal would help operators understand the policy impact. In other words, this may simply be intentional relaxed multipath behavior. I am reporting it because the documentation says "equivalent non-best routes" but does not appear to define that equivalence as AS_PATH-length-based rather than AS_PATH-content-based or same-neighboring-AS-based. Best regards.