Routable domestic ipv6, and auto-discovery

Version $Id: home-ipv6.html,v 1.3 2012/11/07 22:18:39 madhatta Exp $

I've been meaning for some time to get to grips with ipv6 at home. But there's no point running a network unless you have something to talk to, which meant getting v6 to my colocated box, as well.

So we'll skip over the business of getting v6 routed to my colo'ed box, upgrading it to C6, moving colos, cancelling my cable modem service, getting ADSL at home through a provider who could do routed ipv6, learning to do PPPoE so that my external linux box could own its routable v4 address, asking for my v6 netblock, and having it provisioned. Let's assume all that has happened, as indeed it has.

The question this technote focuses on is: given a routed v6 netblock to the house, terminating on a CentOS 6 box, how do I subnet it so that I can give separate address space to my wireline and wireless networks (which are on separate, if RFC1918, v4 networks) and arrange matters such that addresses in the allocated ranges are assigned to hosts that want them?

It may help to have a basic outline of the architecture of the network. It looks like this:

(The dia source for this diagram is here.)

My routed netblock is 2001:484e:ad51:3500::/56. That's working fine, and has been for some months. I started off by assuming that I'd partition it into subnets, say /96 blocks, and assign one of those to my wireline, and one to my wireless, networks. Obviously, the way to hand out addresses to network clients is via DHCP.

dhcpd6

dhcpd6 is in fact straight dhcpd, running with the -6 flag; the same executable will, on any given invocation, serve either v4 or v6 address space, but not both. I setup /etc/dhcp/dhcpd6.conf as follows:

subnet6 2001:484e:ad51:3500:0:0:0:0/96 {
	range6 2001:484e:ad51:3500:0:0:8000:0
	2001:484e:ad51:3500:0:0:8000:ffff ;
#	option dhcp6.routers 2001:484e:ad51:3500::1 ;
}

subnet6 2001:484e:ad51:3500:0:1:0:0/96 {
	range6 2001:484e:ad51:3500:0:1:8000:0
	2001:484e:ad51:3500:0:1:8000:ffff ;
#	option dhcp6.routers 2001:484e:ad51:3500:0:1:0:1 ;
}
The commented-out option was me taking a first guess at how to advertise a route. It didn't work, and caused the daemon to fail, so I removed it. I then started the daemon, which logged as follows:
Nov  7 10:56:08 bill dhcpd: Bound to *:547
Nov  7 10:56:08 bill dhcpd: Listening on Socket/5/eth0.12/2001:484e:ad51:3500:0:1::/96
Nov  7 10:56:08 bill dhcpd: Sending on Socket/5/eth0.12/2001:484e:ad51:3500:0:1::/96
Nov  7 10:56:08 bill dhcpd: Listening on Socket/5/eth0.11/2001:484e:ad51:3500::/96
Nov  7 10:56:08 bill dhcpd: Sending on Socket/5/eth0.11/2001:484e:ad51:3500::/96
OK, so far so good. On the client (laptop running Fedora 16) I ran dhclient -6 -d eth0, and watched STDOUT:
Internet Systems Consortium DHCP Client 4.2.4-P2
Copyright 2004-2012 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/

