Sunday, March 23, 2025

Some Notes from Fixing my Server’s IPv6 / SLAAC

I had a hard time getting IPv6 to work properly on my VPS. It has a static address, which I published to DNS (ages ago), but it wasn’t fully operational. It wasn’t obvious, because it was able to accept and respond to incoming IPv6, but it was not able to generate outgoing IPv6 connections. Thanks to Happy Eyeballs, the system cheerfully fell back to IPv4 and left me none the wiser.  Probably for years. (Since inbound traffic could be responded to, the IPv6 network-transfer graph looked plausible, too.)

SLAAC

The full and basically meaningless name for SLAAC is StateLess Address Auto Configuration, by the way.

My provider uses SLAAC to, in theory, handle the IPv6 address assignment, but the implementation leaves a bit to be desired.

The documentation is clear that router advertisements, RA for short, are required for SLAAC.  The documentation was not so clear that ICMPv6 Echo Requests (aka IPv6 Ping packets) are also required.

The latter was easily handled, through my existing sysctl configuration at /etc/sysctl.d/99-zz-really-final.conf:

net.ipv6.icmp.echo_ignore_all = 0

Previously, I had set it to 1, because a quiet server is a good server.

Logs?  We Don’t Know Her

I also tried to set the accept_ra sysctl in the same file, but it kept being turned off somehow!

That took a while to track down. The key thing to understand was: systemd-networkd controls everything. It will silently alter sysctls to its liking, because the admin’s settings are unimportant in case of conflict.

Some simple log messages could have saved me so much time and trouble here!

What is happening in detail is that systemd-networkd can’t get the information it wants from the kernel if the kernel accepts the RA packet, so systemd-networkd is silently turning off the sysctl and secretly handling the RA in user space. Trying to make kernel-side accept_ra work was a shaggy-dog quest.

Another thing that is happening in the same confusing manner is that systemd-networkd actually chooses IPv6 Privacy Extensions on a per-connection basis; the sysctl file (placed by procps, which seems like the wrong place to do it) that activates them by default is, once again, operationally irrelevant.

I needed them to be off for my server, which has a single /128 assigned to it, but the correct way is to go through systemd-networkd.

The systemd-networkd Magic

To make everything work, the final answer was to (mostly) ignore sysctl tweaking, and write the relevant directives into the network unit file. Besides static addresses for DNS and Address, along with an IPv4 [Route] section, my particular network file also includes:

[Match]
# Your name will vary!
Name=enp0s6
[Network]
DHCP=no
IPv6PrivacyExtensions=false
IPv6AcceptRA=true
[Route]
Gateway=fe80::1
GatewayOnLink=true

Incidentally, that enp0s6 means a single-function card on bus 0, device 6. In lspci terms, it's 00:06.0.  I believe a multi-function card could end up with an address like enp0s6f1, but that’s not something I’ve seen firsthand, working in the PC/small VPS space as I do.

I am a bit confused on why RA needs to be part of this at all, if the server is assigned a static address, and the Gateway remains operational. But hey, I followed the provider’s documentation to the best of my ability, deactivated their auto-config tool, and I finally found some working configuration. I spent two days’ free time on it, and I don't want to sacrifice more to try anything else.

Dusting

Since the VPS has been continually upgraded for over a decade, I also cleaned up some obsolete packages. If I’m using systemd-networkd, for instance, then ifupdown can go. Nothing actually still claimed /etc/network/interfaces, so to avoid confusion, I (made a backup and) deleted that, too. I cleared out the remaining resolvconf related packages, which were marked as “local.” As far as I can tell, only the only mention of it in the package system now is that systemd-resolved provides resolvconf as a virtual package.

Finally, I uninstalled the DHCP client. If DHCP is officially not supported by the provider, and the correct static address is already configured elsewhere, then I don’t want to make unnecessary requests or generally complicate matters. A quiet server is a good server, after all.

In Conclusion

The interesting meta-point, though, is that IPv4 configured “incorrectly” will work; the provider has gone to the effort of making the attractive nuisance on the undocumented path provide valid information.  That is, there’s a DHCP (v4) server that will respond with the VPS’ intended public IP address.

On the flip side, IPv6 can barely be configured “correctly” at all, and all (or most) misconfigurations fail, either by blocking egress connections, or by not routing any traffic. Worse, the provider’s networking tool will not produce a correct result. Then again, maybe DHCPv6 works?  I still don’t want to put in the effort to experiment.

No comments: