Tek's Domain

#<NTA:NnT:SSrgS:H6.6-198:W200-90.72:CBWg>

IPv6 Is a Total Nightmare — This is Why

New and improved!

Table of Contents

So this is just going to be a total rant. IPv6 is, in theory, a solution to many things, including the dwindling IPv4 address space. IPv6 was officially a draft in 1997, and became a real Internet Standard in 2017. And, quite frankly, it’s one of those things that, in my opinion, just adds too much hassle for not enough benefit.

Take 2 this time. More facts. Clarified points, same worthless opinions.


Before anything, this is basically just an opinionated comparison, not everything I mention is something I dislike (as you’ll see), I’m just listing the comparison points off as I remember them.

Address Space

Okay, yes, IPv6’s address space is massive. IPv4 uses 32-bit addresses, allowing for 4,294,967,296 total addresses. IPv6 uses 128-bit addresses, meaning… 340,282,366,920,938,463,463,374,607,431,768,211,4561 addresses. The entire v4 space could fit into the v6 space $7.922816251426434 \times 10^{28}$2 times over. The default allocation to people, a /64, meaning the first half of the addresses is fixed and the entire second half is the part unique to the network, means that most people have 18,446,744,073,709,551,6163 addresses to play with at home. Compared to v4, where most networks are around /24, meaning the first three groupings are fixed, you get… 254 addresses. Again, context, a standard /64 allocation can, again, fit the entire v4 address space inside itself 4,294,967,296 times. (Yes, that’s the number of addresses in the IPv4 address space. That’s what happens when you divide a power by half of that power.)

IPv4 addresses are, well, sparse, given that most high-level authorities have already run out of addresses, but because CIDR and NAT are a thing, we’ve really started compacting down our usage. My entire house of easily over 200 IPs takes up… 2, according to the rest of the world.

Now, make no mistake I am fully in support of a larger address space. I, personally, would have seen if a 64-bit (18,446,744,073,709,551,616) address space would have worked before the explosion of numbers that is 128-bits happened, as you’ll see. Just for some comparisons, The current world population as of now, October 2021, is 7.9 Billion. None of what I’m about to do will be affected significantly either way, so let’s estimate that as a flat 7,900,000,000. With a 64-bit addressing scheme, that leaves room for every human on the planet to have 2,335,030,895 different networked devices on the internet. With 128 bits, that jumps to about… 43,073,717,331,764,362,463,718,304,738. Every human on the planet would need to take up that many unique IP addresses to completely fill a 128-bit scheme. Safe to say we really won’t need to expand. I think this is overkill, I think this is way overkill, but I also watch(ed) enough MythBusters to know the phrase “if it’s worth doing, it’s worth overdoing” all too well.

Allocation Issues

One thing that people have criticized IPv4 for is that the allocations are just horrible. For example, anything starting with 127 is localhost. normally this is 127.0.0.1, but anything from 127.0.0.0 to 127.255.255.255 all mean the exact same thing. That’s 16,777,216 addresses all literally for localhost. By numbers, 0.39% of the address space, but just keep this in mind.

Similarly, anything starting with a 0, is effectively “current network” (only valid as source), again, another 16 million addresses.

There’s also multiple blocks for private networks, 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16. In total, 17,891,328 addresses.

Compared to IPv6, yes, IPv6 is much better… in theory. There’s only one loopback address, ::1. At the same time… fc00::/7 (26,58,455,991,569,831,745,807,614,120,560,689,152 addresses) is the private address space (more on that later), fe80::/10 (332,306,998,946,228,968,225,951,765,070,086,144 addresses) is the local address space, and ff00::/8 (1,329,227,995,784,915,872,903,807,060,280,344,576 addresses.) is multicast. yes, do you see a recurring pattern? Even though the entire “special” address assignments are exactly 1.271% of the entire IPv6 address space, we’re still allocating giant swathes of addresses. History repeats itself, you can see that right here.

And I will admit, that multicast in IPv6 is special since some bits in the address are special flags, and one form of multicast actually includes a response node’s address, so it’s not just an arbitrary number, but… come on, that’s a little uncalled for, having that much space.

You know what? Let’s get some charts in here.

To give you an idea of how huge this is, let’s show those IPv6 allocations, according to Wikipedia, as a table:

