slides: light theme, final edits, single-file bundle

As presented. The palette is light on request of the reviewer; code blocks
and byte dumps keep a dark ground so the byte colours still work. The OPEN
walk lights the hovered bytes as an inverted block and its table cells are
dark text on a tint of their group colour.

build.py inlines reveal.js, the stylesheets, scripts and Markdown sections
into slides/dist/hands-on-bgp.html for uploading.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-01 20:40:55 +02:00
1 parent 8c6e8a80e1
commit 2910a2a800
10 files changed
+338 -98

No files matched your search

+14
View File
@@ -105,6 +105,20 @@ page (add `focus`, and `on` to the spans to keep lit, to dim the rest),
`<!-- .slide: class="bare" -->` drops the panel, the motif and the wordmark `<!-- .slide: class="bare" -->` drops the panel, the motif and the wordmark
for one slide, e.g. a full-bleed image. The title slide does so by itself. for one slide, e.g. a full-bleed image. The title slide does so by itself.
## Publish as one file
slides/vendor-reveal.sh # once, if slides/reveal.js is missing
python3 slides/build.py # writes slides/dist/hands-on-bgp.html
`build.py` inlines everything the page would otherwise fetch: reveal.js and
its plugins, the stylesheets, the favicon, our scripts and the Markdown
sections. The one file (about 1.3 MB) can be uploaded anywhere and also
opens straight from disk. It still uses the network for two things: the IBM
Plex fonts from Google Fonts (without them the browser's fallback fonts are
used) and the live DNS and RIPE lookups in section 1. Speaker view (`S`)
works from the bundle too. `slides/dist/` is not committed; rebuild after
editing.
## PDF ## PDF
Open <http://localhost:8000/?print-pdf> in Chrome and print to PDF with Open <http://localhost:8000/?print-pdf> in Chrome and print to PDF with
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""Bundle the deck into one self-contained HTML file for uploading.
python3 slides/build.py # writes slides/dist/hands-on-bgp.html
Everything the page fetches at runtime is inlined: reveal.js and its plugins
(from the vendored copy, so run vendor-reveal.sh first), the stylesheets, the
favicon, our scripts and the Markdown sections. The result opens from any web
server and from disk. Two things still need a network: the IBM Plex fonts
(Google Fonts) and the live DNS and RIPE lookups.
"""
import base64
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
REVEAL = os.path.join(HERE, "reveal.js", "dist")
OUT = os.path.join(HERE, "dist", "hands-on-bgp.html")
def read(*parts):
with open(os.path.join(HERE, *parts), encoding="utf-8") as f:
return f.read()
def main():
if not os.path.isdir(REVEAL):
sys.exit("reveal.js is not vendored; run slides/vendor-reveal.sh first")
html = read("index.html")
# Stylesheets: reveal's first, then ours, as the loader orders them.
css = "\n".join(read("reveal.js", "dist", p) for p in ["reset.css", "reveal.css", "plugin/highlight/monokai.css"])
css += "\n" + read("theme.css") + "\n" + read("style.css")
html = re.sub(r'\s*<link rel="stylesheet" href="theme\.css[^"]*" id="deck-style" />', "", html)
html = re.sub(r'\s*<link rel="stylesheet" href="style\.css[^"]*" />', f"\n <style>\n{css}\n </style>", html, count=1)
icon = "data:image/svg+xml;base64," + base64.b64encode(read("images", "favicon.svg").encode()).decode()
html = html.replace('href="images/favicon.svg"', f'href="{icon}"')
# Sections: the Markdown goes inline in a text/template script.
def inline_section(m):
attrs, path = m.group(1), m.group(2)
text = read(path)
if "</script" in text.lower():
sys.exit(f"{path} contains '</script', which cannot be inlined")
return f'<section data-markdown{attrs}>\n<script type="text/template">\n{text}\n</script>\n</section>'
html, n = re.subn(r'<section\s+data-markdown="([^"]+)"((?:\s+data-[a-z-]+="[^"]*")*)\s*></section>',
lambda m: inline_section(type("M", (), {"group": lambda self, i: (None, m.group(2), m.group(1))[i]})()), html)
if n == 0:
sys.exit("no data-markdown sections found in index.html")
# Our scripts.
for name in ["dns-lookup.js", "prefix-explorer.js"]:
html, k = re.subn(rf'<script src="{re.escape(name)}[^"]*"></script>', lambda m: f"<script>\n{read(name)}\n</script>", html)
if k != 1:
sys.exit(f"expected one <script src=\"{name}\"> tag")
# The help overlay and its watchdog exist to explain a page that could
# not fetch its parts; this one fetches nothing.
html = re.sub(r'\s*<div id="deck-help" hidden>.*?</div>', "", html, count=1, flags=re.S)
html = re.sub(r'\s*<!-- A classic script: it still runs.*?</script>', "", html, count=1, flags=re.S)
# The loader: the module script that picks a reveal.js location and
# imports it becomes reveal.js itself, its plugins, and the deck setup.
m = re.search(r'<script type="module">(.*?)</script>', html, flags=re.S)
if not m:
sys.exit("loader script not found")
loader = m.group(1)
setup_start = loader.index("// BIRD configuration")
setup = loader[setup_start:]
setup = setup.replace("await deck.initialize().catch(fail(\"reveal.js failed to start\"));", "deck.initialize().then(() => {")
setup = setup.replace("plugins: [Markdown, Highlight, Notes],", "plugins: [RevealMarkdown, RevealHighlight, RevealNotes],")
setup = setup.rstrip() + "\n });\n"
libs = "\n".join(read("reveal.js", "dist", p) for p in ["reveal.js", "plugin/markdown.js", "plugin/highlight.js", "plugin/notes.js"])
html = html[: m.start()] + f"<script>\n{libs}\n</script>\n <script>\n{setup} </script>" + html[m.end():]
if "await " in setup or "import(" in setup:
sys.exit("loader still contains module-only code")
html = html.replace("reveal.js is loaded from ./reveal.js/dist if present", "Built by build.py from index.html: everything is inlined. reveal.js was loaded from ./reveal.js/dist if present")
os.makedirs(os.path.dirname(OUT), exist_ok=True)
with open(OUT, "w", encoding="utf-8") as f:
f.write(html)
print(f"wrote {os.path.relpath(OUT)} ({os.path.getsize(OUT) // 1024} KB, {n} sections)")
if __name__ == "__main__":
main()
+35
View File
@@ -103,3 +103,38 @@
relayout(); relayout();
}); });
})(); })();
// Byte walks: hovering (or tabbing to) a field row lights its bytes in the
// dump above it, and hovering bytes in the dump lights their row and scrolls
// it into view, so the dump can be used as the index while presenting.
(function () {
const pair = (el) => {
const walk = el.closest(".bytewalk");
if (!walk) return [];
const i = el.dataset.i;
return [walk.querySelector(`.bw-dump span[data-i="${i}"]`), walk.querySelector(`.bw-row[data-i="${i}"]`)].filter(Boolean);
};
const target = (e) => e.target.closest && e.target.closest(".bw-row, .bw-dump span[data-i]");
const on = (e) => {
const el = target(e);
if (!el) return;
const both = pair(el);
both.forEach((x) => x.classList.add("on"));
if (el.matches(".bw-dump span")) {
// Scroll the box itself, and instantly: scrollIntoView would also
// move the slide, and Chrome drops smooth scrolls inside the
// transformed slide that reveal positions.
const row = both.find((x) => x.classList.contains("bw-row"));
const box = row && row.closest(".bw-rows");
if (box) box.scrollTop = row.offsetTop - box.clientHeight / 2 + row.offsetHeight / 2;
}
};
const off = (e) => {
const el = target(e);
if (el) pair(el).forEach((x) => x.classList.remove("on"));
};
document.addEventListener("mouseover", on);
document.addEventListener("focusin", on);
document.addEventListener("mouseout", off);
document.addEventListener("focusout", off);
})();
+5 -5
View File
@@ -15,8 +15,8 @@
stylesheets are inserted BEFORE ours by the loader below, so theme.css stylesheets are inserted BEFORE ours by the loader below, so theme.css
and style.css always have the last word. and style.css always have the last word.
--> -->
<link rel="stylesheet" href="theme.css?v=2" id="deck-style" /> <link rel="stylesheet" href="theme.css?v=3" id="deck-style" />
<link rel="stylesheet" href="style.css?v=10" /> <link rel="stylesheet" href="style.css?v=18" />
<link rel="icon" href="images/favicon.svg" type="image/svg+xml" /> <link rel="icon" href="images/favicon.svg" type="image/svg+xml" />
<link rel="shortcut icon" href="images/favicon.svg" /> <link rel="shortcut icon" href="images/favicon.svg" />
</head> </head>
@@ -29,8 +29,8 @@
<h1>Hands-on BGP</h1> <h1>Hands-on BGP</h1>
<h3>Insights into real world operations</h3> <h3>Insights into real world operations</h3>
<p class="byline"> <p class="byline">
Rayhaan Jaufeerally &middot; Network Security &middot; Rayhaan Jaufeerally (rayhaan@rayhaan.ch) &middot;
ETH Z&uuml;rich, 2026 Network Security &middot; ETH Z&uuml;rich, 2026
</p> </p>
</section> </section>
@@ -113,7 +113,7 @@
})(); })();
</script> </script>
<script src="dns-lookup.js?v=3"></script> <script src="dns-lookup.js?v=7"></script>
<script src="prefix-explorer.js?v=4"></script> <script src="prefix-explorer.js?v=4"></script>
<script type="module"> <script type="module">
+7 -1
View File
@@ -12,7 +12,7 @@ Before we look at what's going on in the internet, let's refresh on how the Inte
<div class="dns-result"><span class="dim">Ask a resolver which IPv6 address this name stands for.</span></div> <div class="dns-result"><span class="dim">Ask a resolver which IPv6 address this name stands for.</span></div>
</form> </form>
Your computer cannot send a packet to a name. Every time you use a domain name, it is resolved into an IP address. IP packets are sent to IP addresses, not domain names. Every time you use a domain name, it is resolved into an IP address via DNS.
<!-- .element: class="note" --> <!-- .element: class="note" -->
@@ -64,6 +64,8 @@ Ask: why only once? Because 2001::1::1 could put the zeros in several places.
## A prefix is a range of addresses ## A prefix is a range of addresses
In BGP, the control plane for the internet, it's not viable to talk about single addresses, so we use prefix ranges.
<form class="prefix-explorer" autocomplete="off"> <form class="prefix-explorer" autocomplete="off">
<input name="prefix" value="2001:db8:1::/48" spellcheck="false" aria-label="IPv6 prefix"><input type="range" name="len" min="0" max="128" value="48" aria-label="prefix length"> <input name="prefix" value="2001:db8:1::/48" spellcheck="false" aria-label="IPv6 prefix"><input type="range" name="len" min="0" max="128" value="48" aria-label="prefix length">
<p class="bits px-bits"></p> <p class="bits px-bits"></p>
@@ -162,3 +164,7 @@ Notes:
Nobody "owns" numbers or addresses; they are registered to you. RFC 6996 Nobody "owns" numbers or addresses; they are registered to you. RFC 6996
reserves the private ranges. The upstream router removes the private AS from reserves the private ranges. The upstream router removes the private AS from
the path, so the origin everybody else sees is AS210036. the path, so the origin everybody else sees is AS210036.
---
## Handout Part 1
+58 -19
View File
@@ -1,11 +1,11 @@
# BGP, down to the bytes # BGP in detail
Notes: Notes:
Section 3. The aim: by the end, the bytes view on the lab page reads like text. We're going to look at some implementation details of the protocol
--- ---
## BGP in one slide ##
- Runs over TCP, port `179`, and is exchanged typically between peering routers - Runs over TCP, port `179`, and is exchanged typically between peering routers
- Each side tells the other its best route per prefix - Each side tells the other its best route per prefix
@@ -20,14 +20,14 @@ Security and extensions have had to be bolted on, in perhaps strange ways.
## The four message types ## The four message types
| Type | Name | Says | | Type | Name | Says |
| ---- | ------------ | ------------------------------------------------- | | ---- | ------------ | ------------------------------------------------------- |
| 1 | OPEN | who I am: AS number, router ID, capabilities | | 1 | OPEN | AS number, router ID, capabilities |
| 2 | **UPDATE** | these prefixes are reachable, those no longer are | | 2 | **UPDATE** | these prefixes are reachable, those no longer are |
| 3 | NOTIFICATION | something is wrong, I am closing the session | | 3 | NOTIFICATION | errors and informational messages: e.g. rebooting |
| 4 | KEEPALIVE | still here | | 4 | KEEPALIVE | Heartbeat to keep the session up when there's no update |
OPEN, OPEN, KEEPALIVE, KEEPALIVE, and then it is all UPDATEs. OPEN, OPEN, KEEPALIVE, KEEPALIVE, and then a stream of UPDATEs.
<!-- .element: class="note" --> <!-- .element: class="note" -->
@@ -49,7 +49,7 @@ There is actually a fifth type, ROUTE-REFRESH: "send me everything again, my pol
<g class="pkt hh"><rect x="738" y="30" width="242" height="64" rx="6"/></g> <g class="pkt hh"><rect x="738" y="30" width="242" height="64" rx="6"/></g>
<text x="859" y="59" class="pkt-name">type</text> <text x="859" y="59" class="pkt-name">type</text>
<text x="859" y="81" class="pkt-size">1 byte</text> <text x="859" y="81" class="pkt-size">1 byte</text>
<text x="20" y="132" class="left small pkt-title">OPEN body: who I am</text> <text x="20" y="132" class="left small pkt-title">OPEN: the fixed part</text>
<g class="pkt hl"><rect x="20" y="140" width="150" height="64" rx="6"/></g> <g class="pkt hl"><rect x="20" y="140" width="150" height="64" rx="6"/></g>
<text x="95" y="169" class="pkt-name">version</text> <text x="95" y="169" class="pkt-name">version</text>
<text x="95" y="191" class="pkt-size">1 byte: 4</text> <text x="95" y="191" class="pkt-size">1 byte: 4</text>
@@ -97,13 +97,52 @@ real number rides in a capability, which is how our AS210036 arrives.
--- ---
## An OPEN message example ## BGP Open message
<div class="legend"><span class="hh">header</span><span class="hl">who I am</span><span class="ha">capabilities</span></div> <div class="bytewalk">
<pre class="bytes bw-dump"><span class="hh" data-i="0">ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff</span> <span class="hh" data-i="1">00 37</span> <span class="hh" data-i="2">01</span> <span class="hl" data-i="3">04</span> <span class="hl" data-i="4">5b a0</span> <span class="hl" data-i="5">00 f0</span> <span class="hl" data-i="6">0a ff 00 01</span> <span class="hl" data-i="7">1a</span> <span class="ha" data-i="8">02</span> <span class="ha" data-i="9">18</span> <span class="ha2" data-i="10">01</span> <span class="ha2" data-i="11">04</span> <span class="ha2" data-i="12">00 02</span> <span class="ha2" data-i="13">00</span> <span class="ha2" data-i="14">01</span> <span class="ha2" data-i="15">02</span> <span class="ha2" data-i="16">00</span> <span class="ha2" data-i="17">06</span> <span class="ha2" data-i="18">00</span> <span class="ha2" data-i="19">40</span> <span class="ha2" data-i="20">02</span> <span class="ha2" data-i="21">00 00</span> <span class="ha2" data-i="22">41</span> <span class="ha2" data-i="23">04</span> <span class="ha2" data-i="24">00 03 34 74</span> <span class="ha2" data-i="25">46</span> <span class="ha2" data-i="26">00</span> <span class="ha2" data-i="27">47</span> <span class="ha2" data-i="28">00</span></pre>
<pre class="bytes"><span class="hh">ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff</span> <span class="hh">00 37</span> <span class="hh">01</span> <span class="hl">04</span> <span class="hl">5b a0</span> <span class="hl">00 f0</span> <span class="hl">0a ff 00 01</span> <span class="hl">1a</span> <span class="ha">02 18</span> <span class="ha2">01 04 00 02 00 01</span> <span class="ha2">02 00</span> <span class="ha2">06 00</span> <span class="ha2">40 02 00 00</span> <span class="ha2">41 04 00 03 34 74</span> <span class="ha2">46 00</span> <span class="ha2">47 00</span></pre> <div class="bw-rows">
<div class="bw-group">BGP header: 19 bytes, on every message</div>
<table class="decode"><tr><td>04 · 5b a0 · 00 f0 · 0a ff 00 01 · 1a</td><td>version 4; AS 23456 (AS_TRANS, see below); hold time 240 s; router ID 10.255.0.1; 26 bytes of parameters</td></tr><tr><td>02 18</td><td>parameter type 2, capabilities, 24 bytes</td></tr><tr><td>01 04 00 02 00 01</td><td>multiprotocol: AFI 2 (IPv6), SAFI 1 (unicast). Without this, no IPv6 routes</td></tr><tr><td>41 04 00 03 34 74</td><td>4-byte AS: the real number, 210036</td></tr><tr><td>02 00 · 06 00 · 40 02 00 00 · 46 00 · 47 00</td><td>route refresh, extended message, graceful restart, enhanced refresh, long-lived GR</td></tr></table> <div class="bw-row" data-i="0" tabindex="0"><code class="hh">ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff</code><b>marker</b><span>sixteen bytes of ff.</span></div>
<div class="bw-row" data-i="1" tabindex="0"><code class="hh">00 37</code><b>length</b><span>0x0037 = 55 bytes, header included. At most 4096, or 65535 with the extended-message capability</span></div>
<div class="bw-row" data-i="2" tabindex="0"><code class="hh">01</code><b>type</b><span>1 = OPEN (2 UPDATE, 3 NOTIFICATION, 4 KEEPALIVE, 5 ROUTE-REFRESH)</span></div>
<div class="bw-group">OPEN: the fixed part, 10 bytes</div>
<div class="bw-row" data-i="3" tabindex="0"><code class="hl">04</code><b>version</b><span>BGP version 4</span></div>
<div class="bw-row" data-i="4" tabindex="0"><code class="hl">5b a0</code><b>my AS</b><span>0x5ba0 = 23456, AS_TRANS: the ASN is longer than 16 bits so is encoded in a capability instead</span></div>
<div class="bw-row" data-i="5" tabindex="0"><code class="hl">00 f0</code><b>hold time</b><span>0x00f0 = 240 seconds. both peers use the minimum announced; a KEEPALIVE typically is sent every 1/3rd of this duration</span></div>
<div class="bw-row" data-i="6" tabindex="0"><code class="hl">0a ff 00 01</code><b>BGP identifier</b><span>0a ff 00 01 = 10.255.0.1, the router ID. Looks like an IPv4 address, just needs to be a unique identifier.</span></div>
<div class="bw-row" data-i="7" tabindex="0"><code class="hl">1a</code><b>optional parameters length</b><span>0x1a = 26 bytes follow</span></div>
<div class="bw-group">Optional parameter: 2 + 24 bytes</div>
<div class="bw-row" data-i="8" tabindex="0"><code class="ha">02</code><b>parameter type</b><span>2 = capabilities</span></div>
<div class="bw-row" data-i="9" tabindex="0"><code class="ha">18</code><b>parameter length</b><span>0x18 = 24 bytes of capabilities follow, each as type, length, value</span></div>
<div class="bw-group">Capability 1: multiprotocol (RFC 4760)</div>
<div class="bw-row" data-i="10" tabindex="0"><code class="ha2">01</code><b>code</b><span>1 = multiprotocol extensions</span></div>
<div class="bw-row" data-i="11" tabindex="0"><code class="ha2">04</code><b>length</b><span>4</span></div>
<div class="bw-row" data-i="12" tabindex="0"><code class="ha2">00 02</code><b>AFI</b><span>0x0002 = IPv6. Address Family Identifier</span></div>
<div class="bw-row" data-i="13" tabindex="0"><code class="ha2">00</code><b>reserved</b><span>0</span></div>
<div class="bw-row" data-i="14" tabindex="0"><code class="ha2">01</code><b>SAFI</b><span>1 = unicast. Subsequent Address Family Identifier.</span></div>
<div class="bw-group">Capability 2: route refresh (RFC 2918)</div>
<div class="bw-row" data-i="15" tabindex="0"><code class="ha2">02</code><b>code</b><span>2 = route refresh: “you may ask me to resend everything”</span></div>
<div class="bw-row" data-i="16" tabindex="0"><code class="ha2">00</code><b>length</b><span>0</span></div>
<div class="bw-group">Capability 6: extended message (RFC 8654)</div>
<div class="bw-row" data-i="17" tabindex="0"><code class="ha2">06</code><b>code</b><span>6 = messages up to 65535 bytes are supported</span></div>
<div class="bw-row" data-i="18" tabindex="0"><code class="ha2">00</code><b>length</b><span>0</span></div>
<div class="bw-group">Capability 64: graceful restart (RFC 4724)</div>
<div class="bw-row" data-i="19" tabindex="0"><code class="ha2">40</code><b>code</b><span>0x40 = 64, graceful restart</span></div>
<div class="bw-row" data-i="20" tabindex="0"><code class="ha2">02</code><b>length</b><span>2</span></div>
<div class="bw-row" data-i="21" tabindex="0"><code class="ha2">00 00</code><b>flags and restart time</b><span>0x0000: not restarting, restart time 0, and no address families listed.</span></div>
<div class="bw-group">Capability 65: four-octet AS (RFC 6793)</div>
<div class="bw-row" data-i="22" tabindex="0"><code class="ha2">41</code><b>code</b><span>0x41 = 65, four-octet AS number</span></div>
<div class="bw-row" data-i="23" tabindex="0"><code class="ha2">04</code><b>length</b><span>4</span></div>
<div class="bw-row" data-i="24" tabindex="0"><code class="ha2">00 03 34 74</code><b>AS number</b><span>00 03 34 74 = 210036: the real AS. Included here since it doesn't fit in the header</span></div>
<div class="bw-group">Capability 70: enhanced route refresh (RFC 7313)</div>
<div class="bw-row" data-i="25" tabindex="0"><code class="ha2">46</code><b>code</b><span>0x46 = 70</span></div>
<div class="bw-row" data-i="26" tabindex="0"><code class="ha2">00</code><b>length</b><span>0</span></div>
<div class="bw-group">Capability 71: long-lived graceful restart</div>
<div class="bw-row" data-i="27" tabindex="0"><code class="ha2">47</code><b>code</b><span>0x47 = 71</span></div>
<div class="bw-row" data-i="28" tabindex="0"><code class="ha2">00</code><b>length</b><span>0, no address families listed</span></div>
</div>
</div>
Notes: Notes:
What BIRD on our upstream router sends the lab daemon, byte for byte. Hold What BIRD on our upstream router sends the lab daemon, byte for byte. Hold
@@ -111,7 +150,7 @@ time 240 is BIRD's default; keepalives go every third of it.
--- ---
## The shape of an UPDATE ## Update message format
<svg class="diagram" viewBox="0 0 1000 460" role="img" aria-label="An UPDATE message: the common header, then withdrawn routes, path attributes and NLRI, with one attribute and one prefix expanded"> <svg class="diagram" viewBox="0 0 1000 460" role="img" aria-label="An UPDATE message: the common header, then withdrawn routes, path attributes and NLRI, with one attribute and one prefix expanded">
<text x="20" y="22" class="left small pkt-title">header: 19 bytes</text> <text x="20" y="22" class="left small pkt-title">header: 19 bytes</text>
@@ -173,7 +212,7 @@ understanding any of it, which is what lets unknown attributes pass through.
--- ---
## A real one: 89 bytes ## An example BGP update
<div class="legend"><span class="hh">header</span><span class="hl">lengths</span><span class="ha">path attributes</span><span class="hn">NLRI</span></div> <div class="legend"><span class="hh">header</span><span class="hl">lengths</span><span class="ha">path attributes</span><span class="hn">NLRI</span></div>
+18 -8
View File
@@ -24,7 +24,7 @@ exchange physically is.
- Usually **free**, by mutual benefit - Usually **free**, by mutual benefit
- You exchange _your own_ routes, and your customers' - You exchange _your own_ routes, and your customers'
- Traffic between the two of you only - Traffic between the locally connected parties only
</div> </div>
</div> </div>
@@ -62,13 +62,12 @@ Notes:
Every member plugs one router port into the same layer-2 network and gets one Every member plugs one router port into the same layer-2 network and gets one
address in a shared subnet. No routing inside the exchange: just Ethernet. address in a shared subnet. No routing inside the exchange: just Ethernet.
Dashed blue is a BGP session, green is where the packets actually go. Dashed blue is a BGP session, green is where the packets actually go.
a5ec is 42476 in hex: the route server's address spells its AS number.
--- ---
## What that implies ## What that implies
- Your neighbour is _on your LAN_: one hop, found by neighbour discovery - Peering participants in an exchange are all one hop away, found by neighbour discovery
- A route's next hop is the peer's LAN address, so traffic goes **directly** member to member - A route's next hop is the peer's LAN address, so traffic goes **directly** member to member
- The exchange is a simple layer 2 switch, allowing for a large capacity at low cost - The exchange is a simple layer 2 switch, allowing for a large capacity at low cost
- Strict(ish) rules on what traffic is allowed on the peering lan (e.g. no DHCP or broadcast packets) - Strict(ish) rules on what traffic is allowed on the peering lan (e.g. no DHCP or broadcast packets)
@@ -82,14 +81,25 @@ Notes:
## Route servers ## Route servers
Without one, peering with 200 members is 200 BGP sessions. Are a piece of shared infrastructure that simplifies peering.
- One session to the route server brings routes from everyone who uses it - Most participants peer with the route server and annouce their prefixes there
- It is **not** in the traffic path, and leaves the next hop alone - The route server can then distribute these routes to every other participant
- It does not add its own AS to the path - Simplifes from `n²` to `n` sessions
- You steer it with communities: `42476:0:<as>` "not to them", `42476:1:<as>` "only to them" - Not in the traffic path and does not add its own AS into the path
- Allows controlling behaviour with communities, e.g. `42476:0:<as>` "not to `<as>`", `42476:1:<as>` "only to `<as>`"
Notes: Notes:
A route server is a BGP speaker that only redistributes. Because it is A route server is a BGP speaker that only redistributes. Because it is
transparent, the AS path looks exactly as if you peered with each member transparent, the AS path looks exactly as if you peered with each member
directly. We will use those communities in the lab. directly. We will use those communities in the lab.
---
## Looking glasses
Are a useful tool to inspect the state of another network or internet exchange
- SwissIX https://lg.swissix.ch/
- NL-ix https://lg.nl-ix.net/
- BGP tools https://bgp.tools/
+15 -21
View File
@@ -38,9 +38,9 @@ the right of pr01 is the actual Internet.
`2a0d:d741:<n>::/48` `2a0d:d741:<n>::/48`
* `<n>` is your number **in hex**: student 10 is `2a0d:d741:a::/48` - `<n>` is your number **in hex**: student 10 is `2a0d:d741:a::/48`
* Put your name in *owner*: only the owner can withdraw a route - Put your name in _owner_: only the owner can withdraw a route
* One prefix each - One prefix each
Notes: Notes:
Put the URL and the class password on the board. The page is a snapshot by Put the URL and the class password on the board. The page is a snapshot by
@@ -48,12 +48,12 @@ default; turn on auto-refresh at the top right to watch things change.
--- ---
## What happens when you press *Announce* ## What happens when you press _Announce_
1. The lab speaker builds an UPDATE. Click *bytes* to see it, hover to decode it 1. The lab speaker builds an UPDATE. Click _bytes_ to see it, hover to decode it
2. pr01 checks it against its import filter 2. pr01 checks it against its import filter
3. pr01 decides, per neighbour, whether to pass it on 3. pr01 decides, per neighbour, whether to pass it on
4. Within a minute or so, the Internet can see your prefix 4. Your announcement propagates throughout the whole internet!
5. `ping 2a0d:d741:<n>::1` answers, from anywhere 5. `ping 2a0d:d741:<n>::1` answers, from anywhere
Notes: Notes:
@@ -64,15 +64,15 @@ your prefix depends only on whether your announcement got out.
## Steering your route ## Steering your route
| You add | Who acts | Effect | | You add | Who acts | Effect |
|---|---|---| | ----------------------------- | -------- | --------------------------------- |
| nothing | | announced everywhere | | nothing | | announced everywhere |
| `210036:0:0` | pr01 | announced nowhere | | `210036:0:0` | pr01 | announced nowhere |
| `210036:0:0` `210036:1:42476` | pr01 | only to the SwissIX route servers | | `210036:0:0` `210036:1:42476` | pr01 | only to the SwissIX route servers |
| + `42476:0:0` `42476:1:559` | SwissIX | …and from there only to SWITCH | | + `42476:0:0` `42476:1:559` | SwissIX | …and from there only to SWITCH |
| `NO_EXPORT` | pr01 | accepted, never passed on | | `NO_EXPORT` | pr01 | accepted, never passed on |
Same idea at two hops: *never there*, *only there*, *nowhere*. Same idea at two hops: _never there_, _only there_, _nowhere_.
<!-- .element: class="note" --> <!-- .element: class="note" -->
@@ -83,13 +83,7 @@ wins. The preset menu next to the field has all of these.
--- ---
## Your turn ## Handout Part 2 & 3
1. Announce your prefix with no options. Find it on a looking glass
2. Read the bytes: where is your AS number? Where is your prefix?
3. Restrict it to the SwissIX route servers. Who still sees it?
4. Narrow it to SWITCH. Can you reach it from the ETH network? From your phone?
5. Withdraw it and watch it disappear
Notes: Notes:
Looking glasses: bgp.tools, RIPEstat, and the exchange's own. Global Looking glasses: bgp.tools, RIPEstat, and the exchange's own. Global
+60 -12
View File
@@ -76,7 +76,7 @@
.reveal img.blur-edges { .reveal img.blur-edges {
border-radius: 12px; border-radius: 12px;
box-shadow: 0 0 25px 8px rgba(0, 0, 0, 0.5); box-shadow: 0 0 25px 8px rgba(20, 30, 60, 0.2);
} }
.reveal table.borders td, .reveal table.borders td,
@@ -110,11 +110,11 @@
} }
.boxes > div.ok { .boxes > div.ok {
border-color: #4fd98a; border-color: #1a8a4f;
} }
.boxes > div.bad { .boxes > div.bad {
border-color: #ff6b6b; border-color: #d64545;
} }
/* ---- BGP bytes, matching the four groups of the /statusz bytes view ---------- */ /* ---- BGP bytes, matching the four groups of the /statusz bytes view ---------- */
@@ -124,17 +124,18 @@
line-height: 1.9; line-height: 1.9;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-all;
background: rgba(8, 9, 20, 0.65); background: var(--code-ground);
border-radius: 6px; border-radius: 6px;
padding: 15px 20px; padding: 15px 20px;
box-sizing: border-box; box-sizing: border-box;
color: var(--ink); color: var(--code-ink);
} }
.bytes span, .bytes span,
.legend span { .legend span {
border-radius: 3px; border-radius: 3px;
padding: 1px 3px; padding: 1px 3px;
color: var(--code-ink); /* the fills are dark on any ground */
} }
.bytes .hh, .bytes .hh,
@@ -191,7 +192,7 @@
padding: 0.3em 0.9em; padding: 0.3em 0.9em;
font: inherit; font: inherit;
font-size: 0.8em; font-size: 0.8em;
color: var(--ground); color: #fff;
background: var(--accent); background: var(--accent);
border: 0; border: 0;
border-radius: 6px; border-radius: 6px;
@@ -219,7 +220,7 @@
.reveal .dns-result .type, .reveal .dns-result .type,
.reveal .dns-result .dim { color: var(--muted); } .reveal .dns-result .dim { color: var(--muted); }
.reveal .dns-result .addr { color: var(--accent); font-weight: 500; } .reveal .dns-result .addr { color: var(--accent); font-weight: 500; }
.reveal .dns-result .bad { color: #ff7a7a; } .reveal .dns-result .bad { color: #c62828; }
/* RIPE database objects, laid out like apps.db.ripe.net: a light page with /* RIPE database objects, laid out like apps.db.ripe.net: a light page with
one card per object, one row per attribute, references in blue, rows the one card per object, one row per attribute, references in blue, rows the
@@ -313,11 +314,58 @@
.diagram .pkt.ha rect { fill: #245c3a; } .diagram .pkt.ha rect { fill: #245c3a; }
.diagram .pkt.ha2 rect { fill: #2f7a4b; } .diagram .pkt.ha2 rect { fill: #2f7a4b; }
.diagram .pkt.hn rect { fill: #6b5a1f; } .diagram .pkt.hn rect { fill: #6b5a1f; }
.diagram text.pkt-name { font-size: 18px; font-weight: 500; } .diagram text.pkt-name { font-size: 18px; font-weight: 500; fill: #fff; }
.diagram text.pkt-size { font-size: 13px; font-family: "IBM Plex Mono", monospace; fill: var(--ink); opacity: 0.75; } .diagram text.pkt-size { font-size: 13px; font-family: "IBM Plex Mono", monospace; fill: #fff; opacity: 0.8; }
.diagram text.pkt-title { paint-order: stroke; stroke: var(--panel); stroke-width: 8px; stroke-linejoin: round; } .diagram text.pkt-title { paint-order: stroke; stroke: var(--panel); stroke-width: 8px; stroke-linejoin: round; }
.diagram .pkt-expand { stroke: var(--muted); stroke-width: 1.5; stroke-dasharray: 4 5; fill: none; } .diagram .pkt-expand { stroke: var(--muted); stroke-width: 1.5; stroke-dasharray: 4 5; fill: none; }
/* ---- byte walk: a hex dump and one row per field, in step on hover --------- */
.reveal .bytewalk { text-align: left; }
.reveal .bytewalk .bw-dump { margin-bottom: 8px; }
/* The lit bytes invert: a white block on the dark dump, visible from the back row. */
.reveal .bw-dump span.on { background: #fff; color: var(--code-ground); box-shadow: 0 0 0 3px var(--accent); }
.reveal .bw-rows {
position: relative; /* rows measure their offsetTop against the box */
height: 19em;
overflow-y: auto;
scrollbar-width: thin;
font-size: 46%;
line-height: 1.45;
border: 1px solid var(--rule);
border-radius: 6px;
background: rgba(var(--accent-rgb), 0.04);
}
.reveal .bw-group {
padding: 0.45em 0.8em 0.2em;
color: var(--accent);
font-weight: 700;
}
.reveal .bw-row {
display: grid;
grid-template-columns: 11em 12em 1fr;
gap: 0 1em;
padding: 0.18em 0.8em;
cursor: default;
outline: none;
}
.reveal .bw-row:hover,
.reveal .bw-row:focus,
.reveal .bw-row.on { background: rgba(var(--accent-rgb), 0.14); }
.reveal .bw-dump span[data-i] { cursor: pointer; }
.reveal .bw-row code.hh { background: rgba(52, 64, 110, 0.16); }
.reveal .bw-row code.hl { background: rgba(110, 52, 64, 0.16); }
.reveal .bw-row code.ha { background: rgba(36, 92, 58, 0.16); }
.reveal .bw-row code.ha2 { background: rgba(47, 122, 75, 0.16); }
.reveal .bw-row code.hn { background: rgba(107, 90, 31, 0.16); }
.reveal .bw-row code { display: block; color: var(--ink-strong); min-width: 0; max-width: 100%; justify-self: start; align-self: start; font-family: var(--r-code-font); border-radius: 3px; padding: 0 4px; white-space: normal; word-break: normal; }
.reveal .bw-row b { color: var(--ink-strong); font-weight: 500; }
.reveal .bw-row span { color: var(--ink); }
/* Small two-column explanation under a bytes block. */ /* Small two-column explanation under a bytes block. */
.reveal table.decode { .reveal table.decode {
font-size: 58%; font-size: 58%;
@@ -362,7 +410,7 @@
} }
.reveal .prefix-explorer .px-bits { margin: 0.5em 0 0.4em; min-height: 1.6em; white-space: nowrap; } .reveal .prefix-explorer .px-bits { margin: 0.5em 0 0.4em; min-height: 1.6em; white-space: nowrap; }
.reveal .prefix-explorer .px-bad { color: #ff7a7a; font-size: 70%; } .reveal .prefix-explorer .px-bad { color: #c62828; font-size: 70%; }
.reveal .px-range { font-family: var(--r-code-font); font-size: 50%; min-height: 10.5em; } .reveal .px-range { font-family: var(--r-code-font); font-size: 50%; min-height: 10.5em; }
@@ -400,7 +448,7 @@
} }
.bits .fixed { .bits .fixed {
color: var(--ground); color: #fff;
background: var(--accent); background: var(--accent);
border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px;
padding: 2px 6px; padding: 2px 6px;
@@ -502,7 +550,7 @@
.diagram .host { fill: var(--accent-soft); stroke: none; } .diagram .host { fill: var(--accent-soft); stroke: none; }
.diagram .traffic { .diagram .traffic {
stroke: #4fd98a; stroke: #1a8a4f;
stroke-width: 4; stroke-width: 4;
fill: none; fill: none;
} }
+37 -32
View File
@@ -1,12 +1,14 @@
/* /*
* The look of the deck. Structure borrowed from Matt Godbolt's newer talks * The look of the deck. Structure borrowed from Matt Godbolt's newer talks
* (hrt-theme.css in github.com/mattgodbolt/what-happens-beneath-accu-2026): * (hrt-theme.css in github.com/mattgodbolt/what-happens-beneath-accu-2026):
* near-black navy ground, every slide on a rounded panel, IBM Plex Sans with * every slide on a rounded panel, IBM Plex Sans with thin h1 and bold accent
* thin white h1 and bold accent h2, IBM Plex Mono code, accent-coloured list * h2, IBM Plex Mono code, accent-coloured list markers, links and progress
* markers, links and progress bar, glowing highlighted code lines. * bar, glowing highlighted code lines.
* *
* Made our own: the palette is ours (change it in :root below), and his * Made our own: a light palette (his is near-black; change it in :root
* "thread" corner motif becomes a chain of nodes and links, an AS path. * below), and his "thread" corner motif becomes a chain of nodes and links,
* an AS path. Code blocks and byte dumps stay dark on the light page so the
* monokai colours and the byte palette keep working.
* *
* Try accents without editing: index.html?accent=green (blue, green, * Try accents without editing: index.html?accent=green (blue, green,
* amber, magenta). Make one permanent by copying its values into :root. * amber, magenta). Make one permanent by copying its values into :root.
@@ -14,39 +16,42 @@
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&family=IBM+Plex+Mono:ital,wght@0,400;0,500;1,400&display=swap"); @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&family=IBM+Plex+Mono:ital,wght@0,400;0,500;1,400&display=swap");
:root { :root {
--ground: #11131f; /* behind the panels */ --ground: #e9edf5; /* behind the panels */
--panel: #1b1e33; /* the slide itself */ --panel: #ffffff; /* the slide itself */
--panel-edge: rgba(94, 177, 255, 0.14); --panel-edge: rgba(31, 111, 235, 0.22);
--ink: #eceff9; /* body text */ --ink: #1f2333; /* body text */
--ink-strong: #ffffff; /* h1, h3 */ --ink-strong: #0b0d17; /* h1, h3 */
--muted: #7d7f99; /* h4, captions */ --muted: #6b7088; /* h4, captions */
--rule: #4f526b; /* table rules, borders */ --rule: #c3c8d8; /* table rules, borders */
--accent: #5eb1ff; /* h2, markers, links, motif */ --accent: #1f6feb; /* h2, markers, links, motif */
--accent-soft: #b7dcff; /* inline code, emphasis */ --accent-soft: #0b4ea2; /* inline code, emphasis */
--accent-rgb: 94, 177, 255; --accent-rgb: 31, 111, 235;
/* Code blocks and byte dumps keep a dark ground of their own. */
--code-ground: #1b1e33;
--code-ink: #eceff9;
--r-code-font: "IBM Plex Mono", "SF Mono", Menlo, Consolas, monospace; --r-code-font: "IBM Plex Mono", "SF Mono", Menlo, Consolas, monospace;
--r-heading-text-shadow: none; --r-heading-text-shadow: none;
} }
:root[data-accent="green"] { :root[data-accent="green"] {
--accent: #4fd98a; --accent: #1a8a4f;
--accent-soft: #b5f0cf; --accent-soft: #0f6238;
--accent-rgb: 79, 217, 138; --accent-rgb: 26, 138, 79;
--panel-edge: rgba(79, 217, 138, 0.14); --panel-edge: rgba(26, 138, 79, 0.22);
} }
:root[data-accent="amber"] { :root[data-accent="amber"] {
--accent: #ffb020; --accent: #b86e00;
--accent-soft: #ffdc99; --accent-soft: #7a4a00;
--accent-rgb: 255, 176, 32; --accent-rgb: 184, 110, 0;
--panel-edge: rgba(255, 176, 32, 0.14); --panel-edge: rgba(184, 110, 0, 0.22);
} }
:root[data-accent="magenta"] { :root[data-accent="magenta"] {
--accent: #ff6ac1; --accent: #c2277f;
--accent-soft: #ffc2e6; --accent-soft: #8a1a5a;
--accent-rgb: 255, 106, 193; --accent-rgb: 194, 39, 127;
--panel-edge: rgba(255, 106, 193, 0.14); --panel-edge: rgba(194, 39, 127, 0.22);
} }
/* ---- ground and panels ---------------------------------------------------- */ /* ---- ground and panels ---------------------------------------------------- */
@@ -65,7 +70,7 @@
.reveal .slides section:not(.stack):not(.title-slide):not(.bare) { .reveal .slides section:not(.stack):not(.title-slide):not(.bare) {
background-color: var(--panel); background-color: var(--panel);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 30px rgba(20, 30, 60, 0.1);
border: 1px solid var(--panel-edge); border: 1px solid var(--panel-edge);
padding: 1em 0.8em; padding: 1em 0.8em;
box-sizing: border-box; box-sizing: border-box;
@@ -205,8 +210,8 @@
.reveal pre code { .reveal pre code {
display: block; display: block;
color: var(--ink); color: var(--code-ink);
background: rgba(8, 9, 20, 0.65); background: var(--code-ground);
border-radius: 6px; border-radius: 6px;
padding: 15px 20px; padding: 15px 20px;
max-height: 560px; max-height: 560px;
@@ -264,7 +269,7 @@
} }
.reveal .slide-number { .reveal .slide-number {
color: var(--ground); color: #fff;
font-family: var(--r-code-font); font-family: var(--r-code-font);
font-size: 18px; font-size: 18px;
font-weight: 500; font-weight: 500;
@@ -276,7 +281,7 @@
} }
::selection { ::selection {
color: var(--ground); color: #fff;
background: var(--accent); background: var(--accent);
} }