slides: "Hands-on BGP", restructured into five sections

- Title is now "Hands-on BGP"; the AS-path diagram is gone from the cover.
- Sections: IPv6 addresses and prefixes; where AS numbers and prefixes
  come from (IANA, RIRs, the RIPE database, IRR vs RPKI); BGP and the
  bytes of an UPDATE; IP transit vs Internet exchanges (the peering LAN as
  an Ethernet segment, route servers); the lab (ferrix, pr01.rue, SwissIX,
  NL-ix, France-IX, DE-CIX, transit).
- The UPDATE walked through in section 3 is a real 94-byte capture from
  ferrix for 2a0d:d741:1::/48. Vertical slides light up one part at a
  time with its decode; the generator asserted every quoted number
  against the capture. Session counts come from the tracked bird.conf,
  the route6 object from the RPSL file, SwissIX's LAN from PeeringDB.
- New components: focusable bytes, decode table, prefix bits, and SVG
  diagrams drawn in the theme's colours.
- Opening index.html straight from disk used to give a title and empty
  panels, because browsers block the module and the section files there.
  The page now explains that, and also reports a failed reveal.js load.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-09-20 21:41:31 +02:00
1 parent 106edd1df0
commit 1399cbd39f
15 files changed
+754 -245

No files matched your search

