rustbgpd/wire

Field report · 2026-08-26

First contact with the default-free zone

A BGP daemon that had only ever seen synthetic routes took two full internet tables from a transit provider. The interesting part wasn't what it accepted.

Everything before this was simulation. Eight thousand tests, containerlab topologies, a synthetic benchmark that tops out at 900,000 prefixes — all of it is a program describing the internet to its own code. The routes are well-formed because we generated them well-formed.

A colleague offered a full table from a transit provider's network. Not a route collector replay, not an MRT dump: a live session, with live churn, carrying whatever the default-free zone happens to contain today.

We pointed a private ASN at it and configured the session to accept everything and announce nothing.

The convergence

The host is a constrained virtual machine — four vCPUs behind a masked QEMU CPU model, 3 GB of RAM, hardware old enough to vote. It was retired from benchmarking duty months ago. This is the IPv4 table loading from cold start:

elapsedprefixesRSSinterned attr sets
2:38224,014216 MB39,289
5:53511,968554 MB77,734
9:58863,401721 MB122,109
11:431,014,4041067 MB—
17:371,081,2981094 MB146,980

Later the same process took an IPv6 table alongside it — roughly 248,000 more prefixes, converged in about six minutes, both sessions established on the first attempt and neither flapping once.

More than 1.3 million routes, two address families, on a machine you would not run a build on.

136 observed dispositions, one reason

Here is the part worth caring about.

Before IPv6 was added, the decoder had recorded 136 UPDATE dispositions. Every observed disposition had the same cause:

"message": "malformed path attribute (RFC 7606 revised error handling)"
"attr_type": 2
"disposition": "treat-as-withdraw"
"error": "RFC 9774 prohibits AS path segment type 1 in attribute type 2"

Segment type 1 is AS_SET — a construct deprecated by RFC 9774. These are real aggregates, announced by real networks, that have been quietly wrong in the routing table for years. You cannot manufacture them in a test fixture. You meet them by showing up.

What the daemon did with them is the whole point. It did not reject the UPDATE and reset the session, which would have cost the entire table. It did not shrug and accept a malformed attribute. It applied the RFC 7606 treat-as-withdraw disposition: it withdrew the routes carried in the offending message and left the session standing.

A parser that accepts everything is trivial and useless. Correct classification of bad input, at the right granularity, with the session preserved, is the difference between a decoder and a router. 136 observed UPDATE dispositions, one cause, nothing unexplained — with the live IPv4 table loaded and the session still standing.

The knob that didn't need to exist

The peer was several hops away, so both sessions were multihop eBGP. On most implementations that requires configuration — ebgp-multihop 10 or its equivalent — because they default the outgoing TTL to 1, so an eBGP session can only reach a directly-connected neighbor until you say otherwise.

Both sessions came up on the first attempt with no multihop configuration at all. Not because the feature was implemented well, but because the TTL = 1 default was never implemented in the first place. There is no knob to set because there is nothing to override.

That turned out to be undocumented. The behavior worked; nobody had written down that it worked, and the only occurrence of the word "multihop" in the documentation was in an unrelated deferral list — which read as though the feature were missing. At the time of this observation, rustbgpd exposed GTSM as a boolean: enabling ttl_security required a received TTL of exactly 255, which made the setting unusable for a peer several hops away. RFC 5082 allows an operator-configured hop range; rustbgpd did not yet expose that distance.

Real deployments find your documentation gaps faster than any audit.

What this is not

Two things in this report would be easy to over-read, so here they are explicitly.

  • These are not benchmark numbers. The host was retired from performance work precisely because its measurement noise doesn't transfer, and it runs a masked CPU model with no host feature passthrough. The timings and memory figures describe what happened on one machine on one afternoon. They are a correctness and memory shakedown, not a receipt, and they should not be quoted as one.
  • 136 is the observed count of UPDATE dispositions from the IPv4 session before IPv6 was added, not routes. A single UPDATE carries many prefixes, and the same prefix can be re-advertised and re-rejected as the table churns. There is no route-level cardinality in that number, and dividing it by a prefix count to produce a tidy percentage would imply a precision the metric doesn't carry. The defensible claim is qualitative: the failures stayed narrow, classified, and non-disruptive.

The published synthetic benchmark reports roughly 157 bytes per route of RIB structure at 900,000 prefixes. This deployment sat closer to a kilobyte per prefix of process RSS. Both are correct — one measures a data structure holding uniform single-path routes, the other measures a whole process holding real AS paths and real communities. The gap between them is exactly the sort of thing a synthetic benchmark cannot tell you, and it is a good argument for keeping a live table around.

What it's for now

The session stays up. It is a standing reference deployment rather than an experiment: somewhere to point at when a question about real-world behavior comes up, and a continuously churning table that exercises the RIB with data nobody designed.

It has already produced one documentation fix, one corrected competitive claim, and a caught configuration hazard — a route-export denial that was written as the absence of a policy rather than an explicit one, which the daemon's own strict configuration check flagged before it ever reached the wire.

Which is its own small argument for running your software against the real thing, on bad hardware, as early as you can stand to.

Peer network, addresses, and host details deliberately omitted.

← Wire