Cette page décrit en détail l'architecture de collecte FTTH mutualisée au sein de Grenode. Pour l'heure cette infra est uniquement utilisée pour la collecte FTTH Axione.

Prérequis :

  • DHCP
  • RADIUS: Authentication, Authorization and Accounting

Fonctionnement de la collecte

Principe général

La mutualisation de la collectes FTTH se passe sur les machines ?rosette et ?gabier. D'un point de vue réseau, ces deux machines se retrouvent entre les BNG/RADIUS du fournisseur de collecte et les réseaux des membres de Grenode. Elles assurent un rôle de proxy radius avancé, de serveur DHCP et de routeur.

[BNG]: Broadband Network Gateway : équipements situés sur le réseau de l'opérateur de collecte et servant de passerelle aux routeurs des abonnés.

Processus lors de la connexion d'un abonné FTTH

  1. Le routeur abonné émet un DHCP Discover (v4 ou v6)
  2. L'OLT modifie le DHCP Discover pour y insérer le Circuit-ID et l'envoi au BNG
  3. Le BNG bloque le DHCP Discover et émet une requête d'authentification RADIUS Access-Request contenant le Circuit-ID du lien FTTH.
  4. L'Access-Request arrive sur ?rosette ou ?gabier, qui aiguille la requête vers le Radius du FAI membre, en fonction du Circuit-ID.
  5. Le Radius du FAI répond avec un Access-Accept, qui contient également les paramètres IP à fournir en DHCP et le profil de débit à appliquer
  6. ?rosette ou ?gabier reçoivent l'Access-Accept et provisionnent à la volée leurs serveurs DHCP.
  7. L'Access-Accept est routé jusqu'au BNG, qui libère le DHCP Discover inital. Le BNG agit comme relay DHCP et envoie le Discover en unicast vers ?rosette ou ?gabier.
  8. La suite de l'échange DHCP s'effectue de manière classique entre le BNG et ?rosette ou ?gabier
  9. Le routeur abonné reçoit ses adresses IP publiques.

En schéma:

--- ABONNÉ ---   ---      FOURNISSEUR     ---          --- GRENODE ---      --- MEMBRE ---

ROUTEUR                                 PROXY          PROXY     SERVEUR
ABONNÉ           OLT         BNG        RADIUS         RADIUS     DHCP           RADIUS
   ·     (1)      ·           ·           ·              ·         ·               ·
   o------------->·    (2)    |           ·              ·         ·               ·
   ·              o---------->|    (3)    ·              ·         ·               ·
   ·              ·           o---------->|     (3)      ·         ·               ·
   ·              ·           ·           o------------->|            (4)          ·
   ·              ·           ·           ·              o------------------------>|
   ·              ·           ·           ·              ·            (5)          |
   ·              ·           ·           ·              |<------------------------o
   ·              ·           ·           ·              |   (6)   ·
   ·              ·           .           ·     (7)      o-------->|
   ·              ·           ·    (7)    |<-------------o         ·
   ·              ·           |<----------o                        ·
   ·              ·           |                                    ·
   ·              ·           |                 (8)                ·
   ·              ·           o----------------------------------->|
   ·              ·           ·                 (9)                |
   ·              ·           |<-----------------------------------o
   ·              ·           |
   |<-------------------------o

Principe de routage des flux d'un abonné FTTH

Le BNG agit comme proxy ARP/NDP. Il est donc possible de donner au routeur abonné n'importe quelle adresse IP de passerelle. Dans un soucis de simplicité et de standardisation, nous utilisons la première IP du subnet (.1 et :1)

?rosette et ?gabier effectuent du routage par IP source, afin d'envoyer le flux d'un abonné vers le bon FAI.

Le fournisseur de collecte annonce à Grenode les routes spécifiques à chaque abonné. Un /32 en IPv4, et un /128 + un /56 délégué en IPv6.
Ces routes sont ré-annoncées au FAI membre.

