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:
1 parent
8c6e8a80e1
commit
2910a2a800
10 files changed
+330
-90
No files matched your search
@@ -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
|
||||
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
|
||||
|
||||
Open <http://localhost:8000/?print-pdf> in Chrome and print to PDF with
|
||||
|
||||
Executable
+89
@@ -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()
|
||||
@@ -103,3 +103,38 @@
|
||||
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
@@ -15,8 +15,8 @@
|
||||
stylesheets are inserted BEFORE ours by the loader below, so theme.css
|
||||
and style.css always have the last word.
|
||||
-->
|
||||
<link rel="stylesheet" href="theme.css?v=2" id="deck-style" />
|
||||
<link rel="stylesheet" href="style.css?v=10" />
|
||||
<link rel="stylesheet" href="theme.css?v=3" id="deck-style" />
|
||||
<link rel="stylesheet" href="style.css?v=18" />
|
||||
<link rel="icon" href="images/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="shortcut icon" href="images/favicon.svg" />
|
||||
</head>
|
||||
@@ -29,8 +29,8 @@
|
||||
<h1>Hands-on BGP</h1>
|
||||
<h3>Insights into real world operations</h3>
|
||||
<p class="byline">
|
||||
Rayhaan Jaufeerally · Network Security ·
|
||||
ETH Zürich, 2026
|
||||
Rayhaan Jaufeerally (rayhaan@rayhaan.ch) ·
|
||||
Network Security · ETH Zürich, 2026
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
})();
|
||||
</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 type="module">
|
||||
|
||||
@@ -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>
|
||||
</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" -->
|
||||
|
||||
@@ -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
|
||||
|
||||
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">
|
||||
<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>
|
||||
@@ -162,3 +164,7 @@ Notes:
|
||||
Nobody "owns" numbers or addresses; they are registered to you. RFC 6996
|
||||
reserves the private ranges. The upstream router removes the private AS from
|
||||
the path, so the origin everybody else sees is AS210036.
|
||||
|
||||
---
|
||||
|
||||
## Handout Part 1
|
||||
@@ -1,11 +1,11 @@
|
||||
# BGP, down to the bytes
|
||||
# BGP in detail
|
||||
|
||||
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
|
||||
- Each side tells the other its best route per prefix
|
||||
@@ -21,13 +21,13 @@ Security and extensions have had to be bolted on, in perhaps strange ways.
|
||||
## The four message types
|
||||
|
||||
| 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 |
|
||||
| 3 | NOTIFICATION | something is wrong, I am closing the session |
|
||||
| 4 | KEEPALIVE | still here |
|
||||
| 3 | NOTIFICATION | errors and informational messages: e.g. rebooting |
|
||||
| 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" -->
|
||||
|
||||
@@ -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>
|
||||
<text x="859" y="59" class="pkt-name">type</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>
|
||||
<text x="95" y="169" class="pkt-name">version</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>
|
||||
|
||||
<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>
|
||||
|
||||
<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="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>
|
||||
<div class="bw-rows">
|
||||
<div class="bw-group">BGP header: 19 bytes, on every message</div>
|
||||
<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:
|
||||
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">
|
||||
<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>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ exchange physically is.
|
||||
|
||||
- Usually **free**, by mutual benefit
|
||||
- You exchange _your own_ routes, and your customers'
|
||||
- Traffic between the two of you only
|
||||
- Traffic between the locally connected parties only
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,13 +62,12 @@ Notes:
|
||||
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.
|
||||
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
|
||||
|
||||
- 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
|
||||
- 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)
|
||||
@@ -82,14 +81,25 @@ Notes:
|
||||
|
||||
## 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
|
||||
- It is **not** in the traffic path, and leaves the next hop alone
|
||||
- It does not add its own AS to the path
|
||||
- You steer it with communities: `42476:0:<as>` "not to them", `42476:1:<as>` "only to them"
|
||||
- Most participants peer with the route server and annouce their prefixes there
|
||||
- The route server can then distribute these routes to every other participant
|
||||
- Simplifes from `n²` to `n` sessions
|
||||
- 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:
|
||||
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
|
||||
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/
|
||||
@@ -38,9 +38,9 @@ the right of pr01 is the actual Internet.
|
||||
|
||||
`2a0d:d741:<n>::/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
|
||||
* One prefix each
|
||||
- `<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
|
||||
- One prefix each
|
||||
|
||||
Notes:
|
||||
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
|
||||
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
|
||||
|
||||
Notes:
|
||||
@@ -65,14 +65,14 @@ your prefix depends only on whether your announcement got out.
|
||||
## Steering your route
|
||||
|
||||
| You add | Who acts | Effect |
|
||||
|---|---|---|
|
||||
| ----------------------------- | -------- | --------------------------------- |
|
||||
| nothing | | announced everywhere |
|
||||
| `210036:0:0` | pr01 | announced nowhere |
|
||||
| `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 |
|
||||
| `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" -->
|
||||
|
||||
@@ -83,13 +83,7 @@ wins. The preset menu next to the field has all of these.
|
||||
|
||||
---
|
||||
|
||||
## Your turn
|
||||
|
||||
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
|
||||
## Handout Part 2 & 3
|
||||
|
||||
Notes:
|
||||
Looking glasses: bgp.tools, RIPEstat, and the exchange's own. Global
|
||||
|
||||
+60
-12
@@ -76,7 +76,7 @@
|
||||
|
||||
.reveal img.blur-edges {
|
||||
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,
|
||||
@@ -110,11 +110,11 @@
|
||||
}
|
||||
|
||||
.boxes > div.ok {
|
||||
border-color: #4fd98a;
|
||||
border-color: #1a8a4f;
|
||||
}
|
||||
|
||||
.boxes > div.bad {
|
||||
border-color: #ff6b6b;
|
||||
border-color: #d64545;
|
||||
}
|
||||
|
||||
/* ---- BGP bytes, matching the four groups of the /statusz bytes view ---------- */
|
||||
@@ -124,17 +124,18 @@
|
||||
line-height: 1.9;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
background: rgba(8, 9, 20, 0.65);
|
||||
background: var(--code-ground);
|
||||
border-radius: 6px;
|
||||
padding: 15px 20px;
|
||||
box-sizing: border-box;
|
||||
color: var(--ink);
|
||||
color: var(--code-ink);
|
||||
}
|
||||
|
||||
.bytes span,
|
||||
.legend span {
|
||||
border-radius: 3px;
|
||||
padding: 1px 3px;
|
||||
color: var(--code-ink); /* the fills are dark on any ground */
|
||||
}
|
||||
|
||||
.bytes .hh,
|
||||
@@ -191,7 +192,7 @@
|
||||
padding: 0.3em 0.9em;
|
||||
font: inherit;
|
||||
font-size: 0.8em;
|
||||
color: var(--ground);
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
@@ -219,7 +220,7 @@
|
||||
.reveal .dns-result .type,
|
||||
.reveal .dns-result .dim { color: var(--muted); }
|
||||
.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
|
||||
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.ha2 rect { fill: #2f7a4b; }
|
||||
.diagram .pkt.hn rect { fill: #6b5a1f; }
|
||||
.diagram text.pkt-name { font-size: 18px; font-weight: 500; }
|
||||
.diagram text.pkt-size { font-size: 13px; font-family: "IBM Plex Mono", monospace; fill: var(--ink); opacity: 0.75; }
|
||||
.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: #fff; opacity: 0.8; }
|
||||
.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; }
|
||||
|
||||
/* ---- 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. */
|
||||
.reveal table.decode {
|
||||
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-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; }
|
||||
|
||||
@@ -400,7 +448,7 @@
|
||||
}
|
||||
|
||||
.bits .fixed {
|
||||
color: var(--ground);
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
border-radius: 4px 0 0 4px;
|
||||
padding: 2px 6px;
|
||||
@@ -502,7 +550,7 @@
|
||||
.diagram .host { fill: var(--accent-soft); stroke: none; }
|
||||
|
||||
.diagram .traffic {
|
||||
stroke: #4fd98a;
|
||||
stroke: #1a8a4f;
|
||||
stroke-width: 4;
|
||||
fill: none;
|
||||
}
|
||||
+37
-32
@@ -1,12 +1,14 @@
|
||||
/*
|
||||
* 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):
|
||||
* near-black navy ground, every slide on a rounded panel, IBM Plex Sans with
|
||||
* thin white h1 and bold accent h2, IBM Plex Mono code, accent-coloured list
|
||||
* markers, links and progress bar, glowing highlighted code lines.
|
||||
* every slide on a rounded panel, IBM Plex Sans with thin h1 and bold accent
|
||||
* h2, IBM Plex Mono code, accent-coloured list markers, links and progress
|
||||
* bar, glowing highlighted code lines.
|
||||
*
|
||||
* Made our own: the palette is ours (change it in :root below), and his
|
||||
* "thread" corner motif becomes a chain of nodes and links, an AS path.
|
||||
* Made our own: a light palette (his is near-black; change it in :root
|
||||
* 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,
|
||||
* 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");
|
||||
|
||||
:root {
|
||||
--ground: #11131f; /* behind the panels */
|
||||
--panel: #1b1e33; /* the slide itself */
|
||||
--panel-edge: rgba(94, 177, 255, 0.14);
|
||||
--ink: #eceff9; /* body text */
|
||||
--ink-strong: #ffffff; /* h1, h3 */
|
||||
--muted: #7d7f99; /* h4, captions */
|
||||
--rule: #4f526b; /* table rules, borders */
|
||||
--accent: #5eb1ff; /* h2, markers, links, motif */
|
||||
--accent-soft: #b7dcff; /* inline code, emphasis */
|
||||
--accent-rgb: 94, 177, 255;
|
||||
--ground: #e9edf5; /* behind the panels */
|
||||
--panel: #ffffff; /* the slide itself */
|
||||
--panel-edge: rgba(31, 111, 235, 0.22);
|
||||
--ink: #1f2333; /* body text */
|
||||
--ink-strong: #0b0d17; /* h1, h3 */
|
||||
--muted: #6b7088; /* h4, captions */
|
||||
--rule: #c3c8d8; /* table rules, borders */
|
||||
--accent: #1f6feb; /* h2, markers, links, motif */
|
||||
--accent-soft: #0b4ea2; /* inline code, emphasis */
|
||||
--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-heading-text-shadow: none;
|
||||
}
|
||||
|
||||
:root[data-accent="green"] {
|
||||
--accent: #4fd98a;
|
||||
--accent-soft: #b5f0cf;
|
||||
--accent-rgb: 79, 217, 138;
|
||||
--panel-edge: rgba(79, 217, 138, 0.14);
|
||||
--accent: #1a8a4f;
|
||||
--accent-soft: #0f6238;
|
||||
--accent-rgb: 26, 138, 79;
|
||||
--panel-edge: rgba(26, 138, 79, 0.22);
|
||||
}
|
||||
|
||||
:root[data-accent="amber"] {
|
||||
--accent: #ffb020;
|
||||
--accent-soft: #ffdc99;
|
||||
--accent-rgb: 255, 176, 32;
|
||||
--panel-edge: rgba(255, 176, 32, 0.14);
|
||||
--accent: #b86e00;
|
||||
--accent-soft: #7a4a00;
|
||||
--accent-rgb: 184, 110, 0;
|
||||
--panel-edge: rgba(184, 110, 0, 0.22);
|
||||
}
|
||||
|
||||
:root[data-accent="magenta"] {
|
||||
--accent: #ff6ac1;
|
||||
--accent-soft: #ffc2e6;
|
||||
--accent-rgb: 255, 106, 193;
|
||||
--panel-edge: rgba(255, 106, 193, 0.14);
|
||||
--accent: #c2277f;
|
||||
--accent-soft: #8a1a5a;
|
||||
--accent-rgb: 194, 39, 127;
|
||||
--panel-edge: rgba(194, 39, 127, 0.22);
|
||||
}
|
||||
|
||||
/* ---- ground and panels ---------------------------------------------------- */
|
||||
@@ -65,7 +70,7 @@
|
||||
.reveal .slides section:not(.stack):not(.title-slide):not(.bare) {
|
||||
background-color: var(--panel);
|
||||
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);
|
||||
padding: 1em 0.8em;
|
||||
box-sizing: border-box;
|
||||
@@ -205,8 +210,8 @@
|
||||
|
||||
.reveal pre code {
|
||||
display: block;
|
||||
color: var(--ink);
|
||||
background: rgba(8, 9, 20, 0.65);
|
||||
color: var(--code-ink);
|
||||
background: var(--code-ground);
|
||||
border-radius: 6px;
|
||||
padding: 15px 20px;
|
||||
max-height: 560px;
|
||||
@@ -264,7 +269,7 @@
|
||||
}
|
||||
|
||||
.reveal .slide-number {
|
||||
color: var(--ground);
|
||||
color: #fff;
|
||||
font-family: var(--r-code-font);
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
@@ -276,7 +281,7 @@
|
||||
}
|
||||
|
||||
::selection {
|
||||
color: var(--ground);
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user