Commit Graph
30 Commits
Author SHA1 Message Date
Rayhaan JaufeerallyandClaude Fable 5.1 b1b72fed39 lab: multi-line answer boxes, one sheet per student
The fill-in fields put the label on its own line with dotted lines to write
on. handout.tex takes \studentnum and \studenthex; build-class.py compiles
one copy per student with the prefix filled in, checks each is two pages
and joins them for double-sided printing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-01 20:40:55 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 8c6e8a80e1 chore: ignore .DS_Store and generated lecture output
slides/dist/ (the single-file bundle) and docs/lab/handout/class.pdf (one
sheet per student) are build products.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-01 20:40:55 +02:00
Rayhaan Jaufeerally 597941a70a handout finalized 2026-09-24 10:33:45 +02:00
Rayhaan Jaufeerally b607058650 Ferrix web UI working for demo and slides mostly done 2026-09-23 15:38:06 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 f3bb8cc89e lab: exercise sheet cut to two pages, three simple steps
One double-sided A4 sheet with 1in margins, still in the LNCS look.
1. Who announces the address you are using: find your current address and
   its prefix and origin AS, then whois it and your lab prefix.
2. Announce your prefix, read the bytes, find it from outside, ping it.
3. Announce with communities: only the SwissIX route servers, then only
   SWITCH, with reachability checkboxes, one discussion question, clean up.
Sections 1 and 2 fill page one; section 3 has page two. Dropped the ETH
lookup, the long registry comparison, NO_EXPORT, the byte-decoding task,
the reference appendix and most of the bibliography.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 22:07:23 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 83ae367b1c lab: printable exercise sheet for the Hands-on BGP lecture
docs/lab/handout/handout.tex, typeset with Springer's LNCS class (title
block, abstract and keywords, figure, booktabs tables, RFC references), six
A4 pages so it prints double-sided on three sheets. The built PDF is
committed alongside for printing.

Three sections students work through with the slides:
1. Who holds which addresses: whois their own /48 and ethz.ch's prefix in
   the RIPE database, then compare with what is actually routed.
2. Announcing the prefix on the lab page, reading the UPDATE bytes, finding
   it from outside, and reaching it.
3. Steering with communities until the prefix is reachable only from the
   university network (AS559 via the SwissIX route servers), with
   reachability checkboxes for laptop and phone at each step.
Plus a one-page reference appendix: IPv6 notation and hex, the UPDATE
layout, path attributes, glossary.

The lab host, the lab address space and the prefix pattern are macros at
the top of the file. Expected answers were checked against the RIPE
database and RIPEstat (ETH: 2001:67c:10ec::/48, ASSIGNED PI, origin AS559;
lab: 2a0d:d741::/40, AGGREGATED-BY-LIR, route6 origin AS210036).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 22:02:32 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 e35507d082 lab: track the applied router config and the RIPE objects
- docs/lab/prod/pr01/bird.conf is now the config generated for per-session
  export control (every eBGP session passes its neighbour AS and location
  to the shared export function; customer sessions go through the same
  gate), with the ferrix stanza in its same-host form (::1, multihop).
- docs/lab/ripe/lab-objects.rpsl: the inet6num and the 128 route6 objects
  for the student /48s. Public registry data, no credentials.
- Ignore Python bytecode caches.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 21:21:25 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 c60bd8baf5 docs: use built-in Apache error documents so the login covers every path
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 21:13:56 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 36b73844af docs: keep Apache's /error/ pages out of the proxy so 401 prompts for a password
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 21:09:37 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 9d644a212a tools: concurrent load test for the announce API
tools/loadtest.py releases N simulated students through a barrier so they
announce at the same instant, then verifies behaviour rather than only
status codes: the table holds every route under the right owner, each
route became an UPDATE per established session, the event stream lost no
event, concurrent takeover attempts are all refused, withdrawal leaves
nothing behind, and the daemon's own counters show the matching number of
UPDATEs sent for announcements and withdrawals. Standard library only;
supports basic auth and repeated rounds.

Safe by default against a production router: every route carries
210036:0:0 (announce nowhere), leftovers from earlier runs are removed,
prefixes held by real owners are skipped, and everything is withdrawn even
if the run fails.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 19:49:19 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 1ea2a12bd6 docs: tested Apache reverse proxy with basic auth for the status page
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 19:32:50 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 42d79354e3 docs: peer with BIRD over ::1 on the router; explain the multihop requirement
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 19:29:31 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 fd496d26bf feature: run on the router itself: protoc-free build, per-peer source address
Preparing to run ferrix next to BIRD on the production router.

