← All posts

The Router That Must Never Carry a Packet

Three link-state protocols, three different ways to join a network and be useless to it on purpose

Michel Wijnberg

Post two argued that Osprey does not touch your network. This series is the level underneath that claim: how each protocol is actually joined, what each one refuses to hand over, and where being a well-behaved guest turns out to be harder than it sounds.

Start with the hardest requirement, because everything else follows from it.

To read a link-state database you have to be in it. Not adjacent to it, not polling it: a real adjacency, exchanging real LSAs, appearing in everybody else’s database as a router. There is no observer mode in OSPF or IS-IS. You join or you get nothing.

And the moment you join, every router in the area runs shortest-path over a graph that now contains you. Nobody asked whether you wanted the traffic.

So the requirement is:

Be a fully conformant router. Never be selected as a path.

Those two are in tension by design, and all three protocols solve it differently.


OSPFv2: the maximum cost

RFC 2328 requires an OSPFv2 router to originate a Router-LSA for every area it participates in. There is no observer-only adjacency to fall back to: taking part means being described. So the recorder originates one, advertising its single point-to-point link at cost 65535, the maximum a 16-bit metric holds.

Every other router in the area duly runs Dijkstra including that link, and every alternative path wins. The recorder is in the graph, visible, conformant, and never on a shortest path unless the network has genuinely nothing else, in which case you have larger problems than your monitoring.

That is the whole mechanism in OSPFv2, and it is worth noticing what it is not. It is not a flag saying “do not use me”. It is an ordinary metric set so high that the answer falls out of the normal algorithm. Conformant routers need no special handling, because nothing special is happening.

There is a second half that took an outage to learn, and it is the more interesting one. The recorder advertises the point-to-point link and nothing else, in particular not the stub network for the GRE tunnel’s own inner subnet. Advertising that, even at maximum cost, makes every other router install a route for the tunnel prefix. That route can then steer the tunnel’s own outer packets, which kills the adjacency that was carrying the observation. The monitoring breaks the thing it needs in order to monitor. Post seven tells the version of that story where I found out the hard way.

Maximum cost keeps you off the shortest path. It does not keep you out of the routing table, and those are different problems.


OSPFv3: the same, plus a bit that says it outright

OSPFv3 inherits the metric trick, and then adds something OSPFv2 has no equivalent for.

OSPFv3 carries an R-bit in the Router-LSA options (RFC 5340). When the bit is clear, the advertising router is not considered capable of forwarding transit traffic. It still takes part in topology distribution; it is simply not eligible to be a transit node.

That is not a metric. It is a declaration, so the recorder leaves the R-bit clear, and a conformant OSPFv3 router excludes it from transit computation entirely rather than merely ranking it last.

The difference matters more than it looks. A maximum metric is a statement about preference that happens to be decisive. A cleared R-bit is a statement about capability, and it cannot be outvoted by a topology where every other path is worse.

It also closes a loop on the reading side. Osprey’s own SPF honours the same bit through a per-area no-transit flag, so a recorder is excluded from transit by conformant routers and by Osprey’s own path computation, for the same stated reason. Two implementations, one bit, no special case in either.

OSPFv2 simply has nowhere to put that sentence. The options field has no equivalent, so v2 gets the metric and only the metric, and the asymmetry is a property of the protocols rather than of the effort spent on them.


IS-IS: two doors and a bolt

IS-IS has the strongest single mechanism and, in this implementation, the most belt-and-braces around it.

The self-originated LSP sets the overload bit. ISO 10589 is unambiguous about what that means: other routers may reach this system, but must not compute paths through it. It is the same class of statement as the OSPFv3 R-bit, and it predates it by a long way.

Then two more, and they are not the same kind of thing:

FieldValueWhat it stops
OL bitsetbeing computed as a transit node
ATT bits0000being installed as a default gateway
TLV 22 metric0xFFFFFFanything that treats the OL bit as advisory

The ATT row earns its place because it closes a different door. The overload bit is about path computation. The attached bit is about default routing: an L1L2 router sets it to tell level-1 routers that it can reach outside the area, and those routers then point a default route at it. That is not a path being computed through the recorder, it is every packet with no better match being sent to it, which is worse and arrives by a route the first mechanism does not cover.

The metric row is pure defence. A 24-bit metric of 16,777,215 is doing the OSPFv2 trick underneath a mechanism that should already make it unnecessary, on the assumption that somewhere there is an implementation treating the overload bit as advisory.

(The LSP also clears the partition-repair bit, which is genuinely minor and mentioned only so nobody has to wonder why it is there.)

