Replacing Orange Livebox router with a Linux box

Vincent Bernat

A few months ago, I moved back to France and I settled for Orange as an ISP with a bundle combining Internet and mobile subscription. In Switzerland, I was using my own router instead of the box provided by Swisscom. While there is an abundant documentation to replace the box provided by Orange, the instructions around a plain Linux box are kludgy. I am exposing here my own variation. I am only interested in getting IPv4/IPv6 access: no VoIP, no TV.

Update (2023-02)

Orange has updated the DHCP options required. The value sent for rfc3118-authentication and dhcp6.auth are modified. We also need to send dhcp-client-identifier and dhcp6.client-id. This page has been updated accordingly.

Hardware#

Orange is using GPON for its FTTH deployment. Therefore, an ONT is needed to encapsulate and decapsulate Ethernet frames into GPON frames. Two form-factors are available. It can be small Huawei HG8010H box also acting as a media converter to Ethernet 1000BASE-T:

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

With a recent Livebox, Orange usually provides an SFP to be plugged inside the Livebox. For some reason I got the external ONT instead of the SFP version. As I have a Netgear GS110TP with two SFP ports, I have bought an SFP GPON FGS202 on eBay. It is the same model than Orange is providing with its Livebox 4. However, I did not find the motivation to test it.1

Sercomm SFP ONT
The Sercomm FGS202 GPON SFP ONT

IPv4 configuration#

Internet is provided over VLAN 832 and configured with DHCPv4. The first step is to setup the DHCP client to send some additional information, notably the RFC 3118 authentication string. It includes the alphanumeric connection identifier prefixed by fti/ and the password provided 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;
}

Orange expects some control packets, notably DHCP, to be tagged with 802.1p PCP 6. This is a 3-bit field within the Ethernet frame when using VLANs. By default, Linux leaves this field blank. With ip link, we can translate Linux’s skb->priority to a PCP. On Debian, here is how to declare the VLAN interface: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 appropriate code in /etc/firewall/run to ensure DHCP, ARP, IGMP and ICMP packets have an internal priority of 6. Netfilter’s CLASSIFY target would be the easiest solution. However, ISC DHCP client is using raw sockets and the packets it sent won’t pass through Netfilter. A clean solution is to use tc to modify packets just before handing them to the network card. The skbedit action allows one to change the priority associated 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 configuration in place, ifup internet should get you connected through IPv4.

IPv6 configuration#

Native IPv6 is also available over the same VLAN. SLAAC autoconfiguration should be used to get a default route, but not the IP address. Instead, Orange is providing a /56 prefix through DHCPv6 “prefix delegation.”

The DHCP configuration is completed to send the DHCPv6 equivalents for vendor class, user class and authentication 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 firewall script is amended to classify DHCPv6 and ICMPv6 packets 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 definition of the internet interface is updated to invoke 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 interface to get a link-local address before continuing. The -P option for the DHCPv6 client enables prefix delegation and disables the normal address query.

It is not over: the DHCPv6 client will receive a /56 prefix but there is nothing configured to make use of it. You need to drop a script in /etc/dhcp/dhclient-exit-hooks.d to actually distribute this prefix to your internal network. Here is a simplified non-tested version 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 variable contains the list of internal interfaces. From the /56 provided in $new_ip6_prefix, the script will assign a /64 to each of them—along with the first address. For example, when being assigned 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 using dnsmasq to offer IPv6 router advertisements to hosts in each network. This is done through the dhcp-range directive:

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

The script also handles the default route by switching accept_ra to 2 for the internet interface to accept IPv6 router advertisements even when forwarding is enabled and sending an IPv6 router discovery packet using 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 complete script instead of the shortened code above! If after ifdown internet && ifup internet, you don’t get a /56 prefix, you may have to reboot the ONT to clear an old DHCP lease.


  1. As Orange is using the serial number to authorize the ONT, my plan is to call Orange customer service, pretend I have got a replacement and provide the new serial number. ↩︎

  2. There is no need to have the VLAN number in the interface name. I usually leaves them out as it doesn’t help to describe the interface. The VLAN number can still be recovered with ip -d link show↩︎

  3. The first filter only works if the physical interface supports VLAN offloading. You can check this is the case with ethtool -k eno1 | grep rx-vlan-offload. Otherwise, the installed filter does not handle correctly the VLAN header and does not match the targeted packets. ↩︎