Phase 6 (bonus) : bird-lab, BGP+BFD entre namespaces (rtnetlink/Bird)

- 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 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-09-17 01:17:15 +02:00
parent db306433d5
commit 7802c59931
7 changed files with 269 additions and 1 deletions

View file

@ -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

82
bird-lab/README.md Normal file
View file

@ -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).

View file

@ -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;
};
}

View file

@ -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;
};
}

45
bird-lab/config/root.conf Normal file
View file

@ -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;
};
}

47
bird-lab/run.sh Executable file
View file

@ -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

26
bird-lab/teardown.sh Executable file
View file

@ -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é"