Bound to *:546
Listening on Socket/eth0
Sending on   Socket/eth0
PRC: Soliciting for leases (INIT).
XMT: Forming Solicit, 0 ms elapsed.
XMT:  X-- IA_NA 31:ec:8f:b2
XMT:  | X-- Request renew in  +3600
XMT:  | X-- Request rebind in +5400
XMT: Solicit on eth0, interval 1020ms.
XMT: Forming Solicit, 1020 ms elapsed.
XMT:  X-- IA_NA 31:ec:8f:b2
XMT:  | X-- Request renew in  +3600
XMT:  | X-- Request rebind in +5400
XMT: Solicit on eth0, interval 1990ms.
XMT: Forming Solicit, 3010 ms elapsed.
but it went on, and on, and nothing much happened on my ethernet card. After much head-scratching, I realised that it was being blocked by my client's v6 firewall, and when I'd fixed that, lease acceptance was being blocked by my server's v6 firewall. Doing
iptables -A INPUT -p udp --dport 546:547 -j ACCEPT
at an appropriate point in both my client and server ip6tables rulesets got that to pass through, and then dhclient logged:
Bound to *:546
Listening on Socket/eth0
Sending on   Socket/eth0
PRC: Soliciting for leases (INIT).
XMT: Forming Solicit, 0 ms elapsed.
XMT:  X-- IA_NA 31:ec:8f:b2
XMT:  | X-- Request renew in  +3600
XMT:  | X-- Request rebind in +5400
XMT: Solicit on eth0, interval 1010ms.
RCV: Advertise message on eth0 from fe80::213:72ff:feba:3750.
RCV:  X-- IA_NA 31:ec:8f:b2
RCV:  | X-- starts 1352299944
RCV:  | X-- t1 - renew  +3600
RCV:  | X-- t2 - rebind +7200
RCV:  | X-- [Options]
RCV:  | | X-- IAADDR 2001:484e:ad51:3500::8000:b2f8
RCV:  | | | X-- Preferred lifetime 43200.
RCV:  | | | X-- Max lifetime 86400.
RCV:  X-- Server ID: 00:01:00:01:18:2c:fa:c8:00:13:72:ba:37:50
RCV:  Advertisement recorded.
PRC: Selecting best advertised lease.
PRC: Considering best lease.
PRC:  X-- Initial candidate 00:01:00:01:18:2c:fa:c8:00:13:72:ba:37:50 (s: 155, p: 0).
XMT: Forming Request, 0 ms elapsed.
XMT:  X-- IA_NA 31:ec:8f:b2
XMT:  | X-- Requested renew  +3600
XMT:  | X-- Requested rebind +5400
XMT:  | | X-- IAADDR 2001:484e:ad51:3500::8000:b2f8
XMT:  | | | X-- Preferred lifetime +7200
XMT:  | | | X-- Max lifetime +7500
XMT:  V IA_NA appended.
XMT: Request on eth0, interval 1040ms.
RCV: Reply message on eth0 from fe80::213:72ff:feba:3750.
RCV:  X-- IA_NA 31:ec:8f:b2
RCV:  | X-- starts 1352299945
RCV:  | X-- t1 - renew  +3600
RCV:  | X-- t2 - rebind +7200
RCV:  | X-- [Options]
RCV:  | | X-- IAADDR 2001:484e:ad51:3500::8000:b2f8
RCV:  | | | X-- Preferred lifetime 43200.
RCV:  | | | X-- Max lifetime 86400.
RCV:  X-- Server ID: 00:01:00:01:18:2c:fa:c8:00:13:72:ba:37:50
PRC: Bound to lease 00:01:00:01:18:2c:fa:c8:00:13:72:ba:37:50.
PRC: Renewal event scheduled in 3598 seconds, to run for 3600 seconds.
PRC: Depreference scheduled in 43198 seconds.
PRC: Expiration scheduled in 86398 seconds.
Well, that looks much better. However, ifconfig on the client was telling me that my card had the address:
eth0      Link encap:Ethernet  HWaddr 00:18:31:EC:8F:B2  
          inet addr:192.168.3.208  Bcast:192.168.3.255  Mask:255.255.255.0
          inet6 addr: 2001:484e:ad51:3500::8000:b2f8/64 Scope:Global
          inet6 addr: fe80::218:31ff:feec:8fb2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:53314 errors:0 dropped:1 overruns:0 frame:0
          TX packets:28779 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:65476510 (62.4 MiB)  TX bytes:2502108 (2.3 MiB)
Note the netmask on the globally-scoped v6 address: it's a /64, whereas both my server's NIC, and my dhcpd6.conf, said /96. There followed a bunch of reading around, finally finding this mailing list article, from May 2012 (depressingly recent), which concludes:

"DHCPv6 provides no information on the prefix length or its on-link status - only the host adress (sic). Prefix length and on-link status, as well as suitable gateway adresses are published using router advertisements."

Bugger.

radvd

OK, well I already knew from my options dhcp6.router failure above that I was going to have to find some way to tell clients about their gateway, which I suspected meant running some kind of router advertisement code. I figured it ought to be possible also to get the router advertisement daemon to hand the prefix information out, in which case it might as well allocate the addresses as well. I installed it with sudo yum install radvd, then configured it with the following /etc/radvd.conf, which I poached from this article:
interface eth0.11 {
        AdvManagedFlag on;
        AdvSendAdvert on;
        #AdvAutonomous off;
        AdvOtherConfigFlag on;
        MinRtrAdvInterval 3;
        MaxRtrAdvInterval 60;
        prefix 2001:484e:ad51:3500:0:0:0:0/96 {
                AdvOnLink on;
                AdvRouterAddr on;
        };
};

interface eth0.12 {
        AdvManagedFlag on;
        AdvSendAdvert on;
        #AdvAutonomous off;
        AdvOtherConfigFlag on;
        MinRtrAdvInterval 3;
        MaxRtrAdvInterval 60;
        prefix 2001:484e:ad51:3500:0:1:0:0/96 {
                AdvOnLink on;
                AdvRouterAddr on;
        };
};
However, when I started the daemon, it promptly logged:
Nov  7 15:30:57 bill radvd[19404]: prefix length should be 64 for eth0.11
Nov  7 15:30:57 bill radvd[19404]: prefix length should be 64 for eth0.12