Allocation range Address count
Unspecified (::) 1
Localhost (::1) 1
IPv4 mapped (::ffff:0:0/96) 4,294,967,296
IPv4 translated (::ffff:0:0:0/96) 4,294,967,296
Discard prefix (100::/64) 18,446,744,073,709,551,616
Global 4/6 translation (64:ff9b::/96) 4,294,967,296
Private 4/6 translation (64:ff9b:1::/48) 1,208,925,819,614,629,174,706,176
Teredo tunneling (2001:0000::/32) 79,228,162,514,264,337,593,543,950,336
ORCHIDv2 (2001:20::/28) 1,267,650,600,228,229,401,496,703,205,376
Documentation reserved (2001:db8::/32) 79,228,162,514,264,337,593,543,950,336
6to4 addressing (Deprecated) (2002::/16) 5,192,296,858,534,827,628,530,496,329,220,096
Private networks (fc00::/7) 2,658,455,991,569,831,745,807,614,120,560,689,152
Link-local (fe80::/64) 18,446,744,073,709,551,616
Multicast (ff00::/8) 1,329,227,995,784,915,872,903,807,060,280,344,576
Free addresses 336,289,489,210,617,046,797,563,476,281,328,140,286

Only the “Private networks” and “Multicast” blocks there are large enough to show on the pie chart, and I set the threshold at 0.1%. We’re already off to a good start, but here’s a few things to consider:

  • Currently, 268 million IPv4 addresses are lost to history, in the former private class-E network block, 240.0.0.0/4. That’s 6% of the entire space, gone. Old allocations start whittling down the space you have.
  • IPv6 is showing some signs of this already as well: An entire /16 is considered deprecated because of 6to4, and there’s even two ways of encapsulating an IPv4 address within an IPv6 address, just one has an extra zero in it. Yes, we have more allocations than most people can reasonably count (and more than most modern, 64-bit CPUs can do math with easily, since numbers bigger than the register width need some special care to math), but it seems the lack of transition mechanisms is already showing some issues by locking off entire sections.

Address Representation

We all know what an IPv4 address looks like, right? Four dotted-decimal grouping in the range from 0–255. For example, 192.168.5.225. IPv6 uses eight groupings of four hex digits, colon-separated. For example, 2607:f0d0:1002:0051:0000:0000:0000:0004. That’s… very unwieldy, so we have a few shortening rules. Any zeros that lead the group can be dropped, giving us this: 2607:f0d0:1002:51:0:0:0:4. And since that is still repetitive, you can replace exactly one sequence of more than one group of all zeros with an empty: 2607:f0d0:1002:51::4. For the record this is why the loopback address is ::1. The full address is 0000:0000:0000:0000:0000:0000:0000:0001. Even with those methods, they’re still much longer, harder to remember, and harder to even say than IPv4 addresses. To some point this is inevitable — if you have 128 bits of information to represent, you, well, have to do that. To give some credit here, this scheme, on paper, is nice, and it’s, honestly, the best thing I think could be thought of without some stupidly crazy ideas, like using base64, which would need… 24 characters, counting padding. But saying your IP address is MTI4Yml0ID0gMTZjaGFycw== is not only nonsensical, but that’s, admittedly, less memorable and more prone to error.

So really, while we’re doing the best I think we reasonably can with 128 bits of data to represent, textually, legibly, in a manner that’s not prone to entry errors, I will still add a minor fault here: as good as it is, it’s still unwieldy. I know this is IPv6, meaning that version 5 was skipped, part of me wonders if 64-bit addressing was ever considered, and assuming it was, why was it rejected?

URLs

And remember that this address violates the URL spec, since the : character is specifically to be used to separate the host portion (e.g., google.com) from the port to connect to (assuming nonstandard). As an example, I can reach my torrent client via http://192.168.5.43:9091. See that : there? Because Transmission listens on port 9091, not port 80. How do we fix this? Well, by breaking it again, naturally. To connect to a raw IPv6 address, you wrap it in square brackets, more characters that are disallowed by the specification, but now they’re just de facto standard since every URL parsing library (that’s updated) is going to have to handle them! To connect to 2607:f0d0:1002:51::4 directly, that’s http://[2607:f0d0:1002:51::4]/ Why is this a thing?!.

DNS

Admittedly, IPv6 kinda relies on DNS since… just about everything uses DNS, and of course, actual names are more memorable than 32 hexadecimal digits, but DNS isn’t magic. Unless you have your own DNS server (actually not that hard) that’s configured, you’re still manually typing addresses. Of course if you have, say, pfSense managing your network, every static DHCP lease will be registered in DNS, but it has to take a DHCP lease. And if this device doesn’t… well, I hope you don’t mind typing that out by hand to connect so you can configure it. For everyone I know that responds to the criticism of IPv6 addresses being long and un-memorable with “just use DNS,” please remember that is not always an option. Not every single internal or ad-hoc network has (split-horizon) DNS set up, working, configured for everything, and with all addresses populated. Unless you have some system, like, as I just said, pfSense, that’s automatically adding entries, then somewhere you’re going to have to be handling addresses. If you’re making firewall rules, somewhere, you’re going to be handling addresses. A fundamental part of basically any layer-3 network protocol is that, at some point, you will be handling addresses. “Just use DNS” is such a simplistic answer that it forgets all of the nuance and complexity with actually working with networking.