- build: the config proto is now parsed in pure Rust, so building needs no
  `protoc` binary. It previously worked only where one happened to be
  installed; the Dockerfile in this repo could not have built.
- peers gain `local_address`, the source IP for connections we open (and
  therefore the next hop we advertise). When both speakers share a host
  the OS would otherwise pick an arbitrary local address and the peer,
  which identifies us by address, would refuse the session. Address-family
  mismatches are rejected at config load; an address the host lacks shows
  up as the peer's backoff reason.
- docs/lab/prod/on-router: static musl cross-build instructions, config,
  hardened systemd unit (unprivileged, SIGTERM gives BIRD a Cease),
  loopback addresses for networkd, the BIRD stanza for a same-host
  multihop session, and reverse-proxy examples with TLS and a password,
  since the API has no authentication.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 18:15:09 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 af76ecb345 fix: leave sessions politely on shutdown; answer ROUTE-REFRESH
Against a production BIRD router, restarting ferrix was followed by
several refused connection attempts before the session came back.

- The daemon had no signal handling: Ctrl-C killed the process and the
  peer only ever saw a dead TCP connection. On SIGINT or SIGTERM it now
  sends Cease / Administrative Shutdown (RFC 4486) to every established
  peer, waits up to two seconds for the sessions to close, and stops the
  dialers from reconnecting meanwhile. A NOTIFICATION queued for a session
  ends that session once sent.
- ROUTE-REFRESH was parsed and ignored. A peer sends it when its import
  policy changes (e.g. after `birdc configure`), expecting our routes
  again so the new policy can be applied. The session now reports the
  request and the advertiser re-sends every local route to that peer. The
  request also appears in the event stream and the status page log.

Tests: a refresh request in real wire format re-sends all routes and keeps
the session up; shutdown delivers the Cease to every peer and closes the
sessions. Verified with the real binary: SIGINT, peer logs the Cease.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 17:51:04 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 57099da73d feature: export control by neighbour AS and by location
The production router has the same neighbour AS at several exchanges, so
per-AS control alone cannot express "only at SwissIX". Every session now
has two coordinates, neighbour AS and location, and the scheme gains two
functions:
  210036:0:<as> / 210036:2:<location>   never announce      (wins)
  210036:1:<as> / 210036:3:<location>   announce
  210036:0:0                            announce nowhere
  nothing                               announce everywhere
docs/lab/prod/bird-ferrix.conf documents the functions, the per-session
call (export where peers_out(<as>, <location>)) and the gate that sessions
exporting everything need as well.

Status page: location presets, and AS210036 communities are decoded from
their structure so any value reads well in the bytes hover.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 17:34:57 +02:00
Rayhaan Jaufeerally 98f25a5847 Add existing BIRD config 2026-09-20 17:29:01 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 77c406e7d1 docs: wire lab export control into a shared BIRD filter via a function and wrapper filters
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 17:21:36 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 b28f59f30d feature: AS210036 export-control large communities for lab routes
BIRD side (docs/lab/prod/bird-ferrix.conf): the ferrix import filter now
admits 210036:0:* and 210036:1:*, and lab_export_allowed(peer_as) decides
per eBGP session whether a lab route is announced, with the same shape and
precedence as the SwissIX route-server communities:
  1. 210036:0:<peer-as>  never   2. 210036:1:<peer-as>  announce
  3. 210036:0:0          nobody  4. default             everyone
The per-session export block applies only to routes learned from ferrix,
strips the private ASN and removes the action communities before sending.

Status page: the large-community picker is grouped into "AS210036" and
"SwissIX route servers" presets, including "announce to the SwissIX route
servers" and "send to SWITCH (AS559)"; the bytes hover names both families.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 17:02:42 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 eb77090d01 feature: config zippy on statusz; drop prepend options from the UI
- The status page has a collapsible "Configuration" section showing the
  config file as read at startup, with its path. Served by the new
  GET /api/config; RouterInfo::with_config carries the text.
- Removed the AS-path prepends field and the SwissIX prepend presets
  (42476:101..103) from the form to keep the exercise focused. The API
  still accepts `prepend`.