Dans le sens inverse, le FAI annonce une route par défaut à Grenode. Grenode annonce une route par défaut à l'opérateur de collecte.

Proxy Radius

Contrairement à la Collecte xDSL, il n'est pas possible d'aiguiller facilement les requêtes en fonction d'un royaume (realm). Les requêtes sont aiguillées vers le bon FAI membre en fonction de leur Circuit-ID qui est sous la forme suivante:

olt-bsn42-01 pon 1/1/01/01/4/1/1/

Tout ce qui commence par "olt-XXX42" correspond aux connexions du RIP THD42, et est par défaut aiguillé vers le FAI Illyse dans notre cas.

Le proxy radius utilise le logiciel Freeradius. La configuration Freeradius permettant cet aiguillage se présente sous cette forme:

authorize {
[...]

    #if %{ADSL-Agent-Circuit-Id} exist and realm @gnd.ftth.axione
    if (&ADSL-Agent-Circuit-Id) && !(&ADSL-Agent-Circuit-Id =="") && (&User-Name =~ /@gnd\.ftth\.axione$$/  ) {

    if ( `/bin/grep -i %{ADSL-Agent-Circuit-Id} /srv/data/specific-ils-circuitid` ) {
            update request {
                    Calling-Station-Id := "%{User-Name}"
                    User-Name := "%{ADSL-Agent-Circuit-Id}"
                    User-Password := 'secret01'
            }
            update control {
                    Proxy-To-Realm := 'ils-ftth'
            }
    }
    elsif ( `/bin/grep -i %{ADSL-Agent-Circuit-Id} /srv/data/specific-rzn-circuitid` ) {
            update request {
                    Calling-Station-Id := "%{User-Name}"
                    User-Name := "%{ADSL-Agent-Circuit-Id}"
        User-Password := 'secret02'
            }
            update control {
                    Proxy-To-Realm := 'rzn-ftth'
            }
    }

    #THD42 --> ILS si pas de Circuit-ID spécifique trouvé
    #elsif ("%{ADSL-Agent-Circuit-Id}" =~ /olt-...42-.*/) { 
    elsif ("%{ADSL-Agent-Circuit-Id}" =~ /6f6c742d......34322d.*/) {
            update request {
                    Calling-Station-Id := "%{User-Name}"
                    User-Name := "%{ADSL-Agent-Circuit-Id}"
                    User-Password := 'secret01'
            }
            update control {
                    Proxy-To-Realm := 'ils-ftth'
            }
    }

    }

}

Dans les fichiers /srv/data/specific-XXX-circuitid, sont inscrit les Circuit-ID des liens qui ne rentrent pas dans les cas par défaut.

Avant de transmettre la requête radius au FAI membre, son format est légèrement modifié afin mettre l'adresse MAC du routeur abonné dans l'attribut Calling-Station-Id, et le Circuit-ID dans le champ User-Name.

Format d'une requête Access-Request envoyée par l'opérateur de collecte:

(79) Received Access-Request Id 178 from 109.74.90.58:60534 to 91.216.110.144:1812 length 158            
(79)   User-Name = "00:0d:b9:42:aa:60@gnd.ftth.axione"                                                   
(79)   User-Password = "password"                                                                             
(79)   NAS-IP-Address = 10.200.50.241                                                                    
(79)   ADSL-Agent-Circuit-Id = 0x6f6c742d62736e34322d303120706f6e20312f312f30312f30312f342f312f312f      
(79)   Alc-Client-Hardware-Addr = "00:0d:b9:42:aa:60"                                                    
(79)   Calling-Station-Id = "axione#OLT#"                                                                
(79)   Proxy-State = 0x437123

Format d'une requête Access-Request envoyée par Grenode au FAI:

(79) Sent Access-Request Id 244 from 0.0.0.0:35620 to 89.234.140.4:1812 length 244
(79)   User-Name := "0x6f6c742d62736e34322d303120706f6e20312f312f30312f30312f342f312f312f"
(79)   User-Password := "secret01"
(79)   NAS-IP-Address = 10.200.50.241
(79)   ADSL-Agent-Circuit-Id = 0x6f6c742d62736e34322d303120706f6e20312f312f30312f30312f342f312f312f
(79)   Alc-Client-Hardware-Addr = "00:0d:b9:42:aa:60"
(79)   Calling-Station-Id := "00:0d:b9:42:aa:60@gnd.ftth.axione"
(79)   Event-Timestamp = "Feb  2 2020 12:31:36 CET"
(79)   Message-Authenticator := 0x00
(79)   Proxy-State = 0x313738

Format d'une requête Access-Accept envoyé par le FAI à Grenode:

(79) Received Access-Accept Id 244 from 89.234.140.4:1812 to 91.216.110.144:35620 length 119
(79)   Service-Type = Outbound-User
(79)   Alc-Ipv6-Address = 2a0e:c400:ff:f010:ffff:ffff:ffff:ffff
(79)   Framed-IPv6-Route = "2a0e:c400:0:a00::/56"
(79)   Class = 0x465454482d47502d444c3330306d31306d3530306b2d554c3330306d316d3530306b
(79)   Framed-IP-Address = 5.183.104.10
(79)   Proxy-State = 0x313738

Format d'une requête Access-Accept envoyé par Grenode à l'opérateur de collecte:

(79) Sent Access-Accept Id 178 from 91.216.110.144:1812 to 109.74.90.58:60534 length 0
(79)   Class = 0x465454482d47502d444c3330306d31306d3530306b2d554c3330306d316d3530306b
(79)   Proxy-State = 0x437123

Afin de comprendre le contenu des attributs Circuit-ID et Class, il faut convertir la valeur affiché en hexadécimale en ascii.

ADSL-Agent-Circuit-Id = 0x6f6c742d62736e34322d303120706f6e20312f312f30312f30312f342f312f312f
                  = olt-bsn42-01 pon 1/1/01/01/4/1/1/
Class = 0x465454482d47502d444c3330306d31306d3530306b2d554c3330306d316d3530306b
      = FTTH-GP-DL300m10m500k-UL300m1m500k

Dans la configuration Radius du FAI membre, on peut soit mettre la classe en hexa :

Class := 0x465454482d47502d444c3330306d31306d3530306b2d554c3330306d316d3530306b

soit la mettre sous forme de chaîne de caractère, mais il faut alors bien mettre des quotes :

Class := "FTTH-GP-DL300m10m500k-UL300m1m500k"

Serveurs DHCP

Nous utilisons KEA comme serveur DHCP (aussi bien pour DHCPv4 que DHCPv6).

Lors de la configuration initiale, le FAI déclare ses subnets IP (voir deuxième partie de cette documentation).

Ensuite, pour chaque abonné, une réservation sera créée automatiquement afin d'allouer des adresses stables :

"reservations": [
  {
    "circuit-id": "6f6c742d62736e34322d303120706f6e20312f312f30312f30312f342f312f312f",
    "ip-address": "5.183.104.10",
    "client-classes": [ "ils01" ]
  }
 ]

Pour l'instant, kea-dhcpv6 ne permet pas de faire des réservations en utilisant le Circuit-id. En attendant, on utilise l'adresse mac (apprise dynamique par les échanges radius). Cette méthode est imparfaite, et fonctionne uniquement si le DUID du client DHCPv6 est construit sur la base de l'adresse MAC de l'interface WAN du routeur. (DUID-LLT ou DUID-LL)

"reservations": [
  {
    "hw-address": "00:0d:b9:42:aa:60",
    "ip-addresses": [ "2a0e:c400:ff:f010:ffff:ffff:ffff:ffff" ],
    "prefixes": [ "2a0e:c400:0:a00::/56" ],
    "client-classes": [ "ils01" ]
  }
]

