Specification §5

Addressing and routing.

Observe, plan, commit — and the three MUST stay separate. The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119.

FrogNet Specification — Draft 0.9, revision 2026-08-29. Cite a conformance claim against this revision, not against a section number alone.

1. Model

Croakus: §6, §14, §17

Each node serves one /24 and is identified by its .1 address (§3.2). Routing is the act of bringing the kernel table into agreement with evidence the node gathered itself. No route is accepted on assertion: a node installs only what it measured, or what the broker says must exist (§5.6).

The pipeline is three stages and MUST be kept separate: observe (measure paths), plan (decide the table, purely), commit (apply it to the kernel). The planner is a pure function of its inputs and MUST NOT touch the kernel; the committer MUST NOT decide anything.

2. The unit of evidence

An observation is a measured path, not a claim. It asserts: I measured a path to this destination's probe alias by forcing traffic through a specific device and next hop, the full-stack echo round trip took this long, and the echo identified the peer as this address.

FieldMeaning
destThe /24 the peer advertises, derived from its identity
host_pathThe peer's canonical .1 identity as returned by its own echo. This becomes the via on the committed route.
devThe interface the probe went out on
rtt_msMeasured full-stack echo round trip

The identity on the route comes from the peer's own echo, not from what this node assumed about it. A node that guesses a peer's identity has left the model.

3. Metrics

Metrics are fixed roles, not a ranking dial. All FrogNet /24s are committed at one value, so metric no longer serves as a tiebreaker and an operator can find FrogNet's entire footprint by grepping for it.

ALIAS_METRIC     5    transient aliases
SWEEP_METRIC     5    defensive sweep
PROBE_METRIC     6    transient /32 probes — installed and deleted
                      every walk, regardless of convergence
WINNER_METRIC   22    the committed /24: one per destination
FALLBACK_BASE  100    a surviving alternative path

An implementation MUST NOT vary the winner metric to express preference. Preference is expressed by which observation wins the plan, not by the number in the table.

4. Planning

The planner produces exactly the kernel operations needed to bring the table into agreement with the evidence, and MUST do nothing the evidence did not authorise.

4.1 Which observation wins

Lowest measured RTT wins among observations that compete. But an indirect path MUST NOT compete with a direct one [TRACEROUTE_V1]: where trace data shows at least one observation reaching the destination with no additional transit hops, only those zero-additional observations are eligible, and an indirect-path observation cannot win regardless of how fast it measured.

Ties MUST be broken deterministically by device then next hop — no class weighting and no hardware preference — so two nodes with the same evidence plan the same table.

4.2 One route per destination, and losers are deleted

There is one committed /24 per destination. Every non-winning route for a destination that has a winner becomes a removal, one per distinct device, next hop and metric. Losers are deleted, not demoted.

Where no observation succeeded for a destination, the planner MUST NOT remove anything. Not knowing a path is not evidence that the existing one is wrong. This is the routing form of the rule in §13.1: absence of measurement is not a measurement.

4.3 Fallbacks [FALLBACK_METRIC_V1]

Where a destination has several observed paths — a direct tunnel and a LAN-recursive path through a peer's transit advertisement, say — the winner is committed at the winner metric and every other observation is committed at the fallback metric rather than discarded. Longest-prefix match with the lower metric carries steady-state traffic over the winner; if the winner's interface goes down, the kernel falls through to the alternative immediately, without waiting for the next merge to re-plan.

Two consequences an implementation MUST observe. Stickiness comparisons MUST consider only routes at the winner metric [STICKY_WINNER_ONLY_V1] — a fallback matching an observation must not prevent the winner from switching when a better path appears. And stale removal MUST distinguish a route that matches a current observation, which is a fallback candidate and is kept, from one that matches no observation, which is a stale path and is removed.

4.4 Winner stickiness

Replacing a winner is a route replacement that yanks the next hop on the /24 and interrupts flows in progress, so a challenger MUST be a clear win rather than a marginal one: it must beat the installed winner's measured RTT by at least half (§13.2). Anything less is jitter between near-equal paths, and honouring it rewrites the /24 every pass and latches the re-run flag indefinitely.

5. Committing

Any add, replace or delete of a committed route sets the mutation flag that drives announcement [SYNC_ON_ROUTE_MUTATION_V1]. The flag keys on the routing table, not on the hosts file: the table is what neighbours need to hear about, and it can churn — a winner installed, a verification backed out, a sweep — while the host table sits unchanged.

Transient probe routes MUST be excluded from the flag. They install and delete every walk regardless of convergence, so flagging them fires announcement every cycle as noise (§4a.4.3).

The committer MUST name the exact destinations that changed, so that a mutation can be traced to the /24 that caused it rather than inferred from a table diff.

6. Tunnel existence is not tunnel usage [BROKER_TEARDOWN_V1]

Two concerns MUST be kept separate:

ConcernDriven by
Whether a tunnel existsThe broker's channel list
Which route serves a destinationObservations

A channel the broker says this node should have MUST stay up regardless of whether its remote /24 happens to have a faster non-tunnel observation this merge. Only channels that are up in the kernel and absent from the broker's list are orphans, and only those are torn down. Tearing down a tunnel because an observation beat it this cycle destroys the standby path the next cycle will need.

7. Route sourcing

Every discovered route MUST be sourced from the node's own identity address (§3.2), and MUST NOT be sourced from a transit /30 or a borrowed uplink lease. Neither is propagated across the mesh, so a host beyond a tunnel cannot route a reply back, and the return path fails silently for every child behind the peer — presenting as an application fault several hops from its cause.

The reserved transit range is not walked as a discovery target: it carries the point-to-point carrier blocks of §7 and holds no node identities.

8. References

Specification §3.2, §4 (Discovery), §4a (Triggering), §6, §7 (Broker and tunnels), §13. Source: discovery/routes.py, frognet_route/planner.py, frognet_route/observation.py, frognet_route/committer.py. Build manual: Croakus §6, §14, §17.