Native IPv6 On Comcast
May 30th 2012 @ 9:00 am Tech

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…

  1. Use DHCPv6 to request a prefix (a /64) on the external interface.
  2. Take that prefix, assign one address from it to the internal interface.
  3. 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:

  1. Assign an IP from our prefix to the internal interface
  2. Set the sysctls as appropriate
  3. 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:

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
rss 22 comments
  1. May 31st, 2012 | 7:47 am

    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…

  2. May 31st, 2012 | 7:52 am

    [...] 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 [...]

  3. Dan
    November 30th, 2012 | 1:47 am

    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.

  4. phil
    November 30th, 2012 | 2:04 am

    Dan, are you running dhclient with -6 and -P? Also, what does it say (turn on -v and -d for debugging)?

  5. Dan
    November 30th, 2012 | 2:49 pm

    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.

  6. phil
    November 30th, 2012 | 3:13 pm

    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…

  7. December 9th, 2012 | 6:54 am

    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?

  8. phil
    December 9th, 2012 | 3:18 pm

    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…

  9. Ed
    January 27th, 2013 | 11:01 pm

    I’ve been chasing my tail with this. Found out that EVERY time I run dhclient -6 -P Comcast gives me a different PREFIX (have seen about 3 now) and the PREFIX put into radvd is incorrect. I have had to run dhclient manually then edit the radvd.conf and restart radvd in order to have ipv6 outside connectivity. I am thinking the scripts need a little tweaking.

  10. Ed
    January 28th, 2013 | 3:41 am

    Found the problem. I replace __PREFIX__ with the prefix. All is well now.

  11. PhilH
    February 18th, 2013 | 6:08 pm

    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.

  12. phil
    March 30th, 2013 | 3:37 am

    Good call, fixed, thanks.

  13. Jeffry E
    June 26th, 2013 | 3:56 pm

    I’ve been trying to get this setup for some time; spent time in #ipv6 on freenode; but after a few blog posts, this was the one that finally got internal devices on ipv6. Thanks for writing this!

  14. Jeffry E
    July 22nd, 2013 | 7:48 pm

    P.S. I did need to add the route to my routing table: sudo route -A inet6 add ::/64

  15. phil
    July 22nd, 2013 | 10:44 pm

    Jeffery – you shouldn’t have to. You mean on internal systems? They should pick up the router advertisements from your gateway and add appropriate routes…

  16. July 26th, 2013 | 2:28 am

    [...] DNSMasq is a small application that combines a DNS cache/forwarder, a DHCPv4 server, a DHCPv6 server, a TFTP server, and a router advertisement service. It works well for small networks but adapting it for more complicated situations can be difficult. If you don’t like such kitchen-sink applications, you can use more traditional tools: ISC BIND for DNS forwarding, dhcpd/dhcpd6 for DHCPv4 and DHCPv6, and radvd for SLAAC.  This blog has a good guide to setting everything up with the help of some BASH scripts for automation: Native IPv6 on Comcast. [...]

  17. Kurt
    September 21st, 2013 | 4:52 pm

    I have a ::/60 prefix from my provider.
    I have tried to adjust the script to set the prefix 64 on internal network eth0, but getting only 60 prefix there.
    Is not that good at writing scripts so you have an easy solution for that?

  18. phil
    September 24th, 2013 | 5:04 pm

    Kurt – having a /60 network is fine… it’s bigger than a /64.

  19. December 25th, 2013 | 6:23 am

    Like Kurt, the prefix I get from my external interface is not a /64, but mine is a /56. I changed two lines in your script so that they will work for any number after the slash:

    current_prefix=$(echo $current_ip | /bin/sed -e ‘s@::1/\([0-9]*\)@::/\1@’)

    new_ip=$(echo $new_ip6_prefix | /bin/sed -e ‘s@::/\([0-9]*\)@::1/\1@g’)

    The only things I haven’t figured out yet are, why immediately after a reboot radvdump shows my broadcast packets going out on the wrong interface (if I stop and restart radvd, it corrects itself), and why my internal client (Windows 8.1) doesn’t seem to be picking up the broadcasts (it stubbornly sticks to its link-local FE80 address).

  20. Dan
    January 11th, 2014 | 4:02 am

    Hi,
    I followed this guide a year ago and had IPV6 working, but then after a few months it mysteriously broke and I couldn’t get it to work the last year. I finally figured it out … I’m running Ubuntu Server, and instead of using the “dhclinet” line, look in the file /var/lib/dhcp/dhclient6.leases.

    I don’t know what is going on with “dhclient -6 -P -d -v XXX” but in my case it lies. The whole time it was serving up the wrong prefix, which is why nothing worked. The right prefix is written to the location I show above.

    FYI, more Linux weirdness.

  21. Bill Broadley
    January 29th, 2014 | 10:57 am

    Thanks for the guide. I just got a Motorola SB6121 and have comcast.

    I haven’t automated things, but I just got an ubuntu 13.10 box to work as a router and a second ubuntu 13.10 box on my internal network.

    Now both my router and my internal machine report 10/10 on ipv6 readiness at http://test-ipv6.comcast.net/.

    The only small confusion I had was sometimes you use prefix as meaning
    2001:a:b and sometimes prefix means 2001:a:b::/64, that got me in the radvd.conf file.

    The only other small tweak is that radvd won’t start with this error:
    * IPv6 forwarding seems to be disabled.
    * radvd will *not* be started.

    Easily fixed with:
    sysctl -w net.ipv6.conf.all.forwarding=1

    I looked are various pieces of comcast, ubuntu, and wiki entries for related and found this (phildev.net) post the best.

    Many thanks.

  22. john newlin
    February 13th, 2014 | 11:06 pm

    Hey Phil

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

    The reason that this works is a side effect of this is it also creates an implicit route. You can simply do:

    ip -6 route add $PREFIX $INTERNAL_INTERFACE

    It doesn’t really make sense to have a global address on your internal interface.

    Cheers

    john

comment on this article

Notice: All comments are moderated. Your comment will appear once approved.