Fibre Orange : Remplacer sa Livebox par un routeur CentOS Stream 8
Je suis abonné à l’offre Fibre d’Orange depuis 2016. Et si je suis globalement satisfait de la qualité du réseau, je ne peux pas en dire autant de la Livebox fournie par Orange. Les limitations sont nombreuses pour un geek souhaitant faire de l’hébergement de services à la maison : une seule plage IPv6 en /64, pas de configuration possible des tables de routage pour avoir plusieurs sous-réseaux IPv4, etc. J’ai donc décidé de remplacer la Livebox par un routeur basé sur CentOS Stream 8. Et l’aventure ne fut pas un long fleuve tranquille ! Cet article présente la configuration que j’ai mise en place et qui me donne aujourd’hui satisfaction.
Besoin
En tant que geek, j’ai des besoins bien particuliers quant au routeur qui gère mon accès internet :
- Segmenter mon réseau en 5 sous-réseaux : Administration, DMZ, LAN, IoT et Invité.
- Offrir une plage d’adresse IPv4 et IPv6 à chaque sous-réseau.
- Héberger un Homelab à la maison (avec flux sortants et entrants)
- Faire tourner des VMs pour héberger mes services
J’ai volontairement laissé de coté :
- Le support des chaînes TV d’Orange
- Le support de la téléphonie Orange
Matériel
Pour le serveur en lui-même, j’ai choisi un HP DL20 Gen9 pour les raisons suivantes :
- J’avais déjà du matériel HP dans le Lab
- Taille plutôt compacte (format lame 1U, court)
- Silencieux (une fois démarré, on n’entend quasiment pas les ventilateurs)
- Extensible
Je l’ai déniché pour 660 € sur eBay avec les caractéristiques suivantes :
- 2 disques 3,5 pouces de 4 To chacun, sur carte RAID matérielle
- CPU Intel Xeon E3-1270 v6
- 48 Go de DDR4 ECC
- Carte réseau supplémentaire Intel I350 au format “FlexibleLOM”, avec 4 ports RJ-45
Pour me raccorder au réseau fibre optique d’Orange, j’ai conservé l’ONT (Optical Network Termination) qui m’a été fourni avec la Livebox. D’un coté, je branche la jarretière optique, et de l’autre je branche le câble RJ-45 qui va jusqu’au serveur HP.
Logiciel
Au moment où j’ai commencé à installer le serveur, la dernière version publiée de CentOS Stream était la 8. Mais partez plutôt sur la dernière version disponible !
L’installation de CentOS Stream s’effectue, comme la majorité des distributions Linux :
- Télécharger l’ISO de CentOS Stream
- Copier l’ISO sur une clé USB
- Démarrer le serveur et booter sur la clé USB
Spécificités de l’offre Fibre Orange
L’offre Fibre d’Orange a quelques spécificités, désormais bien connues des geeks de lafibre.info :
- Patcher le client DHCP pour envoyer les paquets avec la priorité 802.1p numéro 6.
- Configurer l’interface réseau pour utiliser le VLAN 832.
- Configurer le client DHCP pour envoyer le login et le mot de passe Orange.
- Assurer la cohérence IPv4/IPv6
Cet article couvre l’ensemble de ces points.
Il est à noter qu’à partir du noyau 5.7, le filtre “egress” de netfilter devrait permettre la capture des paquets DHCPv4 et l’étiquetage de leur priorité. Le client DHCP patché ne serait alors plus nécessaire.
Commit e687ad60af09 (“netfilter: add netfilter ingress hook after handle_ing() under unique static key”) introduced the ability to classify packets on ingress.
Allow the same on egress.
This hook is also useful for NAT46/NAT64, tunneling and filtering of locally generated af_packet traffic such as dhclient.
Installation du client DHCP patché
J’ai backporté le patch de Zoc dans les RPMs dhclient de CentOS Stream 8. Les sources sont dans le dépot Git nmasse-itix/dhclient-orange et les RPMs sont disponibles publiquement sur un partage Backblaze B2.
Installer le client DHCP patché.
sudo curl -o /etc/yum.repos.d/dhclient-orange.repo https://f003.backblazeb2.com/file/dhclient-orange/dhclient-orange.repo
sudo dnf remove dhcp-client
sudo dnf install dhcp-client-orange-isp
Configurer NetworkManager pour qu’il utilise dhclient plutôt que son client DHCP interne.
[main]
dhcp=dhclient
Redémarrer NetworkManager.
sudo systemctl restart NetworkManager
Configuration du client DHCP pour l’authentification Orange
Pour configurer le client DHCP pour l’authentification Orange, il vous faudra trois choses :
- L’adresse MAC de la Livebox que vous souhaitez remplacer (vous pouvez la trouver sur une étiquette sous la Livebox). Notez que dans le fichier de configuration ci-dessous, il faudra préfixer l’adresse MAC par “01:”.
- La chaîne de caractère à envoyer dans l’option 90. Utilisez pour cela le calculateur mis à disposition par la communauté lafibre.info.
- Le nom de l’interface réseau sur laquelle vous avez connecté l’ONT Orange (eno2 dans l’exemple ci-dessous), suffixé par .832.
Saisir le contenu du fichier /etc/dhcp/dhclient.conf.
option rfc3118-authentication code 90 = string;
option dhcp-client-identifier code 61 = string;
interface "eno2.832" {
send vendor-class-identifier "sagem";
send user-class "+FSVDSL_livebox.Internet.softathome.Livebox4";
send dhcp-client-identifier 01:AA:BB:CC:DD:EE:FF;
send rfc3118-authentication 00:00:00:00:00:00:00:00:00:00:00:1a:09:00:00:05:58:01:03:41:01:0B:66:74:69:2F:64:75:6D:6D:79:3c:12:31:32:33:34:35:36:37:38:39:30:31:32:33:34:35:36:03:13:41:b9:80:f2:ea:3f:06:3b:2b:e7:08:ac:ec:9c:38:9e:ba;
request subnet-mask,routers,domain-name,broadcast-address,dhcp-lease-time,dhcp-renewal-time,dhcp-rebinding-time,rfc3118-authentication;
}
Configuration de l’interface réseau pour utiliser le VLAN 832
Configurer l’interface réseau (ici eno2) à l’aide de NetworkManager pour utiliser le VLAN 832.
sudo nmcli con add type ethernet con-name eno2.832 autoconnect yes ipv4.method disabled ipv6.method ignore connection.interface-name eno2
sudo nmcli con add type vlan dev eno2 con-name eno2.832 id 832 egress "0:0,1:0,2:0,3:0,4:0,5:0,6:6,7:0" autoconnect yes ipv4.method auto ipv6.method ignore
À ce stade, vous devez obtenir une adresse IPv4 publique lorsque vous activez l’interface.
sudo nmcli con up eno2.832
La suite de l’article est très dépendante de choix que j’ai pu faire par ailleurs dans mon réseau domestique. Aussi, considérez la comme une indication plus qu’un tutoriel pas à pas.
Configuration nécessaire à IPv6
Activer le routage des paquets IPv4 et IPv6. Ne pas oublier d’adapter le nom de l’interface réseau !
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
net/ipv6/conf/eno2.832/accept_ra=2
net.ipv4.conf.all.src_valid_mark=1
Recharger les paramètres noyaux avec la commande sysctl.
sudo sysctl --system
Configurer ensuite le client DHCPv6 pour l’authentification Orange. Même mode opératoire que pour IPv4, il vous faudra trois choses :
- L’adresse MAC de la Livebox que vous souhaitez remplacer (vous pouvez la trouver sur une étiquette sous la Livebox). Notez que dans le fichier de configuration ci-dessous, il faudra préfixer l’adresse MAC par “00:03:00:01:”.
- La chaîne de caractère à envoyer dans l’option 11. Utilisez pour cela le calculateur mis à disposition par la communauté.
- Le nom de l’interface réseau sur laquelle vous avez connecté l’ONT Orange (eno2 dans l’exemple ci-dessous), suffixé par .832.
Saisir le contenu du fichier /etc/dhcp/dhclient6.conf.
option dhcp6.auth code 11 = string;
option dhcp6.vendorclass code 16 = string;
option dhcp6.userclass code 15 = string;
interface "eno2.832" {
send dhcp6.vendorclass 00:00:04:0e:00:05:73:61:67:65:6d;
send dhcp6.userclass 00:2b:46:53:56:44:53:4c:5f:6c:69:76:65:62:6f:78:2e:49:6e:74:65:72:6e:65:74:2e:73:6f:66:74:61:74:68:6f:6d:65:2e:6c:69:76:65:62:6f:78:34;
send dhcp6.vendor-opts 00:00:05:58:00:06:00:0e:49:50:56:36:5f:52:45:51:55:45:53:54:45:44;
send dhcp6.client-id 00:03:00:01:AA:BB:CC:DD:EE:FF;
send dhcp6.auth 00:00:00:00:00:00:00:00:00:00:00:1a:09:00:00:05:58:01:03:41:01:0B:66:74:69:2F:64:75:6D:6D:79:3c:12:31:32:33:34:35:36:37:38:39:30:31:32:33:34:35:36:03:13:41:b9:80:f2:ea:3f:06:3b:2b:e7:08:ac:ec:9c:38:9e:ba;
also request dhcp6.name-servers, dhcp6.vendorclass, dhcp6.userclass, dhcp6.auth;
}
Créer le script de dispatch pour NetworkManager. Ce script sera appelé automatiquement par NetworkManager après un up ou un down de l’interface réseau et lancera le client DHCPv6 en mode Prefix Delegation. Ne pas oublier d’adapter le nom de l’interface réseau !
#!/bin/bash
set -Eeuo pipefail
if [ "${DEVICE_IFACE:-}" != "eno2.832" ]; then
exit 0
fi
# Set log file for this shell and all commands executed
exec &>>/var/log/orange-ipv6.log
trap 'ret=$? ; if [ $ret -gt 0 ]; then echo "NM dispatcher script called with action = ${NM_DISPATCHER_ACTION:-} finished at $(date -Isecond) with code $ret"; fi' EXIT ERR
case "$NM_DISPATCHER_ACTION" in
up)
signal=TERM
while [ -f "/var/run/NetworkManager/dhclient6-$DEVICE_IFACE.pid" ] && pgrep -F "/var/run/NetworkManager/dhclient6-$DEVICE_IFACE.pid" &>/dev/null; do
if pkill -F /var/run/NetworkManager/dhclient6-$DEVICE_IFACE.pid --signal "$signal"; then
signal=KILL
sleep 5
fi
done
dhclient -P -6 -cf /etc/dhcp/dhclient6.conf -lf "/var/lib/NetworkManager/dhclient-$CONNECTION_UUID-$DEVICE_IFACE.lease" -pf "/var/run/NetworkManager/dhclient6-$DEVICE_IFACE.pid" "$DEVICE_IFACE"
;;
down)
if [ -f "/var/run/NetworkManager/dhclient6-$DEVICE_IFACE.pid" ]; then
pkill -F /var/run/NetworkManager/dhclient6-$DEVICE_IFACE.pid || true
fi
;;
*)
;;
esac
exit 0
Créer le répertoire /etc/dhcp/dhclient-exit-hooks.d.
sudo mkdir -p /etc/dhcp/dhclient-exit-hooks.d
Créer le script de hook pour dhclient. Il sera appelé par le client DHCPv6 après obtention d’un préfixe IPv6 et a pour tâche d’affecter les adresses IPv6 aux différentes interfaces du serveur. Ne pas oublier d’adapter le nom de l’interface réseau et le nom de vos interfaces réseaux internes (chez moi, elles s’appelle ivs1, ivs2, etc.) !
#!/bin/bash
set -Eeuo pipefail
if [ "${interface:-}" != "eno2.832" ]; then
exit 0
fi
# Issue a log in case of error
trap 'ret=$? ; if [ "$ret" -gt 0 ]; then echo "dhclient hook script called with reason = ${reason:-} finished at $(date -Isecond) with code $ret"; fi' EXIT ERR
ifprefix="ivs"
internal_ifaces="$(ifconfig -a -s | egrep -o "^($ifprefix)[0-9]+" | sort -V)"
external_iface="$interface"
temp="$(mktemp -d -t orange.XXXXXX)"
trap 'rm -rf "$temp"' EXIT
function log () {
if [ -n "${DEBUG:-}" ]; then
echo "$@"
fi
"$@"
}
function cleanup_interface () {
local interface="$1"
current_ipv6_addr="$(ip -6 -br addr show dev "$interface" scope global | awk 'NR == 1 { print $3 }')"
if [ -n "$current_ipv6_addr" ]; then
log ip addr delete "$current_ipv6_addr" dev "$interface" || true
fi
}
case "${reason:-}" in
BOUND6|REBIND6)
# This should be set on startup. To be safe, recheck here.
sysctl -q net/ipv6/conf/eno2.832/accept_ra=2
if [ -n "${new_ip6_prefix:-}" ] ; then
ipcalc -S 64 "$new_ip6_prefix" --no-decorate > "$temp/networks"
for interface in $internal_ifaces $external_iface; do
if [ "$interface" == "$external_iface" ]; then
# eno2.832 gets subnet 0
ipv6_network="$(head -n 1 $temp/networks)"
else
if [[ "$interface" =~ ^ivs ]]; then
# ivsX gets subnet X (X is guaranted to start at 1)
n="${interface#ivs}"
else
echo "Unsupported interface: $interface"
continue
fi
ipv6_network="$(awk -v "net_id=$n" 'NR == net_id + 1 { print }' $temp/networks)"
fi
# Determine current and previous IPv6 addresses
new_ipv6_addr="$(echo "$ipv6_network" | sed -r 's|::/([0-9]+)|::1/\1|')"
old_ipv6_addr="$(ip -6 -br addr show dev "$interface" scope global | awk 'NR == 1 { print $3 }')"
# Only replace the previous IPv6 address if the new one is different
if [ "${new_ipv6_addr}" != "$old_ipv6_addr" ]; then
cleanup_interface "$interface"
log ip addr add "$new_ipv6_addr" dev "$interface"
fi
done
fi
;;
RELEASE)
for interface in $internal_ifaces; do
cleanup_interface "$interface"
done
;;
*)
;;
esac
exit 0
N’oubliez pas de rendre ces deux scripts exécutables.
sudo chmod 755 /etc/NetworkManager/dispatcher.d/99-orange-ipv6
sudo chmod 755 /etc/dhcp/dhclient-exit-hooks.d/99-orange-ipv6
Pour que l’IPv6 fonctionne, il faut correctement étiqueter les paquets DHCPv6 en priorité 6. Pour cela, j’utilise nftables. Le script est très dépendant de mes choix de réseau, je vous invite donc à le prendre comme un exemple pour créer le votre ensuite.
Désactiver firewalld.
sudo systemctl stop firewalld
sudo systemctl disable firewalld
sudo systemctl mask firewalld
Éditer le contenu du fichier /etc/sysconfig/nftables.conf.
# Uncomment the include statement here to load the default config sample
# in /etc/nftables for nftables service.
include "/etc/nftables/itix.nft"
# To customize, either edit the samples in /etc/nftables, append further
# commands to the end of this file or overwrite it after first service
# start by calling: 'nft list ruleset >/etc/sysconfig/nftables.conf'.
Créer le fichier /etc/nftables/update.nft.
#!/usr/sbin/nft -f
flush table inet itix-fw
delete table inet itix-fw
flush table ip itix-nat
delete table ip itix-nat
include "/etc/nftables/itix.nft"
Créer le fichier /etc/nftables/itix.nft.
#!/usr/sbin/nft -f
table inet itix-fw {
chain Public-Services {
# Allow Ping
icmp type echo-request counter accept
# Allow SSH
tcp dport { 22 } counter accept
}
chain Forward-IPv6-from-Internet {
# Allow IPv6 ICMP
ip6 nexthdr ipv6-icmp counter accept
# Enable TCP/UDP ports > 1024
tcp dport > 1024 counter accept
udp dport > 1024 counter accept
}
chain Orange-IPv6-Priority {
# DSCP is "Differenciated Service Code Point". See RFC 4594.
# CS6 is "Class Selector 6 (Internetwork Control)".
icmpv6 type { nd-neighbor-solicit, nd-router-solicit } ip6 dscp set cs6 meta priority set 0:6 counter
udp sport { dhcpv6-client, dhcpv6-server } ip6 dscp set cs6 meta priority set 0:6 counter
}
chain Input {
type filter hook input priority filter + 20
policy drop
# Accept packets related to existing connections
ct state invalid counter drop
ct state { established, related } counter accept
# Loopback
iifname lo counter accept
# Accept all ethernet frames on the public interface so that we can then handle the VLANs
iifname eno2 accept
# Filter packets arriving on VLAN 832
iifname eno2.832 counter jump Public-Services
# Internal Interfaces
iifname { ivs1, ivs2, ivs3, ivs4, ivs5 } counter accept
}
chain Output {
type filter hook output priority filter + 20
policy accept
# Accept packets related to existing connections
ct state invalid counter drop
ct state { established, related } counter accept
# Tag all DHCPv6 packets with priority 6
oifname eno2.832 counter jump Orange-IPv6-Priority
}
chain Forward {
type filter hook forward priority filter + 20
policy drop
# Accept packets related to existing connections
ct state invalid counter drop
ct state { established, related } counter accept
# Loopback
iifname lo counter accept
# From the internal network to the internet
iifname { ivs1, ivs2, ivs3, ivs4, ivs5 } oifname eno2.832 counter accept
# From the internal network to the internal network
iifname { ivs1, ivs2, ivs3, ivs4, ivs5 } oifname { ivs1, ivs2, ivs3, ivs4, ivs5 } counter accept
# From the internet to the internal network
iifname eno2.832 oifname { ivs1, ivs2, ivs3, ivs4, ivs5 } counter jump Forward-IPv6-from-Internet
}
}
table ip itix-nat {
chain Post-Routing {
type nat hook postrouting priority srcnat
policy accept
# Masquerade all connections to the Internet
iifname { ivs1, ivs2, ivs3, ivs4, ivs5 } oifname eno2.832 counter masquerade
}
}
Activer et démarrer le service nftables.
sudo systemctl enable nftables
sudo systemctl start nftables
Vérifier avec la commande sudo nft list tables que les deux tables itix-fw et itix-nat ont bien été chargées.
A ce moment là, vous pouvez essayer de faire un nmcli con down eno2.832 puis nmcli con up eno2.832 et vérifier que vous avez bien une adresse IPv4 publique et une adresse IPv6 globale.
Vérification périodique et cohérence IPv4/IPv6
Orange applique des règles de cohérence entre les protocoles IPv4 et IPv6. Rien n’est documenté officiellement, mais sur le forum lafibre.info quelques indications ont été données.
Pour mettre en oeuvre ces règles, j’ai développé un script qui vérifie que les piles IPv4 et IPv6 sont opérationnelles et force un nouveau cycle DHCPv4 + DHCPv6 si nécessaire. Pour éviter tout problème, je force également un renouvellement des baux DHCP toutes les 12 heures.
#!/bin/bash
set -Eeuo pipefail
ORANGE_IFACE="eno2.832"
declare -a TARGET_IPV4=("8.8.8.8" "8.8.4.4" "1.1.1.1" "1.0.0.1" "208.67.222.222" "208.67.220.220")
declare -a TARGET_IPV6=("2001:4860:4860::8888" "2001:4860:4860::8844" "2606:4700:4700::1111" "2606:4700:4700::1001" "2620:119:53::53" "2620:119:35::35")
function help () {
echo "Usage: $0 {help|health-check|renew}"
}
function error () {
echo "$1" >&2
}
function msg () {
echo "$1"
}
function die () {
error "$1" "$@"
exit 1
}
function ping () {
/bin/ping "$1" -c 4 -I "$ORANGE_IFACE" -n -q -W 10 "$2" > /dev/null 2>&1
}
function orange_healthcheck () {
declare ipv4_check=0
for ipv4 in ${TARGET_IPV4[@]}; do
if ping -4 "$ipv4"; then
ipv4_check=1
else
msg "$ipv4 is not reachable!"
fi
done
if [ "$ipv4_check" == "0" ]; then
error "IPv4 stack is down!"
fi
declare ipv6_check=0
for ipv6 in ${TARGET_IPV6[@]}; do
if ping -6 "$ipv6"; then
ipv6_check=1
else
msg "$ipv6 is not reachable!"
fi
done
if [ "$ipv6_check" == "0" ]; then
error "IPv6 stack is down!"
fi
if [[ "$ipv4_check" == "0" || "$ipv6_check" == "0" ]]; then
return 1
fi
return 0
}
function kill_process () {
declare signal=TERM
while :; do
if [ -f "/var/run/NetworkManager/$1-$ORANGE_IFACE.pid" ] && pkill -F /var/run/NetworkManager/$1-$ORANGE_IFACE.pid --signal "$signal"; then
msg "Killed $1 with $signal!"
signal=KILL
sleep 5
else
break
fi
done
return 0
}
function is_dhclient4_running () {
if [ ! -f /var/run/NetworkManager/dhclient-$ORANGE_IFACE.pid ] || ! pgrep -F /var/run/NetworkManager/dhclient-$ORANGE_IFACE.pid &>/dev/null; then
return 1
fi
return 0
}
function orange_renew () {
# Stop all dhclient instances
kill_process dhclient6
kill_process dhclient
# Sometimes the "nmcli device reapply" fails to restart the DHCP client.
# Monitor the dhclient process to know when it succeeded.
while ! is_dhclient4_running; do
nmcli device reapply "$ORANGE_IFACE"
sleep 30
done
return 0
}
case "${1:-}" in
health-check)
if ! orange_healthcheck; then
error "Renewing IPv4 and IPv6 DHCP leases..."
nmcli connection down $ORANGE_IFACE
sleep 2
nmcli connection up $ORANGE_IFACE
fi
;;
renew)
orange_renew
;;
help)
help
;;
*)
error "Unkown action '${1:-}'!"
help
exit 1
;;
esac
exit 0
Rendre le script exécutable.
sudo chmod 755 /usr/local/bin/fibre-orange
Créer la crontab associée pour lancer ce script périodiquement.
*/5 * * * * root /usr/local/bin/fibre-orange health-check
0 */12 * * * root /usr/local/bin/fibre-orange renew
Conclusion
Cela fait déjà plusieurs années que le serveur est installé et avec les dernières informations glanées sur le forum lafibre.info, la configuration est robuste. Je n’ai pas tout expliqué dans l’article car la configuration que j’ai mise en place est complexe et n’apporterait pas grand chose pour le geek souhaitant remplacer sa Livebox par un routeur CentOS Stream 8.
Par exemple, pour les sous-réseaux internes j’ai mis de l’Open vSwitch. Pour acheminer les requètes HTTP & HTTPS, j’ai mis en place Traefik. Pour héberger mes services, j’ai déployé Kubernetes. Ça, je le garde pour un prochain article !