Gentoo Gateway/Router, Comcast, & IPv6

After many attempts and stumbles, I’ve finally been able to get my home network setup with IPv6 on Comcast. Many thanks to this Native IPv6 on Comcast article; I used it as my starting point to building the network I wanted.

The Goal

My IPv4 setup is your standard NAT where the gateway machine holds the external IP. Comcast will give you an IPv6 prefix, and I wanted to use that for the internal network instead of NAT. The goal is to have an IPv6 address bound to my external interface, and an IPv6 prefix advertised on my internal network for stateless configuration.

The Setup

Before I get into the details, I have a few notes to share about my setup.

Gentoo Linux: the Linux for tinkerers, which also means this was all a little more difficult than it should have been (but that’s the way I like it). If you’re using another distribution then you won’t be able to follow this guide step-by-step. But hopefully parts of it will still be useful.

Interface Names: my instructions and scripts will refer to two interface names: ethwan and brint. My external interface is ethwan and is a physical device. My internal interface is brint (for bridged internal) and is a bridge of my two internal interfaces (ethwlan and ethlan). You’ll have to change the scripts to match your interface names.

iproute2: Many of the scripts assume you’re using the iproute2 package.

Kernel 3.14: I spent a lot of time scratching my head because directions I found on the internet just weren’t working. I think it’s because of subtle changes in the 3.x kernel that made advice for 2.6 kernels obsolete. YMMV.

Getting the IPv6 Address

First thing we need to do is get an IPv6 address allocated to ethwan. For the longest time I’ve used the dhcpcd package, but I found dhclient worked much better for IPv6. Unfortunately, Gentoo doesn’t provide the right netifrc modules to support both DHCPv4 and DHCPv6 on the same interface. Starting with the existing dhclient module, I modified it to support DHCPv6: /lib64/netifrc/net/dhclientv6.sh

Now setup your /etc/conf.d/net configuration to use both:

modules="dhclient
         dhclientv6"

config_ethwan="dhcp
               dhcpv6"
dhcp_ethwan="release nodns nontp nonis nosendhost"
dhcpv6_ethwan="release nodns nontp nonis nosendhost"
dhclientv6_ethwan="-P"
rc_net_ethwan_after="net.brint"

Couple notes:

  • I run my own DNS, hence the “nodns”. You might not want this option.
  • Ibid with NTP: “nontp”.
  • The argument for dhclientv6 of “-P” tells it to request the prefix we’ll use for the internal network.
  • The scripts need brint started, so start ethwan after it.
  • For a long time, my MTU was set to 576. This is far to small for IPv6, and you’ll get a cryptic error when dhclient tries to assign an address. Increase it to 1500.

Restarting ethwan should now give you an IPv6 address! Launch a browser on the gateway and test it.

Assigning the Prefix

Having the prefix isn’t enough. You also have to setup forwarding in the kernel, accept router announcements from the cable modem, add a routing table entry, and start advertising the prefix to the internal network.

Complicating things is that the prefix could change at any time. But there is a way to automate all the configuration changes and make sure the internal network always stays current.

Next we’re going to write a hook for dhclient that runs whenever our prefix changes and adjusted the settings appropriately. It does this by looking at environment variables that dhclient passes to dhclient-script, which will then call our script.

The packaged dhclient-script will check for /etc/dhcp/dhclient-exit-hooks and sources that. Lots of distributions provide the /etc script, but not Gentoo. No worries, it’s easy to write our own dhclient-exit-hooks:

#!/bin/bash

if [ -d /etc/dhcp/dhclient-exit-hooks.d ]; then
	for f in /etc/dhcp/dhclient-exit-hooks.d/*.sh ; do
		if [ -x ${f} ]; then
			"${f}"
		fi
	done
fi

Important: my version of this script executes the hooks in /etc/dhcp/dhclient-exit-hooks.d. I did this so that I could use bash in my script, and not deal with the abomination that is sh syntax. If your distribution sources the individual hooks, then you’ll have to either adjust your dhclient-exit-hooks script, or adjust the next script that sets up the prefix.

The main script is /etc/dhcp/dhclient-exit-hooks.d/ipv6.sh. At the very least you will need to adjust the variables at the start. A couple notes:

  • It starts, stops, and checks radvd by running /etc/init.d/radvd. Other systems manage services other ways; adjust accordingly.
  • This will only run radvd when it thinks a prefix is defined. You should not set radvd to start with your system; the script will completely manage it.
  • It turns IPv6 forwarding on for all interfaces, and accept_ra to “2” for ethwan. I’ve found that on a 3.14 kernel, forwarding only works if enabled this way. If you don’t want forwarding for all interfaces, you can use iptables to block it.

Configuring radvd

The ipv6.sh script looks for a template file in the same directory as the configuration: radvd.conf.template. It will replace “__PREFIX__” with the DHCPv6 assigned prefix. Mine contains this:

interface brint {
        AdvSendAdvert on;
        MaxRtrAdvInterval 300;

        RDNSS fd11:2233:4455:1::1 { };
        DNSSL example.org { };

        prefix __PREFIX__ {
                AdvOnLink on;
                AdvAutonomous on;
        };

        prefix fd11:2233:4455:1::/64 {
                AdvOnLink on;
                AdvAutonomous on;
        };
};

This is setup for stateless configuration, which is the only way OS X and iOS devices work. If you’d like to use DHCPd then the process would be similar.

I’ve also registered a Unique Local Address prefix for my internal network. No, it’s not “fd11:2233:4455::”. I use this for contacting internal services on IPv6 since the Comcast prefix isn’t stable. The “prefix” line for this space advertises it to the internal network, for stateless configuration.

Since I run my own internal DNS, I put the IPv6 address of the server in my radvd.conf. If you don’t have one then leave the RDNSS and DNSSL lines out.

Also configure radvd to not touch the kernel forwarding setting since we do this in the ipv6.sh script. On Gentoo, it’s in /etc/conf.d/radvd, setting: FORWARD=”no”

Troubleshoot

Within a short period your internal devices should get an IPv6 address. With Comcast, the address assigned to ethwan and the prefix we advertise on brint are entirely different. No worries; this is expected!

You might also notice that unlike previous instructions, brint does not have an address allocated from the prefix. This is not required, all it needs is the routing table entry. Routing of packets from internal devices to the internet happens using the link-local addresses.

On an internal device, test your connection again. You can also use ping6 to contact “www.kame.net” or “ipv6.google.com”.

Internal devices are not getting an address. Make sure IPv6 is enabled on your device and set for automatic configuration. Check the logs for radvd and make sure it is running and advertising the right prefix on the right interface. You can run it in debug mode as: “radvd -d 5 -m stderr”.

Internal devices have an address but can’t contact services. Make sure the routing entry is set on brint. Make sure forwarding is set on all interfaces: “sysctl net.ipv6.conf.all.forwarding”.

Gateway gets an address but can’t contact services. Make sure that it is accepting routing advertisements on ethwan: “sysctl net.ipv6.conf.ethwan.accept_ra”. This must be set to “2” because Linux will ignore a setting of “1” if forwarding is also set. Check that the routing table has as its default IPv6 route the link-local address of the cable modem (should happen automatically).