IPv6 with 6to4

Swiss ISPs don't make it easy for consumers who want IPv6. It is generally not available for the "small man". If you happen to run your own little Linux router (one that gets a dynamic public IP) however it is easy to connect your home LAN via 6to4 to the IPv6 internet. Once again a Gentoo Wiki was very helpful for the configuration.

First you need a 6to4 tunnel using the tunneling device sit0. This is very simple. Just add the configuration to /etc/conf.d/net (Gentoo specific):

# 6to4 autoconfig
config_sit0=( "ip6to4" )
depend_sit0() {
    need net.eth1

Of course you need an init script as well (Gentoo specific):
ln -s /etc/init.d/net.lo /etc/init.d/net.sit0
/etc/init.d/net.sit0 start
Check the sit0 interface: ifconfig sit0

sit0      Link encap:IPv6-in-IPv4
          inet6 addr: :: Scope:Compat
          inet6 addr: 2002:0c22:384e::1/16 Scope:Global
          inet6 addr: :: Scope:Compat
          inet6 addr: :: Scope:Unknown
          UP RUNNING NOARP  MTU:1480  Metric:1
          RX packets:756 errors:0 dropped:0 overruns:0 frame:0
          TX packets:601 errors:7 dropped:0 overruns:0 carrier:7
          collisions:0 txqueuelen:0
          RX bytes:858115 (838.0 Kb)  TX bytes:92792 (90.6 Kb)

Our public IPv4 is and thus the corresponding 6to4 prefix is 2002:0c22:384e:: (0x0c = 12, 0x22 = 34, etc.). 6to4 addresses always start with 2002:. We can use the whole /48 IPv6 subnet of this prefix. It's our's, because nobody else has this prefix as it corresponds directly to a public IPv4 IP. The sit0 device was automatically assigned IP 2002:0c22:384e::1 from this subnet.

So we can already use IPv6 right now:

# ping6 www.switch.ch
PING www.switch.ch(aslan.switch.ch) 56 data bytes
64 bytes from aslan.switch.ch: icmp_seq=1 ttl=59 time=39.0 ms

What's missing is the routing. We decide to just route a /64 subnet from our whole /48 prefix to the LAN. This lets us freely choose another 16 bits in the prefix. We'll use 2002:xxxx:yyyy:8888::/64 here. The remaining 64 bits will be from the MAC address of every network card in the LAN to form their IPv6 address.

Setup the routing entry (eth0 is LAN):
ip route add 2002:0c22:384e:8888::/64 dev eth0

Now we just need to advertise our IPv6 router on the LAN. The router advertising daemon (radvd) can do that. It's config file is (eth0 is LAN, eth1 is WAN):

interface eth0
  IgnoreIfMissing on;
  AdvSendAdvert on;
  AdvLinkMTU 1480;
  MaxRtrAdvInterval 30;
  prefix 0:0:0:8888::/64
    AdvOnLink on;
    AdvAutonomous on;
    Base6to4Interface eth1;
    AdvValidLifetime 86400;
    AdvPreferredLifetime 7200;

That's it. No configuration necessary on the clients in the LAN. They will notice the IPv6 router and configure themselves!

One big difference to a IPv4/NAT configuration is that alle the machines in the LAN get real public IPv6 addresses. These 6to4 addresses are a mapping of the IPv4 public address to IPv6 address space. This means if our IPv4 address changes, so will the IPv6 prefix! If we are on a dynamic IPv4 IP this means that we have to reconfigure the sit0 tunnel and the routing entries when the address changes. We can do this by hooking into the DHCP client (dhcpcd) events. This litte bash script /usr/local/sbin/setroute6 updates the routing table with the new route when the IP has changed. We hook into DHCP events by installing a dhcpcd-run-hooks script into /lib/dhcpcd/dhcpcd-hooks:99-setroute6.

Finally we should look into ip6tables configuration. It should look something along the lines of (ip6tables-save/restore format):

:log - [0:0]
# packets coming from this machine only
# routed packets
# do not filter invalid state (lost packets between us and dest)
# [0:0] -A FORWARD -m state --state INVALID -j log
# don't break established connections
# all ICMP (filter at destination)
[0:0] -A FORWARD -p icmpv6 -j ACCEPT
# all ACKs (also repeated ones, filter at destination)
[0:0] -A FORWARD -p tcp -m tcp --tcp-flags ACK ACK -j ACCEPT
# any new connections from the inside
[0:0] -A FORWARD -i eth0 -m state --state NEW -j ACCEPT

# packets to this machine
# local traffic
[0:0] -A INPUT -i lo -j ACCEPT
# don't break established conns
[0:0] -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# important ICMP
[0:0] -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT
[0:0] -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT
[0:0] -A INPUT -p icmpv6 --icmpv6-type neighbour-advertisement -j ACCEPT
[0:0] -A INPUT -p icmpv6 --icmpv6-type neighbour-solicitation -j ACCEPT
[0:0] -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
[0:0] -A INPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT
# invalid state (put after ICMP because of ip6tables conntrack bug)
[0:0] -A INPUT -m state --state INVALID -j log
# DNS server in LAN
[0:0] -A INPUT -i eth0 -p udp -m udp --dport 53 -m state --state NEW -j ACCEPT
[0:0] -A INPUT -i eth0 -p tcp -m tcp --dport 53 --syn -m state --state NEW -j ACCEPT
# NTP server in LAN
[0:0] -A INPUT -i eth0 -p udp -m udp --dport 123 -j ACCEPT
# Web server https
[0:0] -A INPUT -p tcp -m tcp --dport 443 --syn -m state --state NEW -j ACCEPT
# SSH server in LAN
[0:0] -A INPUT -i eth0 -p tcp -m tcp --dport 22 --syn -m state --state NEW -j ACCEPT
# debugging: log all dropped
#[0:0] -A INPUT -j log

# ---- Logging
[0:0] -A log -m limit --limit 30/min -j LOG --log-prefix "ip6tables "
[0:0] -A log -j DROP