- Docs follow: the handout loses its prepending exercise, the lab README
  its prepend row, and the BIRD config notes that the 42476 namespace is
  still admitted for hand-typed values.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 22:46:07 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 3f04152f19 docs: simplify prod ferrix config, name the peer pr01.rue.rayhaan.net
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 22:44:46 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 d23a578d72 docs: listen on 9179 in the prod ferrix config (179 needs root on macOS)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 16:08:22 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 22384d0b07 docs: use the real WireGuard tunnel addresses in the prod peering configs
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 16:05:44 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 e41776f253 docs: allow SwissIX large action communities through the ferrix import filter
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 16:00:42 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 160e28690a fix: capability list parsing; advertise IPv6 unicast; BIRD peering config
Preparing to peer with a production BIRD router turned up two problems.

- packet: most Capability::from_wire arms returned the per-capability
  buffer (always empty by then) instead of the bytes following the
  capability. The list parser then saw an empty remainder and silently
  dropped every later capability. Any OPEN listing extended message, role,
  graceful restart, add-path, enhanced route refresh, LLGR, extended next
  hop or an unknown capability before the 4-octet ASN capability lost the
  ASN, so the peer appeared as AS_TRANS (23456) and a configured
  remote_asn was refused with Bad Peer AS. BIRD and FRR both do this.
  Single-capability round-trip tests could not catch it; new tests parse
  every capability with another following it, and a full BIRD capability
  list.
- server: advertise MultiProtocol IPv6 unicast alongside IPv4. Without it
  a peer will not accept the IPv6 routes we originate via MP_REACH_NLRI.
- Interop test: byte-for-byte BIRD 2 OPEN is accepted with the right ASN,
  and an IPv6 End-of-RIB neither drops the session nor creates a route.
- docs/lab/prod: BIRD config for an announce-only ferrix neighbour
  (strict /48 import filter, community scrubbing, unreachable next hop,
  import limit, export none, passive) and the matching ferrix config.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 15:57:23 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 87f96f9389 feature: RFC 4271 §6.8 connection collision detection
When both ends dial each other, exactly one TCP connection must survive
and both ends must pick the same one. Previously ferrix relied on the
other router to sort this out and could briefly hold two sessions.

- collision: a shared map keyed by the peer's BGP Identifier holds the one
  connection currently preferred for that peer plus a kill signal. Each
  connection calls admit() on receiving the peer's OPEN. The connection
  initiated by the higher BGP Identifier wins; the loser gets Cease /
  Connection Collision Resolution, whether it is the new connection or an
  existing one in OpenConfirm running in another task.
- A collision with an Established session closes the new connection (RFC
  default). The optional CollisionDetectEstablishedState is available as
  collision_detect_established_state: a restarted peer's new connection
  then replaces the stale session at once instead of being refused until
  the hold timer expires. Enabled in the lab config.
- Two connections opened by the same side: the newer wins.
- OPEN validation rejects a peer using our own BGP Identifier or 0.0.0.0
  with Bad BGP Identifier; collision resolution needs distinct identifiers.
- OpenConfirm no longer times out instantly when the negotiated hold time
  is zero.
- Sessions record which side opened them; shown as "opened by" on the
  status page and as `direction` in /api/peers. New counter
  bgp_connection_collisions_total{closed}.
- Tests: unit tests for the rule (including that both ends agree), and
  integration tests for simultaneous open in both identifier orders and
  both arrival orders, Established default, restart replacement with route
  flush, distinct identifiers, slot release, and Bad BGP Identifier.
  Existing fan-out test now gives its two peers distinct identifiers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 15:05:13 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 a137160b48 feature: lab topology, student handout; drop peer routes on session end
Phase 5 of the teaching demo plus two fixes found while exercising the
dialer on the status page.

- Adj-RIB-In: routes learned from a peer are removed when its session ends
  (RFC 4271 §8.2.2), emitting a Withdrawn event per prefix before the
  session Down event. Previously stale routes from dead sessions stayed in
  the received table.
- statusz: poll the peers table every 5 s so dial state (Connecting /
  Backoff and its retry countdown) stays current; those changes carry no
  event.