Ces réservations sont crées dynamiquement sur la base du message Radius Access-Accept renvoyé par le FAI membre. Freeradius exécute pour ce faire un script bash maison qui utilise l'API de KEA pour ajouter les réservations. On crée la réservation dans les serveurs DHCP de rosette et gabier

    post-auth {

    #rosette
    `/srv/script/createDhcpReservation.sh -c %{User-Name} -4 %{&proxy-reply:Framed-IP-Address} -m %{Proxy-request:Calling-Station-Id} -6 %{&proxy-reply:Alc-Ipv6-Address} -p %{&proxy-reply:Framed-IPv6-Route} -k http://91.216.110.144/kea`

    #gabier
    `/srv/script/createDhcpReservation.sh -c %{User-Name} -4 %{&proxy-reply:Framed-IP-Address} -m %{Proxy-request:Calling-Station-Id} -6 %{&proxy-reply:Alc-Ipv6-Address} -p %{&proxy-reply:Framed-IPv6-Route} -k http://91.216.110.145/kea`

    }

Les fonctions disponibles dans l'API de KEA sont très limitées (sans les "Premium Hook"). Le script récupère donc la totalité de la configuration, ajoute ou modifie la réservation, et pousse la nouvelle configuration. Cette opération de rechargement de configuration n'est effectuée qu'a la toute première connexion d'un nouvel abonné FTTH, ou si un paramètre à changé (Adresses IP, Mac-address, etc)

Le script interroge un reverse-proxy nginx, qui lui même interroge isc-kea-ctrl-agent, qui lui même configure kea-dhcp4-server et kea-dhcp6-server.

FreeRadius → createDhcpReservation.sh → nginx → isc-kea-ctrl-agent → kea-dhcp4-server
                                                                   → kea-dhcp6-server

Routage par IP source

Dans le sens remontant (abonné vers Internet), du routage par IP source est en place. En effet, l'ensemble des flux abonné arrivent sur rosette ou gabier sur la même interface réseau.

Afin d'envoyer les flux vers le bon FAI membre, on effectue un routage par ip source, avec une table de routage par FAI.

La méthode est documenté sur ?Mettre en place du source routing

Redondance

A l'heure actuelle, Grenode ne dispose que d'un tronc de collecte FTTH. Il a cependant été choisi d'avoir deux routeurs de collecte: ?rosette et ?gabier Gabier peut, si nécessaire, prendre le relai de rosette (en cas de panne ou de maintenance).

Il n'a pas été possible d'obtenir 2 sessions BGP vers le fournisseur de collecte. La session est donc montée sur une IP virtuelle qui peut basculer d'un routeur à l'autre.

Les configurations Freeradius, DHCP et routage sont entièrement symétriques.

Procédure de bascule vers gabier pour maintenance sur rosette:

rosette:~# systemctl stop freeradius
rosette:~# iptables -t nat -A PREROUTING  -d 91.216.110.144 -p udp --dport 1812 -j DNAT --to 91.216.110.145:1812 
rosette:~# iptables -t nat -A POSTROUTING -d 91.216.110.145 -p udp --dport 1812 -j SNAT --to-source 91.216.110.144
rosette:~# systemctl stop isc-kea-dhcp4-server
rosette:~# systemctl stop isc-kea-dhcp6-server
rosette:~# birdc disable ils_thorondor
rosette:~# birdc6 disable ils_thorondor
rosette:~# systemctl stop keepalived

Si besoin de redémarrer la machine, on désactive les services (systemctl disable XXX) et les sessions BGP de livraison dans les fichiers de configuration de bird, afin d'éviter une réactivation non voulue au reboot.

Les règles de DNAT/SNAT permettent de renvoyer les échanges Radius vers gabier. Elles ne sont pas strictement nécessaires, mais il a été constaté que les équipements du fournisseur ont du mal à basculer sur le radius secondaire. Cela peut entrainer des problèmes au renouvellement des baux DHCP. (routeur abonné sans IP pendant quelques secondes/minutes)

Configuration nécessaire pour rajouter un FAI membre

