At the Wikimedia Foundation they configure basically all servers with IPv4/IPv6 dual stack, at least in the control plane interface (those used for SSH management, etc). IPv6 is not supported yet on the Cloud Services dataplane (openstack), but it will in the “near” future.

An elegant solution for this IPv4/IPv6 dual stack configuration in the control plane is to embed the IPv4 address into the IPv6 address, something like this:

IPv6: 2620:0:861:1:208:80:154:23

The gateways send prefix advertisements and by default IPv6 autoconfiguration uses such routers prefixes plus the interface MAC address to build the final address. This behavior can be somewhat changed, you can explicitly set some data instead of the MAC address for the kernel to use when building the final IPv6 address.

The trick is to use the ip token command, and further context and reasoning for this can be seen in the puppet manifest that the SRE team uses to configure this:

The token command explicitly configures the lower 64 bits that will be used with any autoconf
address, as opposed to one derived from the macaddr. This aligns the autoconf-assigned address with
the fixed one set above, and can do so as a pre-up command to avoid ever having another address
even temporarily, when this is all set up before boot.

For example:

user@debian:~$ sudo ip token list
token ::208:80:154:23 dev eno1
token :: dev eno2
token :: dev eno3
token :: dev eno4
user@debian:~$ sudo ip token set dev eno2 ::beef
user@debian:~$ ip token get dev eno2
token ::beef dev eno2
user@debian:~$ ip token list
token ::208:80:154:23 dev eno1
token ::beef dev eno2
token :: dev eno3
token :: dev eno4

I was installing a new server the other day and the networking service (ifupdown) was failing to bring the interface up. The issue was the ip token command failing:

root@cloudgw2002-dev:~# ip token set ::10:192:20:18 dev eno1
RTNETLINK answers: Invalid argument

RTNETLINK error reporting is awful. What could be wrong about that command?

Well, I had to dig into the kernel source code to try figuring out where was that EINVAL being generated. The routine can be seen in the function inet6_set_iftoken() (source):

static int inet6_set_iftoken(struct inet6_dev *idev, struct in6_addr *token)
	struct inet6_ifaddr *ifp;
	struct net_device *dev = idev->dev;
	bool clear_token, update_rs = false;
	struct in6_addr ll_addr;


	if (!token)
		return -EINVAL;
	if (dev->flags & (IFF_LOOPBACK | IFF_NOARP))
		return -EINVAL;
	if (!ipv6_accept_ra(idev))
		return -EINVAL;
	if (idev->cnf.rtr_solicits == 0)
		return -EINVAL;

I couldn’t identify the reason at first glance, apparently I pass every check in there:

  • the token is being set, obviously
  • the interface is not loopback
  • the interface has the sysctl net.ipv6.conf.eno1.accept_ra = 1 (enabled)
  • the interface has the sysctl net.ipv6.conf.eno1.router_solicitations = -1 (disabled)

Something was wrong here. Upon deeper checks, I finally read the ipv6_accept_ra() function (source):

static inline bool ipv6_accept_ra(struct inet6_dev *idev)
	/* If forwarding is enabled, RA are not accepted unless the special
	 * hybrid mode (accept_ra=2) is enabled.
	return idev->cnf.forwarding ? idev->cnf.accept_ra == 2 :

That was it! Forwarding was enabled on the interface, and it shouldn’t. I fixed my configuration with a simple patch for the sysctl configuration.

Original issue on the Wikimedia Foundation ticketing system: Phabricator T277287