28 February 2021

Configurer l’accès fibre Orange pour Edge Router

Ce tutoriel concerne la configuration d’un EdgeRouter X en remplacement d’une LiveBox 4 pour un abonnement Sosh pour obtenir une connectivité IPv4 et IPv6. Il se base sur de nombreux autres trouvés en ligne cités en référence en fin d’article, donc rien de bien nouveau. Pas toujours très clairs, pas toujours adaptés, et nécessitant d’exécuter un binaire inconnu sur mon routeur, j’ai décidé de faire une compil.

Matériel

  • EdgeRouter X Ubiquiti ;
  • MC220L TP-Link ;
  • module adaptateur fibre SFP fourni par Sosh, indispensable car il est reconnu côté opérateur, on ne peut donc pas le remplacer par un autre.

Il semblerait que ce SFP ne soit pas compatible avec certains équipements, notamment les routeurs Ubiquiti avec slot SFP, pour les plus aventureux, il existerait une modification hardware.

Quelques explications rapides

La configuration par DHCP Orange nécessite trois choses :

  • de communiquer sur le VLAN 832, dans les exemples donnés plus loin, la connexion à Internet se fait sur eth0, c’est là qu’est branché le MC220L ;
  • une authentification RFC 3118 ;
  • le marquage des paquets DHCP dans la bonne classe de service (QoS) : c’est pour cela qu’on patch dhclient, il y a d’autres solutions comme intercaler un switch ou utiliser tc, mais c’est plutôt lourd ou non disponible dans EdgeOS.

EdgeOS utilise dhcp6c comme client DHCPv6 pour configurer le préfixe délégué. Comme il nous faut le dhclient patché, pas possible d’utiliser la configuration standard du routeur.

Il faut aussi empêcher la post config par défaut de casser la config DNS quand une adresse v6 est récupérée, cela se fait en inhibant le hook dans ce cas précis en surchargeant make_resolv_conf, cf. dhclient-ipv6-nodns-hook.

Compiler pour le ER-X