+24 -4
View File
@@ -7,7 +7,22 @@ Sans and Mono, monokai code with glowing stepped lines, 16:9 with fade
transitions, and the content in one Markdown file per section.
What is ours: the palette, and the corner motif. His ring-and-thread became a
chain of nodes and links, an AS path, which also opens the title slide.
chain of nodes and links, an AS path.
## The talk
| Section | File |
|---|---|
| IP addresses and prefixes (IPv6) | `sections/01-addresses.md` |
| Where AS numbers and prefixes come from (IANA, RIPE) | `sections/02-registries.md` |
| BGP and the bytes of an UPDATE | `sections/03-bgp-update.md` |
| IP transit and Internet exchanges | `sections/04-transit-and-ix.md` |
| The lab: ferrix, pr01.rue and its exchanges | `sections/05-lab.md` |
The UPDATE in section 3 is a real one captured from ferrix. Its byte slides
step through it one part at a time (press down); every number on them was
checked against the capture when the slides were generated. If you replace the
capture, regenerate rather than edit the hex by hand.
| File | Holds |
|---|---|
@@ -27,8 +42,10 @@ For a full-bleed title image, empty the title `<section>` and give it
## Show it
The sections are loaded over HTTP, so open the deck through a server, not as a
file:
The sections are loaded over HTTP, so open the deck through a server, not by
double-clicking `index.html`. Opened from disk, browsers block reveal.js and
the section files, and you would see a title and empty panels; the page now
says so instead:
python3 -m http.server 8000 --directory slides
@@ -69,7 +86,10 @@ Apply a class to the element above with `<!-- .element: class="..." -->`.
Blocks: `<div class="cols">` two columns, `<div class="boxes">` labelled cards
(`us`, `ok`, `bad`), `<pre class="bytes">` BGP bytes in the colours of the lab
page. Code fences take a language and optional line steps, e.g.
page (add `focus`, and `on` to the spans to keep lit, to dim the rest),
`<table class="decode">` for the explanation under them,
`<svg class="diagram">` for diagrams drawn in the theme's colours (`hot`,
`fabric`, `link`, `bgp`, `traffic`). Code fences take a language and optional line steps, e.g.
` ```bird [1-3|5-9] `; `bird` is a small highlighter defined in `index.html`.
`<!-- .slide: class="bare" -->` drops the panel, the motif and the wordmark
-5
View File
@@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 480" font-family="sans-serif">
<rect width="960" height="480" rx="12" fill="#11131f" stroke="#4f526b" stroke-dasharray="10 8"/>
<text x="480" y="230" text-anchor="middle" font-size="40" fill="#eceff9">your picture here</text>
<text x="480" y="280" text-anchor="middle" font-size="22" fill="#7d7f99">slides/images/</text>
</svg>

Before

Width:  |  Height:  |  Size: 391 B

+39 -18
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BGP hands-on: announce your own prefix</title>
<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
@@ -26,16 +26,8 @@
<!-- Title slide. For a full-bleed image instead, empty it and add
data-background="images/title.png" like Matt does. -->
<section class="title-slide">
<svg class="as-path" viewBox="0 0 1000 90" preserveAspectRatio="xMinYMid meet" aria-hidden="true">
<line x1="20" y1="30" x2="980" y2="30" />
<g class="you"><circle cx="20" cy="30" r="11" /><text x="4" y="72">you</text></g>
<g><circle cx="250" cy="30" r="11" /><text x="196" y="72">AS4200000001</text></g>
<g><circle cx="500" cy="30" r="11" /><text x="463" y="72">AS210036</text></g>
<g><circle cx="750" cy="30" r="11" /><text x="722" y="72">AS42476</text></g>
<g><circle cx="980" cy="30" r="11" /><text x="890" y="72">the Internet</text></g>
</svg>
<h1>Announce your own prefix</h1>
<h3>A hands-on introduction to BGP</h3>
<h1>Hands-on BGP</h1>
<h3>Announce your own prefix to the Internet</h3>
<p class="byline">Rayhaan Jaufeerally &middot; Network Security &middot; ETH Z&uuml;rich, 2026</p>
</section>
@@ -45,16 +37,41 @@
--- next vertical slide (goes down, for detail you may skip)
Notes: everything after this is speaker notes (press S)
-->
<section data-markdown="sections/01-intro.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
<section data-markdown="sections/02-bgp.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
<section data-markdown="sections/03-lab.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
<section data-markdown="sections/04-communities.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
<section data-markdown="sections/05-wrap-up.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
<section data-markdown="sections/01-addresses.md" data-separator="^-----" data-separator-vertical="^---" data-separator-notes="^Notes:"></section>
<section data-markdown="sections/02-registries.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>ferrix</b>_bgp &middot; 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 type="module">
const VERSION = "6.0.2";
const LOCAL = "./reveal.js/dist";
@@ -69,6 +86,10 @@
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(
@@ -89,7 +110,7 @@
["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.
@@ -136,7 +157,7 @@
plugins: [Markdown, Highlight, Notes],
});
await deck.initialize();
await deck.initialize().catch(fail("reveal.js failed to start"));
window.deck = deck; // handy in the console: deck.slide(3), deck.getTotalSlides()
</script>
</body>
+92
View File
@@ -0,0 +1,92 @@
# IP addresses and prefixes
Notes:
Section 1. IPv6 only: the whole lab is IPv6, and the ideas carry over to IPv4
unchanged (32 bits, dotted decimal, /24 where we say /48).
-----
## An IPv6 address is 128 bits
```text
2a0d:d741:0001:0000:0000:0000:0000:0001 eight groups of 16 bits, in hex
2a0d:d741:1:0:0:0:0:1 drop leading zeros in a group
2a0d:d741:1::1 one run of zero groups becomes ::
```
`::` may appear once, or the address would be ambiguous.
<!-- .element: class="note" -->
Notes:
Ask: why only once? Because 2a0d::1::1 could put the zeros in several places.
-----
## A prefix is a range of addresses
`2a0d:d741:1::/48`
<p class="bits"><span class="fixed">2a0d:d741:0001</span><span class="free">xxxx:xxxx:xxxx:xxxx:xxxx</span></p>
The first <span class="fragment pop">48</span> bits are fixed. The other 80 are yours.
Notes:
2^80 addresses in one student prefix. About 1.2 million billion billion.
The /n is a count of leading bits, nothing more.
-----
## Prefixes nest
| Prefix | Who | Holds |
|---|---|---|
| `2a0d:d740::/29` | the network we borrow from | 524,288 /48s |
| `2a0d:d741::/40` | this lab | 256 /48s |
| `2a0d:d741:<n>::/48` | **you** | 65,536 /64s |
| `…/64` | one LAN | the hosts on it |
`<n>` is your student number, written *in hex*.
<!-- .element: class="fragment note" -->
Notes:
Student 10 is 2a0d:d741:a::/48, student 16 is 2a0d:d741:10::/48.
A /40 holds exactly 256 /48s, which caps the class size at 255.
-----
## The most specific route wins
```text [1-3|3]
2a0d:d740::/29 via AS210036 the aggregate
2a0d:d741::/40 via AS210036 the lab
2a0d:d741:1::/48 via AS210036 AS… student 1 <-- chosen for 2a0d:d741:1::1
```
Routers forward on the *longest matching prefix*, before anything else is compared.
<!-- .element: class="note" -->
Notes:
This is the single most important forwarding rule for today. It is also why a
hijacker announces a more specific prefix: it wins everywhere it is accepted,
no matter how good the legitimate route is.
-----
## How small can a prefix be?
<div class="boxes">
<div class="ok">IPv6: up to <b>/48</b></div>
<div class="ok">IPv4: up to <b>/24</b></div>
<div class="bad">anything longer: filtered by almost everyone</div>
</div>
Not a protocol rule. A convention, so the global table stays finite.
<!-- .element: class="note" -->
Notes:
Which is why the lab hands out /48s: the smallest thing the Internet at large
will carry. The global IPv6 table is around 230k routes, IPv4 around 1M.
-31
View File
@@ -1,31 +0,0 @@
## Today
* What BGP is, in ten minutes <!-- .element: class="fragment" -->
* You announce a real prefix to the real Internet <!-- .element: class="fragment" -->
* You steer where it goes with communities <!-- .element: class="fragment" -->
* We look at what could go wrong <!-- .element: class="fragment" -->
Notes:
Speaker notes go after a line that starts with "Notes:". Press S to open the
speaker view; it shows these, a timer, and the next slide.
-----
The Internet is about
<span class="fragment pop">75,000</span>
networks that agreed to talk to each other.
<!-- .element: class="big" -->
Notes:
One idea per slide, large. The class="big" comment applies to the paragraph
above it. "pop" is a fragment that grows when it appears.
A slide with class "bare" drops the panel and the path motif: <!-- .slide: class="bare" -->
-----
<img class="r-stretch blur-edges" src="images/placeholder.svg" alt="Replace with a picture">
Replace `images/placeholder.svg`. `r-stretch` makes an image fill the slide.
<!-- .element: class="attribution" -->
-53
View File
@@ -1,53 +0,0 @@
## An autonomous system
<div class="boxes">
<div class="us">AS210036<br>our upstream</div>
<div>AS559<br>SWITCH</div>
<div>AS6939<br>Hurricane Electric</div>
<div>AS13335<br>Cloudflare</div>
</div>
A network under one administration, with a number.
Notes:
"boxes" lays out small labelled cards. Classes: us, ok, bad.
-----
## A route is a prefix plus a promise
```text
2a0d:d741:1::/48 via AS210036 AS4200000001
^ nearest ^ origin
```
"Send me traffic for this prefix. It crossed these networks to reach you."
<!-- .element: class="note" -->
-----
## What actually goes on the wire
<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>
<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 50</span> <span class="hh">02</span> <span class="hl">00 00</span> <span class="hl">00 35</span> <span class="ha">40 01 01 00</span> <span class="ha2">40 02 0a 02 02 00 00 fd e8 00 00 fd e8</span> <span class="ha">40 03 04 7f 00 00 01</span> <span class="ha2">80 04 04 00 00 00 32</span> <span class="ha">c0 08 04 fd e8 00 64</span> <span class="hn">18 0a 64 01</span></pre>
Same colours as the bytes view on the lab page, so you can point from one to the other.
<!-- .element: class="note" -->
---
## Going deeper (a vertical slide)
Press <kbd>↓</kbd>. Put detail here that you can skip when short of time.
| Attribute | Type | Meaning |
|---|---|---|
| ORIGIN | 1 | how the route entered BGP |
| AS_PATH | 2 | networks it crossed |
| COMMUNITIES | 8 | 32-bit tags, meaning by agreement |
| LARGE_COMMUNITIES | 32 | three 32-bit numbers |
<!-- .element: class="borders" -->
+89
View File
@@ -0,0 +1,89 @@
# Where numbers come from
Notes:
Section 2. Nobody "owns" addresses or AS numbers; they are registered to you.
-----
## A hierarchy of registries
<div class="boxes">
<div>IANA<br><span class="small">the root</span></div>
<div class="us">RIPE NCC<br><span class="small">Europe, Middle East</span></div>
<div>ARIN</div>
<div>APNIC</div>
<div>LACNIC</div>
<div>AFRINIC</div>
</div>
IANA hands large blocks to five regional registries. They hand smaller ones to members (LIRs), who assign to end users.
Notes:
RIR = Regional Internet Registry. LIR = Local Internet Registry, a member of
an RIR, typically an ISP. Our space comes from RIPE NCC via an LIR.
-----
## AS numbers
* 16 bits at first: `1` to `65535` <!-- .element: class="fragment" -->
* 32 bits since RFC 6793: up to `4294967295` <!-- .element: class="fragment" -->
* Old routers see `AS23456` (AS_TRANS) in place of a big one <!-- .element: class="fragment" -->
* Private use: `64512–65534` and `4200000000–4294967294` <!-- .element: class="fragment" -->
The lab daemon is `AS4200000001`. It is stripped before the Internet sees your route.
<!-- .element: class="fragment note" -->
Notes:
RFC 6996 reserves the private ranges. The upstream router removes the private
AS from the path, so the origin everybody else sees is AS210036.
-----
## The RIPE database says who may announce what
```text [1-5|3]
route6: 2a0d:d741:1::/48
descr: ETHZ Netsec 2026 BGP lab - student 1
origin: AS210036
mnt-by: mnt-ch-ray-1
source: RIPE
```
One of these exists for every student prefix. Networks build their filters from them.
<!-- .element: class="note" -->
Notes:
This is a real object; try the whois on the next slide. `mnt-by` names the
maintainer: whoever holds its credentials can change the object. An `inet6num`
object records the address block itself, a `route6` object who may originate it.
-----
## Two ways to say "this origin is allowed"
| | IRR (`route6`) | RPKI (ROA) |
|---|---|---|
| What it is | an entry in a database | a signed certificate |
| Who can create it | whoever holds the maintainer | only the holder of the addresses |
| Says | prefix + origin AS | prefix + origin AS + **max length** |
| Used for | building prefix filters | dropping "invalid" routes |
Notes:
Max length matters for us: a ROA for the /40 with max length 40 would make
every student /48 RPKI-invalid, and many networks would drop it.
-----
## Look it up yourself
```bash
whois -h whois.ripe.net 2a0d:d741:1::/48
whois -h whois.ripe.net AS210036
```
Or in a browser: RIPEstat, bgp.tools, or a looking glass at an exchange.
<!-- .element: class="note" -->
+147
View File
@@ -0,0 +1,147 @@
# BGP, down to the bytes
Notes:
Section 3. The aim: by the end, the bytes view on the lab page reads like text.
-----
## BGP in one slide
* Runs over TCP, port `179`, between neighbours you configure by hand <!-- .element: class="fragment" -->
* Each side tells the other its best route per prefix <!-- .element: class="fragment" -->
* Only *changes* are sent: no periodic refresh <!-- .element: class="fragment" -->
* If the session drops, every route learned over it is gone <!-- .element: class="fragment" -->
Notes:
No discovery, no authentication of content, no notion of truth. A neighbour
says "I can reach X" and you believe it, subject to your filters.
-----
## Four kinds of message
| Type | Name | Says |
|---|---|---|
| 1 | OPEN | who I am: 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 |
OPEN, OPEN, KEEPALIVE, KEEPALIVE, and then it is all UPDATEs.
<!-- .element: class="fragment note" -->
Notes:
There is a fifth, ROUTE-REFRESH: "send me everything again, my policy changed".
-----
## The shape of an UPDATE
| Bytes | Field |
|---|---|
| 19 | header: marker, length, type |
| 2 + n | withdrawn routes: prefixes that are no longer reachable |
| 2 + n | path attributes: everything *about* the route |
| the rest | NLRI: the IPv4 prefixes being announced |
One UPDATE carries one set of attributes, and every prefix that shares them.
<!-- .element: class="note" -->
-----
## A real one: 94 bytes
<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>
<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 5e</span> <span class="hh">02</span> <span class="hl">00 00</span> <span class="hl">00 47</span> <span class="ha">40 01 01 00</span> <span class="ha2">40 02 06 02 01 fa 56 ea 01</span> <span class="ha">80 0e 1c 00 02 01 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 30 2a 0d d7 41 00 01</span> <span class="ha2">c0 20 18 00 03 34 74 00 00 00 00 00 00 00 00 00 03 34 74 00 00 00 01 00 00 a5 ec</span></pre>
`2a0d:d741:1::/48`, as the lab daemon sent it. The same view is on the lab page under *bytes*.
<!-- .element: class="note" -->
Notes:
Captured from ferrix, not typed. Press down to walk through it part by part.
Note there is nothing yellow: the NLRI field is empty. Hold that thought.
---
## Header
<pre class="bytes focus"><span class="hh on">ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff</span> <span class="hh on">00 5e</span> <span class="hh on">02</span> <span class="hl">00 00</span> <span class="hl">00 47</span> <span class="ha">40 01 01 00</span> <span class="ha2">40 02 06 02 01 fa 56 ea 01</span> <span class="ha">80 0e 1c 00 02 01 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 30 2a 0d d7 41 00 01</span> <span class="ha2">c0 20 18 00 03 34 74 00 00 00 00 00 00 00 00 00 03 34 74 00 00 00 01 00 00 a5 ec</span></pre>
<table class="decode"><tr><td>ff × 16</td><td>marker. Once carried authentication, now just filler to find message boundaries</td></tr><tr><td>00 5e</td><td>length of the whole message: 94 bytes (maximum 4096)</td></tr><tr><td>02</td><td>type 2 = UPDATE</td></tr></table>
---
## Two lengths
<pre class="bytes focus"><span class="hh">ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff</span> <span class="hh">00 5e</span> <span class="hh">02</span> <span class="hl on">00 00</span> <span class="hl on">00 47</span> <span class="ha">40 01 01 00</span> <span class="ha2">40 02 06 02 01 fa 56 ea 01</span> <span class="ha">80 0e 1c 00 02 01 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 30 2a 0d d7 41 00 01</span> <span class="ha2">c0 20 18 00 03 34 74 00 00 00 00 00 00 00 00 00 03 34 74 00 00 00 01 00 00 a5 ec</span></pre>
<table class="decode"><tr><td>00 00</td><td>0 bytes of withdrawn routes: nothing is being taken back</td></tr><tr><td>00 47</td><td>71 bytes of path attributes follow</td></tr><tr><td></td><td>94 − 19 − 2 − 2 − 71 = 0 bytes left over for NLRI</td></tr></table>
---
## ORIGIN
<pre class="bytes focus"><span class="hh">ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff</span> <span class="hh">00 5e</span> <span class="hh">02</span> <span class="hl">00 00</span> <span class="hl">00 47</span> <span class="ha on">40 01 01 00</span> <span class="ha2">40 02 06 02 01 fa 56 ea 01</span> <span class="ha">80 0e 1c 00 02 01 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 30 2a 0d d7 41 00 01</span> <span class="ha2">c0 20 18 00 03 34 74 00 00 00 00 00 00 00 00 00 03 34 74 00 00 00 01 00 00 a5 ec</span></pre>
<table class="decode"><tr><td>40</td><td>flags: well-known, transitive</td></tr><tr><td>01</td><td>type 1 = ORIGIN</td></tr><tr><td>01</td><td>1 byte of value</td></tr><tr><td>00</td><td>IGP: the route was originated inside this AS</td></tr></table>
Every attribute is *flags, type, length, value*.
<!-- .element: class="note" -->
---
## AS_PATH
<pre class="bytes focus"><span class="hh">ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff</span> <span class="hh">00 5e</span> <span class="hh">02</span> <span class="hl">00 00</span> <span class="hl">00 47</span> <span class="ha">40 01 01 00</span> <span class="ha2 on">40 02 06 02 01 fa 56 ea 01</span> <span class="ha">80 0e 1c 00 02 01 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 30 2a 0d d7 41 00 01</span> <span class="ha2">c0 20 18 00 03 34 74 00 00 00 00 00 00 00 00 00 03 34 74 00 00 00 01 00 00 a5 ec</span></pre>
<table class="decode"><tr><td>40 02 06</td><td>well-known, transitive; type 2; 6 bytes</td></tr><tr><td>02</td><td>an AS_SEQUENCE, order matters</td></tr><tr><td>01</td><td>of 1 AS number</td></tr><tr><td>fa 56 ea 01</td><td>4200000001, the lab daemon. Four bytes each (RFC 6793)</td></tr></table>
Each network prepends itself on the way out. The length of this list is what best-path selection compares.
<!-- .element: class="note" -->
---
## MP_REACH_NLRI: where IPv6 lives
<pre class="bytes focus"><span class="hh">ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff</span> <span class="hh">00 5e</span> <span class="hh">02</span> <span class="hl">00 00</span> <span class="hl">00 47</span> <span class="ha">40 01 01 00</span> <span class="ha2">40 02 06 02 01 fa 56 ea 01</span> <span class="ha on">80 0e 1c 00 02 01 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 30 2a 0d d7 41 00 01</span> <span class="ha2">c0 20 18 00 03 34 74 00 00 00 00 00 00 00 00 00 03 34 74 00 00 00 01 00 00 a5 ec</span></pre>
<table class="decode"><tr><td>80 0e 1c</td><td>optional, non-transitive; type 14; 28 bytes</td></tr><tr><td>00 02 · 01</td><td>address family IPv6, unicast</td></tr><tr><td>10 · 00…01</td><td>next hop, 16 bytes: ::1</td></tr><tr><td>00</td><td>reserved</td></tr><tr><td>30 · 2a 0d d7 41 00 01</td><td>48 bits of prefix: 2a0d:d741:1::/48</td></tr></table>
Notes:
BGP-4 was built for IPv4: its NLRI field cannot carry anything else. RFC 4760
bolts other address families on as an attribute, each with its own next hop.
A prefix is sent as a bit count plus only as many bytes as the count needs.
The next hop is ::1 because the daemon and the router share a machine.
---
## LARGE_COMMUNITIES
<pre class="bytes focus"><span class="hh">ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff</span> <span class="hh">00 5e</span> <span class="hh">02</span> <span class="hl">00 00</span> <span class="hl">00 47</span> <span class="ha">40 01 01 00</span> <span class="ha2">40 02 06 02 01 fa 56 ea 01</span> <span class="ha">80 0e 1c 00 02 01 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 30 2a 0d d7 41 00 01</span> <span class="ha2 on">c0 20 18 00 03 34 74 00 00 00 00 00 00 00 00 00 03 34 74 00 00 00 01 00 00 a5 ec</span></pre>
<table class="decode"><tr><td>c0 20 18</td><td>optional, transitive; type 32; 24 bytes = two communities</td></tr><tr><td>00033474 · 0 · 0</td><td>210036:0:0 our router: announce nowhere…</td></tr><tr><td>00033474 · 1 · 0000a5ec</td><td>210036:1:42476 …except to AS42476, the SwissIX route servers</td></tr></table>
Three 32-bit numbers. They mean whatever the receiving network says they mean.
<!-- .element: class="note" -->
-----
## The flags byte
| Bit | Set means | Clear means |
|---|---|---|
| `0x80` | optional | well-known: every BGP speaker must understand it |
| `0x40` | transitive: pass it on, even if not understood | drop it at the next hop |
| `0x20` | partial: someone on the way did not understand it | |
| `0x10` | length field is 2 bytes | 1 byte |
Transitive is why a community you set can act three networks away.
<!-- .element: class="fragment note" -->
-61
View File
@@ -1,61 +0,0 @@
## The lab
<div class="cols">
<div>
* Open the status page
* Your prefix is `2a0d:d741:<n>::/48`
* `n` is your number, **in hex**
* Use your name as owner
</div>
<div>
```text
you ─▶ ferrix ─▶ AS210036 ─▶ SwissIX ─▶ …
AS42000… (BIRD)
```
</div>
</div>
Notes:
"cols" gives two equal columns. Leave a blank line after the opening div and
before the closing one, or the Markdown inside is not parsed.
-----
## The router's side
```bird [1-3|5-9|11]
filter ferrix_import {
if !(net ~ [ 2a0d:d741::/40{48,48} ]) then reject;
if bgp_path.first != 4200000001 then reject;
bgp_community.filter([ (65535, 65281), (65535, 65282) ]);
bgp_large_community.filter([ (42476, *, *), (210036, 0..3, *) ]);
bgp_ext_community.empty;
dest = RTD_UNREACHABLE;
accept;
}
```
`[1-3|5-9|11]` steps through the highlighted lines on each click.
<!-- .element: class="note" -->
-----
## Live
<iframe class="live" data-src="about:blank" data-preload width="1100" height="440"></iframe>
Point `data-src` at the status page to have it live on the slide.
<!-- .element: class="attribution" -->
Notes:
A live page on the slide is very much the Godbolt move. It needs the network,
and a page behind a password will prompt inside the frame. Have a screenshot
on a vertical slide below as the fallback.
-25
View File
@@ -1,25 +0,0 @@
## Communities
Tags on a route. They mean whatever the receiving network says they mean.
<div class="boxes">
<div class="ok">NO_EXPORT<br><code>65535:65281</code></div>
<div class="bad">BLACKHOLE<br><code>65535:666</code></div>
<div class="us">210036:0:0<br>announce nowhere</div>
<div>42476:1:559<br>SwissIX: only SWITCH</div>
</div>
-----
## Two hops, one idea
| You set | Who acts | Effect |
|---|---|---|
| `210036:0:0` `210036:1:42476` | our router | only the SwissIX route servers |
| `42476:0:0` `42476:1:559` | SwissIX | only SWITCH |
<!-- .element: class="borders" -->
Now find your prefix on a looking glass. Who can see it?
<!-- .element: class="fragment note" -->
+115
View File
@@ -0,0 +1,115 @@
# Transit and exchanges
Notes:
Section 4. Two very different ways of getting routes, and what an Internet
exchange physically is.
-----
## Two ways to reach other networks
<div class="cols">
<div>
### IP transit
* You **pay** a provider
* They give you routes to *everyone*
* They announce you to *everyone*
</div>
<div>
### Peering
* Usually **free**, by mutual benefit
* You exchange *your own* routes, and your customers'
* Traffic between the two of you only
</div>
</div>
Notes:
Transit is a customer-provider relationship; peering is between equals. A peer
does not carry your traffic to third parties. Most networks have both: transit
as the fallback, peering to keep traffic cheap and close.
-----
## An exchange is a big Ethernet switch
<svg class="diagram" viewBox="0 0 1000 400" role="img" aria-label="Members attached to a shared Ethernet peering LAN, with a route server">
<g class="fabric"><rect x="60" y="175" width="880" height="56" rx="10"/></g>
<text x="500" y="200" class="accent">peering LAN: one Ethernet segment, one subnet</text>
<text x="500" y="220" class="small">SwissIX 2001:7f8:24::/64</text>
<g class="hot"><rect x="70" y="40" width="180" height="70" rx="8"/></g>
<text x="160" y="70">AS210036</text><text x="160" y="92" class="small">::c9</text>
<rect x="300" y="40" width="180" height="70" rx="8"/><text x="390" y="70">AS559 SWITCH</text><text x="390" y="92" class="small">::35</text>
<rect x="530" y="40" width="180" height="70" rx="8"/><text x="620" y="70">AS6939 HE</text><text x="620" y="92" class="small">::aa</text>
<rect x="760" y="40" width="180" height="70" rx="8"/><text x="850" y="70">AS13335</text><text x="850" y="92" class="small">Cloudflare</text>
<line class="link" x1="160" y1="110" x2="160" y2="175"/><line class="link" x1="390" y1="110" x2="390" y2="175"/>
<line class="link" x1="620" y1="110" x2="620" y2="175"/><line class="link" x1="850" y1="110" x2="850" y2="175"/>
<rect x="385" y="300" width="230" height="70" rx="8"/><text x="500" y="330">route servers</text><text x="500" y="352" class="small">AS42476 ::a5ec:a:1</text>
<line class="link" x1="500" y1="231" x2="500" y2="300"/>
<path class="bgp" d="M 140 110 C 140 290, 300 335, 385 335"/>
<path class="bgp" d="M 410 110 C 410 250, 440 270, 470 300"/>
<path class="traffic" d="M 180 110 C 180 165, 370 165, 370 110"/>
<text x="275" y="138" class="small" style="fill:#4fd98a">traffic</text>
<text x="215" y="300" class="small accent">BGP</text>
</svg>
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 <!-- .element: class="fragment" -->
* A route's next hop is the peer's LAN address, so traffic goes **directly** member to member <!-- .element: class="fragment" -->
* The exchange never routes your packets. It switches frames <!-- .element: class="fragment" -->
* So the LAN is policed hard: one MAC address per port, no stray broadcasts, no proxy ARP <!-- .element: class="fragment" -->
Notes:
A misbehaving port can hurt every member, which is why exchanges publish strict
rules for what may appear on the peering LAN and quarantine ports that break them.
-----
## Route servers
Without one, peering with 200 members is 200 BGP sessions.
* One session to the route server brings routes from everyone who uses it <!-- .element: class="fragment" -->
* It is **not** in the traffic path, and leaves the next hop alone <!-- .element: class="fragment" -->
* It does not add its own AS to the path <!-- .element: class="fragment" -->
* You steer it with communities: `42476:0:<as>` "not to them", `42476:1:<as>` "only to them" <!-- .element: class="fragment" -->
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.
-----
## Our upstream router, for real
| Where | BGP sessions | Neighbour networks |
|---|---|---|
| Transit (CommunityRack) | 2 | 1 |
| SwissIX, Zürich | 28 | 12 |
| DE-CIX, Frankfurt | 26 | 8 |
| France-IX, Paris | 23 | 10 |
| NL-ix, Amsterdam | 22 | 6 |
One small router, `pr01.rue`, AS210036. IPv4 and IPv6 are separate sessions.
<!-- .element: class="note" -->
Notes:
Counted from its BIRD configuration. It is also on two smaller Swiss exchanges.
The same network (Cloudflare, Apple, Hurricane Electric) often appears at
several exchanges, which matters when you choose where to announce.
+117
View File
@@ -0,0 +1,117 @@
# The lab
Notes:
Section 5. What the students are about to touch, and what stands between a
typo and the Internet.
-----
## What you are connected to
<svg class="diagram" viewBox="0 0 1000 430" role="img" aria-label="Browser to ferrix to the BIRD router pr01.rue, which peers at four exchanges and buys transit">
<rect x="20" y="170" width="150" height="80" rx="8"/><text x="95" y="205">you</text><text x="95" y="228" class="small">a browser</text>
<g class="hot"><rect x="230" y="170" width="200" height="80" rx="8"/></g><text x="330" y="202" class="accent">ferrix</text><text x="330" y="226" class="small">AS4200000001</text>
<rect x="490" y="170" width="200" height="80" rx="8"/><text x="590" y="202">pr01.rue (BIRD)</text><text x="590" y="226" class="small">AS210036</text>
<line class="link" x1="170" y1="210" x2="230" y2="210"/><text x="200" y="160" class="small">HTTPS</text>
<line class="bgp" x1="430" y1="210" x2="490" y2="210"/><text x="460" y="160" class="small accent">BGP</text>
<text x="330" y="280" class="small">web page + BGP speaker</text><text x="590" y="280" class="small">filters, then decides where</text>
<rect x="780" y="20" width="200" height="56" rx="8"/><text x="880" y="54">SwissIX</text>
<rect x="780" y="98" width="200" height="56" rx="8"/><text x="880" y="132">NL-ix</text>
<rect x="780" y="176" width="200" height="56" rx="8"/><text x="880" y="210">France-IX</text>
<rect x="780" y="254" width="200" height="56" rx="8"/><text x="880" y="288">DE-CIX</text>
<rect x="780" y="352" width="200" height="56" rx="8"/><text x="880" y="386">transit</text>
<path class="bgp" d="M 690 195 C 740 195, 730 48, 780 48"/><path class="bgp" d="M 690 203 C 740 203, 735 126, 780 126"/>
<path class="bgp" d="M 690 210 L 780 204"/><path class="bgp" d="M 690 217 C 740 217, 735 282, 780 282"/>
<path class="bgp" d="M 690 225 C 740 225, 730 380, 780 380"/>
</svg>
Notes:
ferrix is a small BGP daemon written for this course; it runs on the same
machine as the router. Both dashed hops are real BGP sessions. Everything to
the right of pr01 is the actual Internet.
-----
## Your prefix
`2a0d:d741:<n>::/48`
* `<n>` is your number **in hex**: student 10 is `2a0d:d741:a::/48` <!-- .element: class="fragment" -->
* Put your name in *owner*: only the owner can withdraw a route <!-- .element: class="fragment" -->
* One prefix each <!-- .element: class="fragment" -->
Notes:
Put the URL and the class password on the board. The page is a snapshot by
default; turn on auto-refresh at the top right to watch things change.
-----
## What happens when you press *Announce*
1. ferrix builds an UPDATE. Click *bytes* to see it, hover to decode it <!-- .element: class="fragment" -->
2. pr01 checks it against its import filter <!-- .element: class="fragment" -->
3. pr01 decides, per neighbour, whether to pass it on <!-- .element: class="fragment" -->
4. Within a minute or so, the Internet can see your prefix <!-- .element: class="fragment" -->
5. `ping 2a0d:d741:<n>::1` answers, from anywhere <!-- .element: class="fragment" -->
Notes:
Step 5: pr01 answers for every address in the lab space, so reachability of
your prefix depends only on whether your announcement got out.
-----
## What stands between you and an outage
```bird [2-3|5-7|9]
filter ferrix_import {
if !(net ~ [ 2a0d:d741::/40{48,48} ]) then reject; # only lab /48s
if bgp_path.first != 4200000001 then reject; # only from ferrix
bgp_community.filter([ (65535, 65281), (65535, 65282) ]);
bgp_large_community.filter([ (42476, *, *), (210036, 0..3, *) ]);
bgp_ext_community.empty; # drop what we did not offer
dest = RTD_UNREACHABLE; # announce-only
accept;
}
```
Notes:
This is the router's real import filter. ferrix refuses anything outside the
lab /40 before it gets this far, and pr01 takes at most 130 routes from it.
Two independent nets, because the web form has no idea who you are.
-----
## 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*.
<!-- .element: class="fragment note" -->
Notes:
210036:0:<as> never to that AS, 210036:1:<as> to that AS, 210036:2/3:<n> the
same by exchange (1 SwissIX, 2 NL-ix, 3 France-IX, 4 DE-CIX). A "never" always
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
Notes:
Looking glasses: bgp.tools, RIPEstat, and the exchange's own. Global
visibility takes a minute or two to settle after each change.
-21
View File
@@ -1,21 +0,0 @@
## What could go wrong?
* Anyone can announce anything <!-- .element: class="fragment" -->
* Filters are the only thing between a typo and an outage <!-- .element: class="fragment" -->
* IRR objects and RPKI say who *may* announce a prefix <!-- .element: class="fragment" -->
-----
> "The Internet works because a lot of people are careful, most of the time."
Quotes use a plain Markdown blockquote.
<!-- .element: class="attribution" -->
-----
## Thank you
Questions?
<p class="note">Slides: <code>slides/</code> in the ferrix_bgp repository</p>
+108
View File
@@ -175,3 +175,111 @@
border-radius: 8px;
background: #fff;
}
/* ---- bytes with one part in focus: everything not .on is dimmed --------------- */
.bytes.focus span:not(.on) {
opacity: 0.22;
}
.bytes.focus span.on {
box-shadow: 0 0 0 2px rgba(var(--accent-rgb), 0.9);
}
/* Small two-column explanation under a bytes block. */
.reveal table.decode {
font-size: 58%;
margin-top: 10px;
}
.reveal table.decode td:first-child {
font-family: var(--r-code-font);
color: var(--accent-soft);
white-space: nowrap;
}
/* ---- bits of an address: fixed part vs. free part ------------------------------ */
.bits {
font-family: var(--r-code-font);
font-size: 80%;
letter-spacing: 0.02em;
}
.bits .fixed {
color: var(--ground);
background: var(--accent);
border-radius: 4px 0 0 4px;
padding: 2px 6px;
}
.bits .free {
color: var(--ink);
background: var(--ground);
border: 1px solid var(--rule);
border-left: none;
border-radius: 0 4px 4px 0;
padding: 1px 6px;
}
/* ---- diagrams: inline SVG drawn with the theme's colours ------------------------ */
.reveal svg.diagram {
width: 100%;
height: auto;
max-height: 470px;
display: block;
margin: 0 auto;
}
.diagram rect,
.diagram ellipse {
fill: var(--ground);
stroke: var(--rule);
stroke-width: 2;
}
.diagram .hot rect {
stroke: var(--accent);
}
.diagram .fabric rect {
fill: rgba(var(--accent-rgb), 0.18);
stroke: var(--accent);
}
.diagram text {
fill: var(--ink);
font-family: "IBM Plex Sans", sans-serif;
font-size: 19px;
text-anchor: middle;
}
.diagram text.small {
font-size: 14px;
fill: var(--muted);
font-family: "IBM Plex Mono", monospace;
}
.diagram text.accent {
fill: var(--accent);
}
.diagram .link {
stroke: var(--muted);
stroke-width: 2.5;
fill: none;
}
.diagram .bgp {
stroke: var(--accent);
stroke-width: 2.5;
stroke-dasharray: 7 6;
fill: none;
}
.diagram .traffic {
stroke: #4fd98a;
stroke-width: 4;
fill: none;
}
+23 -27
View File
@@ -6,7 +6,7 @@
* markers, links and progress bar, glowing highlighted code lines.
*
* Made our own: the palette is ours (change it in :root below), and his
* "thread" motif becomes a chain of nodes and links, an AS path.
* "thread" corner motif becomes a chain of nodes and links, an AS path.
*
* Try accents without editing: index.html?accent=green (blue, green,
* amber, magenta). Make one permanent by copying its values into :root.
@@ -379,7 +379,7 @@
.reveal section.title-slide h3 {
font-weight: 400;
color: var(--accent);
margin-bottom: 1.6em;
margin-bottom: 2.2em;
}
.reveal section.title-slide .byline {
@@ -388,37 +388,33 @@
margin: 0;
}
.reveal section.title-slide .as-path {
display: block;
width: 100%;
max-width: none;
height: 90px;
margin: 0 0 1em;
/* ---- shown by index.html when the deck cannot start --------------------------- */
#deck-help {
position: fixed;
inset: 0;
z-index: 100;
padding: 8vh 10vw;
background: var(--ground);
color: var(--ink);
font: 20px/1.5 "IBM Plex Sans", "Helvetica Neue", Arial, sans-serif;
}
.as-path circle {
fill: var(--ground);
stroke: var(--accent);
stroke-width: 3;
#deck-help h2 {
color: var(--accent);
font-weight: 700;
}
.as-path line {
stroke: var(--accent);
stroke-width: 3;
#deck-help pre,
#deck-help code {
font-family: var(--r-code-font);
color: var(--accent-soft);
}
.as-path text {
font-family: "IBM Plex Mono", monospace;
font-size: 15px;
fill: var(--muted);
}
.as-path .you circle {
fill: var(--accent);
}
.as-path .you text {
fill: var(--ink-strong);
#deck-help pre {
background: var(--panel);
padding: 12px 16px;
border-radius: 6px;
}
/* ---- print ------------------------------------------------------------------ */