// BLOG

Testing the HubN Peering Workflow

HubN’s peering portal can take two autonomous systems from a request to a set of BGP and tunnel configurations. It supports BIRD2, FRR, VyOS and Pathvector, direct links, WireGuard and IPsec, bilateral peering and several transit relationships.

It also used to be tested mostly one layer at a time.

The frontend tests knew that the right text appeared. The Registry tests knew that an allowed request became a session. Individual router snippets passed their native parsers. None of that proved that two routers using the exact text copied from the portal would establish a session, exchange the intended routes and reject the routes they shouldn’t receive.

I built a separate end-to-end lab to answer that. It found enough problems that “the generated config parses” is no longer a milestone I find particularly comforting.

Starting at the Request Button

The test doesn’t call a template function and declare the output good. It logs into the actual portal through Authentik, impersonates one of the dedicated test handles, creates a peering request, switches to the other side, accepts it and completes the setup.

A HubN peering request created by a test identity. The portal has inferred that the T1 side will provide transit to the T2 side and offers the transport types supported by both profiles.

A request in the test portal. The tier rules, relationship direction and available transports are part of the test, not fixture text pasted around the API.

Once a session exists, Playwright visits it as both parties and captures every platform tab from the rendered DOM. The raw steps are retained, hashed and assembled with only the ordinary daemon boilerplate needed to make a complete configuration.

The configuration tab for an active test session, showing the generated Pathvector steps, the session role and the policy snapshot used to create them.

The generated configuration is copied from the same page a participant would use. The tests do not replace it with a cleaner lab version.

BIRD2, FRR and Pathvector configurations are checked by their native implementations. VyOS gets its own isolated validator container; the test loads the generated set commands as a candidate and then discards it. After that, a dedicated pair of lab routers receives the configuration and has to do something useful with it.

There is a 24-router topology running beside the isolated pair as a control. The test routers don’t borrow hand-written route policy from it, but the collector can observe the resulting sessions in the same way it observes the rest of HubN.

The peering test matrix

The annoying part about supporting four routing platforms is that every relationship has an orientation. A BIRD provider with an FRR customer is not the same generated path as an FRR provider with a BIRD customer. Bilateral sessions remove some ordering, but transit adds it straight back.

The direct matrix ended up with 94 portal-created cases and 3,290 checks. It covers the required tier relationships and every relevant platform pairing. A separate negative matrix tries the relationships which the tier policy should refuse through both the UI and the Registry API.

WireGuard adds 12 positive cases and 656 checks. The generated configuration has to produce a handshake, keep peer routes out of the main table where intended, establish BGP, pass payload traffic, survive endpoint roaming and recover after either router restarts.

The negative cases are at least as useful. They try addresses outside the allocated prefix, malformed and doubled CIDRs, repeated addresses across active sessions and documentation-only endpoints which must parse but never form a tunnel. The one-view PSK exchange is also driven through the browser. The Registry relays encrypted material, while the generated configuration only contains a placeholder for the secret link rather than the key itself.

IPsec has eight cases and 332 checks across native VyOS VTI configuration and route-based XFRM on the other routers. The lab checks which side initiates, whether exactly one SA pair appears, BGP and payload state, and restart recovery.

Then there are the routing services around the session. The RPKI matrix moves routes through Valid, NotFound, Invalid and back to Valid without restarting BGP. The IRR tests change AS-SET membership, run the generated update jobs, check atomic reloads and make WHOIS fail to ensure the old filters remain in place.

By the time the complete run closed, it covered the portal, Registry, Looking Glass, WHOIS, ROA service, route collector and the four router implementations. There were no manual policy edits in the passing run.

What Broke

The early failures were not exotic. Some were exactly the sort of small template mistake which slips through when the person writing the generator also reads its output:

The last one caused the most work. The first complete run passed, and a later production handoff audit showed that the functional large-community actions had not been exercised deeply enough. I reopened the test closure instead of treating the existing report as sacred.

The corrected run checks local-preference requests, no-export, one/two/three-AS prepends, strict RPKI, remote-triggered blackholing, RFC 7999 and the controls which should remain inside the T1 mesh. All eight functional cases are created through the portal and use its rendered policy. The additional 104 assertions now pass across BIRD2, FRR, VyOS and Pathvector.

That was a useful reminder that a large test count can still contain a very precisely shaped hole.

A Session Has Several Kinds of State

One outcome of the lab is that the portal no longer treats an active Registry session as proof that the network is active.

There is workflow state: both parties accepted the relationship and supplied the necessary details. There is configuration state: the portal can render complete input for the chosen platform. There is transport state: the direct link or tunnel actually passes traffic. Then there is BGP and policy state: the session is established, the collector sees it, and the received routes have the expected validation and communities.

The page can show those separately. A session waiting for a router is not failed merely because the collector has not seen it yet. A configured tunnel with no handshake is not active merely because both users filled in the form.

The distinction also made the next bit of work possible. HubN already had peerings which were established before this workflow existed. They appeared in the Looking Glass, but the Registry knew nothing about them.

Previously: Making HubN Registry Updates Durable.

Next: Importing the Network That Already Existed.