In an ongoing effort to migrate from IPv4 to IPv6 addresses, it was time to assign each container a public IPv6.
The setup I’ve had before for IPv4 was like this: Proxmox with a VyOS VM routing an internal /24 subnet to each LXC container, and each LXC container did NAT for a reverse proxy (Caddy) docker container.
For the IPv6 expansion I added a public IPv6 network to the VyOS router and configured it to send router advertisements to a second internal network.
With IPv6 it would have been possible to just assign each LXC an address and do NAT66 for the docker containers, but I wanted to do it correctly. That meant assigning each and every container its own address.
I first tried implementing this with docker, but that experiment failed because even if you explicitly set enable_ipv4: false, it still assigns internal IPv4 addresses. This then causes problems with some applications, since they see the IPv4 address and try to use it, but since docker can’t route these packets it just silently fails. Even if the applications do fall back on the IPv6 address, it sometimes takes a few seconds for that fallback to kick in, which is not desirable either.
This lead me to attempting the setup with podman next, and that did work, though not without some difficulties.
First there were some cgroup issues with podman. I did not look too deeply into that one, but podman apparently attempts to allocate a cgroup for the pod it tries to spawn, which conflicts with Proxmox somewhere (likely because the LXC is not privileged). That was rather simple to fix, since you can just tell it to not attempt to create a pod.
The second issue was to ensure that the container would actually receive the same IPv6 address every time. Although it would be possible to not deal with this and instead solve it with some fancy dynamic DNS, I simply went ahead and set a mac_address, since SLAAC relies on it to generate the IPv6 address.
It is also necessary to set the net.ipv6.conf.eth0.autoconf sysctl parameter to 1, as it wouldn’t do SLAAC otherwise. It might also work if you set some other combination of parameters, but I went with that one.
It should be noted that the eth0 in the sysctl parameter is different from the eth0 in the network definition. The first is the podman internal network device and the second is the “external” LXC container network device.
x-podman:
in_pod: false
services:
alpine:
image: alpine
mac_address: 00:00:00:00:00:00
sysctls:
- net.ipv6.conf.eth0.autoconf=1
networks:
default:
name: ipv6net
driver: macvlan
enable_ipv4: true
enable_ipv6: true
driver_opts:
parent: eth0
ipam:
config:
- subnet: "fd00::/8"
The only issue with this setup is that although IPv6 is now done correctly, that also means that every container is now reachable from any other IPv6 address. I addressed that security problem in Proxmox.
Proxmox Firewall Settings
For IPv6 to work correctly, I set the default IN policy to DROP and excepted the following items:
- Protocol: ipv6
- Protocol: ipv6-route
- Protocol: ipv6-icmp
- Macro: NeighborDiscovery
As the host I was testing was a web server, it also had the following entries:
- Macro: HTTPS
- Macro: HTTP
- Macro: SSH
It is also necessary to turn off the MAC-filter in the firewall options, since the container has a different MAC-address assigned than the LXC host.