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.
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:
# http://www.phildev.net/linux/dhclient-ipv6
#
# 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.
#
# Make sure these are correct.
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:
# http://www.phildev.net/linux/99-ipv6
#
# Debian-style if-up.d script for firing off dhclient for ipv6
#
# Change as appropriate
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 my website:
- dhclient script: http://www.phildev.net/linux/dhclient-ipv6
- if-up script: http://www.phildev.net/linux/99-ipv6
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

What is the best DOCSIS 3 & IPv6 capable cable modem compatible with Comcast Internet service?…
Arris, according to Comcast, has the best IPv6 implementation for CMTS’s and their cable modems. I have an Arris WBM760A and it’s got great performance for me. Others have Motorola SURFboard SB6121 working without issue. The Netgear CMD31T is known t…
[...] your own gateway on Linux, the following blog post is very detailed on how to get up and running:http://www.phildev.net/phil/blog…Via Tom Cook.Comment Loading… • Post • 11:52pm Add [...]
Thanks for the post. Running Ubuntu Server 12.1 on a (California Comcast) account “dhclient” doesn’t find anything. Using “rdisc6″ however did find two prefixes. I’m not sure why two are being served up but the prefixes look valid. Still working on getting it working however.
Dan, are you running dhclient with -6 and -P? Also, what does it say (turn on -v and -d for debugging)?
Yup, exactly as you specify here in this post. It just spins along, spitting out X– IA_PD XX:XX:XX:XX … and Request renew comments. Doing the other things (sysctl etc) with the prefix isn’t working yet.
That’s very strange. Let me know what you find… if you email me the debug output, and your modem type, I’ll gladly have a look, and possibly poke one of my contacts over at Comcast…
using dhcpv6 I can get a /64 IP for my gateway but when I try to get a prefix with -P, dhclient just tries in vain. Do I need to call Comcast to lift some sort of firewall or enable my account to obtain “prefixes” rather than an ipv6 IP?
Brad, no, anyone who requests then should be able to get them, although I know they haven’t been rolled out to business-class customers yet, and possibly a few states…
Found a little bug: your 99-ipv6 script still has a hard-coded eth1 instead of $IFACE in the name of the .leases file. You have it correct on the web page here, but not on the actual script download.
Good call, fixed, thanks.