Replacing Orange Livebox router with a Linux box

Vincent Bernat

A few months ago, I moved back to France and I set­tled for Or­ange as an ISP with a bun­dle com­bin­ing In­ter­net and mo­bile sub­scrip­tion. In Switzer­land, I was us­ing my own router in­stead of the box pro­vided by Swiss­com. While there is abun­dant doc­u­men­ta­tion to re­place the box pro­vided by Or­ange, the in­struc­tions around a plain Linux box are kludgy. I am ex­pos­ing here my own vari­a­tion. I am only in­ter­ested in get­ting IPv4/IPv6 ac­cess: no VoIP, no TV.

Up­date (2023-02)

Or­ange has up­dated the DHCP op­tions re­quired. The value sent for rfc3118-authentication and dhcp6.auth are mod­i­fied. We also need to send dhcp-client-identifier and dhcp6.client-id. This page has been up­dated ac­cordingly.

Hardware#

Or­ange is us­ing GPON for its FTTH de­ploy­ment. There­fore, an ONT is needed to en­cap­su­late and de­cap­su­late Eth­er­net frames into GPON frames. Two form-fac­tors are avail­able. It can be small Huawei HG8010H box also act­ing as a me­dia con­verter to Eth­er­net 1000BASE-T:

Huawei ONT rebranded as Orange
The rebranded Huawei HG8010H is acting as an ONT and media converter

With a re­cent Live­box, Or­ange usu­ally pro­vides an SFP to be plugged in­side the Live­box. For some rea­son I got the ex­ter­nal ONT in­stead of the SFP ver­sion. As I have a Net­gear GS110TP with two SFP ports, I have bought an SFP GPON FGS202 on eBay. It is the same model as Or­ange is pro­vid­ing with its Live­box 4. How­ever, I did not find the mo­ti­va­tion to test it.1

Sercomm SFP ONT
The Sercomm FGS202 GPON SFP ONT

IPv4 configuration#

In­ter­net is pro­vided over VLAN 832 and con­fig­ured with DHCPv4. The first step is to set up the DHCP client to send some ad­di­tional in­for­ma­tion, no­tably the RFC 3118 au­then­ti­ca­tion string. It in­cludes the al­phanu­meric con­nec­tion iden­ti­fier pre­fixed by fti/ and the pass­word pro­vided by snail mail. /etc/dhcp/dhclient.conf looks like this:

option rfc3118-authentication code 90 = string;
interface "internet" {
  timeout 60;
  retry 1;
  select-timeout 0;
  send vendor-class-identifier "sagem";
  send user-class "+FSVDSL_livebox.Internet.softathome.Livebox4";
  # See https://jsfiddle.net/kgersen/3mnsc6wy/ to generate this string
  send rfc3118-authentication 00:00:00:00:...;
  # Use the MAC address of the Livebox
  send dhcp-client-identifier 01:xx:xx:xx:xx:xx:xx;
  request subnet-mask, routers,
          domain-name-servers, domain-name,
          broadcast-address,
          dhcp-lease-time, dhcp-renewal-time, dhcp-rebinding-time,
          rfc3118-authentication;
}

Or­ange ex­pects some con­trol pack­ets, no­tably DHCP, to be tagged with 802.1p PCP 6. This is a 3-bit field within the Eth­er­net frame when us­ing VLANs. By de­fault, Linux leaves this field blank. With ip link, we can trans­late Linux’s skb->priority to a PCP. On De­bian, here is how to de­clare the VLAN in­terface:2

auto internet
iface internet inet dhcp
  pre-up    ip link add link eno1 name internet type vlan id 832 egress-qos-map 0:0 6:6
  pre-up    /etc/firewall/run
  post-down ip link del internet

The last step is to add the ap­pro­pri­ate code in /etc/firewall/run to en­sure DHCP, ARP, IGMP and ICMP pack­ets have an in­ter­nal pri­or­ity of 6. Net­fil­ter’s CLASSIFY tar­get would be the eas­i­est so­lu­tion. How­ever, ISC DHCP client is us­ing raw sock­ets and the pack­ets it sends will not pass through Net­fil­ter. A clean so­lu­tion is to use tc to mod­ify pack­ets just be­fore hand­ing them to the net­work card. The skbe­dit ac­tion al­lows one to change the pri­or­ity as­so­ci­ated to a packet:3

# We need a qdisc to set filters
tc qdisc replace dev internet root handle 1: prio
tc filter del dev internet

# DHCP (raw sockets, do not specify "protocol ip")
tc filter add dev internet parent 1: prio 1 u32 \
     match ip protocol 17 ff \
     match ip dport 67 ffff \
     action skbedit priority 0:6
# ARP
tc filter add dev internet parent 1: prio 2 protocol 0x806 u32 \
     match u32 0 0 \
     action skbedit priority 0:6
# IGMP
tc filter add dev internet parent 1: prio 3 protocol ip u32 \
     match ip protocol 2 ff \
     action skbedit priority 0:6
# ICMP
tc filter add dev internet parent 1: prio 4 protocol ip u32 \
     match ip protocol 1 ff \
     action skbedit priority 0:6

With this con­fig­u­ra­tion in place, ifup internet should get you con­nected through IPv4.

IPv6 configuration#

Na­tive IPv6 is also avail­able over the same VLAN. SLAAC au­to­con­fig­u­ra­tion should be used to get a de­fault route, but not the IP ad­dress. In­stead, Or­ange is pro­vid­ing a /56 pre­fix through DHCPv6 “pre­fix del­e­gation.”

