IPv6 on Comcast via Debian

My Internet router at home is low-power computer running Debian. I recently moved, and I now have Comcast as my ISP. (I know, I know. The only alternative here is appallingly slow.) I had heard that Comcast supports IPv6, so I decided to give it a try. It took most of the afternoon and part of th evening, so I thought I would write down what I did here for the benefit of others walking the same path.

Also, on my router, eth0 is the interface on my network, and eth1 is the interface on the Internet.

The Pieces

We need the following:

  • a few settings in sysctl
  • radvd (IPv6 router advertisement daemon; helps clients configure themselves)
  • DHCPv6 client (I use wide-dhcpv6-client)
  • /etc/network/interfaces entry
  • iptables rules (Not strictly necessary, but it's good to lock things down a bit.)

sysctl

In order for this to work, we need a few sysctl parameters.

ipv6/30-ipforward.conf

# For IPv4 forwarding
net.ipv4.ip_forward=1

# For IPv6 forwarding
net.ipv6.conf.all.forwarding=1

# This is needed because a router "shouldn't" accept
# router advertisements in theory, but in practice,
# this kind of router should.
# 0 = Don't accept (we don't want this)
# 1 = Accept if we're not a router (i.e. forwarding is disabled; we don't want this)
# 2 = Accept even if we're a router (we DO want this)
# Without this parameter being 2, we don't get a default route.
net.ipv6.conf.eth1.accept_ra=2

radvd

I needed to run dhcp6c -d -D -f eth1 &> /root/dhcp6c.log (the DHCPv6 client, with output redirected to a logfile) in order to determine my prefix. I saw two different prefixes; I had to guess which one was correct. I will admit that I still don't fully understand this.

ipv6/radvd.conf

interface eth0
{
   AdvSendAdvert on;
   prefix YOUR:PREFIX:HERE::/64
   {
   };
};   

This should probably be automated in some way at some point; my setup will break if Comcast ever gives me a different prefix.

DHCPv6

This actually works, despite having sla-len of 4. However, radvd needs to advertise /64. I suspect the sla-len is just getting (somewhat) harmlessly ignored.

ipv6/dhcp6c.conf

interface eth1
{
  send ia-na 1;
  send ia-pd 0;

  request domain-name-servers;
  request domain-name;

  script "/etc/wide-dhcpv6/dhcp6c-script";
};

id-assoc pd {
  prefix-interface eth0 {
    sla-id 0;
    ifid 1;
    sla-len 4; # This should actually be 0 right now since I couldn't figure out how to get a /60 from Comcast and thus have a /64 for my delegated prefix
  };
};

id-assoc na 1 {
};

interfaces

ipv6/interfaces

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
# dhcp6c handles IPv6 configuration for this interface.
auto eth0
iface eth0 inet static
        address 192.168.0.1
        netmask 255.255.255.0
        network 192.168.0.0
        broadcast 192.168.0.255

# dhcp6c probably also handles all the IPv6 configuration for this interface,
# but this doesn't hurt.
auto eth1
iface eth1 inet dhcp
iface eth1 inet6 auto

I'm not 100% certain that this is actually needed, given the use of wide-dhcpv6-client, but it doesn't hurt.

iptables

First off, bit of basic iptables info. iptables defines INPUT, OUTPUT, and FORWARD chains; a packet will only ever hit one of these. If the current machine is the destination, then it hits INPUT. If the current machine is the source, it hits OUTPUT. If the source or destination is a different machine, it hits FORWARD. See this StackOverflow question for more information.

I did not know this going in, and I was a bit confused. Also, the first version of these rules that I saw used the state module, while my existing IPv4 rules used conntrack. As it turns out, conntrack is a replacement for state, so I just converted the IPv6 rules to use conntrack.

Without further ado, here are my IPv6 rules:

ipv6/ip6tables

# Adapted from http://madduck.net/docs/ipv6/
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
:in-new - [0:0]

### INPUT chain

# allow all loopback traffic
-A INPUT -i lo -j ACCEPT

# allow all ICMP traffic (see link above for discussion of security implications)
-A INPUT -p icmpv6 -j ACCEPT

# allow packets belonging to an established connection or related to one
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# packets that are out-of-sequence are silently dropped
-A INPUT -m conntrack --ctstate INVALID -j DROP
# new connections unknown to the kernel are handled in a separate chain
-A INPUT -m conntrack --ctstate NEW -j in-new

# ...and here's that separate chain:

# allow SYN packets for SSH and HTTPS (RELATED,ESTABLISHED above handles it from there)
-A in-new -p tcp -m tcp --dport 22 --syn -j ACCEPT
-A in-new -p tcp -m tcp --dport 443 --syn -j ACCEPT
# allow DHCPv6 traffic
-A in-new -p udp -m udp --dport 546 -j ACCEPT

# log and reject everything else
-A INPUT -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[INPUT6]: "
-A INPUT -j REJECT

### OUTPUT chain

# allow outgoing traffic, explicitly (despite chain policy)
-A OUTPUT -j ACCEPT

### FORWARD chain

# for fowarded traffic, allow outgoing and related incoming
-A FORWARD -i eth0 -o eth1 -j ACCEPT
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

COMMIT

I applied these like so:

root@aetherius:~# ip6tables-restore < ip6tables
root@aetherius:~# ip6tables-save > /etc/iptables/rules.v6

That last bit saves the rules to a standard location so that they will be applied automatically during boot.

It Works!

At this point, everything seemed to be working! When I replaced that last iptables rule with -A FORWARD -i eth1 -o eth0 -j ACCEPT, I was able to establish a connection from an outside machine directly to my laptop. After setting it back to the one in the listing above, I was no longer able to do so, but I could still establish a connection in the other direction, indicating that my inbound rules are correct.

I hope this helps other people trying to do the same.