# IPv6 Is a Total Nightmare — This is Why

By Teknikal_Domain

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 a draft in 1997(!), and became a real Internet Standard in 2017. And, quite frankly, it’s one of those things that just adds too much hassle for not enough benefit.

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,456 addresses.1 The entire v4 space could fit into the v6 space $7.922816251426434 \times 10^{28}$ times over.2 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,616 addresses to play with at home.3 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 60 IPs takes up… 2, according to the rest of the world.

## 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.

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 unweildy, so we have a few shortening rules. Any zeros that lead the droup 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.

## 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. To connect to 2607:f0d0:1002:51::4 directly, that’s http://[2607:f0d0:1002:51::4]/ Why is this a thing?!.

## DNS

okay, 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.

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
;; ->>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


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 simply 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.

## 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, 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. 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 pas 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 not only means that I’m exposing my entire network, but I need a different IP for every different destination, and, additionally, I’m also giving away 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 this blog, with NNTP), 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. Gone are the days of “only the traffic I explicitly create a rule for can get in”. See, now, just adding that one step will, be default, expose your entire network! You now need firewall rules to block what you don’t want and add explicit allows, this time manually as an additional step. This is more of a “default pass” routing — unless I tell you not to, let it through. Now, most firewalls do by default drop unknown traffic, but with NAT, if there’s no NAT rule for something it literally has no clue where to route it even if it is allowed through. With NPt, you don’t have that layer of security, the firewall is the only thing between the outside world, and your network.

Really if you remember one thing about IPv6, it’s this. right. here. And this alone is the reason that for the time being I will never start running IPv6 networks.

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. And if you want, hah, privacy, that’s what firewalls are for. It’s… okay, seriously, it’s like a group of people that have no clue how tech should work were asked to design something that the tech-illiterate can actually understand and use fluently, and we have… this. 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? 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 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:

• Gateway
• Up to 4 DNS servers
• Up to 2 WINS servers (depricated)
• 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, a unique enterprise number based DUID, just the MAC, and a UUID based one. 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 giid 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 thats 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 either through DHCPv6 or SLAAC
• Stateless DHCP : Get address through SLAAC, everything else through DHCPv6 (DHCP server keeps no state)

I do not know if assisted mode prefers DHCP over SLAAC, but in either case… usually it’s a good idea to pick one or the other. Either your network is unmanaged, feel free, or your network is managed, check in with a DHCP server since it’s not a good idea to have only some clients known in DHCP and some not.

## 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.

## 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.5 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 IPv6, 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 .

## 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 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), 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

And this is just the sign that you’ve made a stupid protocol: 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.

## 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 it’s 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.

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.