Even better, rDNS. rDNS, or Reverse DNS, is where a DNS query is performed with an IP address that returns the hostname associated, in a PTR record. For example, the IPv4 that google.com resolves to, 216.58.192.142, can be queried as 142.192.58.216.in-addr.arpa to get it’s “real” name, a PTR record for ord36s01-in-f142.1e100.net. With dig, specifying a -x and then the IP will convert it to the correct format. And if you look close, the query name is the IP, backwards, with in-addr.arpa at the front. It’s backwards because of the hierarchical nature of DNS, which runs right to left, the opposite of IPs. Of course, there’s also rDNS for IPv6:

$ dig -x 2607:f0d0:1002:51::4

; <<>> DiG 9.11.3-1ubuntu1.12-Ubuntu <<>> -x 2607:f0d0:1002:51::4
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22821
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.5.0.0.2.0.0.1.0.d.0.f.7.0.6.2.ip6.arpa. IN PTR

;; ANSWER SECTION:
4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.5.0.0.2.0.0.1.0.d.0.f.7.0.6.2.ip6.arpa. 3600 IN PTR 4000.0000.0000.0000.1500.2001.0d0f.7062.ip6.static.sl-reverse.com.

;; Query time: 731 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Sun Aug 30 00:52:50 EDT 2020
;; MSG SIZE  rcvd: 180

That is insane. The IPv6 rDNS TLD is just ip6.arpa, and the IP part is… every single hex digit, reversed.

2607f0d0100200510000000000000004
4000000000000000150020010d0f7062

Header Changes

One of the core pillars of IPv6 is that most of the processing of traffic should happen at the endpoints — routers don’t do much besides read, and forward, with very little actual data processing. The IPv6 header while being gargantuan in comparison (because the giant addresses), is also much simpler. A fixed version code (6), the traffic class (DiffServ + ECN), a flow label which is effectively a random value that’s constant for every packet that’s part of the same logical connection, length, type of next header, and a TTL (then addresses, obviously). That is it. One interesting note: the TTL field is now called the “Hop limit” because it specifies the number of nodes a given packet can be forwarded to (each called a “hop”, obviously) before it’s dropped. In IPv4, this field was the time in seconds the packet could live, always rounded up to a minimum of… one second. In practice, this meant that the TTL was a hop limit, but according to spec it was actually time based, just that nothing (usually) moved slow enough for that to be important. Another change that’s, well, I have nothing specific to say but I might as well point it out as long as I’m here, if the final destination receives a packet with a hop limit of 0, it will process the packet. In IPv4, the packet would be received, the TTL decremented, and if that left it at expiry, it was dropped, and an ICMP message was fired off in response.

The IPv4 header contains 13 fields plus optional sections, and the IPv6 header contains a flat 8. Of course, you may actually need other details, and that’s where the next header field comes in. Instead of packing all sorts of options into the standard, global header, IPv6 uses additional header extensions that can get tacked on one after another to provide that information, say for fragmentation or IPsec.4 The value in the next header field identifies what’s going to come next, and the final header in the line will use this field to indicate the contained protocol, be it TCP, UDP, or something else.

This has some benefits, mostly from the ease-of-processing department. The addresses are also aligned to a word boundary, meaning they should be able to be loaded into processor registers rather quickly. This, again, is something I can commend, somewhat. I can see the beauty in having a bunch of smaller headers daisy-chaining onto each other, as compared to one big variable header that packs in everything. I do also think that, to some extent, this was a design that almost made itself necessary, with how large those addresses are, they made it almost mandatory to split everything up, and as long as they’re going that route, might as well take it all the way.

Also note there’s no checksum anymore. I mean, just about every device uses Ethernet, which has a Frame Check Sequence (FCS), UDP has a checksum option, TCP has a required checksum… If we want to simplify packet processing, drop the sum. And really, it does make sense, in a way. Even without that one, that means that most programs have two checksums: the Ethernet FCS as the frame gets transmitted point to point, and the transport layer checksum, making sure the entire packet is still valid. Also, the IPv4 checksum also included the TTL (max number of hops in the path before the packet is dropped), meaning that at every stop along the way, the checksum had to be recalculated.

As a final point, this also does bring a change to another protocol: UDP. With IPv4, the UDP checksum field can (and often would) be left blank as all zeros, meaning “no checksum”. In IPv6, this is now disallowed, and a all zero checksum will still be checked… and then found invalid. All UDP packets over IPv6 must have a valid checksum calculated.

Some Extension Headers

Let’s add some examples here. I’m fairly certain the two most commonly-known extra headers are the AH and ESP headers, used for IPsec, the first one literally being called “Authentication Header.” (the latter is “Encapsulating Security Payload”.)

