I have an abusive relationship with IPv6.

Admittedly, I’m a bit of a stickler for “doing things the right way,” and part of that includes making sure my self-hosted apps are accessible over an IPv6-only network. In today’s age that means using dual-stack networking.

Unfortunately, it seems most residential ISPs are not doing IPv6 “the right way.” Or, the designers of IPv6 were a bit more… delusional hopeful let’s say that perfect end-to-end connectivity could be a reality.

What they didn’t account for was that if there was any way ISPs could screw up that dream, then they damn well would do it. And they did.

One of the shortcomings of IPv6 in this scenario is you would normally get a delegated prefix for your internet connection, which you would then divide into other /64 prefixes for your own use, e.g. for VLANs. However, if your ISP changes your prefix (which, at least here in the U.S., is pretty common for residential connections) then it could take some fiddling to get your client machines to notice and then request a new IPv6 address, because the one they were just using is no longer in the network you’ve been assigned. Normally this is transparent when you’re using SLAAC, but if your devices need to do other special things (like have a host firewall) then your firewall rules need to be changed as well. Additionally, using only SLAAC forfeits some of the benefits of using a DHCPv6 server such as being able to control static addressing.

All of this seems to point to the feeling that IPv6 was never designed to handle the bullshittery that ISPs dish out on a regular basis (is a static IPv6 prefix so hard? We only have like… [checks notes] 18,446,744,073,709,551,616 of them).

For me, this manifests as several known problems that I’m too lazy to fix, but recently I finally had enough of one and decided to do something about it.

On my home network, I have (among others) two separate VLANs for my “services” network and my “users” network. The former is for my home lab and other servers which I may or may not expose to the internet via port forwarding, and the latter is where my desktop, laptop, and Android phone reside. Both of these networks are using dual-stack networking.

I’m making great use of NetworkPolicy objects in my Kubernetes cluster for security reasons, but unfortunately I would need to manually change them to accomodate source traffic from the user network in order for it to be allowed.

To fix this, I wrote a Kubernetes operator of sorts that runs as a DaemonSet on each of the nodes. It checks and monitors a config object that looks like so:

apiVersion: apps.fuwafuwatime.moe/v1
kind: IP6UpdateConfig
metadata:
  name: ip6-update-config
spec:
  delegatedPrefixLength: 60
  ciliumCIDRGroups:
  - ciliumCIDRGroupName: ip6-local-lan
    prefixIds:
    - 0
    - 1
    - 2

Then, it determines what node it is running on and checks the corresponding config for that node:

apiVersion: apps.fuwafuwatime.moe/v1
kind: IP6UpdateNodeConfig
metadata:
  name: ip6-update-ram
spec:
  nodeSelector:
    kubernetes.io/hostname: ram.fuwafuwatime.moe
  interface: br0

Next, it checks the IPv6 address on the interface specified in that configs’s spec.interface and performs the bitmasking to calculate the prefixes that would be created from that prefix according to the main config’s spec.delegatedPrefixLength and the listed spec.ciliumCIDRGroups[].prefixIds. Finally, if they differ from what is listed in the named CiliumCIDRGroup, it will update it accordingly with the new CIDRs and post a status to the config object:

Status:
  Conditions:
    Last Transition Time:  2025-04-25T22:18:06Z
    Message:               Updated IPv6 address in ip6-local-lan from {} to {"2345:0425:2ca1:1::/64", "2345:0425:2ca1:2::/64", "2345:0425:2ca1:0::/64"}
    Reason:                UpdateSucceeded
    Status:                True
    Type:                  Ready

Please note that the above example uses fictitious IPv6 addresses for demonstration.

Then, when the CiliumCIDRGroup is patched, I can just write a corresponding CiliumNetworkPolicy referencing it. Then, whenever my prefix changes, the firewall rules update automatically.

The operator is written in Rust and the full source code can be found here: https://github.com/0xC0ncord/k6u. Please feel free to criticize my Rust skills (but be nice!) — I’m still learning after all.

I have some other ideas for a few features I’d like to add such as only adding CIDRs for specific IPv6 tokens rather than an entire /64, but what I have so far is working well enough for me for the moment. It at least allows me to not have to resort to IPv4-only or using static addressing for accessing my self-hosted email when I’m at home anymore.

’til next time. See ya!