Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
164 lines
7.0 KiB
Plaintext
164 lines
7.0 KiB
Plaintext
# 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:<peer-as> do not redistribute to peer 42476:0:0 to nobody
|
|
# 42476:1:<peer-as> redistribute to peer 42476:1:0 to all
|
|
# 42476:101|102|103:<peer-as or 0> prepend 1x / 2x / 3x
|
|
# 42476:901|902:<peer-as> 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, evaluated by
|
|
# lab_export_allowed() below:
|
|
# 210036:0:<peer-as> do not announce to that neighbour AS
|
|
# 210036:1:<peer-as> announce to that neighbour AS
|
|
# 210036:0:0 announce to nobody (unless 210036:1:<peer-as> says so)
|
|
bgp_large_community.filter([ (42476, *, *), (MY_ASN, 0..1, *) ]);
|
|
|
|
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.
|
|
#
|
|
# Same shape and same precedence as the SwissIX route-server communities, so
|
|
# students apply one idea at two hops: first here, then at the exchange.
|
|
#
|
|
# 1. 210036:0:<peer-as> never announce to that neighbour
|
|
# 2. 210036:1:<peer-as> announce to that neighbour
|
|
# 3. 210036:0:0 announce to nobody
|
|
# 4. (nothing) announce to everyone <- default
|
|
#
|
|
# <peer-as> is the AS a session's wrapper filter passes in (see below). For a
|
|
# route server that is the route server's AS, e.g. 42476 for SwissIX.
|
|
# Sessions without their own wrapper cannot be selected individually.
|
|
#
|
|
# "Only to SwissIX": 210036:0:0 210036:1:42476
|
|
# "...and there only to SWITCH": add 42476:0:0 42476:1:559
|
|
# ---------------------------------------------------------------------------
|
|
function lab_export_allowed(int peer_as)
|
|
{
|
|
if (MY_ASN, 0, peer_as) ~ bgp_large_community then return false;
|
|
if (MY_ASN, 1, peer_as) ~ bgp_large_community then return true;
|
|
if (MY_ASN, 0, 0) ~ bgp_large_community then return false;
|
|
return true;
|
|
}
|
|
|
|
# Wiring it into a shared export filter
|
|
# -------------------------------------
|
|
# BIRD export filters cannot see which neighbour they are exporting to, and
|
|
# filters take no arguments; functions do. So the shared filter's body lives
|
|
# in a function taking the neighbour AS, and named filters are thin wrappers
|
|
# around it. Sessions that should be individually selectable get their own
|
|
# wrapper; every other session keeps using "peers_out" unchanged and is
|
|
# evaluated with AS 0, which means it obeys 210036:0:0 but cannot be singled
|
|
# out. Add a wrapper per neighbour as the need arises.
|
|
#
|
|
# function peers_out_fn(int peer_as)
|
|
# {
|
|
# bgp_med = 0;
|
|
# bgp_large_community.add((210036, 10, 2));
|
|
# if (net = 193.36.104.0/24) then return true;
|
|
# if (net = 2a0d:d740::/48) then return true;
|
|
# if (net = 2a0d:d740:105::/48) then return true;
|
|
# if proto = "delroth_v6" then return true;
|
|
# if proto = "pkern_v6" then return true;
|
|
# if proto = "home_v4" then return true;
|
|
# if proto = "ferrix" then {
|
|
# if !lab_export_allowed(peer_as) then return false;
|
|
# # Origin must appear as AS210036, matching the route6 objects.
|
|
# bgp_path.delete([ 64512..65534, 4200000000..4294967294 ]);
|
|
# # Our action communities have done their job; do not leak them.
|
|
# bgp_large_community.delete([ (MY_ASN, 0..1, *) ]);
|
|
# return true;
|
|
# }
|
|
# return false;
|
|
# }
|
|
#
|
|
# filter peers_out { if peers_out_fn(0) then accept; reject; }
|
|
# filter peers_out_swissix { if peers_out_fn(42476) then accept; reject; }
|
|
#
|
|
# Then only the SwissIX route-server sessions change, to
|
|
# export filter peers_out_swissix;
|
|
#
|
|
# lab_export_allowed() must be defined above peers_out_fn in the file.
|
|
#
|
|
# The ferrix branch only ever applies to routes learned from ferrix, so these
|
|
# communities cannot be used by anyone else to steer your other exports.
|
|
#
|
|
# Check what a session would send, without waiting for a looking glass:
|
|
# birdc show route 2a0d:d741:1::/48 export <protocol-name> 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.
|
|
# ---------------------------------------------------------------------------
|