The two GRE recorders as the OSPF database actually holds them, filtered to the recorder tunnel range. Each is a first-class router with its own LSA and a sequence number, and each has exactly one link and no ABR or ASBR flag: present, minimal, and by construction a dead end
The two GRE recorders as the OSPF database actually holds them, filtered to the recorder tunnel range. Each is a first-class router with its own LSA and a sequence number, and each has exactly one link and no ABR or ASBR flag: present, minimal, and by construction a dead end

That is the whole argument in one table. The recorders are in the database, with their own LSAs and their own sequence numbers, exactly as any other router would be. One link each, no flags, nothing to redistribute. The safety model is not what keeps them out; it is what makes being in harmless.


What the title does not claim

Worth separating before going further, because the title overstates if you read it as a statement about the machine.

Nothing above makes the host physically incapable of forwarding. On the lab recorder it happens to be incapable, because net.ipv4.ip_forward is 0 and so is the IPv6 equivalent. But that is a host setting. Somebody can change it in one command, and nothing in the protocol layer would notice.

So the requirement is not “this box cannot forward a packet”. It is:

No routing computation anywhere in the network can select this box as a transit node.

The difference is where the guarantee lives. If the recorder’s harmlessness depended on a sysctl, then every operator deploying one would be one sysctl -w away from putting a monitoring host into the forwarding path, and the network would have no idea. Because it depends on the topology instead, the network’s own shortest-path computation declines to route through the recorder whether the kernel would have forwarded or not.

The sysctl is a good belt. It is just the weakest thing in this post, and the next section is about exactly that gradient.


The one that is a default rather than an invariant

All three recorders also run with a DR election priority of zero, so they can never become designated router for a segment. A recorder that won a DR election would become the thing every other router on that segment depends on for flooding, which is the opposite of the entire premise.

Priority zero is the default, and I want to be precise about what kind of protection that is: it is a configuration field. Nothing in the code refuses a higher value. Set it and the recorder will contest the election like any other router.

That is worth separating from the rest of this post, because everything above it is a constant in code. The overload bit is OLBit: true at the point of origination with no way to configure it off. The OSPFv2 metric is 65535 in the LSA builder. The R-bit is never set. Those are invariants. DR priority is a default, and defaults are the class of safety that survives exactly as long as nobody edits a YAML file.


Making the mistake unavailable

Which brings me to the part I actually care about.

Preventing transit is a preference problem. Preventing injection is a correctness problem, and it is much worse, because the failure mode is not a suboptimal path. It is a route in somebody else’s table that does not correspond to anything.

The self-originated IS-IS LSP therefore carries no IP reachability at all. No TLV 128, 130, 135 or 236. No TLV 134 traffic-engineering router ID, which would put the recorder in the TE database. No TLV 222, no TLV 242.

The way that is enforced is the point. The origination routine takes its input from a small configuration struct holding a level, a system ID, an area address, a peer system ID, a sequence number, an interface address and an optional authenticator. There is no field on it that can hold a prefix. A caller cannot pass a route in, because there is nowhere to pass one.

I want to be exact about the limit of that, because it is tempting to claim more. The LSP being built holds a generic list of TLVs, and the routine appends to it. Someone editing that routine could append a reachability TLV; the type system would not object. What the design removes is not the possibility, it is the accident: no configuration, no caller, and no combination of settings can introduce a route. Injecting one requires editing a function whose documentation block lists the invariants it exists to hold.

That distinction is the whole difference between the two halves of this post, and by now there are four rungs of it in play. A host setting is something an operator can change without ever knowing what it was protecting. A runtime check is something you can forget to write. A default is something you can override without noticing. An input surface with nowhere to put the mistake is something you have to go out of your way to defeat, and the going-out-of-your-way is visible in a diff.

None of these is absolute. They are just very different amounts of accidental, and the engineering worth doing is moving a guarantee down that list rather than adding another check at the top of it.


The point

Three protocols, three mechanisms, one requirement, and the mechanisms are not interchangeable. OSPFv2 can only express “expensive”. OSPFv3 and IS-IS can express “not a transit router”, which is a stronger and more honest thing to say, and they express it in bits that predate any of this by decades.

The interesting engineering was not writing the drain. It was noticing that maximum cost does not keep you out of a routing table, that a default is not an invariant, and that the safest place to put a rule is somewhere the rule cannot be expressed incorrectly.


Next: leaving. A recorder that restarts finds the network still holding its previous self at a higher sequence number, and the message that fixes it is the one it sends while shutting down. The LSA You Cannot Take Back.