There’s also two ‘options’ headers, the hop-by-hop options, which any node may inspect or change, and the destination options header, who’s only intended recipient is, well, the destination, not potentially any device it passes through. Both of these are identical in format, and just specify some space for type-length-value options to go.5

There’s also a fragmentation header, for if a packet had to be fragmented. I’ll cover that shortly, but know that it holds basically the same information as the IPv4 header: identification for reassembly, offset into the full payload, and a flag to specify if more follow.

Fragmentation

So every link between devices has an MTU, the Maximum Transmission Unit. For normal Ethernet links, minus the frame overhead, this is 1500 bytes. If your equipment supports jumbo frames, that’s closer to 9000 bytes. Well not all links are equal. Some devices might have a high-MTU link on one end, and a lower MTU link on the other. For example, my router might support jumbo frames internally, but the WAN side doesn’t allow that. To deal with, this, we have fragmentation.

If a router is unable to forward a frame due to MTU differences, it will, if allowed, split the packet into multiple chunks, using the More Fragments flag and the Fragment Offset field of the IPv4 header, and send the packet in multiple frames piece by piece, which the other end can reassemble. Note that I said “if allowed.” There is a Don’t Fragment flag in the IPv4 header, and if this is set by the sending device, a router that cannot support a packet of that size will send back an ICMP “packet too big” message which bounces back along the chain. Any node in the network path can perform this fragmentation, meaning that for sending data, the only MTU you care about is the MTU of the device you’re directly connected to; your own link.

IPv6 does not allow this. Intermediate routers are not allowed to fragment a packet, and instead will send back an ICMP error. If you’re going to fragment a packet (which is also heavily discouraged), then the sending device can add a fragmentation header extension, as mentioned above. So, either packets are not fragmented at all, or packets are fragmented from the originating device. IPv6 also expects senders to perform Path MTU discovery, by actually listening to ICMP packet too big messages, which contains the MTU of that node. The sender is expected to read this, and then adjust accordingly, repeating this in a loop until the packet can pass just fine. Alternatively… don’t exceed the IPv6 minimum MTU, 1280 bytes.

Firewalls

If you know me, you know I always say that computer networking is a miracle that only holds together by duct tape, prayers of engineers, and dumb luck. Nowhere does this hold true more than the introduction of IPv6 into a network, where just about everyone that I talk to has flat out disabled IPv6 on everything that gives them the option — it’s just way too much headache to have to deal with it all, and if it’s enabled, 90% of your new network problems become the fact that devices are now doing unexpected IPv6 things that you never thought of.

But besides that point, if you want to run a network with IPv6, you’re likely going to have to operate both 4 and 6 just because IPv4 is still going strong. So that means for every firewall rule that involves a specific host or network, you need two: one for the IPv4 block, and one for the IPv6 block. And heaven forbid if you change one and forget the other.

NAT

Does not exist. Generally when you get an IP address, that address will be globally routable — anyone can access it, from anywhere, Hollywood style.

However, if you want a private network, there is one prefix for that: anything from fc00:0000:0000:0000:0000:0000:0000:0000 to fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff (fc00::/7) is considered non-routable for private networks. Technically the first bit here you’re allowed to modify should always be 1, meaning your actually RFC compliant range is fd00::/8, which is fd00:0000:0000:0000:0000:0000:0000:0000 to fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff. half the addresses, but still plenty. Yes, the actual spec is more complicated and defines a few parts in the “network” area, but… well, you get the point.

So here’s the question: Say you’ve done that. How do you route packets to these private IPs? The answer is Network Address Prefix Translation. Wait… what?

Yes. NAT is an IPv4 thing. NPT is an IPv6 thing. With IPv4, you can scan for packets and, if they match certain criteria (say, going to a known address, like your WAN address, on a known port), swap the destination (or source) address with a new one. This is how I am using just one IP for all my services: the destination port decides what server your request gets routed to. In this sense, all unknown traffic is dropped, and traffic that I have NAT rules for are also allowed pass the firewall. This is a “default drop” system. Nothing gets through unless I say so.

IPv6 uses NPT, where you can transform one prefix into another prefix.

Say, for example, I have a host at the private address of fd2c:a7c6:2aae:ef93::41. I could then add an NPT rule for to transform fd2c:a7c6:2aae:ef93:: into 2607:f0d0:1002:51::. This is effectively a 1:1 mapping, meaning that it works both ways, both inbound and outbound will be translated.

For this, I could then, say, advertise (with an AAAA record, perhaps) the public IP of the server as 2607:f0d0:1002:51::41, and when the packet comes in…

ORIG DEST: 2607:f0d0:1002:0051::41
   PREFIX: |||||||||||||||||||
  REPLACE: fd2c:a7c6:2aae:ef93
 NEW DEST: fd2c:a7c6:2aae:ef93::41