Ci-dessous la procédure pour l'ajout d'un FAI membre. Tout est à faire en double sur rosette et gabier.

Configuration BGP

La première étape est d'établir des sessions BGP entre Grenode (rosette + gabier) et le FAI (idéalement 2 routeurs, sinon un seul peut suffire) :

  • mettre en place un VLAN et des subnets d'interconnexion (IPv4 et IPv6)
  • monter les sessions BGP avec le FAI
  • mettre en place la mécanique de "pipe" dans Bird côté Grenode pour mettre les routes du FAI dans une table de routage séparée

Pour la configuration des routes et des filtres, il faut identifier les IP Radius du FAI (IPv4 uniquement), puis s'assurer que les routes suivantes sont bien acceptées :

  • routes reçues du FAI : le FAI doit annoncer en BGP à Grenode ses IP Radius + une route par défaut
  • routes envoyées au FAI : Grenode envoie les IP de ses Radius, ainsi que les IP des abonnés du FAI

Configuration Radius

  • Ajouter les IP Radius du FAI dans /etc/freeradius/3.0/sites-enabled/proxy.conf
  • Recopier la mécanique de proxy Radius existante d'un FAI dans /etc/freeradius/3.0/sites-enabled/default

Il faut le faire sur rosette et gabier.

Configuration routage par IP source

Il s'agit de "policy routing" qui se base sur l'adresse IP source des paquets. Le but est que tout le trafic d'un FAI utilise la bonne table de routage (remplie par Bird).

Il faut pour cela ajouter des règles "ip rule" dans /etc/network/interfaces, et ce pour chacun des préfixes IP du FAI. Par exemple :

# ensX: cust: Illyse FTTH (FTTH) {vlan226}
auto ensX
iface ensX inet static
        address 172.16.226.106/24
        up ip link set $IFACE alias "cust: Illyse FTTH (FTTH)"
        up ip rule add from 5.183.104.0/24 table ils_table
        up ip rule add from 192.0.2.0/24 table ils_table
        down ip rule del from 192.0.2.0/24 table ils_table || true
        down ip rule del from 5.183.104.0/24 table ils_table || true

iface ensX inet6 static
        address 2001:912:800:226::106/64
        up ip -6 rule add from 2a0e:c400::/40 table ils_table
        down ip -6 rule del from 2a0e:c400::/40 table ils_table || true

Attention, il faut penser à le faire à chaque ajout de nouveau subnet IP !

Configuration DHCP

C'est la partie la plus complexe. Ca se passe dans /etc/kea/kea-dhcp4.conf et /etc/kea/kea-dhcp6.conf.

Il faut d'abord que le FAI membre déclare une ou plusieurs "client-class" avec sa configuration :

{
  "Dhcp4": {
    ...
    "client-classes": [
      {
        "boot-file-name": "",
        "name": "ils01",
        "next-server": "0.0.0.0",
        "option-data": [
          {
            "always-send": false,
            "code": 3,
            "csv-format": true,
            "data": "5.183.104.1",
            "name": "routers",
            "space": "dhcp4"
          },
          {
            "always-send": false,
            "code": 6,
            "csv-format": true,
            "data": "89.234.140.1, 89.234.140.2",
            "name": "domain-name-servers",
            "space": "dhcp4"
          }
        ],
        "option-def": [ ],
        "server-hostname": ""
      },
      ...
    ]
  }
}

Il faut créer une telle entrée pour chaque /24 du FAI : si il y a plusieurs entrées, il faut les nommer ils01, ils02, etc. Tout le /24 du FAI ne sera pas forcément alloué au FTTH : il est parfaitement possible d'utiliser une partie des IP de ce /24 pour le FTTH, et une autre partie des IP de ce /24 pour complètement autre chose. C'est le Radius du FAI qui fait foi pour décider quelles IPs sont allouées aux abonnés.

