Poor man's IPv6

Motivation

I wanted to download some videos of the 27C3 from an IPv6 only site. My goal was to setup IPv6 connectivity with minimal effort. I went for 6to4 as it does not require any tunnel brokers and works out of the box on a recent FreeBSD (and Linux) box.

Router

My router at home runs a selfmade NanoBSD, which is basically a FreeBSD i386 that boots from a readonly compact flash card (see /usr/src/tools/tools/nanobsd). It is connected to the Internet thru DSL and gets an official, but dynamically allocated IPv4 address by PPPoE. Every 24 hours the provider drops the PPP session so that my router has to reconnect. The router has two internal interfaces, vr0 and vr1 to which my other computers are connected. I added the following to /etc/rc.conf:

ipv6_enable="YES"
ipv6_gateway_enable="YES"
ipv6_network_interfaces="vr0 vr1"
ipv6_defaultrouter="2002:c058:6301::"
rtadvd_enable="YES"
rtadvd_interfaces="vr0 vr1"
What are those entries good for:
ipv6_enable="YES"
enable IPv6 at all
ipv6_gateway_enable="YES"
this box becomes an IPv6 router
ipv6_network_interfaces="vr0 vr1"
limit IPv6 (auto-)configuration to these interfaces
ipv6_defaultrouter="2002:c058:6301::"
same as defaultrouter, but for IPv6; 2002:c058:6301:: is the anycast address of the nearest 6to4 relay router
rtadvd_enable="YES"
rtadvd is the router advertisement daemon which is part of the FreeBSD base system; it announces our IPv6 prefix to our local network
rtadvd_interfaces="vr0 vr1"
limit advertisements to these interfaces, don't announce our router on other segments
You can start, stop, or restart rtadvd manually by /etc/rc.d/rtadvd start/stop/restart. You could also start IPv6 networking by /etc/rc.d/network_ipv6 start, but at the current stage this might yield an error. As all interfaces have just been assigned a link local address (fe80::/10), the defaultrouter 2002:c058:6301:: is out of scope. So we have to create a stf (six-to-four) tunnel interface. This interface will be assigned an IPv6 address like 2002:AABB:CCDD::1/16 where AABB:CCDD is the hexadecimal representation of the official IPv4 address that the provider currently assigned us. For example, if my DSL router has been assigned an IPv4 address like 212.62.68.23, then the stf tunnel interface will get the IPv6 address 2002:d43e:4417::1/16. You might randomly choose the local address part (lower 64 bits), but for readability I will stick with ::1. We have to use a prefixlen (read: subnet mask) of 16 bits so that the IPv6 defaultrouter becomes reachable. Because the IPv4 address is dynamically assigned, we cannot simply put the IPv6 address into /etc/rc.conf. Instead our ppp linkup script writes out /etc/rc.conf.local and does a /etc/rc.d/network_ipv6 restart every time the pppd reconnects. My /etc/ppp/ppp.linkup looks like this:
MYADDR:
	bg /etc/ppp/ppp.linkup.sh MYADDR
Whenever my provider assigns me a new IPv4 address (when I receive a fresh MYADDR), the pppd executes the /etc/ppp/ppp.linkup.sh shell script and runs it in background. The first parameter MYADDR is replaced by the actual IPv4 address. So my /etc/ppp/ppp.linkup.sh starts like this:
#!/bin/sh

MYADDR="$1"
echo "stf_interface_ipv4addr=\"$MYADDR\"" >/etc/rc.conf.local
This way /etc/rc.d/network_ipv6 will create a stf tunnel interface and assign it the correct 6to4 address. As it is valid for any interface to have multiple IPv6 addresses, none of the scripts in /etc/rc.d will remove old addresses. So we have to do this on our own and extend ppp.linkup.sh to remove any 6to4 address (IPv6 addresses starting with 2002:):
ifconfig stf0 | \
awk '{if(/inet6 2002/){print $2;}}' | \
while read L; do
  ifconfig stf0 inet6 "$L" -alias
done
Now we can safely call network_ipv6:
/etc/rc.d/network_ipv6 restart
After restarting the pppd by /etc/rc.d/ppp restart you should be able to ping any IPv6 host on the Internet from the router, eg. ping6 ipv6.ogris.de or ping6 www.heise.de. You will also notice that a stf0 interface has been created. Did we miss something? Yip. Our inside interfaces vr0 and vr1 still have link local addresses. My Gentoo box on the internal network also has a link local address, as link local addresses are automatically created upon interface initialization. So I can ping and connect from my router to my Linux box and vice versa. But I cannot reach any IPv6 host outside my local network because link local addresses won't get routed - not even within your internal network (hence the name link local). Of course there is no NAT for IPv6. So ppp.linkup.sh has to reconfigure vr0 and vr1, too. My complete script looks like this:
#!/bin/sh

# remove 6to4 addresses from all selected interfaces
for i in stf0 vr0 vr1; do
  ifconfig $i | \
  awk '{if(/inet6 2002/){print $2;}}' | \
  while read L; do
    ifconfig $i inet6 "$L" -alias
  done
done

# MYADDR is a official dynamic IPv4 address
# MYNET is 2002:AABB:CCDD where AABB:CCDD is hex for MYADDR
MYADDR="$1"
MYNET="2002:"`echo "$MYADDR" | \
        /usr/bin/awk -F'.' '{printf("%02x%02x:%02x%02x",$1,$2,$3,$4)}'`

# stf0 gets 2002:AABB:CCDD::1/16
# vr0 gets 2002:AABB:CCDD::2/64
# vr1 gets 2002:AABB:CCDD::3/64
echo "stf_interface_ipv4addr=\"$MYADDR\"
ipv6_ifconfig_vr0=\"$MYNET::2/64\"
ipv6_ifconfig_vr1=\"$MYNET::3/64\"" >/etc/rc.conf.local

/etc/rc.d/network_ipv6 restart
Luckily, we don't have to restart rtadvd. It monitors $rtadvd_interfaces and announces the 6to4 prefix (2002:AABB:CCDD::/64) on these interfaces. So my Linux box will configure itself with an IPv6 address based on the given 6to4 prefix.
There is no piece of software or whatever that reconfigures your internal interfaces when your provider assigns you a new prefix. As there is no NAT for IPv6 you either have to program this yourself or providers are required to assign static prefixes to all their customers.

Firewall

I use OpenBSD's pf for firewalling. First of all, I had to allow any IPv6 traffic encapsulated in IPv4 (read: 6to4 traffic) on the PPP interface:

pass out quick on tun0 proto ipv6
Filtering IPv6 traffic then takes place at stf0, eg. for HTTP:
pass out quick on stf0 proto tcp to port http

Server

Setting up 6to4 on a server with a static IPv4 address (eg. _this_ webserver) is quite simple. Just added the following to my /etc/rc.conf:

ipv6_enable="YES"
ipv6_interfaces=""
ipv6_defaultrouter="2002:c058:6301::"
stf_interface_ipv4addr="212.62.68.23"
I set $ipv6_interfaces to an empty string as I wanted to omit native IPv6 configuration on any interface. After /etc/rc.d/network_ipv6 start an interface named stf0 appeared:
stf0: flags=1 metric 0 mtu 1280
        inet6 2002:d43e:4417::1 prefixlen 16
