← All posts

When the Network Disagrees With Itself

Six sources of truth, one answer, and what to do when they contradict each other

Michel Wijnberg

Osprey learns about a network from six places:

SourceTells you
OSPF / IS-IS LSDBwhat each protocol believes the topology is
SNMP routing tablewhat a router actually installed
BMP Adj-RIB-Inwhat each BGP peer advertised to this router, and from whom
LLDP / CDPwhat is physically cabled to what
IP-MIBwhich address lives on which port
BGP session tablewho is peered with whom

Every one of them is authoritative about something. None of them is authoritative about everything. And they do not always agree.

The naive answer is to pick a winner globally (“trust LLDP for topology, trust BMP for BGP”) and move on. That fails on the first network where LLDP is stale on one side of a link, or where a bridge sits between two routers that believe they are directly connected.

The answer I ended up with is duller and works: rank the evidence per claim, and record where each claim came from.


Ports: four situations, four different answers

The sharpest version of this problem is annotating an eBGP transition with physical ports. Two routers in different autonomous systems have a session. Which cable does it cross?

Osprey distinguishes four cases and gives a different kind of answer to each:

One link pair, no fabric alternative. Ports stated as fact:

jfk1-gw1 Et1/1 → i-jfk1-gw1 Et1/2

Parallel links. The pair is not determined, so it does not claim one:

3 parallel L2 links: Gi2↔Et1/0, … (session link undetermined)

Both borders on a shared bridge, no direct link. It reports the bridge’s presence with attachment ports, a statement that a path exists through it, never a claim that this session takes it:

shared L2 fabric: ix-sw (wn-edge Et0/3 ↔ ix-sw Et0/1; ix-sw Et0/2 ↔ nn-edge Et0/3) (session path unconfirmed)

Both a direct link and a shared fabric. Ambiguity, named explicitly, with neither chosen.

That fourth case is the one that makes the design worth it. It is genuinely common at an exchange point, it is genuinely undecidable from L2 alone, and it is exactly where a tool that wants to look confident will pick the direct link and be quietly wrong.

The rung that turns a guess into a fact

There is one way out, and it is not a heuristic. If both BGP session addresses resolve to specific ports through the devices’ IP-MIB bindings, then the ports are no longer inferred. The router itself has told you which interface owns that address. That evidence overrides the ambiguity gates:

A cross-AS path whose eBGP transition names both physical ports with ip-bound evidence
A cross-AS path whose eBGP transition names both physical ports with ip-bound evidence

eBGP transition 200 → 300 jfk1-gw1 Et1/1 → i-jfk1-gw1 Et1/2 (bmp-peer+l2; ip-bound)

Read that parenthesis as a chain of custody:

  • bmp-peer: an established eBGP session between them, seen via BMP
  • +l2: the far side matched through the borders’ LLDP/CDP adjacency
  • ip-bound: both session addresses resolved to those exact ports via IP-MIB

Three independent sources agreeing. The claim is as strong as the weakest thing it rests on, and the notation says what that is.

Freshness is part of the evidence

L2 data goes stale, and stale L2 data is worse than none because it looks identical to fresh L2 data. So the supporting rows carry rules:

  • Rows must be fresher than 13 hours to support a port claim at all
  • Age is disclosed above 6 hours
  • A bridge matched only on sysname, rather than a resolved device identity, vetoes a port claim without ever being shown as a fact itself

That last one is subtle and I like it: weak evidence is allowed to destroy confidence without being allowed to create it. A half-identified bridge is a good enough reason to stop claiming a direct port pair, and nowhere near good enough to claim the bridge.


Provenance: the same table, two different pedigrees

In post two I showed the BGP peer table: 378 sessions across 57 peers. What that screenshot does not show is that they did not all arrive the same way:

SourceSessions
BMP: the router pushes its Adj-RIB-In (RFC 7854)24
SNMP: a BGP4-MIB walk at poll time354

Both are true. They are not equally good. BMP carries every path the peer advertised, plus Peer Up and Peer Down as the router sees them, in real time. An SNMP walk gives you the session table as of the last poll and nothing about what happened in between.

Worth being precise about which RIB, because it decides what you may claim. RFC 7854 monitors the Adj-RIB-In: what arrived from each peer, before this router chose anything. The router’s own selected table, the Loc-RIB, is a later and separate extension (RFC 9069) that the exporters here do not send. So this lab can say “peer X offered me this path” with a router’s authority, while “this is the path the router installed” is Osprey’s own computation over those candidates. Those are different claims and they are labelled differently.

So every row records which one it is, in a source column, and that value is load-bearing rather than decorative. When Osprey stitches a cross-AS path and needs an inter-domain hop, an eBGP session counts as evidence with its provenance attached: bmp-peer and snmp-peer are different rungs on the ladder, and the path annotation says which one it stood on.

There is a related rule I am fond of, buried in the path engine. To classify a BGP route as internal or external, the obvious heuristic is to look at the AS_PATH: a single-element path implies iBGP. Osprey does not do that first. It joins the route to the real peer type from the session table, live from bgp_peer or as-of-T from bgp_peer_history, and only falls back to the AS_PATH heuristic when no source peer resolves. The heuristic is right almost always. “Almost always” is precisely the failure mode this whole series is about.


Where nothing agrees: say so

Sometimes the sources do not merely disagree, they run out. Cross-domain path stitching grades every segment on a strict ladder:

RungEvidenceConfidence
aA RIB row at a border covering the destinationresolved
a2A route server whose own BMP RIB covers it via the next ASinferred (bmp-rib-rs)
bAn established eBGP session toward the next ASinferred
cNothingopaque: a segment with a note and no hops drawn

And the grade is per segment, not per path, because a path can be certain in one AS and guesswork in the next. That is why the cross-domain screenshot above carries two different badges on two halves of the same answer.

Rung (a2) exists for one specific reason worth naming: a transparent route server never appears in the AS_PATH. Without reading the route server’s own RIB there is no way to see an IX detour at all. The path would look direct when it is not. A whole class of real internet topology is invisible unless you go looking for the thing that hides it.


The rule

When two sources disagree, there are three things you can do:

  1. Pick one. Fast, looks decisive, silently wrong at unknown times.
  2. Average them. Produces a number that describes nothing that exists.
  3. Rank them, and say which one you used.

Osprey does the third, everywhere, and it is the reason I trust its output more than I trust my own memory of what a network looked like last Tuesday.

The cost is that the interface has to carry more words than a competitor’s: resolved, inferred, opaque, ip-bound, session link undetermined, bmp-peer versus snmp-peer. Every one of those is a place where a prettier product would have shown a clean line.

A clean line is a claim. If you cannot back it, do not draw it.


Next, the post where this philosophy turns on its author: the day I found out my own tool had been drawing fiction: The Day My Topology Lied to Me.