Globally routable IPv6 for your Incus Linux Containers

Preliminary

Goals
  • Direct connection to the containers over the internet with IPv6
    • Hint: If you point a Cloudflare proxied FQDN to the IPv6 of the container Cloudflare will add IPv4 compatibility for you
  • Optional for Ubuntu users: Disable netplan and use systemd-networkd directly, because netplan doesn’t handle NDP proxying at the moment of writing this
  1. Making it work if your ISP uses MAC address based routing
  2. Making it work if they use NDP by using NDP proxying with systemd-networkd
Prerequisites
  • Having an /64 or larger IPv6 subnet assigned to your Incus host server
  • Running a newer Distro with
    • Recent systemd version with systemd-networkd for networking (or netplan)
  • For testing IPv6 connectivity from your local browser you will have to have IPv6 connectivity fomr your local network. Alternatively you can use something like Cloudflare WARP to achieve that.

Networking

If you want to keep netplan you can skip Networking and continue with Incus. You will have to make sure though that forwarding is enabled (2.1).

0. [Optional] for Ubuntu users: Remove Netplan

:rotating_light:WARNING: This might break things for you. Do this at your own risk. :rotating_light:

Run the following snippet in your terminal:

if apt -q list --installed 2>/dev/null | grep -q "^netplan.io"; then
  systemctl enable systemd-networkd
  apt -y autoremove --purge netplan.io netplan-generator python3-netplan
  rm -r /etc/netplan
  apt -y autoremove netplan-generator python3-netplan libnetplan1
  rm -f /usr/lib/systemd/system-generators/netplan
  rm -f /run/systemd/network/*.network
fi

1. Setup systemd-networkd

Here a basic networking setup with networkd, you will most probably have to change things here:

instance_mac_address="REPLACE WITH YOURS"
instance_ipv4_gateway="$(ip route list default | 
    grep -o "via [0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | 
    grep -o "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*")"
instance_ipv4_netmask_cidr="$(ip a | grep "inet " | grep -v "host lo" | 
    grep " e[a-z0-9]*" | grep -o "/[0-9]*" | sed "s|/||")"
instance_ipv6_gateway="fe80::1"

tee /etc/systemd/network/ethernet.network <<EOF
[Match]
PermanentMACAddress=$instance_mac_address
Name=e*

[Network]
LinkLocalAddressing=ipv6
Address=$instance_ipv4_address/$instance_ipv4_netmask_cidr
Address=$instance_ipv6_address/128
DNS=1.1.1.1
DNS=8.8.8.8
DNS=2606:4700:4700::1111
DNS=2001:4860:4860::8888
Domains=invalid
#IPv6ProxyNDP=true

[Route]
Destination=0.0.0.0/0
Gateway=$instance_ipv4_gateway
GatewayOnLink=true

[Route]
Destination=::/0
Gateway=$instance_ipv6_gateway
GatewayOnLink=true
EOF

systemctl restart systemd-networkd

2. Turn on IPv6 forwarding and NDP proxying in the Kernel (in case we need it)

tee --append /etc/sysctl.conf <<EOF
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.eth0.forwarding=1
net.ipv6.conf.all.proxy_ndp=1
net.ipv6.conf.eth0.proxy_ndp=1
EOF

Also make sure IPv6 is not disabled in /etc/sysctl.conf by looking at the whole file.

You may then run: sysctl --system

2.1 Change firewall settings if forwarding is blocked through the firewall.

If you are using UFW you may check the /etc/default/ufw file for DEFAULT_FORWARD_POLICY="ACCEPT"

No matter what frontend you may use for firewalling, you can check all nftables rules with this command: nft list ruleset

Incus

3. Install and setup Incus

When initiating Incus after the install put the IPv6/64 range as the incusbr0 IPv6 address. Beware that you should not use the same address on the host’s ethernet connection and on incusbr0. The ipv4 stuff can be left alone and set to auto and stay with NAT.

If already installed you can run:

incus network set incusbr0 ipv6.address $ipv6address2/64 # Other one than host ethernet

This way the containers are going to get an ipv6 address from incusbr0.

Also the following options should be set:

incus network set incusbr0 ipv6.dhcp false
incus network set incusbr0 ipv6.nat false
incus network set incusbr0 ipv6.routing true

4. Run a Linux Container and test connectivity

Enjoy a container with an universally routable IPv6.
To get the address you can run incus list

Install NGINX inside the container and paste the IPv6 in a browser on your local computer in [ ] braces.

If you get the NGINX standard page you are done. If not read on about NDP proxying.

NDP Proxying

If you kept netplan you can use the instructions by @candlerb from a comment below: Globally routable IPv6 for your Incus Linux Containers - #4 by candlerb
After augmenting your netplan config with these steps NDP Proxying will also work.

5. Enable NDP proxying

Enable NDP proxying in /etc/systemd/network/ethernet.network by removing the hash in front of IPv6ProxyNDP and adding the IPv6 of the container with IPv6ProxyNDPAddress.

For each container IPv6 there will have to be an additional line like below.

DNS=2606:4700:4700::1111
DNS=2001:4860:4860::8888
Domains=invalid
IPv6ProxyNDP=true
IPv6ProxyNDPAddress=$ipv6_container

[Route]
Destination=0.0.0.0/0

Then simply reload networkd:

systemctl reload systemd-networkd

After a short while and making a few requests through your browser it should work and you should finally get the standard page of NGINX. Going inside of the container and doing some network requests to IPv6 addresses/hosts might also help.

2 Likes

Why would you remove netplan?

Disable netplan and use systemd-networkd directly, because netplan doesn’t handle NDP proxying at the moment of writing this

2 Likes

Right… but netplan uses systemd-networkd as its backend, and you can make override files to get the additional functionality. You don’t need to kill netplan completely.

To test this, I made an incus VM from images:ubuntu/24.04/cloud --vm; it has interface enp5s0. If I create /etc/systemd/network/10-netplan-enp5s0.network.d/proxy.conf containing:

[Network]
IPv6ProxyNDP=true
IPv6ProxyNDPAddress=2001:db8::1

Then do netplan generate && netplan apply, I see:

# ip -6 neighbor show proxy
2001:db8::1 dev enp5s0 proxy

# sysctl -a | grep proxy_ndp
net.ipv6.conf.all.proxy_ndp = 0
net.ipv6.conf.default.proxy_ndp = 0
net.ipv6.conf.enp5s0.proxy_ndp = 1
net.ipv6.conf.lo.proxy_ndp = 0

(and there’s no need to edit sysctl.conf either)

1 Like

Okay, netplan by itself does not have a renderer and uses either NetworkManager or systemd-networkd.
What’s the preferred way to bypass netplan and therefore use directly either of the two renderers?

That’s what I meant by “backend”. You’re right that “renderer” is technically the correct name.

I don’t understand the question.

I already showed how to “bypass netplan”, or perhaps more accurately “augment netplan”, by passing just the extra parameters to systemd-networkd - the ones which netplan doesn’t know about - whilst leaving netplan doing the rest of the configuration of systemd-network (*).

That to me seems like the best, simplest, and safest option. If you have a cloud-hosted VM which was configured with netplan, and you decided to throw that away and replace it with direct, low-level configuration of systemd-networkd, then that’s a risky change to make, for no benefit that I can see.


(*) Under the hood, it does this by creating temp files under /run/systemd/network/

# ls /run/systemd/network/
10-netplan-br0.netdev   10-netplan-enp1s0.link     10-netplan-enp2s0.network
10-netplan-br0.network  10-netplan-enp1s0.network

Files you create under /etc/systemd/network/ are augmenting these.

1 Like

Understood.

You will do all of this on a machine you are not running in production yet and later preconfigure your image or automate it in another way, so the risky part makes no sense to me.

Netplan is not that high-level that it is worth it, you are replacing a frontend for the thing itself. The advantage that I could take that YAML and stuff it into a laptop (info from other sources) also is a bit far fetched for me.

Nonetheless, I will take your suggestion and offer it in the tutorial above as another option to make it useful for more people.