Il faut modifier le binaire dhclient3 pour qu’il applique une priorité au trafic DHCP. En utilisant le support fourni par WireGuard, il est assez facile de le faire soi-même.

  • récupérer les sources EdgeOS [GPL.ER-e50.v2.0.9.5346345.tar.bz2](https://dl.ui.com/firmwares/edgemax/v2.0.9/gpl/GPL.ER-e50.v2.0.9.5346345.tar.bz2)
  • puis vyatta-dhcp3_4.1-ESV-R15-ubnt1+t5350120.dev.stretch.24c30f9.tar.gz
  • générer l’environnement de compilation
    $ git clone git@github.com:WireGuard/wireguard-vyatta-ubnt.git
    $ docker build --pull -t edgeos50-mipsel:latest -f ci/DOCKERFILE-mipsel .
    
  • voici le patch que j’ai appliqué, adapté de celui-ci
  • compiler le client DHCP
    $ docker run -it --rm --entrypoint /bin/bash --volume /<path>/GPL.ER-e50.v2.0.9.5346345/edgeos-vyatta-dhcp:/var/src edgeos50-mipsel
    $ cd /var/src/
    $ apt-get update
    $ apt-get install debhelper
    $ dpkg-buildpackage -us -uc -amipsel
    ** fail
    $ cd client
    $ mipsel-linux-gnu-strip dhclient
    

Générer les configurations

  • générer la chaine d’authentification rfc3118-auth à partir de son identifiant Orange, exemple avec fti/wwwwwww :
    $ echo -n fti/wwwwwww | od -A n -t x1 | sed 's/^ *//g' | sed 's/ *$//g' | sed 's/  */:/g'
    66:74:69:2f:77:77:77:77:77:77:77
    

    y ajouter le préfixe 00:00:00:00:00:00:00:00:00:00:00:1a:09:00:00:05:58:01:03:41:01:0d:

  • créer la configuration dans le routeur, par exemple via l’onglet Config Tree, sinon en ligne de commande, ne pas oublier d’activer le firewall pour IPv6, je ne mets pas les règles ici, il suffit de dupliquer celles configurées par défaut pour v4 en gros
    interfaces {
      ethernet eth0 {
          description Internet
          duplex auto
          speed auto
          vif 832 {
              address dhcp
              description "Orange Data fti/wwwwwww"
              dhcp-options {
                  client-option "send vendor-class-identifier &quot;sagem&quot;;"
                  client-option "send user-class &quot;\053FSVDSL_livebox.Internet.softathome.Livebox4&quot;;"
                  client-option "send rfc3118-auth 00:00:00:00:00:00:00:00:00:00:00:1a:09:00:00:05:58:01:03:41:01:0d:66:74:69:2f:77:77:77:77:77:77:77;"
                  client-option "send dhcp-client-identifier <address MAC SFP>;" # ne semble pas utile
                  client-option "request subnet-mask, routers, domain-name-servers, domain-name, broadcast-address, dhcp-lease-time, dhcp-renewal-time, dhcp-rebinding-time, rfc3118-authentication, domain-search, SIP-servers, Vendor-Specific-Information;"
                  default-route update
                  default-route-distance 210
                  global-option "option rfc3118-auth code 90 = string;"
                  global-option "option SIP-servers code 120 = string;"
                  global-option "option Vendor-Specific-Information code 125 = string;"
                  name-server update
              }
              egress-qos "0:0 1:0 2:0 3:0 4:0 5:0 6:6 7:0"
              firewall {
                  in {
                      ipv6-name WAN_IN_6
                      name WAN_IN
                  }
                  local {
                      ipv6-name WAN_LOCAL_6
                      name WAN_LOCAL
                  }
              }
              ipv6 {
                  address {
                      autoconf
                  }
                  dup-addr-detect-transmits 1
              }
          }
      }
    
  • copier les fichiers nécessaires sur le routeur, cf. en fin d’article
    user@host:~$ scp \
      edgeos-vyatta-dhcp/client/dhclient \
      dhclient-ipv6-nodns-hook \
      dhclient-ipv6-pd-hook \
      gandi-update-hook \
      dhclient6_eth0_832.conf \
      dhclient6.service \
      router:/tmp
    
  • déplacer les fichiers aux bons endroits sur le routeur
    user@router:~$ sudo cp /tmp/dhclient /sbin/dhclient3
    user@router:~$ sudo cp /tmp/dhclient-ipv6-nodns-hook /etc/dhcp3/dhclient-enter-hooks.d/
    user@router:~$ sudo cp /tmp/dhclient-ipv6-pd-hook /etc/dhcp3/dhclient-exit-hooks.d/
    user@router:~$ sudo cp /tmp/gandi-update-hook /etc/dhcp3/dhclient-exit-hooks.d/
    user@router:~$ sudo cp /tmp/dhclient6_eth0_832.conf /etc/dhcp3
    user@router:~$ sudo cp /tmp/dhclient6.service /etc/systemd/system
    user@router:~$ systemctl daemon-reload
    user@router:~$ systemctl start dhclient6
    user@router:~$ systemctl enable dhclient6
    

Références

Fichiers

  • dhclient-ipv6-nodns-hook
# prevent messing with DNS configs when receiving V6 PD from Orange
# since they don't provide any v6 DNS information in their DHCPv6 reply

LOG_FILE=/tmp/dhclient6.log

echo `date` >> $LOG_FILE
echo "entering dhclient-ipv6-nodns-hook" >> $LOG_FILE

case "$reason" in
    PREINIT6|BOUND6|RENEW6|REBIND6|DEPREF6|EXPIRE6|RELEASE6|STOP6)
make_resolv_conf() {
    echo "*** skipping make_resolv_conf" >> $LOG_FILE
}
        ;;
esac

echo "---" >> $LOG_FILE
  • dhclient-ipv6-pd-hook
# this script gets
# $reason $interface $old_ip6_prefix $new_ip6_prefix
# https://vincent.bernat.ch/fr/blog/2019-orange-livebox-linux

LOG_FILE=/tmp/dhclient6.log

IA_PD_IFACES="switch0"

echo `date` >> $LOG_FILE
echo "entering dhclient-ipv6-pd-hook" >> $LOG_FILE

for i in reason interface old_ip6_prefix new_ip6_prefix; do
    echo $i=\'${!i}\' >> $LOG_FILE
done

ipv6_prefix_setup() {
    # If old prefix differs from new one, flush it.
    [ -n "$old_ip6_prefix" ] && [ "$old_ip6_prefix" != "$new_ip6_prefix" ] && {
        for iface in $IA_PD_IFACES; do
            ip -6 addr flush dev "$iface" scope global || \
                echo "failed to flush global IPv6 addresses from interface $iface" >>  $LOG_FILE
        done
    }
    # If new prefix, assign it.
    [ -n "$new_ip6_prefix" ] && {
        offset=0
        for iface in $IA_PD_IFACES; do
            offset=$((offset + 1)) # should be < 10 otherwise broken IPv6 address below
            new_ip6=$(echo $new_ip6_prefix | sed -e "s@00::/56@0$offset::1/64@g")
            echo "setting  $new_ip6 on $iface" >> $LOG_FILE
            if ! ip -6 addr show dev "$iface" | grep -qwF "$new_ip6"; then
                ip -6 addr add "$new_ip6" dev "$iface" || \
                    echo "failed to add new address $new_ip6 to interface $iface" >> $LOG_FILE
            else
                echo "address $new_ip6 already assigned to interface $iface" >> $LOG_FILE
            fi
        done
    }
    ip -brief -6 a show scope global >> $LOG_FILE
}

case "$reason" in
    BOUND6|EXPIRE6|REBIND6|REBOOT6|RENEW6)
        if [ -n "$old_ip6_prefix" ] || [ -n "$new_ip6_prefix" ]; then
            ipv6_prefix_setup
        fi
        # Handle default route
        case $old_ip6_prefix,$new_ip6_prefix in
            ,) : ;;
            *,)
                echo "remove default route for $interface" >> $LOG_FILE
                sysctl -qw net/ipv6/conf/$interface/accept_ra=0
                ip -6 route del default proto ra || true
                echo "stop RA daemon" >> $LOG_FILE
                ;;
            *)
                echo "request default route for $interface" >> $LOG_FILE
                sysctl -qw net/ipv6/conf/$interface/accept_ra=2
                rdisc6 $interface || \
                    echo "cannot request default route for $interface" >> $LOG_FILE
                ip -6 route list default >> $LOG_FILE
                systemctl restart radvd.service
                ;;
        esac
    ;;
    *)
        echo "$reason ignored for $interface" >> $LOG_FILE
    ;;