Which means I need a different IP for every different destination, and, additionally, I’m also giving away some details about the internal structure of my network! You may not know the prefix but you’ll know the exact subnet address since I’m only translating a prefix!!!

That “different IPs” bit may sound a bit… duh, then remember that for some systems I run (like a VCS that has a web interface, and SSH), the port number alone is what decides the destination, you could even still go to the same domain name and it counts. With NPT, you cannot do this, you’d have to have an additional device like a layer 7 proxy (like HAProxy) to take in everything and send it to the correct destination, meaning I need a dedicated host to do the thing that IPv4 NAT could already do natively!

And it gets better. Remember, this will blindly just swap prefixes in and out. With IPv4 and NAT, you, technically, bear with me because I’m about to make some stupid claims, have two layers of security: First is the firewall, any packets that get dropped get dropped. Second is NAT. While NAT should not be how you secure things, its effect is actually pretty substantial: even if a packet comes in that the firewall should have caught, without a matching NAT rule, there’s no way of knowing what host to actually send it to, and it’ll just hit the firewall itself, likely generating either a useless message, or just a TCP RST. NPT removes this, meaning the only thing with any say in what comes and goes is the firewall. Now, a proper firewall will adhere to some basic security guidelines… like packets not matching any rule will be dropped. However, coming from the IPv4 world where literally only the traffic that I told it how to pass could get through, that really sounds like a firewall mistake waiting to happen, somewhere.

But I’m not done yet. The entire practice is… just flat out discouraged. The pfSense manual even says that what I just said might also not work correctly, so, nice, but also, the entire point of IPv6 is that all nodes are globally routable, you don’t need special private address spaces or translation of any kind, it just works. Like, really now, was the IETF watching some cybercrime flicks as they wrote the RFC? Every computer just by default accessible anywhere in the world unless you specifically firewall things (and/or let DEFAULT DROP RULE handle most of your traffic)? I get that even with IPv4 that’s how things worked until you set up a subnet boundary, but here, in v6, it’s either a firewall or no protection. This whole “end-to-end” focus nature really feels poorly thought out, from a techie perspective. And, as data shows, the kind of devices that do actively use IPv6 (mobile devices, mainly), are able to just zeroconf themselves perfectly, which is nice from a “just works” perspective, but like many things recently, the “well it needs to work seamlessly” side seriously clashes with the “actual useful functionality” side.

DHCP and Router Advertisements

DHCP in IPv4 is a four step process:

  1. Client sends out a DHCPDISCOVER packet from a source of 0.0.0.0 to the broadcast address
  2. Server sends a DHCPOFFER to broadcast IP (but destination MAC) with the offered client IP, subnet mask, DNS servers, lease time, and other information
  3. Client sends a DHCPREQUEST for that specific IP address
  4. Server sends a DHCPACK with the same information in the offer, thus confirming the IP assignment

DHCP here also includes a lot of data in the form of numbered options, but here’s some common values:

  • IP address (duh)
  • Subnet mask
  • Gateway
  • Up to 4 DNS servers
  • Up to 2 WINS servers (deprecated)
  • Domain name (for the subnet)
  • Domain search list (domain name prefixes to try resolving hosts with)
  • Up to 2 NTP servers
  • Valid IP, hostname or URL for a TFTP server
  • Network LDAP server URI
  • PXE network boot (PXE compatible server IP, file names)

Some additional values like the default MTU (option 26) can also be sent. There’s 254 valid options, as 0x00 is padding and 0xff marks the end of the message.

In just four UDP messages, a host just powering on can gain just about every bit of information that it may need.

DHCPv6… not so much. The protocol is split in two, and let’s go over DHCPv6 first. This is, again, a four step process:

  1. Client sends a SOLICIT from its link-local to the “All DHCP” multicast address, ff02::1:2
  2. Server responds with an ADVERTISE with the client IP
  3. Client sends a REQUEST for that IP.
  4. Server gives a REPLY, and confirms the assignment.

DHCPv6 also has some extra data, too:

  • Up to 4 DNS servers
  • Domain name (for the subnet)
  • Domain search list (domain name prefixes to try resolving hosts with)
  • Up to 2 NTP servers
  • Network LDAP server URI
  • Network boot file URL

