Native IPv6 On Comcast

Update 5/31/12: Scripts are now configurable and various bugs have been fixed.

Update 6/07/12: Broken link to if-up script fixed, typo in script fixed.

Update 10/30/15: Scripts moved to GitHub.

They’re not advertising it, but Comcast is turning on IPv6 for all their California customers (and presumably others) who have a supported modem this week. Most people’s equipment won’t request v6 addresses yet, but for those running one of the Linksys-style devices that are set to do it by default, they will get a /64 and start advertising internally and voila, their network is IPv6-ified.

That’s all well and good, but you’re running a Linux firewall like a good geek, so how do you get the IPv6 goodness? It’s actually not as straight-forward as you’d think, but I have all the bits worked out here so you can get up and running.

Lets talk about what we’re going to be doing. We will…

Use DHCPv6 to request a prefix (a /64) on the external interface. Take that prefix, assign one address from it to the internal interface. Tell radvd to advertise that prefix to our internal network.

I have automted this whole process and included the scripts below… but we will first start by walking through it manually so you understand what’s going on.

Get A Prefix

First, lets just look at how we get a prefix! Run the following command:

dhclient -6 -P -d -v $YOUR_EXTERNAL_INTERFACE

The -6 option is to run in IPv6 mode, the -P option says to request a prefix, and -d -v are debug and verbose so you can see what’s going on.

In the output look at the line with IAPREFIX in it… that’s your /64! However, dhclient won’t do anything with that. (Depending on how new your dhclient is, if you did not use -P, then you’d get a single address which it would assign to eth1, but since we want our whole network to have IPv6, we requested a prefix. Older dhclient’s would write out the lease file but not do anything with any addresses, even when not in prefix mode.)

Assign An Address To Your Box

So we want to do something with that. First, we need to assign some address in our /64 to our internal interface. Note that all routing is done via link-local addresses so your external interface doesn’t need a global address. In theory we could do this manually, with something like:

ip addr add $PREFIX::1/64 dev $YOUR_INTERNAL_INTERFACE

So for example, if your prefix was 2001:a:b::/64 then the address there would be 2001:a:b::1/64. And don’t forget to replace your internal interface there.

Accept Router Advertisements

Then we need to make sure we accept Router Advertisements (RA) so we know how to send traffic to the internet.

But first we need to understand something about IPv6 as well as something about linux’s IPv6 implementation. The RFC states that if you’re a router you shouldn’t accept router advertisements (RA) – the rationale being that you’re a router or your a host, but you are not both. Thus, even if you set net.ipv6.conf.all.accept_ra=1, if you also have ip_forward=1, linux will silently drop all RAs.

In our case, however we want to accept router advertisements on our external interface and send our own router advertisements on our internal interface. Fortunately, Linux added a new value to accept_ra which says “accept RAs even if ip_forward is on”… and that value is 2.

Sadly, in kernels before that was added (anything before 2.6.37), a value of 2 is accepted, but it has no benefit (it’s treated the same as 1). In these older kernels you can set ip_forward=0 on the external interface and 1 on the internal interface and packets will still be forwarded and you will accept RAs. See this blog for more details.

Anyway, here’s what we do to ensure we get RAs:

# Ensure we'll get router advertisements

sysctl -w net.ipv6.conf.$YOUR_EXTERNAL_INTERFACE.accept_ra=2

# Needed for kernels before 2.6.37, don't worry,

# forwarding will still work as long as you have it set

# on your other interface.

sysctl -w net.ipv6.conf.$YOUR_EXTERNAL_INTERFACE.forwarding=0



And with that you should have connectivity:

ping6 www.facebook.com

Advertising Your Space Internally

IPv6 is great at autoconfiguration, but in order for devices to configure themselves, we need to advertise our IPv6 space – and our selves as a router for that space – to the network. First, install radvd as appropriate for your distribution and create a config in /etc/radvd.conf that looks like this:

interface YOUR_INTERNAL_INTERFACE { AdvSendAdvert on; RDNSS 2001:4860:4860::8888 2001:4860:4860::8844 {}; prefix YOUR_PREFIX { AdvOnLink on; AdvAutonomous on; }; };

And replace “YOUR_INTERNAL_INTERFACE” with your internal interface and “YOUR_PREFIX” with your prefix. Start radvd and you should see devices on your network auto-configure ipv6 addresses and routes.

This config hands out Google DNS servers as the DNS servers, but you can change to taste.

Automating Everything

Well, it’s neat that it works, but who wants to do that by hand on every boot? Ew.

Fortunately, dhclient is pluggable, so I wrote a dhclient-script to do that for us. Drop this into /etc/dhcp/dhclient-exit-hooks.d/ and call it something like ipv6.

#!/bin/bash # vim:tw=80:tabstop=2:shiftwidth=2 # Copyright (c) 2012-present, Phil Dibowitz # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in # binary form must reproduce the above copyright notice, this list of # conditions and the following disclaimer in the documentation and/or other # materials provided with the distribution. # * Neither the name of the author nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # You can find the latest version of this at: # https://github.com/jaymzh/v6-gw-scripts # # Will, given a 'dhclient -6 -P ...' on $EXT_IFACE and assign the prefix # given to the $INT_IFACE, and twiddle radvd. # # For radvd, it takes /etc/radvd.conf.tmpl, replaces "__PREFIX__" with your # prefix, and - if it's different from /etc/radvd.conf - replaces the config # file and restarts the daemon. # # Change INT_IFACE or EXT_IFACE by setting them in the config file CONF='/etc/ipv6_prefix_dhclient.conf' INT_IFACE='eth0' EXT_IFACE='eth1' [ -r $CONF ] && . $CONF ipv6_prefix_setup() { current_ip=$(/sbin/ip -6 addr show dev $INT_IFACE scope global |\ /usr/bin/awk '/inet6/ {print $2}') current_prefix=$(echo $current_ip | /bin/sed -e 's@::1/64@::/64@') if [ "$current_prefix" == "$new_ip6_prefix" ] ; then return fi # Setup the new IP new_ip=$(echo $new_ip6_prefix | /bin/sed -e 's@::/64@::1/64@g') if [ ! -z "$current_ip" ] ; then ip -6 addr del $current_ip dev $INT_IFACE fi ip -6 addr add $new_ip dev $INT_IFACE # Ensure we'll get router advertisements sysctl -w "net.ipv6.conf.$EXT_IFACE.accept_ra=2" # Needed for kernels before 2.6.37, don't worry, # forwarding will still work as long as you have it set # on your other interface. sysctl -w "net.ipv6.conf.$EXT_IFACE.forwarding=0" # Update radvd tmpfile=/tmp/radvd.conf.$$ sed -e "s@__PREFIX__@$new_ip6_prefix@g" /etc/radvd.conf.tmpl > $tmpfile diff $tmpfile /etc/radvd.conf >/dev/null if [ $? == 1 ]; then mv $tmpfile /etc/radvd.conf /etc/init.d/radvd restart else rm $tmpfile fi } if [ "$interface" != "$EXT_IFACE" ] ; then return fi case "$reason" in BOUND6|REBIND6) # We will get called twice here - once for the temp address # and once for the prefix. We only care about the prefix. if [ ! -z "$new_ip6_prefix" ] ; then ipv6_prefix_setup fi ;; esac

Be sure to set the two variables at the top as appropriate.

This script does several things things:

Assign an IP from our prefix to the internal interface Set the sysctls as appropriate Configure radvd

It makes one assumption: that you have a templated radvd.conf file in /etc/radvd.conf.tmpl that has “__PREFIX__” instead of a prefix, ala:

interface YOUR_INTERNAL_INTERFACE { AdvSendAdvert on; RDNSS 2001:4860:4860::8888 2001:4860:4860::8844 {}; prefix __PREFIX__ { AdvOnLink on; AdvAutonomous on; }; };

This script is configurable… just drop a file in /etc/ipv6_prefix_dhclient.conf and define $INT_IFACE and $EXT_IFACE like so:

INT_IFACE='eth0' EXT_IFACE='eth1'

As before, this config hands out Google DNS servers as the DNS servers, but you can change to taste.

So now we have dhclient doing all of the configuration work for us, there’s only one more piece: to launch dhclient correctly. For this, I dropped a simple script which fires off dhclient into /etc/network/if-up.d which I called 99-ipv6. This is Debian-like-distro specific, but you could do this with some ifup-local magic on Redhat-like-distros.

#!/bin/bash # vim:tw=80:tabstop=2:shiftwidth=2 # Copyright (c) 2012-present, Phil Dibowitz # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in # binary form must reproduce the above copyright notice, this list of # conditions and the following disclaimer in the documentation and/or other # materials provided with the distribution. # * Neither the name of the author nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # You can find the latest version of this at: # https://github.com/jaymzh/v6-gw-scripts # # Debian-style if-up.d script for firing off dhclient for ipv6 # # Change EXT_IFACE by updating the config file CONF='/etc/ipv6_prefix_dhclient.conf' EXT_IFACE="eth1" [ -r $CONF ] && . $CONF # We only care about the external interface. if [ "$IFACE" != "$EXT_IFACE" ]; then exit 0 fi # Only run from ifup. if [ "$MODE" != start ]; then exit 0 fi # If there's a stale dhclient, kill it kill `cat /var/run/dhclient6.$IFACE.pid` # Start our new one dhclient -6 -P -pf /var/run/dhclient6.$IFACE.pid \ -lf /var/lib/dhcp/dhclient6.$IFACE.leases $IFACE & # reload firewall rules /etc/network/ip6tables reload exit 0

This script uses the same configuration file to determine the configuration of your network.

In my particular case I have a script /etc/network/ip6tables which setups my v6 firewall rules and is run here (and a similar one for v4 which I run as an if-up rule), but your configuration may differ.

You can find these scripts on GitHub.

Other Considerations

Some other thoughts:

iptables’ conntrack module, until recently, couldn’t statefully track dhcpv6. This was fixed with this patch but if you don’t have the nf_conntrack_dhcpv6 module available in your kernel, you will need to allow ports UDP traffic to/from fe80::/10 and to port 546/from port 547.

Debian was using the old dhclient3 scripts (even after the move to isc-dhcp-client) until very recently. You need version 4.1.1-P1-17 or later to have basic IPv6 support

-phil