The DHCP con­fig­u­ra­tion is com­pleted to send the DHCPv6 equiv­a­lents for ven­dor class, user class and au­then­ti­ca­tion string:

# […]
option dhcp6.auth code 11 = string;
option dhcp6.userclass code 15 = string;
option dhcp6.vendorclass code 16 = string;
interface "internet" {
  timeout 60;
  retry 1;
  select-timeout 0;
  # […]
  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.auth 00:00:00:00:...;
  send dhcp6.client-id 00:03:00:01:xx:xx:xx:xx:xx:xx;
  also request dhcp6.auth, dhcp6.vendor-opts;
}

The fire­wall script is amended to clas­sify DHCPv6 and ICMPv6 pack­ets with priority 6:

# DHCPv6
tc filter add dev internet parent 1: prio 5 protocol ipv6 u32 \
     match ip6 protocol 17 ff \
     match ip6 dport 547 ffff \
     action skbedit priority 0:6
# ICMPv6
tc filter add dev internet parent 1: prio 6 protocol ipv6 u32 \
     match ip6 protocol 58 ff \
     action skbedit priority 0:6

The def­i­ni­tion of the internet in­ter­face is up­dated to in­voke the DHCPv6 client:

auto internet
iface internet inet dhcp
  pre-up    ip link add link eno1 name internet type vlan id 832 egress-qos-map 0:0 6:6
  pre-up    /etc/firewall/run
  post-down ip link del internet
  up        /lib/ifupdown/wait-for-ll6.sh && \
            dhclient -6 -P -pf /run/dhclient6.$IFACE.pid \
                           -lf /var/lib/dhcp/dhclient6.$IFACE.leases \
                           -df /var/lib/dhcp/dhclient.$IFACE.leases \
                           $IFACE
  post-down dhclient -6 -r -pf /run/dhclient6.$IFACE.pid dhclient \
                           -lf /var/lib/dhcp/dhclient6.$IFACE.leases \
                           -df /var/lib/dhcp/dhclient.$IFACE.leases \
                           $IFACE || true

The /lib/ifupdown/wait-for-ll6.sh script waits for the in­ter­face to get a link-lo­cal ad­dress be­fore con­tin­u­ing. The -P op­tion for the DHCPv6 client en­ables pre­fix del­e­ga­tion and dis­ables the nor­mal ad­dress query.

It is not over: the DHCPv6 client will re­ceive a /56 pre­fix, but there is noth­ing con­fig­ured to make use of it. You need to drop a script in /etc/dhcp/dhclient-exit-hooks.d to ac­tu­ally dis­trib­ute this pre­fix to your in­ter­nal net­work. Here is a sim­pli­fied non-tested ver­sion of this script:

#!/bin/sh
IA_PD_IFACES="lan-trusted lan-guest lan-games"

case $reason in
  BOUND6|EXPIRE6|REBIND6|REBOOT6|RENEW6)
    offset=0
    for iface in $IA_PD_IFACES; do
      # Remove old /64 prefix if there is a change
      [ -n "$old_ip6_prefix" ] && \
        [ "$old_ip6_prefix" != "$new_ip6_prefix" ] && \
        ip -6 addr flush dev $iface scope global
      # Compute and add new /64 prefix
      [ -n "$new_ip6_prefix" ] && {
        offset=$((offset + 1))
        address=$(sipcalc --v6split=64 --split-verbose "$new_ip6_prefix" \
                   | grep '^Compressed' \
                   | awk "(NR == $offset)"' { print $NF }')1/64
        ! ip -6 addr show dev $iface | grep -qwF $address || \
          ip -6 addr add $address dev $iface
    done
esac

At the top of the script, the IA_PD_IFACES vari­able con­tains the list of in­ter­nal in­ter­faces. From the /56 pro­vided in $new_ip6_prefix, the script will as­sign a /64 to each of them—along with the first ad­dress. For ex­am­ple, when be­ing as­signed 2001:db8:f:b00::/56, we get:

$ ip -brief -6 a show scope global
lan-trusted@eno1  UP  2001:db8:f:b00::1/64
lan-guest@eno1    UP  2001:db8:f:b01::1/64
lan-games@eno1    UP  2001:db8:f:b02::1/64

I am us­ing dns­masq to of­fer IPv6 router ad­ver­tise­ments to hosts in each net­work. This is done through the dhcp-range di­rective:

dhcp-range=::,constructor:lan-trusted,ra-names
dhcp-range=::,constructor:lan-guest,ra-names
dhcp-range=::,constructor:lan-games,ra-names

The script also han­dles the de­fault route by switch­ing accept_ra to 2 for the internet in­ter­face to ac­cept IPv6 router ad­ver­tise­ments even when for­ward­ing is en­abled and send­ing an IPv6 router dis­cov­ery packet us­ing rdisc6:

case $old_ip6_prefix,$new_ip6_prefix in
  *,)
    # No IPv6 prefix delegation, remove old route
    sysctl -qw net/ipv6/conf/$interface/accept_ra=0
    ip -6 route del default proto ra || true
    ;;
  *)
    # Otherwise, get a default route
    sysctl -qw net/ipv6/conf/$interface/accept_ra=2
    rdisc6 $interface
    ;;
esac

Be sure to use the com­plete script in­stead of the short­ened code above! If af­ter ifdown internet && ifup internet, you don’t get a /56 pre­fix, you may have to re­boot the ONT to clear an old DHCP lease.