- docs/lab: docker-compose topology with ferrix (AS65000) and two FRR
  routers. r2 accepts nothing directly from ferrix so student prefixes
  only reach it via r1, then come back with AS path 65002 65001 65000. r1
  applies community-driven policy: NO_EXPORT stops forwarding, 65001:100
  prepends, BLACKHOLE drops. Dockerfile for the daemon.
- docs/lab/HANDOUT.md: student exercises walking through announcement,
  reading the bytes, prepending, NO_EXPORT, action communities,
  blackholing and withdrawal.

The container files have not been executed (no runtime available here);
docs/lab/README.md lists what is most likely to need adjusting.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 23:26:10 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 e2de44ba78 feature: active dialer for configured peers
Phase 4 of the teaching demo: the daemon now brings up its own sessions.

- New dialer task per configured peer: connect with a timeout, run the
  same FSM as the passive side, back off exponentially (from
  connect_retry_seconds, capped at two minutes) and retry forever. Stays
  idle while a session with the same remote IP and ASN already exists.
- The peer's OPEN must carry the configured ASN, otherwise the session is
  refused with a Bad Peer AS notification. Inbound connections from a
  configured peer's address get the same check plus the configured name
  and hold time.
- SessionRegistry tracks configured peers and their dial state
  (Idle / Connecting / Backoff with last error and retry time).
- /api/peers merges configured peers with live sessions so the page shows
  peers that are down, why, and when the next attempt is; unconfigured
  inbound sessions are listed after them.
- fake_peer gains --listen so it can stand in for a router the daemon
  dials.
- Integration tests: dial and establish, reconnect after the peer closes,
  wrong ASN refused, inbound from a configured address checked and named.

Not implemented: RFC 4271 §6.8 collision detection; a real router on the
other end resolves the rare race by closing one connection.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 23:22:08 +02:00
Rayhaan JaufeerallyandClaude Fable 5.1 ad07233196 feature: locally originated routes, session registry, outbound UPDATEs
Phase 1 of the BGP teaching demo: the daemon can now originate routes and
advertise them to every established peer.

- rib: add LocalRib actor holding locally originated routes with owner,
  origin, AS-path prepend, MED, standard and large communities. Enforces a
  LocalRibPolicy (prefix allowlist, per-owner limit, owner match on
  replace/withdraw) and emits LocalRibEvents.
- server: add SessionRegistry with per-session outbound queues; sessions
  register on Established and deregister via a drop guard. Emits
  SessionEvent::{Up,Down}.
- server: advertiser task fans LocalRib changes out to all sessions and
  replays the table to new sessions. UPDATE builders handle IPv4 (legacy
  NLRI) and IPv6 (MP_REACH/MP_UNREACH).
- fsm: merge the timer and timer-less established loops into one select!
  with an outbound branch; reset the hold timer on any received message
  per RFC 4271 §8.2.2 (previously only on KEEPALIVE).
- config: new local_route_policy proto message.
- packet: fix Communitites typo, expose CommunitiesPathAttribute inner vec.
- primitives: IpPrefix::contains.
- integration tests: announce/withdraw/replay/fan-out/policy coverage.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 22:56:29 +02:00
Rayhaan JaufeerallyandClaude Sonnet 4.6 1ec60255ea feature: BGP server, FSM, Adj-RIB-In actor, and integration tests
- Implement passive-side BGP FSM (OpenSent → OpenConfirm → Established)
  with hold timer, keepalive timer, and NOTIFICATION on expiry
- Add bgp-server crate: config validation, TCP listener accept loop
  (BgpServerBuilder → BgpServer), connection dispatch
- Add bgp-rib crate: Adj-RIB-In actor owning route state with event
  broadcast for streaming consumers and snapshot for introspection
- Wire Adj-RIB-In into FSM: announce/withdraw on UPDATE for both legacy
  IPv4 NLRI fields and MP_REACH/MP_UNREACH path attributes
- Add bgp-integration-tests crate with 15 end-to-end tests covering
  session establishment, timers, UPDATE handling, and RIB introspection
- Fix require_empty! macro bug in open.rs (checked wrong buffer)
- Fix FourByteAsn capability parser returning consumed buffer as remainder
- Fix tracing subscriber not being installed in TracingArgs::install
- Expand docs/RFCs.md with full implementation status across 15 RFCs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:21:51 +02:00
Rayhaan Jaufeerally 5ae8f6e249 Cleanup and implementation of Update / Notification parser / serializers. 2026-04-18 12:58:51 +02:00