Again d43e:4417 is hex for 212.62.68.23.
Name resolution is straightforward, too. Just setup an AAAA record for ipv6.ogris.de:
ipv6.ogris.de.	86400	IN	AAAA	2002:d43e:4417::1
Reverse DNS is awkward. First, you have to create a zone like 7.1.4.4.e.3.4.d.2.0.0.2.ip6.arpa on your nameservers. This zone has to contain your PTR record, eg.:
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 86400s IN PTR ipv6.ogris.de.
To get the reverse delegation working, you have to use the webinterface at 6to4.nro.net. NRO is a joint venture of several RIRs. Note that you have to connect right from your 6to4 address you requesting the PTR for. Luckily it's a simple CGI that you can use by command line tools:
fetch -6 'https://6to4.nro.net/cgi-bin/6to4_reverse_wget.pl?method=direct&new_password=PASSWORD&email=EMAILADDRESS&ns1=NAMESERVER1&ns2=NAMESERVER2'
As of 2011-01-02, 6to4.nro.net resolves to 2 IPv6 addresses. One of theses IPs seems to be dead. So you could directly use the remaining one:
fetch -6 'https://[2001:dc0:2001:11::234]/cgi-bin/6to4_reverse_wget.pl?method=direct&new_password=PASSWORD&email=EMAILADDRESS&ns1=NAMESERVER1&ns2=NAMESERVER2'
You have to replace I know that 6to4 is an interim solution and reverse delegation is considered experimental. But why didn't they hand off it to RIRs like RIPE which then could hand off it to a LIR like the company I work for..?
Apache's httpd is already enabled for IPv6. Just extended the listen section to something like this:
Listen 0.0.0.0:80
Listen 0.0.0.0:443
Listen [::]:80
Don't touch the NameVirtualHost statement. The following will yield a warning:
NameVirtualHost 0.0.0.0:80
NameVirtualHost [::]:80
Either NameVirtualHost 0.0.0.0:80, NameVirtualHost [::]:80, or NameVirtualHost *:80 will do the trick. I am using
NameVirtualHost 0.0.0.0:80
There is no need for NameVirtualHosts with IPv6 as there are sufficient addresses. My VirtualHost section reads like this:
<VirtualHost [2002:d43e:4417::1]:>
	DocumentRoot ...
	ServerName ipv6.ogris.de
	ErrorLog ...
	CustomLog ...
	...
</VirtualHost>

Impact on home networks

If you are connected to the Internet by DSL, ISDN, or cable, then your router at home normally gets one official, dynamically allocated IPv4 address. Your provider's dialin router assigns this ip address either by PPPoE, PPP, or DHCP. Your home network runs a private network as defined by RfC1918. Your router maps each internal address to its one external address by using NAT. The internal hosts do not know anything about the external address. When the provider assigns you a new ip address (eg. most providers in Germany disconnect you every 24 hours), it does not have an impact on your internal network. This will change with IPv6. One design goal of IPv6 was to provide each device connected to the Internet an unique/public/official/fully routeable IP address. That led to such a large IPv6 address space. Hence an ISP must not only assign you a single address, but a whole subnet or, in terms of IPv6, a prefix. The smallest size of a prefix is a /64. I assume that providers will assign the typical home user a /56 prefix, while business customers will receive a /48. However, your nodes on the internal network will have to know which prefix is currently assigned to your router. This could be done statically, eg. assign each computer on your network its own official IPv6 address taken from your prefix. Of course you will have to change all IP addresses once your provider reassigns you a new prefix. Luckily, another design goal of IPv6 was autoconfiguration. Thatswhy we have rtadvd on the BSDs or radvd on Linux (and on the BSDs, too). You normally run rtadvd or radvd on the inside interface of your router. It will announce your site's prefix to any host on the internal network. But there is currently no defacto mechanism to reconfigure rtadvd or radvd! FreeBSD's ppp(8) is not able to reconfigure rtadvd or any interfaces besides its tun interface. rtadvd itself is not able to fetch a site's prefix from another interface than the ones it announces the prefixes to. Three solutions come to mind:

I think that we will have to use the last option (DHCPv6) although this means that we have to run 3 daemons (ppp, dhcp6c, rtadvd) just to connect your home network to the IPv6 based Internet. Hopefully someone will update either FreeBSD's ppp or rtadvd to incorporate that functionality of dhcp6c so that customers and ISPs can skip DHCP and stick with PPP solely.