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
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…
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.
Found the problem. I replace __PREFIX__ with the prefix. All is well now.
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.
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!
P.S. I did need to add the route to my routing table: sudo route -A inet6 add ::/64
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…
[…] 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. […]
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?
Kurt – having a /60 network is fine… it’s bigger than a /64.
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).
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.
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.
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
[…] This blog post from Phil Dibowitz was very helpful setting me on the right track and gives you more details about what is going on. Also I recommend reading Recommended Simple Security Capabilities in Customer Premises Equipment for Providing Residential IPv6 Internet Service. […]
[…] and forums have similar instructions for several devices. I decided to follow the ones in this blog, mainly because it was for Debian which could allow me to modify the scripts to run out of […]
[…] setup native IPv6 I used this article as a basic and took scripts from this repo. But I need some changes to make it working in my case. […]