Some obsolete options like WINS servers are removed, but you’ll notice some things like the network gateway are completely missing. Also the DHCP server just provides the local part of the address, it doesn’t even give out the network prefix. But before we get into that, here’s something fun: DHCP(v4) uses the MAC address of the client as it’s identifier, IPs are leased to a particular MAC. DHCPv6 uses a DHCP Unique Identifier (DUID), which is usually the MAC address, with other things. There’s four types: one for the MAC + timestamp (DUID-LLT, Link-Layer + Time), a unique enterprise number based DUID (DUID-EN), just the MAC (DUID-LL), and a UUID based one (DUID-UUID). IPs are leased out to DUIDs not MACs, and so it’s actually really difficult to make reservations ahead of time. The easiest course of action is to wait for the client to grab a lease, then upgrade that to a static assignment. Oh, and yes, of course, here’s an example MAC + time DUID: 00-01-00-01-18-BA-30-56-D8-9D-67-C9-FA-33. Are you noticing a pattern here with IPv6? Everything is just getting long, unwieldy, and, in my opinion, creates needless complexity in the interest of simplicity in one of the most ironic twists I’ve seen in computing and networking.

Also note that this also doesn’t happen by default either. DHCP will only be used if SLAAC / RA permits. And while I’ll get to SLAAC in a second, let’s talk about the Neighbor Discovery Protocol, and Router Advertisements.

RS / RA

When an IPv6 capable host joins a network, it will send out Router Solicitation (hmm, I’m seeing “solicit” as a verb here a few times, I think I found everyone’s new favorite word) message. Available IPv6 gateways that can forward frames will periodically send out Router Advertisement messages, or, if they see a solicitation, will immediately send out an advert.

The advertisements contain the M and O flags (hold on), a lifetime for which the advert should be considered valid, up to three DNS servers, a search list (same as DHCP), and the network prefix. The advert also contains a priority, one of low, normal, or high, for that particular router. Why only three? Beats me. And no, it’s not usually a good idea to have more than one router on the same priority level. If you have more than three gateways on a network (because of course that’s a thing you can do now), have fun. Anyways, the Managed and “Other stateful” flags control the behavior of hosts a bit more in-depth. There’s even more flags concerning the prefix data, but at the end of the day, it all boils down to a list of modes you can pick from:

  • Router only : Just advertise the router as a valid gateway
  • Unmanaged : Network has no DHCP or other infrastructure, obtain everything through SLAAC
  • Managed : Network has DHCP, obtain all config through DHCPv6
  • Assisted : Network has DHCP, obtain config through DHCPv6 and SLAAC
  • Stateless DHCP : Get address through SLAAC, everything else (network options) through DHCPv6 (DHCP server keeps no state)

“Assisted” as far as I can tell means that clients will obtain one address through DHCPv6, and then also generate a link-local address as well.

Prefix Delegation

One small footnote here to DHCPv6: prefix delegation. If a client asks, then the DHCPv6 server can give out entire prefixes of its own valid address space. This makes hierarchical DHCP possible, where your root server with the full /64 can hand out, say, /56s to requesters, who could hand out /48s to their requesters… And yes, you can have multiple delegations at once, because the options allow for a start and end range, and then the prefix length. Similar to the start and end of the plain address range. This is specifically an “if asked for” thing though, keep in mind. Only clients that, likely, are gateways for their own LANs are really going to ask for a delegation. It is pretty cool that the address space is large enough to handle hierarchical assignments like this.

SLAAC / APIPA

Primer: SLAAC is the IPv6 version of APIPA.

The basics for both protocols is to, essentially, give a new host an IP address that works, without relying on other external mechanisms like DHCP. In IPv4, this is called Automatic Private IP Addressing, or APIPA. APIPA addresses occupy the 169.254.0.0/16 block, so anything from 169.254.0.0 to 169.254.255.255.6 A client will pick a random value in this range, run an ARP query to make sure it’s free, then bind to it. In IPv4, APIPA is usually a last resort. And, if a new address is given, like a public address or one from one of the proper private reserved blocks, that address will overwrite the APIPA address, meaning it only exists as long as there’s nothing that’s giving a host an IP.

In IPv6, SLAAC (Stateless Address Auto Configuration) is always present, and the first resort. the fe80::/10 block is reserved for link-local, which, in practice, will actually run from fe80::1 to fe80::ffff:ffff:ffff:ffff, since the 54 bits between the prefix and the address are all zeros. This link-local address is used for everything else, like NDP (router solicitation!), DHCPv6, and the like. And unlike IPv4, the link-local address is always valid, even with a globally routable address. (Yes, IPv6 capable hosts will have multiple IPv6 addresses on one interface. That’s not mildly confusing at all.) Note that the link-local address is either deterministic (MAC address based), or partially randomized, which should be stored, if possible, so that reboots don’t cause the device to change addresses. Part of the SLAAC process (NDP) gives it the rest of the data it might need besides just a valid local address.

Oh, did… did you think I was done on this point? Nope! It’s very likely that a host can generate the same IPv6 link local address for two different network interfaces. In terms of incoming data this doesn’t matter, since, data received is data received. But for sending, the network driver needs to know what device to use. Thus enters the zone index. A zone index is either a string, or a numeric value. Numeric values are required to be supported, but many Unix-like systems allow you to specify the interface name itself textually. Example? fe80::1ff:fe23:4567:890a%eth2. So, did you know, this means that a full URL that uses every part available to it looks like this:

http://username:[email protected][fe80::1ff:fe23:4567:890a%eth2]:9091/transmission/web/?query=value#confirm

Be glad you can’t hear what I’m shouting at my monitor right now.

ARP / NDP

The Address Resolution Protocol is the protocol used in IPv4 to translate link layer addresses (MAC addresses) into internet layer addresses (IP addresses). Even though the protocol is more generalized (it works on more than just Ethernet and IPv4, even Chaosnet has identifiers for ARP), in general, a client will ask a simple question: “Who has IP address X? Tell IP address Y.” The ARP message contains fields for both the IP and MAC of both the sender and receiver. On receiving an ARP request for your IP, the host will respond with a “MAC address Z has IP address X” message.

This protocol relies heavily on broadcast. Well wouldn’t you know it, IPv6 has no broadcast. There’s an “all nodes” link-local multicast which does effectively the same thing, but it’s technically not the same thing. So instead, we have the Neighbor Discovery Protocol. Router solicitations and advertisements are part of NDP, as well as neighbor solicitations and advertisements (again with the soliciting!), the only difference are that NAs aren’t periodically sent out like RAs are. But besides that there’s really nothing to it, other than the fact that we had to create an entirely new protocol to do the exact same job as an already existing protocol because someone removed broadcast, the second most common type of traffic. Need to find someone? Send out a Neighbor Solicitation, and wait for the responding Neighbor Advertisement.

Oh, and for the record, NDP is actually just a set of 5 different ICMPv6 messages.

IPsec

Did you know that IPsec was not only originally developed for IPv6, but also planned to be a mandatory part of all implementations? Yeah… that got downgraded to “recommendation” pretty quickly. At least if you do implement IPv6 IPsec, you need to implement IKEv2 and certain ciphers, so there’s a guaranteed level of compatibility.

Anyways, IPsec has found good deployment in IPv4 networks (I use IPsec as a VPN for my phone since OpenVPN sucks on it),7 and some parts of it really tell you just how IPv6-thinking the IETF was. I mean, the two main modes of operation, AH (Authentication and resistant to changes) and ESP (authentication and encryption), are actually IPv6 extension headers.

Hey at least IPsec wasn’t a requirement on the wire, like HTTPS practically is today. Imagine somehow literally needing to configure IPsec credentials for ANY connection on the internet. We might get there some day, when we make a way to automatically configure IPsec in the same way that TLS “just works”, but right now I’m glad that’s nowhere near likely.

One Other Random Remark

Enabling IPv6 on the LAN side of Sophos UTM 9 causes the WAN side to lose it’s link. …Why? I can’t even make my local network IPv6 for link-local communications because then the machine can’t connect to the rest of the internet. I honestly cannot for the life of me begin to fathom a reason as to what could cause this, other than bad drivers.

Because yes, that is one serious problem here: bad drivers. IPv6, on paper, is wonderful, with some glaring holes in it. The major issue with it is the implementations, from what I’ve experienced. All the joys in the world can’t hold up anything on a sloppy implementation. Or even better, just handling in general. Devices that lose their links when you enable IPv6 on one side and not the other, which, in a decently configured network, shouldn’t actually cause a problem but yet it does (probably with router advertisements, now that I’ve brained it a bit longer). Programs that legit refuse to listen to IPv4 localhost unless you actually force the underlying VM to forget its entire existence (looking at you, certain unnamed Java-based log ingest and processing platform). Programs that take an instruction to bind to 0.0.0.0 as implicitly saying to bind to ::, not binding to 0.0.0.0 and making netstats output confusing until I stop telling it to only show IPv4 results… the thing I asked the program to use.

Conclusion

IPv6 is a revamped version of the Internet Protocol built for modern times, complete with a massive address space, simplified headers, less expectation of in-flight processing, and a few other benefits I haven’t talked about. But in the pursuit of making things simpler and focusing on a more end-to-end approach like the early days of the internet, we’ve gotten something so monstrously complex compared to its predecessor that it writes its own death warrant. Nobody wants to undertake such a large responsibility if almost nobody uses it… yeah that’s an apparently unexpected catch 22. But it’s not the only one either — IPv6 really only makes sense if everything along the line supports IPv6, a lot of benefits get removed if you have to 6in4 tunnel, or at some point literally just downgrade to IPv4. And, again, that’s not happening, so why bother? IPv6 in a mad dash to make a simple protocol really, in my opinion, threw out the baby with the bathwater. Parts need to be redesigned (NDP) to fit, the second most common addressing mode (broadcast) got removed in favor of massively expanding the least used addressing mode (multicast), for some reason…

