Guides

BGP

BGP par VRF avec un speaker GoBGP embarqué (actif-actif).

SiHA embarque un speaker GoBGP qui s’exécute à l’intérieur du processus de l’appliance (pilotd). Il annonce des préfixes aux switches de fabric en amont, apprend des routes de leur part, et programme les meilleurs chemins dans la FIB de VPP — une entrée par VRF. Les next-hops multiples de coût égal sont installés en ECMP, ce qui constitue la base de la démo d’équilibreur de charge actif-actif.

Deux types de ressources pilotent la fonctionnalité :

  • BGPConfig — un par VRF, contient l’ASN, le router-id et le commutateur de drain de maintenance.
  • BGPPeer — un par voisin, nomme le distant et contrôle les filtres de préfixes.

Un troisième type, BGPLearnedRoute, est en lecture seule : pilotd écrit une entrée par meilleur chemin appris et installé.

Le modèle par VRF

L’id de chaque ressource BGPConfig est le nom de la VRF. L’id réservé default est la VRF par défaut (table VPP 0) et doit laisser vrf vide ; tout autre id doit renseigner vrf pour qu’il corresponde. Chaque VRF présente son propre localAsn sur le fil, de sorte qu’une appliance peut exécuter des ASN distincts vers différentes fabrics tenant depuis un unique processus GoBGP.

Le router-id est global en v1 (défini sur la config default). Toutes les VRF le partagent.

BGPConfig

type: BGPConfigs.bgp.siha
metadata:
  namespace: bgp
  id: default            # "default" → default VRF, table 0
spec:
  localAsn: 65010
  routerId: 192.168.100.1    # required on id=default; IPv4 only
  # announceNextHop overrides the next-hop attribute on outbound UPDATE messages
  # (useful when the appliance is behind a NAT or re-announcing from a VRF).
  announceNextHop: ""        # omit to use the local session address
  # listenAddresses makes GoBGP bind TCP/179 on these addresses so remote peers
  # can dial in (e.g. Cilium agents, a fleet of k8s nodes). Each address must
  # be on a linux-cp-mirrored Interface. Empty = dial-out only.
  listenAddresses: []
  # dynamicNeighbors accepts inbound sessions from any address in a CIDR
  # without requiring a BGPPeer resource per host. Requires listenAddresses.
  dynamicNeighbors: []
  # drained withdraws every announced prefix while keeping sessions alive and
  # continuing to learn routes — the maintenance drain switch. Flip to true
  # before draining traffic from this node; flip back to restore.
  drained: false

Pour une VRF tenant, l’id doit être égal au champ vrf :

type: BGPConfigs.bgp.siha
metadata:
  namespace: bgp
  id: tenant-a
spec:
  vrf: tenant-a          # must match id; no routerId required here
  localAsn: 65020        # this VRF presents AS 65020 on the wire
  drained: false

BGPPeer

type: BGPPeers.bgp.siha
metadata:
  namespace: bgp
  id: spine1
spec:
  neighborAddress: 192.168.100.254   # remote BGP speaker; must be IPv4 (v1)
  peerAsn: 65000
  localAddress: 192.168.100.1        # the local address GoBGP dials from
                                     # (must be on a linux-cp-mirrored Interface)
  holdTimeSec: 0                     # 0 = controller default (90s); min 3
  # announce: prefixes this appliance originates and sends to the peer.
  announce:
    - 10.100.0.0/24
    - 10.100.1.0/24
  # acceptPrefixes: import filter — only routes in these CIDRs are installed in
  # the FIB. Empty accepts everything the peer advertises.
  acceptPrefixes:
    - 0.0.0.0/0
  # exportPrefixes: export filter — only routes in these CIDRs are advertised
  # to this peer (includes re-advertised routes learned from other peers).
  # When set it must cover every prefix in announce.
  exportPrefixes: []
  # maxPrefixes: tears down the session if the peer sends more than this many
  # prefixes. 0 = unlimited.
  maxPrefixes: 0
  # md5Password: TCP MD5 session authentication (optional).
  md5Password: ""
  vrf: ""                # empty = default VRF; set to bind to a tenant VRF

Attacher un peer à une VRF tenant :

type: BGPPeers.bgp.siha
metadata:
  namespace: bgp
  id: bird2-tenant-a
spec:
  neighborAddress: 10.30.0.1
  peerAsn: 65030
  localAddress: 10.30.0.2
  announce:
    - 10.100.2.0/24
  vrf: tenant-a          # learned routes go into VPP table for tenant-a

Accessibilité (linux-cp)

VPP possède les NIC de données ; le noyau du nœud n’a aucune route vers les sous-réseaux de données. La session TCP BGP doit donc s’exécuter sur une adresse miroir linux-cp — déclarez lcpHostIf sur l’Interface qui porte l’adresse locale. VPP dévie le trafic TCP/179 entrant vers le tap lcp, où la socket de GoBGP le reçoit ; les SYN sortants partent par le même tap et ré-entrent dans VPP par le chemin miroir. La même contrainte s’applique à listenAddresses sur BGPConfig.

