From 7802c599312d035038e18de9a10b44d71ce781d3 Mon Sep 17 00:00:00 2001 From: Adam Belkacemi Date: Thu, 17 Sep 2026 01:17:15 +0200 Subject: [PATCH] Phase 6 (bonus) : bird-lab, BGP+BFD entre namespaces (rtnetlink/Bird) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Topologie en étoile : racine (AS 65000) en transit entre bird-a (AS 65010) et bird-b (AS 65011), namespaces créés par tripoli-netctl (Phase 4). Chaque feuille origine une route factice, jamais installée dans la table de routage réelle (export none partout) - labo pédagogique, ne pilote aucun trafic de production. - Vérifié en conditions réelles : sessions BGP Established dans les deux sens, route de bird-b vue par bird-a avec next-hop = racine (transit confirmé, pas un peering direct), sessions BFD Up (200ms/200ms), coupure de lien détectée en < 5ms. - Découverte : les namespaces n'ont aucun pare-feu propre, mais ufw côté racine bloquait le retour des paquets BFD (chaque bout envoie indépendamment, conntrack ne le reconnaît pas comme un flux BGP/TCP existant) - même schéma que Caddy -> tripoli-edge en Phase 2. Règle ufw ajoutée par run.sh, retirée par teardown.sh : rien de permanent. - Pas de service systemd : labo lancé, vérifié, puis proprement démonté (processus Bird, namespaces et règle ufw temporaire tous supprimés). Co-Authored-By: Claude Sonnet 5 --- README.md | 2 +- bird-lab/README.md | 82 +++++++++++++++++++++++++++++++++++++ bird-lab/config/bird-a.conf | 35 ++++++++++++++++ bird-lab/config/bird-b.conf | 33 +++++++++++++++ bird-lab/config/root.conf | 45 ++++++++++++++++++++ bird-lab/run.sh | 47 +++++++++++++++++++++ bird-lab/teardown.sh | 26 ++++++++++++ 7 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 bird-lab/README.md create mode 100644 bird-lab/config/bird-a.conf create mode 100644 bird-lab/config/bird-b.conf create mode 100644 bird-lab/config/root.conf create mode 100755 bird-lab/run.sh create mode 100755 bird-lab/teardown.sh diff --git a/README.md b/README.md index 76eeec9..69e90e9 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ tenté, non simulé. | `tripoli-api` (Rust, Phase 3) | Fait et déployé en prod — `tripoli-api/`, auth par session (Argon2), upload/download, PostgreSQL dédié (Docker), vérifié via `https://tripoli.cloud/api/...` | | `tripoli-netctl` (Rust, Phase 4) | Fait — `tripoli-netctl/`, labo pédagogique (netns + veth via `rtnetlink`), testé sur le serveur (création, ping traversant la veth, destruction propre), **volontairement pas branché sur le trafic de prod** | | Observabilité (Phase 5) | Fait — pas de stack dédiée (réutilise le Prometheus/Grafana partagé existant), scrape tripoli-edge + alertes (down, disque) + dashboard versionné (`monitoring/`), vérifié via l'API Prometheus/Grafana | -| Bird/BGP (Phase 6, bonus) | Pas commencé | +| Bird/BGP (Phase 6, bonus) | Fait — `bird-lab/`, BGP+BFD entre 2 namespaces via la racine en transit (AS 65000/65010/65011), vérifié (sessions Established, BFD Up, propagation de route, détection de panne sur coupure de lien), puis proprement démonté | ## Dépôt diff --git a/bird-lab/README.md b/bird-lab/README.md new file mode 100644 index 0000000..4a77815 --- /dev/null +++ b/bird-lab/README.md @@ -0,0 +1,82 @@ +# bird-lab (Phase 6, bonus) + +Labo pédagogique un-seul-poste : BGP + BFD entre les network namespaces +créés par `tripoli-netctl` (Phase 4). **Ne pilote aucun trafic de +production** - tripoli-edge et tripoli-api continuent de tourner +nativement sur l'hôte, dans le namespace racine, complètement en dehors +de ce labo. Rien de ce qui est appris ici n'est installé dans la table +de routage réelle du noyau (`export none;` dans chaque config Bird). + +## Topologie + +Une étoile, pas un maillage complet : le namespace racine (AS 65000) +sert de transit entre deux namespaces "feuilles" qui ne se parlent +jamais directement. + +``` + namespace bird-a (AS 65010) namespace bird-b (AS 65011) + origine 10.99.10.0/24 origine 10.99.11.0/24 + 10.200.10.2 10.200.11.2 + | | + | veth (tripoli-netctl) | veth (tripoli-netctl) + | | + 10.200.10.1 10.200.11.1 + +---------------------------------------------+ + | namespace racine (AS 65000) | + | BGP+BFD vers bird-a BGP+BFD vers bird-b | + +---------------------------------------------+ +``` + +Chaque namespace "feuille" origine une route factice +(`10.99.1x.0/24 blackhole`, juste pour avoir quelque chose de concret à +observer traverser le labo) ; le namespace racine la réapprend par BGP +et la réexporte vers l'autre feuille - démontrant un vrai comportement +de transit eBGP, pas un simple peering point à point. + +## Ce qui a été vérifié + +- **Sessions BGP établies** dans les deux sens (`Established`) après + démarrage des trois instances Bird. +- **Propagation de route à travers le transit** : `bird-a` voit + `10.99.11.0/24` appris via `AS65011i` avec next-hop `10.200.10.1` + (l'adresse du namespace racine, pas celle de bird-b - la route a bien + transité par le hub) ; symétriquement pour `bird-b`. +- **Sessions BFD `Up`**, intervalle 200ms. +- **Détection de panne** : `ip link set tv-birdah down` fait passer la + session BGP correspondante en `Idle` en moins de 5ms - avant même que + le timer BFD (600ms-1s) n'ait eu besoin de se déclencher, Bird réagit + à l'événement netlink d'interface directement. Le vrai apport de BFD + se voit sur une panne "silencieuse" (lien physique up, mais plus de + paquets) - non isolé précisément dans cette passe, faute de temps ; + la mécanique (sessions BFD `Up`, intervalles sous la seconde) est en + place et vérifiée, la détection de ce cas précis reste à chronométrer + séparément. + +## Découverte : ufw bloque le retour BFD + +Les namespaces `bird-a`/`bird-b` n'ont aucune règle de pare-feu (leur +netfilter est vierge - seul le namespace racine a `ufw`). Pourtant, +leurs paquets BFD sortants se faisaient bloquer en arrivant côté racine : +contrairement à BGP (TCP, donc suivi par conntrack - le retour d'une +connexion initiée est autorisé automatiquement), chaque bout de BFD +envoie indépendamment depuis son propre port éphémère vers le port 3784 +de l'autre. Du point de vue de conntrack côté racine, le paquet BFD émis +par `bird-a` n'est le retour d'aucun flux connu : `ufw` le traite comme +une connexion entrante neuve et le rejette par défaut. Il a fallu une +règle explicite (`ufw allow from 10.200.0.0/16 to any port 3784 proto +udp`), ajoutée par `run.sh` et retirée par `teardown.sh` - exactement le +même schéma que la règle ajoutée en Phase 2 pour laisser Caddy joindre +tripoli-edge. + +## Utilisation + +```bash +sudo ./run.sh # crée les namespaces, démarre Bird, affiche l'état +sudo ./teardown.sh # arrête tout, détruit les namespaces, retire la règle ufw temporaire +``` + +Volontairement pas de service systemd permanent : faire tourner ce labo +en continu sur un serveur de production partagé n'apporterait rien de +plus pédagogiquement, juste de la RAM utilisée pour rien. Vérifié dans +cette session, puis proprement démonté (namespaces, processus Bird et +règle ufw temporaire tous supprimés). diff --git a/bird-lab/config/bird-a.conf b/bird-lab/config/bird-a.conf new file mode 100644 index 0000000..5000dc1 --- /dev/null +++ b/bird-lab/config/bird-a.conf @@ -0,0 +1,35 @@ +router id 10.200.10.2; +log "/var/log/bird-lab/bird-a.log" all; + +protocol device { +} + +protocol kernel { + ipv4 { + export none; + }; +} + +# Réseau fictif "originé" par ce namespace, pour avoir quelque chose de +# concret à observer traverser le labo par BGP. +protocol static local_net { + ipv4; + route 10.99.10.0/24 blackhole; +} + +protocol bfd { + interface "*" { + min rx interval 200ms; + min tx interval 200ms; + }; +} + +protocol bgp to_root { + local 10.200.10.2 as 65010; + neighbor 10.200.10.1 as 65000; + bfd on; + ipv4 { + import all; + export all; + }; +} diff --git a/bird-lab/config/bird-b.conf b/bird-lab/config/bird-b.conf new file mode 100644 index 0000000..5f99d1f --- /dev/null +++ b/bird-lab/config/bird-b.conf @@ -0,0 +1,33 @@ +router id 10.200.11.2; +log "/var/log/bird-lab/bird-b.log" all; + +protocol device { +} + +protocol kernel { + ipv4 { + export none; + }; +} + +protocol static local_net { + ipv4; + route 10.99.11.0/24 blackhole; +} + +protocol bfd { + interface "*" { + min rx interval 200ms; + min tx interval 200ms; + }; +} + +protocol bgp to_root { + local 10.200.11.2 as 65011; + neighbor 10.200.11.1 as 65000; + bfd on; + ipv4 { + import all; + export all; + }; +} diff --git a/bird-lab/config/root.conf b/bird-lab/config/root.conf new file mode 100644 index 0000000..746f35a --- /dev/null +++ b/bird-lab/config/root.conf @@ -0,0 +1,45 @@ +router id 10.200.10.1; +log "/var/log/bird-lab/root.log" all; + +protocol device { +} + +protocol direct { + interface "*"; +} + +# Rien n'est installé dans la table de routage réelle du noyau : ce +# labo ne doit rien changer au routage effectif de la machine qui sert +# tripoli-edge/tripoli-api en production. +protocol kernel { + ipv4 { + export none; + }; +} + +protocol bfd { + interface "*" { + min rx interval 200ms; + min tx interval 200ms; + }; +} + +protocol bgp bird_a { + local 10.200.10.1 as 65000; + neighbor 10.200.10.2 as 65010; + bfd on; + ipv4 { + import all; + export all; + }; +} + +protocol bgp bird_b { + local 10.200.11.1 as 65000; + neighbor 10.200.11.2 as 65011; + bfd on; + ipv4 { + import all; + export all; + }; +} diff --git a/bird-lab/run.sh b/bird-lab/run.sh new file mode 100755 index 0000000..9aac4f1 --- /dev/null +++ b/bird-lab/run.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Labo BGP/BFD un-seul-poste : deux namespaces (bird-a, bird-b) créés par +# tripoli-netctl, chacun relié au namespace racine, trois instances Bird +# qui échangent des routes par BGP avec BFD pour la détection rapide de +# panne. Racine = AS 65000, transit entre bird-a (AS 65010) et bird-b +# (AS 65011) - une topologie en étoile, pas un maillage complet. +# +# À exécuter en root. Ne touche à AUCUN service de production : les +# routes échangées ici ne sont jamais installées dans la table de +# routage réelle du noyau (voir `export none;` dans chaque config). +set -euo pipefail + +NETCTL=/opt/tripoli-src/target/debug/tripoli-netctl +CONFIG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/config" && pwd)" + +mkdir -p /var/log/bird-lab /run/bird-lab /etc/bird-lab +cp "$CONFIG_DIR"/*.conf /etc/bird-lab/ + +echo "--- création des namespaces (tripoli-netctl) ---" +"$NETCTL" create bird-a --subnet-id 10 +"$NETCTL" create bird-b --subnet-id 11 + +# Les namespaces bird-a/bird-b n'ont aucun pare-feu (netfilter y est +# vierge - seul le namespace racine a une politique ufw), mais leurs +# paquets BFD, une fois arrivés côté racine, ne sont PAS reconnus comme +# le retour d'une connexion existante par conntrack (chaque bout envoie +# sur son propre port éphémère, ce n'est pas un flux TCP bidirectionnel +# comme BGP) : ufw les bloque par défaut, exactement comme il avait +# fallu une règle dédiée pour laisser Caddy joindre tripoli-edge en +# Phase 2. Règle temporaire, retirée par teardown.sh. +ufw allow from 10.200.0.0/16 to any port 3784 proto udp comment 'bird-lab : BFD (labo, temporaire)' + +echo "--- démarrage des instances Bird ---" +bird -c /etc/bird-lab/root.conf -s /run/bird-lab/root.ctl +ip netns exec bird-a bird -c /etc/bird-lab/bird-a.conf -s /run/bird-lab/bird-a.ctl +ip netns exec bird-b bird -c /etc/bird-lab/bird-b.conf -s /run/bird-lab/bird-b.ctl + +sleep 3 +echo "--- sessions BGP (racine) ---" +birdc -s /run/bird-lab/root.ctl show protocols all bird_a +birdc -s /run/bird-lab/root.ctl show protocols all bird_b + +echo "--- routes vues par bird-a (doit inclure 10.99.11.0/24, apprise via la racine) ---" +birdc -s /run/bird-lab/bird-a.ctl show route + +echo "--- routes vues par bird-b (doit inclure 10.99.10.0/24, apprise via la racine) ---" +birdc -s /run/bird-lab/bird-b.ctl show route diff --git a/bird-lab/teardown.sh b/bird-lab/teardown.sh new file mode 100755 index 0000000..7e55e92 --- /dev/null +++ b/bird-lab/teardown.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Arrête les trois instances Bird et détruit les namespaces du labo. +# Volontairement pas de service systemd permanent pour ce labo : le +# faire tourner en continu sur un serveur de production partagé n'aurait +# aucune valeur pédagogique de plus, juste de la RAM utilisée pour rien. +set -euo pipefail + +NETCTL=/opt/tripoli-src/target/debug/tripoli-netctl + +for ctl in root bird-a bird-b; do + if [ -S "/run/bird-lab/$ctl.ctl" ]; then + birdc -s "/run/bird-lab/$ctl.ctl" down || true + fi +done + +sleep 1 +pkill -f "bird -c /etc/bird-lab" || true +sleep 1 + +"$NETCTL" destroy bird-a || true +"$NETCTL" destroy bird-b || true + +ufw delete allow from 10.200.0.0/16 to any port 3784 proto udp || true + +rm -rf /run/bird-lab +echo "labo BGP/BFD arrêté et nettoyé"