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.