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>
231 lines
10 KiB
HTML
231 lines
10 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Hands-on BGP</title>
|
|
<!--
|
|
Deck structure and look follow Matt Godbolt's newer talks (e.g.
|
|
github.com/mattgodbolt/what-happens-beneath-accu-2026): reveal.js with
|
|
no stock theme, a custom dark theme (theme.css), monokai code blocks,
|
|
16:9, fade transitions, and one Markdown file per section.
|
|
|
|
reveal.js is loaded from ./reveal.js/dist if present (run
|
|
./vendor-reveal.sh once; works offline), otherwise from the CDN. Its
|
|
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=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>
|
|
<body>
|
|
<div class="reveal">
|
|
<div class="slides">
|
|
<!-- Title slide. For a full-bleed image instead, empty it and add
|
|
data-background="images/title.png" like Matt does. -->
|
|
<section class="title-slide">
|
|
<h1>Hands-on BGP</h1>
|
|
<h3>Insights into real world operations</h3>
|
|
<p class="byline">
|
|
Rayhaan Jaufeerally (rayhaan@rayhaan.ch) ·
|
|
Network Security · ETH Zürich, 2026
|
|
</p>
|
|
</section>
|
|
|
|
<!--
|
|
One Markdown file per section. Inside a file:
|
|
----- next horizontal slide
|
|
--- next vertical slide (goes down, for detail you may skip)
|
|
Notes: everything after this is speaker notes (press S)
|
|
-->
|
|
<section
|
|
data-markdown="sections/00-intro.md"
|
|
data-separator="^-----"
|
|
data-separator-vertical="^---"
|
|
data-separator-notes="^Notes:"
|
|
></section>
|
|
<section
|
|
data-markdown="sections/01-resources.md"
|
|
data-separator="^-----"
|
|
data-separator-vertical="^---"
|
|
data-separator-notes="^Notes:"
|
|
></section>
|
|
<section
|
|
data-markdown="sections/03-bgp-update.md"
|
|
data-separator="^-----"
|
|
data-separator-vertical="^---"
|
|
data-separator-notes="^Notes:"
|
|
></section>
|
|
<section
|
|
data-markdown="sections/04-transit-and-ix.md"
|
|
data-separator="^-----"
|
|
data-separator-vertical="^---"
|
|
data-separator-notes="^Notes:"
|
|
></section>
|
|
<section
|
|
data-markdown="sections/05-lab.md"
|
|
data-separator="^-----"
|
|
data-separator-vertical="^---"
|
|
data-separator-notes="^Notes:"
|
|
></section>
|
|
</div>
|
|
</div>
|
|
<!-- Sits where Matt's decks carry the employer logo. -->
|
|
<div class="wordmark"><b>AS210036</b> · netsec 2026</div>
|
|
|
|
<div id="deck-help" hidden>
|
|
<h2>The slides did not load</h2>
|
|
<p id="deck-help-why"></p>
|
|
<pre>python3 -m http.server 8000 --directory slides</pre>
|
|
<p>
|
|
then open <code>http://localhost:8000</code>. Details in
|
|
<code>slides/README.md</code>.
|
|
</p>
|
|
</div>
|
|
<!-- A classic script: it still runs when the browser refuses to run the
|
|
module below, which is exactly the case it has to explain. -->
|
|
<script>
|
|
(function () {
|
|
function help(why) {
|
|
document.getElementById("deck-help-why").textContent = why;
|
|
document.getElementById("deck-help").hidden = false;
|
|
}
|
|
if (location.protocol === "file:") {
|
|
help(
|
|
"This page was opened straight from disk. Browsers block a page opened that way from loading reveal.js and the section files, so serve the folder over HTTP:",
|
|
);
|
|
return;
|
|
}
|
|
window.addEventListener("deck-failed", function (e) {
|
|
help(
|
|
e.detail +
|
|
" Serve the folder over HTTP, and run slides/vendor-reveal.sh if there is no network:",
|
|
);
|
|
});
|
|
setTimeout(function () {
|
|
if (!window.deck)
|
|
help(
|
|
"reveal.js has not started after 10 seconds. Without a network it needs the local copy from slides/vendor-reveal.sh. Serve the folder like this:",
|
|
);
|
|
}, 10000);
|
|
})();
|
|
</script>
|
|
|
|
<script src="dns-lookup.js?v=7"></script>
|
|
<script src="prefix-explorer.js?v=4"></script>
|
|
|
|
<script type="module">
|
|
const VERSION = "6.0.2";
|
|
const LOCAL = "./reveal.js/dist";
|
|
const CDN = `https://cdn.jsdelivr.net/npm/reveal.js@${VERSION}/dist`;
|
|
|
|
// Prefer the vendored copy so the deck works without a network.
|
|
async function pickBase() {
|
|
try {
|
|
const r = await fetch(`${LOCAL}/reveal.css`, {
|
|
method: "HEAD",
|
|
});
|
|
if (r.ok) return LOCAL;
|
|
} catch (_) {}
|
|
return CDN;
|
|
}
|
|
const base = await pickBase();
|
|
const fail = (what) => (e) => {
|
|
window.dispatchEvent(
|
|
new CustomEvent("deck-failed", {
|
|
detail: `${what} (${e && e.message ? e.message : e}).`,
|
|
}),
|
|
);
|
|
throw e;
|
|
};
|
|
|
|
const deckStyle = document.getElementById("deck-style");
|
|
await Promise.all(
|
|
["reset.css", "reveal.css", "plugin/highlight/monokai.css"].map(
|
|
(href) =>
|
|
new Promise((resolve) => {
|
|
const link = document.createElement("link");
|
|
link.rel = "stylesheet";
|
|
link.href = `${base}/${href}`;
|
|
link.onload = link.onerror = resolve;
|
|
deckStyle.before(link);
|
|
}),
|
|
),
|
|
);
|
|
|
|
const [
|
|
{ default: Reveal },
|
|
{ default: Markdown },
|
|
{ default: Highlight },
|
|
{ default: Notes },
|
|
] = await Promise.all(
|
|
[
|
|
"reveal.mjs",
|
|
"plugin/markdown.mjs",
|
|
"plugin/highlight.mjs",
|
|
"plugin/notes.mjs",
|
|
].map((m) => import(/* @vite-ignore */ `${base}/${m}`)),
|
|
).catch(fail(`Could not load reveal.js from ${base}`));
|
|
|
|
// BIRD configuration, so router config on slides is highlighted. Same
|
|
// trick Matt uses for the assembly dialects in his talks.
|
|
function bird() {
|
|
return {
|
|
name: "bird",
|
|
keywords: {
|
|
keyword:
|
|
"protocol template filter function define table if then else case return accept reject " +
|
|
"import export where none all limit action block neighbor local as direct multihop passive " +
|
|
"description ipv4 ipv6 bgp kernel device static pipe include router id",
|
|
built_in:
|
|
"net proto source dest bgp_path bgp_community bgp_large_community bgp_ext_community " +
|
|
"bgp_med bgp_local_pref bgp_next_hop",
|
|
literal: "true false on off RTD_UNREACHABLE RTS_BGP",
|
|
},
|
|
contains: [
|
|
{ className: "comment", begin: "#", end: "$" },
|
|
{ className: "string", begin: '"', end: '"' },
|
|
{
|
|
className: "number",
|
|
begin: "\\b[0-9a-fA-F:]*::?[0-9a-fA-F:]*(/[0-9]+)?(\\{[0-9]+,[0-9]+\\})?\\b",
|
|
},
|
|
{
|
|
className: "number",
|
|
begin: "\\b[0-9]+(\\.[0-9]+){3}(/[0-9]+)?\\b",
|
|
},
|
|
{ className: "number", begin: "\\b[0-9]+\\b" },
|
|
],
|
|
};
|
|
}
|
|
|
|
// ?accent=green|amber|magenta previews another accent; blue is the default.
|
|
const accent = new URLSearchParams(location.search).get("accent");
|
|
if (accent) document.documentElement.dataset.accent = accent;
|
|
|
|
// 16:9. Nudge scaleFactor to fit the projector, as Matt does.
|
|
const scaleFactor = 0.65;
|
|
const deck = new Reveal({
|
|
hash: true,
|
|
transition: "fade",
|
|
slideNumber: false,
|
|
margin: 0.15,
|
|
width: 1920 * scaleFactor,
|
|
height: 1080 * scaleFactor,
|
|
markdown: { smartypants: true },
|
|
highlight: {
|
|
beforeHighlight: (hljs) =>
|
|
hljs.registerLanguage("bird", bird),
|
|
},
|
|
plugins: [Markdown, Highlight, Notes],
|
|
});
|
|
|
|
await deck.initialize().catch(fail("reveal.js failed to start"));
|
|
window.deck = deck; // handy in the console: deck.slide(3), deck.getTotalSlides()
|
|
// The slides exist now: scripts that fill live boxes wait for this.
|
|
window.dispatchEvent(new Event("deck-ready"));
|
|
</script>
|
|
</body>
|
|
</html>
|