5 min read

Gearing up your development environment with IPv6 Prefix Delegation

Life in communication technology exile

A while back I moved to a house where there was no wired infrastructure available. The national incumbent had turned off the signals on the old copper wires long ago without offering any alternatives. A mobile broadband provider has been the rescue, and while they have provided a service reliable as shared and wireless services can be, their lack of investment in technology has been what has bothered me the most. The most prominent effect of the lack of technology investment was no IPv6 support, which for a communication service provider is a sure way to paint yourself into a corner.

Having been in connectivity exile for a while made the day the national incumbent rolled out a fiber internet service in the area even more joyful. Not only am I now cruising at speeds everyone else takes for granted, I also have a provider that takes their avoidance of the impending internet addressing doom seriously and as a consequence have enabled IPv6 service to all their subscribers.

Have my provider enabled IPv6 Prefix Delegation?

After the initial joy of decent connectivity had dissipated I started to wonder if Prefix Delegation was at all enabled.

A quick way to find out is to consult our old friend dhclient:

sudo /sbin/dhclient -v -6 -P --prefix-len-hint 62 \
    -pf test-prefix_delegation.pid \
    wlp112s0

Internet Systems Consortium DHCP Client 4.4.1
Copyright 2004-2018 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/

Listening on Socket/wlp112s0
Sending on   Socket/wlp112s0
PRC: Confirming active lease (INIT-REBOOT).
XMT: Forming Rebind, 0 ms elapsed.
XMT:  X-- IA_PD 01:23:45:67
XMT:  | X-- Requested renew  +3600
XMT:  | X-- Requested rebind +5400
XMT:  | | X-- IAPREFIX 2001:db8:1234:8::/62
XMT:  | | | X-- Preferred lifetime +7200
XMT:  | | | X-- Max lifetime +7500
XMT:  V IA_PD appended.
XMT: Rebind on wlp112s0, interval 1020ms.
RCV: Reply message on wlp112s0 from fe80::0253:0000:0000:0001.
RCV:  X-- Preference 255.
RCV:  X-- IA_PD 01:23:45:67
RCV:  | X-- starts 1591547747
RCV:  | X-- t1 - renew  +1200
RCV:  | X-- t2 - rebind +1200
RCV:  | X-- [Options]
RCV:  | | X-- IAPREFIX 2001:db8:1234:8::/62
RCV:  | | | X-- Preferred lifetime 1200.
RCV:  | | | X-- Max lifetime 1200.
RCV:  X-- Server ID: 00:03:00:01:00:53:00:00:00:00:00:01
PRC: Bound to lease 00:03:00:01:00:53:00:00:00:00:00:01.

Lo and behold, not only does my provider service regular IPv6 service on the LAN, it also responds to IPv6 Prefix Delegation requests!

IPv6 Prefix Delegation and you

One of the underappreciated features of IPv6 is Prefix Delegation. While the IPv6 Stateless Address Autoconfiguration provides a highly scalable and flexible way for hosts to automatically figure out what address to use, how to do name resolution, and where to route packets destined to foregin networks. It does not provide hosts with means to provide addresses to the many containers and virtual machines running on them.

My laptop has at the time of this writing four LXD machine containers, and two libvirt/KVM backed virtual machines running. Wouldn’t it be great if I could provide the various bridges and routers used to route traffic to and from these with routable IPv6 prefixes?

Automating configuration of the delegated prefix

Since this is a laptop running Ubuntu and GNOME I’ll use the dispatcher entry point that NetworkManager provides to automate the required configuration once we are delegated a prefix.

An example script named ‘/etc/NetworkManager/dispatcher.d/dhcpv6-prefix-delegation’:

#!/bin/bash

IFACE=$1
ACTION=$2

if [ -n "$IFACE" -a "$IFACE" = "wlp112s0" ]; then
    if [ -n "$ACTION" -a "$ACTION" = "up" ]; then
        # first release any already delegated prefix, this is done because
        # the likelihood of us having abruptly left the network is high and
        # we need to clear out any now invalid routing information for our
        # prefix before re-requesting it.
        /sbin/dhclient -v -6 -r -P \
            -pf /var/run/dhclient6_prefix_delegation.pid \
            -sf /sbin/dhclient-script \
            $IFACE
        # request and maintain the IPv6 Prefix Delegation for use with
        # LXD, VMs etc.
        /sbin/dhclient -v -nw -6 -P --prefix-len-hint 62 \
            -pf /var/run/dhclient6_prefix_delegation.pid \
            -sf /sbin/dhclient-script \
            $IFACE
    fi
    if [ -n "$ACTION" -a "$ACTION" = "pre-down" ]; then
        # Release the prefix on clean network shutdown
        /sbin/dhclient -v -6 -r -P \
            -pf /var/run/dhclient6_prefix_delegation.pid \
            -sf /sbin/dhclient-script \
            $IFACE
    fi
    if [ -n "$ACTION" -a "$ACTION" = "down" ]; then
        # Just kill dhclient on abrupt network shutdown
        /sbin/dhclient -v -6 -x -P \
            -pf /var/run/dhclient6_prefix_delegation.pid \
            -sf /sbin/dhclient-script \
            $IFACE
    fi
fi

We are requesting a prefix length of /62, which translates into four /64 prefixes. Take a look at the RIPE IPv6 CIDR Chart.

At the point in time when NetworkManager executes this script it knows nothing about if we were delegated a prefix and what that prefix would be.

We make use of the plugin system that the good old dhclient scripts provide to handle that part.

An example script named ‘/etc/dhcp/dhclient-exit-hooks.d/’ inspired by the Debian IPv6 Prefix Delegation Wiki page:

case $reason in
    BOUND6|EXPIRE6|REBIND6|REBOOT6|RENEW6|RELEASE6)
        # Only execute if either an old or a new prefix is defined
        if [ -n "$old_ip6_prefix" ] || [ -n "$new_ip6_prefix" ]; then
            # Assign new prefix
            if [ -n "$new_ip6_prefix" ]; then
                local prefix="$(echo $new_ip6_prefix|cut -f1 -d/|sed 's/..$//')"
                local addr="${prefix}::1/64"
                /snap/bin/lxc network set lxdbr0 ipv6.address=$addr
                local squid="${prefix}:216:3eff:feef:3fc6"
                echo "Acquire::http { Proxy \"http://[${squid}]:3128\"; };" > /etc/apt/apt.conf.d/02proxy
            else
                # We don't have a prefix, remove it from LXD
                /snap/bin/lxc network set lxdbr0 ipv6.address=none
                local squid=10.219.3.124
                echo "Acquire::http { Proxy \"http://[${squid}]:3128\"; };" > /etc/apt/apt.conf.d/02proxy
            fi
        fi
        ;;
esac

In the above rudimentary example we configure LXD to provide the one of the /64s in the delegated prefix to my LXD containers, I also update my local APT configuration to point to the IPv6 address of the caching proxy I have running in one of those containers.

Note that the latter part of that address will remain the same regardless of prefix.

I plan to expand this further to configure the prefix on eligible libvirt networks and other places where I could benefit from real end to end internet connectivity without being hindered by Network Address Translation.

This post is a work in progress and I’ll update it once I have done more experiments with how I can leverage this in my every day development environment.