# BIRD 2.x / 3.x: peer the production router (AS210036) with ferrix_bgp over # WireGuard. ferrix is announce-only: we accept the lab /48s from it and send # it nothing. # # Include from bird.conf: include "/etc/bird/bird-ferrix.conf"; # Check and apply: birdc configure check && birdc configure # Inspect: birdc show protocols all ferrix # birdc show route protocol ferrix all define MY_ASN = 210036; define FERRIX_ASN = 4200000001; define LAB_PREFIXES = [ 2a0d:d741::/40{48,48} ]; filter ferrix_import { # Only the students' /48s, nothing shorter or longer, nothing else. if !(net ~ LAB_PREFIXES) then reject; # ferrix allows at most 16 prepends on top of its own ASN. if bgp_path.len > 17 then reject; if bgp_path.first != FERRIX_ASN then reject; # Students type communities freely into a web form with no login, so # only namespaces we have chosen to expose get through. # # Standard: just the two well-known ones BIRD itself honours, so # NO_EXPORT / NO_ADVERTISE visibly work. Notably BLACKHOLE (65535:666), # which SwissIX acts on, stays scrubbed. bgp_community.filter([ (65535, 65281), (65535, 65282) ]); # Large: the SwissIX route-server action communities, all of which live # under their route-server ASN 42476 # (https://www.swissix.ch/resources/action-bgp-communities/): # 42476:0: do not redistribute to peer 42476:0:0 to nobody # 42476:1: redistribute to peer 42476:1:0 to all # 42476:101|102|103: prepend 1x / 2x / 3x # 42476:901|902: add NO_EXPORT / NO_ADVERTISE # Everything else is dropped, including the rest of our own # (210036, *, *) space, so students cannot trigger anything else this # network acts on. Add further namespaces here deliberately. # # And our own export-control communities, functions 0 to 3, evaluated by # lab_export_allowed() below. bgp_large_community.filter([ (42476, *, *), (MY_ASN, 0..3, *) ]); bgp_ext_community.empty; # Announce-only: there is nothing behind these prefixes. Do not forward # their traffic into the tunnel; answer with "unreachable" instead. dest = RTD_UNREACHABLE; accept; } protocol bgp ferrix { description "ferrix_bgp teaching lab (announce only)"; local 2a0d:d747:1::1 as 210036; neighbor 2a0d:d747:1:: as FERRIX_ASN; direct; # ferrix dials us. Staying passive avoids log noise from failed attempts # towards a laptop that is asleep or firewalled. passive on; # ferrix supports none of these; leave them off. # password "..."; (TCP MD5) # ttl security on; (GTSM) # bfd on; ipv6 { import filter ferrix_import; # 128 students, one /48 each. Extra routes are ignored, the session # stays up. import limit 130 action block; # Send nothing: ferrix keeps received routes in memory and renders # every one of them on its status page. export none; }; } # --------------------------------------------------------------------------- # Export control for lab routes. # # BIRD export filters cannot see which neighbour they are exporting to, and # filters take no arguments; functions do. So every eBGP session states who it # talks to and where, by calling the shared export function with its neighbour # AS and a location: # # ipv6 { export where peers_out(42476, AT_SWISSIX); ... }; # # Students steer their prefix with large communities, in this precedence: # # 1. 210036:0: never announce to that neighbour AS # 210036:2: never announce at that location # 2. 210036:1: announce to that neighbour AS # 210036:3: announce at that location # 3. 210036:0:0 announce nowhere (unless rule 2 says otherwise) # 4. nothing announce everywhere <- default # # A "never" always wins, so the two dimensions combine. The shape and # precedence match the SwissIX route-server communities on purpose, so one # idea is applied at two hops. # # only the SwissIX route servers: 210036:0:0 210036:1:42476 # ...and there only SWITCH: add 42476:0:0 42476:1:559 # every session at SwissIX: 210036:0:0 210036:3:1 # Cloudflare, but not at DE-CIX: 210036:0:0 210036:1:13335 210036:2:4 # --------------------------------------------------------------------------- define AT_SWISSIX = 1; define AT_NLIX = 2; define AT_FRANCEIX = 3; define AT_DECIX = 4; define AT_FREEIX = 5; define AT_COMMUNITYIX = 6; define AT_TRANSIT = 100; define AT_DOWNSTREAM = 200; function lab_export_allowed(int peer_as; int location) { if (MY_ASN, 0, peer_as) ~ bgp_large_community then return false; if (MY_ASN, 2, location) ~ bgp_large_community then return false; if (MY_ASN, 1, peer_as) ~ bgp_large_community then return true; if (MY_ASN, 3, location) ~ bgp_large_community then return true; if (MY_ASN, 0, 0) ~ bgp_large_community then return false; return true; } # Decides whether a lab route goes out on a session and, if so, makes it # presentable: the origin must appear as AS210036 (matching the route6 # objects) and our action communities must not leak. function lab_route_out(int peer_as; int location) { if !lab_export_allowed(peer_as, location) then return false; bgp_path.delete([ 64512..65534, 4200000000..4294967294 ]); bgp_large_community.delete([ (MY_ASN, 0..3, *) ]); return true; } # The shared export function then ends its list of accepted routes with # # if proto = "ferrix" then return lab_route_out(peer_as, location); # return false; # # and returns true for everything else it accepts. Sessions that export # everything (downstream customers) need the same gate, or they receive lab # routes regardless of what the student asked for: # # export filter { # if proto = "ferrix" then { # if lab_route_out(210400, AT_DOWNSTREAM) then accept; # reject; # } # accept; # }; # # The ferrix branch only ever applies to routes learned from ferrix, so these # communities cannot be used by anyone else to steer other exports. # # If this BIRD rejects "int peer_as; int location", it wants a comma there. # # Check what a session would send, without waiting for a looking glass: # birdc show route 2a0d:d741:1::/48 export all # # The status page no longer offers AS-path prepending or the SwissIX prepend # communities (42476:101..103:*), to keep the exercise focused. The import # filter still admits the whole 42476 namespace, so a student who types one # by hand can use it; narrow the filter if that is not wanted. # # The session towards the SwissIX route servers must send large communities # unmodified apart from the delete above; check nothing else clears them. # ---------------------------------------------------------------------------