And, okay, the lack of NAT, an intentional design decision, is where I draw the line and say this is not for me. Maybe one day when IPv4 really is being put into obsolescence I’ll make the change, but there’s no attempt to make the two compatible besides literally running dual stack — also meaning managing two networks instead of one. At the very least we could have been given some useful transition mechanisms, but… not really. There’s just no point. But, that’s intentional. It was, from what I can tell, a conscious decision to not add any transition mechanisms from 4 to 6, because 6 is different enough, and it didn’t want to be tied down to the problems with IPv4 that it’s trying to get rid of, so you get nothing… okay, you get a method to represent IPv4 addresses inside IPv6 addresses. Besides that, officially, you get nothing.

IPv6 wants to do a lot of good, it has the potential to do a lot of good, but it makes it so difficult to handle that, that it’s just not worth it. The entire network and routing model is different enough that not much of your IPv4 knowledge will carry over, sure, it’s still the Internet Protocol, but the name is about the only thing it really has in common at the actual network level. It genuinely, genuinely doesn’t feel like IPv6 was something meant for consumers to handle, heck it doesn’t even feel like something that most IT departments were meant to handle. It feels like something that wants to just revamp the backbone of the internet, be in the hands of ISPs and major companies who can afford to basically re-train their entire engineering staff to work with this, and have them be knowledgeable enough to have all the major communications on the world on IPv6, and leave little change to the rest of us, those that don’t have the ability to handle that much headache.

Actually, you know what? That’s the best way of putting it: IPv6 feels like it was made solely for computers, not people. The addresses are large because future proofing, and a computer likely doesn’t care if it’s 32 bits or 128, as long as it can load read them. A computer doesn’t care if the numbers at play are unfathomably large, and if the underlying changes break a lot of existing knowledge, because they have no reason to. IPv6 is so forward-thinking, and so packed full of “enhancements” that it’s a beautiful thing if you’re a network driver, but a never-ending headache for a human, unless you’re a human that has little to no knowledge of IPv4, because any attempt to try and find some common ground, to try and understand how one is like the other, will just leave you with more questions than answers, unless you’re able to just stonewall off one from the other, acknowledge them as basically being similar in name only, and learning about the IPv6 way of doing things.

Really, that’s what I say when I read about most of the things IPv6 has done — “Why? You had almost 0 real reason to do this and you did it anyways!” I’m really looking forward to seeing how much IPv6 changes in the future, if some of these decisions will get reversed, or if it’ll just saunter on, further pushing itself into the dual catch 22 that causes some low amounts of adoption today.

I will address this though: For the mobile and non-tech-savvy market, IPv6 is huge, I believe the USA has roughly 33% adoption according to Google’s traffic logs. Anything local network, or anything where you have no fancy equipment, just plug a computer or two into your modem and watch cat videos, IPv6 will configure itself, since your ISP put a lot of time and effort into getting it right, and then you can just have fun. But the moment someone with some networking smarts starts poking around, adding equipment, and building their own network, IPv6 is something that will almost immediately get thrown to the bottom of the garbage bin, never to be considered again, because you’re not an ISP that can use the money it’s gained from predatory business practices (ahem, Comcast) to pay a team of engineers to sort this one out, you’re one person, in one home, with a few pieces of gear, and once you start reading how to do everything, you swap out your morning coffee for an alcohol of choice, gulp it down, and decide that maybe that’s something best left untouched.


  1. That’s 340 undecillion, 282 decillion, 366 nonillion, 920 octillion, 938 septillion, 463 sextillion, 463 quintillion, 374 quadrillion, 607 trillion, 431 billion, 768 million, 211 thousand and 456. What is this, Scrooge McDuck’s net worth?! ↩︎

  2. Also known as 79,228,162,514,264,340,000,000,000,000, or 79 octillion, 228 septillion, 162 sextillion, 514 quintillion, 264 quadrillion, 340 trillion. ↩︎

  3. 18 quintillion, 446 quadrillion, 744 trillion, 73 billion, 709 million, 551 thousand and 616. ↩︎

  4. The IPsec mode that just provides authentication but not encryption is called AH, or Authentication Header. And this explains why. IPsec (as you’ll read later if you immediately jumped to this footnote) was originally meant to be a core part of IPv6, and you could authenticate your packets with an authentication header extension after the IPv6 header. ↩︎

  5. The one that I see a lot of reference to is the jumbo payload option. Normal IPv6 packets have a maximum payload of 65 KiB, though this option extends that to 4 GiB - 1B, even though both far exceed most link’s MTUs. ↩︎

  6. The first and last 256 addresses, so anything starting with 169.254.0 and 169.254.255, are reserved for future use, and not to be used. Valid APIPAs then are anything between 169.254.1.0 and 169.254.254.254. ↩︎

  7. Update: Actually Android broke this at one point in an update, and I’ve since changed to WireGuard. ↩︎