← All posts

Push, Don't Poll

The one source that comes to you, and the two things that change about what you are allowed to believe

Michel Wijnberg

Every source in this series so far is something Osprey goes and asks. A GRE recorder joins an area and asks for a database. An SNMP walk asks a device for a table. Ask, wait, receive, repeat.

BMP is the only one that arrives on its own. The router opens a TCP session to the monitoring station and starts talking, and it keeps talking for as long as it has something to say (RFC 7854). Nobody polls anything.

That sounds strictly better, and in one dimension it is. In two others it changes what you are entitled to conclude, and both of those are more interesting than the speed.


What arriving on its own buys

Polling has a sampling interval, and a sampling interval has all the properties of a sampling interval. Anything that happens and reverses inside one cycle did not happen, as far as your data is concerned. A route that flaps twice between two walks looks like a route that never moved.

A pushed stream has no cycle. A withdrawal arrives because a withdrawal happened, and the timestamp on it is the router’s, not the poller’s.

For BGP that matters more than it would for most things, because BGP changes far more often than topology does. A five-minute walk of a full table is both enormously expensive and, for anything short-lived, blind. The push model is the only one where the question “when did this prefix change” has an answer worth trusting.


Cost one: silence stops meaning anything

Now the part polling gets for free.

A poller that asks and receives nothing knows precisely what happened: it asked and received nothing. The absence is itself an observation, with a timestamp, and it can be retried.

A listener that hears nothing has no such luxury. Silence is the same on the wire whether the network is quiet or the session has gone away, and the second case is indistinguishable from the first until something else tells you otherwise.

The specific danger is worth naming, because it inverts the metric you would reach for. If you treat a dropped session as an absence of change, then the peer you have stopped hearing from becomes the most stable peer in your report. The quietest line on the screen is the one you have lost. Every ranking that sorts by churn puts your blind spot at the top, labelled as your healthiest relationship.

Post twelve covers what Osprey does about that, which is to record a session drop as a monitoring gap rather than as a mass withdrawal, and I will not retell it here. The point for this series is narrower: push moves the burden of interpreting silence from the protocol to you. The polling model handed you that answer and you did not have to think about it.


Cost two: you are reading someone else’s conversation

The second cost is subtler and produces real parsing bugs.

BMP does not give you a BGP session. It gives you a copy of the messages on somebody else’s session, wrapped and forwarded. You were not present for the negotiation, and BGP messages are not self-describing: how you decode an UPDATE depends on what the two peers agreed in their OPEN.

The obvious approach is to reconstruct the context. Peer Up carries both OPEN messages, so you can read what was negotiated and remember it for the session. Osprey does that.

And then the per-peer header on each subsequent message carries an A-flag, indicating whether the encapsulated PDU uses the legacy two-byte AS_PATH format (RFC 7854 §4.2). It is per message, so the parser decodes that message according to it rather than assuming the format from the Peer Up context.

Those two can disagree. When they do, Osprey decodes by the flag and logs the discrepancy rather than resolving it quietly, and the reasoning is worth stating because it generalises: the capability records what two routers agreed to do, and the flag describes what this exporter did to this message. Where evidence about an agreement and evidence about the bytes in hand conflict, the bytes in hand are the ones being parsed.

There is a matching subtlety about Add-Path, and it is the same shape. Whether the encapsulated NLRI carries path identifiers depends on a negotiation that is directional, and which direction applies depends on which RIB is being monitored: the default Adj-RIB-In, or the Adj-RIB-Out that RFC 8671 added and that is signalled by another flag in the same per-peer header. So the parser reads that flag first and picks the negotiation to believe from it. Same field, two readings, selected by a bit you would not consult if you were a party to the session.

None of this arises when you poll a device. You are asking a device about itself, in a protocol you are a first-class party to. BMP hands you the router’s own view and takes away the context it was negotiated in, and every field that reconstructs that context is load-bearing.

The BGP peers table with a Source column carrying BMP and SNMP badges in the same list. The BMP rows were last seen a day ago and carry negotiated timers and a router ID; the SNMP rows say 'just now' and leave both blank
The BGP peers table with a Source column carrying BMP and SNMP badges in the same list. The BMP rows were last seen a day ago and carry negotiated timers and a router ID; the SNMP rows say 'just now' and leave both blank

Both acquisition methods land in one table, and the difference is visible in two columns. The BMP rows were last seen a day ago and that is not a fault: nothing about those sessions changed, and a push source is silent when nothing changes. The SNMP rows say just now because something went and asked. Same claim, two very different reasons to believe it, and only one of them where silence is information.


The report that never comes unless you ask

One last thing, and it is the smallest and the most annoying.

BMP defines Statistics Reports: periodic per-peer counters, including the rejection and treat-as-withdraw counts that tell you what a peer’s policy is discarding. Excellent data. Exactly what you want for a scorecard.

They do not arrive.

Not because of a parser bug, and not because the exporter is broken. IOS simply does not send Statistics Reports unless it has been told a reporting period, and there is no default. Without stats-reporting-period under the BMP server configuration, that message type never appears on the wire, and every per-peer statistics field stays empty forever with no error anywhere.

The lab ran that way for a while, and the honest reading of the empty columns was not “this peer rejects nothing”. It was “nobody has ever told this router to say”. The fix was one configuration line on six routers, written to startup-config so a reload does not quietly undo the measurement.

That is a small operational detail with a general shape: a push source can only tell you what it was configured to send. With polling you control when the question is asked. With push you observe only what the exporter was set up to emit, and an unsent message is indistinguishable from one that was never generated.


The point

Push is better and it is not free.

You gain the only timeline worth trusting for something that changes as fast as BGP, and you give up the two things polling never made you think about: that silence used to be an observation, and that you used to be a party to the conversation you were reading.

Both of those show up in this codebase as small, specific pieces of care. A session drop recorded as a gap. An AS_PATH width taken from the flag rather than the capability. A statistics column that stays empty and knows why. None of them is clever. They are all the same admission, which is that a message arriving on its own tells you less about its own absence than a question you asked yourself.


That closes this series. The through-line, if there is one, is that every answer in this product is worth exactly as much as the account it can give of how it was obtained.