Un peer dont l’adresse localAddress n’est pas sur une interface activée lcp échouera à s’établir (le noyau n’a aucune route pour émettre le SYN TCP).

ECMP actif-actif

Lorsque plusieurs peers annoncent le même préfixe, GoBGP sélectionne le meilleur chemin et ses éventuels frères de coût égal. Tous sont programmés dans la table FIB VPP de la VRF comme une entrée multipath. VPP hache les flux entre les next-hops, offrant un équilibrage de charge par flux sans aucune configuration supplémentaire.

C’est le modèle derrière la démo LB actif-actif : deux nœuds d’appliance, chacun un speaker BGP, annoncent le même préfixe de VIP ; la fabric hache en ECMP les flux entrants entre eux.

Drainage pour maintenance

Positionnez drained: true sur un BGPConfig pour retirer tous les préfixes annoncés tout en gardant les sessions établies et en continuant d’installer les routes apprises. L’appliance cesse d’attirer de nouveaux flux mais continue d’acheminer les flux existants au travers de sa propre FIB VPP. Repassez à false pour ré-annoncer.

S="sihactl --sihaconfig ~/.siha/config --addr <node-ip>:6443"

$S get bgpconfig default -o yaml         # inspect current drain state
$S edit bgpconfig default                # set drained: true / false

BGPConfigStatus.drained reflète l’état de drain en direct à des fins d’observabilité.

Voisins dynamiques

Pour les grands parcs (agents Cilium, nœuds k8s) où ajouter un BGPPeer par hôte est impraticable, dynamicNeighbors permet à n’importe quelle adresse d’un CIDR d’établir une session sans ressource dédiée :

spec:
  localAsn: 65010
  routerId: 192.168.100.1
  listenAddresses: ["192.168.100.1"]   # GoBGP must bind for peers to dial in
  dynamicNeighbors:
    - prefix: 10.0.0.0/24
      peerAsn: 65000
      acceptPrefixes: ["10.0.0.0/8"]
      exportPrefixes: ["10.100.0.0/16"]

Les sessions dynamiques sont éphémères : leur statut apparaît sous BGPPeerStatus avec un id préfixé par dyn- (ou dyn-<vrf>- pour les VRF non par défaut). Elles ne survivent pas à un redémarrage de pilotd, à moins que le peer ne recompose la session.

Exploitation

S="sihactl --sihaconfig ~/.siha/config --addr <node-ip>:6443"

# BGP global state (one row per VRF).
$S get bgpconfig

# Peer list: vrf, neighbor, asn, announced/accepted/exported prefixes, session
# state, last error.
$S get bgppeer

# Full session detail for one peer.
$S get bgppeer spine1 -o yaml

# Learned routes in the FIB (prefix, next-hops, peer, vrf, conflict).
$S get bgplearnedroute

# Watch route convergence live.
$S watch bgplearnedroute

La colonne state dans sihactl get bgppeer reflète la FSM de session GoBGP : ESTABLISHED signifie que la session est établie et que les préfixes circulent ; ACTIVE signifie que GoBGP compose mais ne s’est pas encore connecté ; les autres valeurs (IDLE, OPENSENT, …) indiquent des états transitoires ou des sessions en attente.

BGPLearnedRoute.conflict, lorsqu’il est non vide, explique pourquoi une route apprise n’a pas été installée dans VPP — par exemple, une ressource Route statique possède déjà le préfixe (le statique l’emporte sur BGP). Un conflict non vide n’est pas une erreur ; c’est le résultat attendu chaque fois qu’une route configurée localement recouvre une route apprise par BGP.

BGPConfigStatus (lecture seule, même id que la config) rapporte running, establishedSessions, totalSessions et lastError. Inspectez-le avec :

$S get bgpconfigstatus default -o yaml

Limitations de la v1

  • IP de voisins non chevauchantes entre VRF. GoBGP v1 clé les sessions par adresse IP de manière globale. Deux ressources BGPPeer (dans des VRF différentes) avec la même neighborAddress entrent en collision. Ceci est rejeté au moment de l’application. L’isolation par netns (autorisant des IP de transport chevauchantes par VRF) est prévue pour la v2 et conditionnée à un spike en direct.
  • Router-id unique. Le message OPEN utilise le router-id global déclaré sur la BGPConfig default. Les router-ids par VRF sont pour la v2.
  • IPv4 unicast uniquement. Toutes les adresses, préfixes et next-hops doivent être en IPv4. L’IPv6 est pour la v2 ; VPNv4 / le route leaking inter-VRF pour la v3.
  • L’annonce en VRF est expérimentale. Originer des préfixes dans une VRF non par défaut (announce sur un BGPPeer lié à une VRF) est complet côté code mais n’a aucune fixture livrée qui l’exerce de bout en bout. Considérez-le comme expérimental jusqu’à ce qu’un environnement en direct le valide.