Les paramètres importants sont :

  • les résolveurs DNS envoyés à l'abonné, ici 89.234.140.1 et 89.234.140.2
  • la gateway, ici 5.183.104.1. Elle doit être dans le même /24 que l'IP de l'abonné. A part ça, c'est une adresse IP complètement arbitraire qui n'a besoin d'être configurée nulle part. En effet, Axione fait du proxy-ARP sur tout le /24 : lorsque le routeur abonné demande l'adresse MAC de 5.183.104.X (et en particulier 5.183.104.1), c'est un équipement Axione qui répond avec sa propre MAC. Pour autant, cette IP 5.183.104.1 n'est pas configurée sur l'équipement d'Axione. Pour éviter les malentendus, il est conseillé de ne pas utiliser cette IP ailleurs sur le réseau du FAI.

Il faut également créer un subnet pour chaque /24, qui sera utilisé pour les renew DHCP :

"shared-networks": [
  {
    "authoritative": false,
    "calculate-tee-times": false,
    "interface": "dummy0",
    "match-client-id": true,
    "name": "axione-ftth",
    "next-server": "0.0.0.0",
    "option-data": [ ],
    "relay": {
      "ip-addresses": [ "91.216.110.148", "91.216.110.149", "91.216.110.150", "91.216.110.151" ]
    },
    "reservation-mode": "all",
    "subnet4": [
      {
        "4o6-interface": "",
        "4o6-interface-id": "",
        "4o6-subnet": "",
        "authoritative": false,
        "calculate-tee-times": false,
        "id": 1,
        "interface": "dummy0",
        "match-client-id": true,
        "next-server": "0.0.0.0",
        "option-data": [ ],
        "pools": [ ],
        "rebind-timer": 6300,
        "relay": {
          "ip-addresses": [ ]
        },
        "renew-timer": 3600,
        "reservation-mode": "global",
        "reservations": [ ],
        "subnet": "5.183.104.0/24",
        "t1-percent": 0.5,
        "t2-percent": 0.875,
        "valid-lifetime": 7200
      },
      ...
    ]
  }
]

Attention à bien utiliser un ID de subnet différent à chaque fois, sinon Kea refusera de démarrer.

En IPv6, la configuration est similaire, mais il n'y a (a priori) besoin que d'une seule client-class avec les résolveurs DNS à envoyer :

{
  "Dhcp6": {
    "calculate-tee-times": true,
    "client-classes": [
      {
        "name": "ils01",
        "option-data": [
          {
            "always-send": false,
            "code": 23,
            "csv-format": true,
            "data": "2a00:5881:4000::1, 2a00:5881:4000::2",
            "name": "dns-servers",
            "space": "dhcp6"
          }
        ]
      },
      ...
    ],
    ...
  }
}

Déclarer aussi un subnet IPv6 :

"shared-networks": [
  {
    "calculate-tee-times": true,
    "interface": "dummy0",
    "name": "axione-ftth6",
    "option-data": [ ],
    "preferred-lifetime": 0,
    "rapid-commit": false,
    "relay": {
      "ip-addresses": [ "2001:912:900:a000::148", "2001:912:900:a000::149", "2001:912:900:a000::150", "2001:912:900:a000::151" ]
    },
    "reservation-mode": "all",
    "subnet6": [
      {
        "calculate-tee-times": true,
        "id": 1,
        "interface": "dummy0",
        "option-data": [ ],
        "pd-pools": [ ],
        "pools": [ ],
        "preferred-lifetime": 7200,
        "rapid-commit": false,
        "rebind-timer": 5400,
        "relay": {
          "ip-addresses": [ ]
        },
        "renew-timer": 3600,
        "reservation-mode": "global",
        "reservations": [ ],
        "subnet": "2a0e:c400::/40",
        "t1-percent": 0.5,
        "t2-percent": 0.8,
        "valid-lifetime": 10800
      },
      ...
    ],
    "t1-percent": 0.5,
    "t2-percent": 0.8,
    "valid-lifetime": 10800
  }
],