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>
This commit is contained in:
1 parent
eb77090d01
commit
b28f59f30d
2 files changed
+89
-29
No files matched your search
@@ -89,8 +89,8 @@ View: <a href="/metricz">variables</a>, <a href="/api/status">status json</a>, <
|
||||
<tr><td><label for="f-communities">Communities:</label></td><td><input id="f-communities" size="50" placeholder="65000:100, NO_EXPORT">
|
||||
<select id="f-wk" aria-label="Add a well-known community"><option value="">+ well-known...</option></select>
|
||||
<span class="dim">comma separated, ASN:VALUE or a name</span></td></tr>
|
||||
<tr><td><label for="f-large">Large communities:</label></td><td><input id="f-large" size="50" placeholder="42476:0:6939">
|
||||
<select id="f-lc" aria-label="Add a large community preset"><option value="">+ SwissIX...</option></select>
|
||||
<tr><td><label for="f-large">Large communities:</label></td><td><input id="f-large" size="50" placeholder="210036:0:0, 210036:1:42476">
|
||||
<select id="f-lc" aria-label="Add a large community preset"><option value="">+ presets...</option></select>
|
||||
<span class="dim">GLOBAL:LOCAL1:LOCAL2</span></td></tr>
|
||||
<tr><td></td><td><input type="submit" value="Announce"> <input type="button" id="f-clear" value="Clear">
|
||||
<span id="announce-msg"></span></td></tr>
|
||||
@@ -119,16 +119,28 @@ View: <a href="/metricz">variables</a>, <a href="/api/status">status json</a>, <
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
// Large-community presets offered next to the field. "PEER" is replaced by
|
||||
// an AS number the user is asked for. Source:
|
||||
// https://www.swissix.ch/resources/action-bgp-communities/
|
||||
const LARGE_PRESETS = [
|
||||
['42476:0:PEER', 'do not send to peer AS'],
|
||||
['42476:1:PEER', 'send to peer AS'],
|
||||
['42476:0:0', 'send to nobody'],
|
||||
['42476:1:0', 'send to all (default)'],
|
||||
['42476:901:PEER', 'add NO_EXPORT towards peer AS'],
|
||||
['42476:902:PEER', 'add NO_ADVERTISE towards peer AS'],
|
||||
// Large-community presets offered next to the field, grouped by who acts
|
||||
// on them. "PEER" is replaced by an AS number the user is asked for. Both
|
||||
// groups share one shape and precedence: 0:<as> never, 1:<as> send,
|
||||
// 0:0 nobody, otherwise everyone.
|
||||
// AS210036: docs/lab/prod/bird-ferrix.conf (lab_export_allowed)
|
||||
// SwissIX: https://www.swissix.ch/resources/action-bgp-communities/
|
||||
const LARGE_PRESET_GROUPS = [
|
||||
['AS210036', 'AS210036, our upstream router', [
|
||||
['210036:0:0', 'announce to nobody'],
|
||||
['210036:1:42476', 'announce to the SwissIX route servers'],
|
||||
['210036:1:PEER', 'announce to neighbour AS'],
|
||||
['210036:0:PEER', 'do not announce to neighbour AS'],
|
||||
]],
|
||||
['SwissIX', 'SwissIX route servers (AS42476)', [
|
||||
['42476:0:0', 'send to nobody'],
|
||||
['42476:1:559', 'send to SWITCH (AS559)'],
|
||||
['42476:1:PEER', 'send to peer AS'],
|
||||
['42476:0:PEER', 'do not send to peer AS'],
|
||||
['42476:1:0', 'send to all (default)'],
|
||||
['42476:901:PEER', 'add NO_EXPORT towards peer AS'],
|
||||
['42476:902:PEER', 'add NO_ADVERTISE towards peer AS'],
|
||||
]],
|
||||
];
|
||||
|
||||
const $ = id => document.getElementById(id);
|
||||
@@ -261,9 +273,11 @@ View: <a href="/metricz">variables</a>, <a href="/api/status">status json</a>, <
|
||||
return out;
|
||||
}
|
||||
function largeLabel(c) {
|
||||
for (const [pattern, label] of LARGE_PRESETS) {
|
||||
// Exact presets first, so 42476:1:559 reads "SWITCH" rather than "AS559".
|
||||
for (const pass of [false, true]) for (const [who, , presets] of LARGE_PRESET_GROUPS) for (const [pattern, label] of presets) {
|
||||
if (pattern.includes('PEER') !== pass) continue;
|
||||
const m = c.match(new RegExp('^' + pattern.replace('PEER', '(\\d+)') + '$'));
|
||||
if (m) return label.replace('peer AS', 'AS' + (m[1] || ''));
|
||||
if (m) return `${who}: ${label.replace(/(peer|neighbour) AS/, 'AS' + (m[1] || ''))}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -292,7 +306,7 @@ View: <a href="/metricz">variables</a>, <a href="/api/status">status json</a>, <
|
||||
case 4: rows.push(['MED', `${u(v, 0, 4)} (lower is preferred, compared between routes from the same neighbour AS)`]); break;
|
||||
case 5: rows.push(['local preference', `${u(v, 0, 4)} (higher is preferred)`]); break;
|
||||
case 8: for (let o = 0; o + 4 <= v.length; o += 4) { const c = `${u(v, o, 2)}:${u(v, o + 2, 2)}`; rows.push(['community', c + (state.wellKnown[c] ? ` = ${state.wellKnown[c]}` : '')]); } break;
|
||||
case 32: for (let o = 0; o + 12 <= v.length; o += 12) { const c = `${u(v, o, 4)}:${u(v, o + 4, 4)}:${u(v, o + 8, 4)}`, l = largeLabel(c); rows.push(['large community', c + (l ? ` = SwissIX: ${l}` : '')]); } break;
|
||||
case 32: for (let o = 0; o + 12 <= v.length; o += 12) { const c = `${u(v, o, 4)}:${u(v, o + 4, 4)}:${u(v, o + 8, 4)}`, l = largeLabel(c); rows.push(['large community', c + (l ? ` = ${l}` : '')]); } break;
|
||||
case 14: {
|
||||
const afi = u(v, 0, 2), nhLen = v[3], nh = v.slice(4, 4 + nhLen);
|
||||
rows.push(['address family', `${AFI[afi] || afi} ${SAFI[v[2]] || v[2]}`]);
|
||||
@@ -449,7 +463,7 @@ View: <a href="/metricz">variables</a>, <a href="/api/status">status json</a>, <
|
||||
let v = e.target.value; e.target.value = '';
|
||||
if (!v) return;
|
||||
if (v.includes('PEER')) {
|
||||
const peer = (prompt('Peer AS number?') || '').trim().replace(/^AS/i, '');
|
||||
const peer = (prompt('AS number of the peer or neighbour?') || '').trim().replace(/^AS/i, '');
|
||||
if (!/^\d+$/.test(peer)) { if (peer) setMsg(`"${peer}" is not an AS number`, 'bad'); return; }
|
||||
v = v.replace('PEER', peer);
|
||||
}
|
||||
@@ -469,7 +483,11 @@ View: <a href="/metricz">variables</a>, <a href="/api/status">status json</a>, <
|
||||
|
||||
// ---- boot --------------------------------------------------------------
|
||||
try { $('f-owner').value = localStorage.getItem('owner') || ''; } catch (_) {}
|
||||
LARGE_PRESETS.forEach(([value, label]) => { const o = document.createElement('option'); o.value = value; o.textContent = `${value.replace('PEER', '<peer-as>')} ${label}`; $('f-lc').appendChild(o); });
|
||||
LARGE_PRESET_GROUPS.forEach(([, title, presets]) => {
|
||||
const g = document.createElement('optgroup'); g.label = title;
|
||||
presets.forEach(([value, label]) => { const o = document.createElement('option'); o.value = value; o.textContent = `${value.replace('PEER', '<as>')} ${label}`; g.appendChild(o); });
|
||||
$('f-lc').appendChild(g);
|
||||
});
|
||||
getJSON('/api/communities/well-known').then(list => {
|
||||
list.forEach(c => { state.wellKnown[c.value] = c.name; const o = document.createElement('option'); o.value = c.name; o.textContent = `${c.name} (${c.value})`; $('f-wk').appendChild(o); });
|
||||
renderLocal(); renderReceived();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
# 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} ];
|
||||
|
||||
@@ -34,10 +35,16 @@ filter ferrix_import
|
||||
# 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 our own (210036, *, *) space so
|
||||
# students cannot trigger anything this network acts on internally. Add
|
||||
# further namespaces here deliberately.
|
||||
bgp_large_community.filter([ (42476, *, *) ]);
|
||||
# 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;
|
||||
|
||||
@@ -76,21 +83,56 @@ protocol bgp ferrix
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Your upstream / IXP export filters must let these routes out, and must
|
||||
# strip ferrix's private ASN so the origin seen by the Internet is AS210036,
|
||||
# matching the route6 objects. Add, before the final reject of each such
|
||||
# filter:
|
||||
# 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 number on the session's "neighbor ... as N" line. For a
|
||||
# route server that is the route server's AS, e.g. 42476 for SwissIX.
|
||||
#
|
||||
# "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;
|
||||
}
|
||||
|
||||
# In the export filter of EVERY eBGP session (transit, IXP route servers,
|
||||
# bilateral peers), before its final reject, add this block with that
|
||||
# session's neighbour AS in place of 42476:
|
||||
#
|
||||
# if proto = "ferrix" then {
|
||||
# if !lab_export_allowed(42476) then reject;
|
||||
# # 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, *) ]);
|
||||
# accept;
|
||||
# }
|
||||
#
|
||||
# The block only ever applies to routes learned from ferrix, so these
|
||||
# communities cannot be used by anyone else to steer your other exports.
|
||||
# A session that lacks the block never announces lab routes at all, which is
|
||||
# the safe failure.
|
||||
#
|
||||
# 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 above 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.
|
||||
# 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 also send large
|
||||
# communities unmodified; check that its export filter does not clear them.
|
||||
# The session towards the SwissIX route servers must send large communities
|
||||
# unmodified apart from the delete above; check nothing else clears them.
|
||||
# ---------------------------------------------------------------------------
|
||||
Reference in new issue
Block a user