esac

echo "---" >> $LOG_FILE
  • gandi-update-hook : dépend évidemment du prestataire, c’est juste un exemple de mise à jour des entrées DNS sur changement d’adresse
# update Gandi DNS entries

API_KEY=thisIsTheKey
DOMAIN=theDomain.here
NAME=theName

LOG_FILE=/tmp/dhclient6.log

echo `date` >> $LOG_FILE
echo "entering gandi-update-hook" >> $LOG_FILE

IPADDR=""

update() {
    curl -X PUT -H "Authorization: Apikey $API_KEY" -H "Content-Type: application/json" -d '{"rrset_type":"'$FAMILY'","rrset_ttl":1800,"rrset_name":"'$NAME'","rrset_href":"https://api.gandi.net/v5/livedns/domains/'$DOMAIN'/records/'$NAME'/'$FAMILY'","rrset_values":["'$IPADDR'"]}' https://api.gandi.net/v5/livedns/domains/$DOMAIN/records/$NAME/$FAMILY >> $LOG_FILE
    echo -e "\nupdated $IPADDR" >> $LOG_FILE
}

case "$reason" in
    BOUND|RENEW|REBIND|REBOOT)
        FAMILY=A
        IPADDR=`ip -4 addr show dev eth0.832 | grep -Po 'inet \K[\d.]+'`
        ;;
    BOUND6|RENEW6|REBIND6)
        FAMILY=AAAA
        IPADDR=`ip -6 addr show dev switch0 scope global | grep -Po -m 1 'inet6 \K[0-9a-f:]+'`
        ;;
esac

[ x${IPADDR} = x ] || update

echo "---" >> $LOG_FILE
  • dhclient6_eth0_832.conf
option dhcp6.auth code 11 = string;
option dhcp6.vendorclass code 16 = string;
option dhcp6.userclass code 15 = string;

interface "eth0.832" {
    # Orange France specific options
    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;        

    # Authentication to Orange France DHCP server (same as ipv4)
    send dhcp6.auth 00:00:00:00:00:00:00:00:00:00:00:1a:09:00:00:05:58:01:03:41:01:0d:66:74:69:2f:77:77:77:77:77:77:77;

    # Replace xx:xx:xx:xx:xx:xx with the MAC address of your external interface
    send dhcp6.client-id 00:03:00:01:xx:xx:xx:xx:xx:xx;

    request dhcp6.auth, dhcp6.vendor-opts, dhcp6.name-servers, dhcp6.domain-search;
}
  • dhclient6.service
[Unit]
Description=DHCPv6 Prefix Delegation
Wants=network.target
Before=network.target

[Service]
Type=forking
ExecStart=/sbin/dhclient -6 -P -nw -cf /etc/dhcp3/dhclient6_eth0_832.conf -pf /var/run/dhclient6_eth0_832.pid -lf /var/run/dhclient6_eth0_832.leases eth0.832

[Install]
WantedBy=multi-user.target