This wikipedia article makes it fairly clear that, rather than handing out addresses as v4 dhcpd does, v6 auto-discovery gives clients some basic network information and lets them construct their own globally-unique address from their already-fairly-unique MAC address, plus the guaranteed-globally-unique ipv6 /64 they've been given to do it in. The Wikipedia article explains the details, but the upshot is that a MAC address is 48 bits, and there are 16 bits devoted to an identifying ff:fe label in the middle, giving a "64-bit interface identifier in modified EUI-64 format". The consequence of this is that you can only use this form of auto-addressing in a /64 (or bigger) network.

(NB: this article suggests otherwise as regards DHCP6 servers handing out prefixes, but I haven't yet had a chance to test it.)

I was doing v4 networks before CIDR, when it was all classful, and this looks to me like the same stupid idea: to avoid clients having to either know or discover too much information about their networks, we'll assume we can parcel up the network in gigantic chunks (class A, anyone?) and subtitute address space for bandwidth, because hey, address space is huge, we'll never run out. A /64 holds over ten billion billion IPv6 addresses. It's rampant fucking insanity to assume that's the "basic unit" of networking, that noone would ever want to split up a v6 network any smaller than that. This smacks of MicroSoft and Apple, and all those other lovers of zero-knowledge, auto-discovery protocols that don't need properly sizing, setting up, and configuring (and therefore, network administrators). Thanks guys.

Anyway, there's nothing I can do about that, and it's now clearer to me why my ISP assigned me a /56 (at the time, I remember thinking "what can I possibly do at home with four thousand billion billion IP addresses?"; now I realise that they were actually allocating me 256 subnets). So I rewrote my radvd.conf, and re-IPed my server's interfaces, to say:

interface eth0.11 {
[...]
        prefix 2001:484e:ad51:3500:0:0:0:0/64 {

interface eth0.12 {
[...]
        prefix 2001:484e:ad51:3501:0:0:0:0/64 {
and restarted the daemon. Now, without amending the client setup in any way, or explicitly running any v6-discovery at all, the interface magically picks up a globally-valid v6 address on the relevant subnet within a few seconds:
eth0      Link encap:Ethernet  HWaddr 00:18:31:EC:8F:B2  
          inet6 addr: 2001:484e:ad51:3500:218:31ff:feec:8fb2/64 Scope:Global
          inet6 addr: fe80::218:31ff:feec:8fb2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
[...]
and a proper route as well:
[root@anni ~]# netstat -A inet6 -rn
Kernel IPv6 routing table
Destination                                 Next Hop 			Flags	Metric Ref    Use Iface
[...]
::/0                                        fe80::213:72ff:feba:3750	UGDA	1024   0        1 eth0   
[...]

I've shown only the default route (::/0 is the v6 equivalent of 0.0.0.0/0, or "everywhere") to avoid putting a lot of other confusing lines in there.

It worked equally-well on my wireless interface as well. Note that the route above has an fe80: prefix, that is, it's a link-local address and not, therefore, globally-routable. But that doesn't matter, because next-hop-route needs to be on the local network anyway. That's a v6 efficiency I find atttractive.

Preventing static devices from auto-discovering

An annoying side-effect of getting router discovery working on the network is that statically-addressed wireline clients, which already have statically-allocated v6 addresses and gateways, started picking up those modified EUI-64 addresses in addition to their hardwired ones:
[root@risby network-scripts]# ifconfig eth3
eth3      Link encap:Ethernet  HWaddr 70:73:BC:AC:44:5A  
          inet addr:192.168.3.11  Bcast:192.168.3.255  Mask:255.255.255.0
          inet6 addr: 2001:484e:ad51:3500:7273:bcff:feac:445a/64 Scope:Global                    <--- EUI-64 address - WRONG!
          inet6 addr: fe80::7273:bcff:feac:445a/64 Scope:Link
          inet6 addr: 2001:484e:ad51:3500::9/96 Scope:Global                                     <--- hardwired address - right.
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
[...]

which I found really annoying. This article suggested that it could be overcome with appropriate /etc/sysctl.conf entries, but this turned out not to be the case on either CentOS 6 or Fedora 16. Instead, on both OSes, adding
IPV6_AUTOCONF=no
to /etc/sysconfig/network-scripts/ifcfg-ethN, for relevant N, did the trick.

And that's it, so far.

Back to Technotes index
Back to main page