IPv6 tokenized interface identifier support

Linux supports a feature called "IPv6 tokenized interface identifier": https://man7.org/linux/man-pages/man8/ip-token.8.html This feature allows setting an IPv6 address of an interface by combining the network prefix gotten from upstream router, with a statically configured host suffix. This is helpful in the case your network prefix is a subject to change, but you want to keep your host suffixes static.

I'm getting my IPv6 prefix through Router Advertisement, no DHCPv6 in use. The prefix is non-delegated, which requires every device that needs an IPv6 address to announce its address with NDP to the upstream router. This also means, that they are free to decide the host portion of the address by themselves, but the announced address must conform to the prefix announced by the upstream router.

Is there support for this "IPv6 tokenized interface identifier" feature in OpenWRT? I can't certainly find anything that mentions it. On command line, the ip command looks like it would support the feature, because token is mentioned in the usage.

# ip help
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip [ -force ] -batch filename
where  OBJECT := { link | address | route | rule | neigh | tunnel | maddress |
                   mroute | mrule | monitor | netns | macsec | token | ila |
                   vrf | sr }

However, trying it out, it fails:

# ip token set '::1' dev eth0.2@eth0
Object "token" is unknown, try "ip help".

Also, the documentation of UCI and Luci don't mention this feature anywhere. I'm also unable to find mentions on this forum, but I realise that I might be using just the wrong keywords for the search.

Any clues?

1 Like

I think ip6ifaceid does what you're talking about.


Also keep in mind that there are three implementations of /sbin/ip in Openwrt, busybox ip, and two different variants packaged from the iproute2 source (ip-tiny and ip-full).


@lantis1008 Unfortunately ip6ifaceid is only available in wan6 interface if I choose "Static address" as the protocol – but choosing it, and letting the fields empty, will make the interface lose the global IPv6 address. It works if I specify everything by hand: the upstream (ISP) router gateway address, my own global prefix etc. But these are not static (they seldom change, though), and the information is distributed by RA and NDP, so that's what I want to use. Setting the protocol to DHCPv6 makes it pick up the settings automatically, although DHCPv6 doesn't actually seem to be in use.

@slh Thanks for the pointer, I'll test out what I can do with the iproute2 binaries.

So, I managed to do it! I left the protocol of the wan6 interface to "static address" with empty settings. After that (an excerpt from my installation script):

# IPv6 tokenized interface identifier support
cat << EOF > /etc/init.d/ipv6_ra_tokenized
#!/bin/sh /etc/rc.common
start() {
until test -e /sys/class/net/eth0.2 ; do sleep 1; done
echo "Setting up RA+NDP-based IPv6 with tokenized host address."
sysctl -w net.ipv6.conf.eth0.2.accept_ra=2
ip token set '::1' dev eth0.2
chmod 0755 /etc/init.d/ipv6_ra_tokenized
/etc/init.d/ipv6_ra_tokenized enable

The point is: setting net.ipv6.conf.eth0.2.accept_ra to 2 makes it accept RA even if Linux kernel normally wouldn't in presence of the net.ipv6.conf.eth0.2.forwarding = 1 kernel parameter (that is needed for basic routing functionality, I guess?). It only accepts ip token set '::1' dev eth0.2 if accept_ra is enabled and use_tempaddr is disabled. (This was not a problem on OpenWRT, but on some other distros it was.) Setting it as an init script makes it always set it up on startup.

What's a bit unclear for me, is how are the "standard OpenWRT" UCI/Luci network settings implemented; how much do they just implement functionality by setting kernel parameters, and how much do they delegate to dnsmasq and/or odhcpd? I'd like to understand the whole system a bit better so that I don't accidentally mess up something else. Oh well, I guess the main thing is that it seems to work.

Create a static alias:

@vgaetera I read the page you linked to, but I fail to see how creating an alias would help the situation. If I create an alias of wan6, and set its protocol to static, I would still get all the downsides of static for the aliased interface, i.e. having to define the address, gateway etc. manually, don't I?

In other news, here's an example of the device eth0.2@eth0 that backs up wan6 having successfully obtained an IPv6 address in the way I want it.

# ip -6 a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::1aa6:f7ff:fe8d:c0d4/64 scope link
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::1aa6:f7ff:fe8d:c0d3/64 scope link
       valid_lft forever preferred_lft forever
7: br-lan: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::1aa6:f7ff:fe8d:c0d3/64 scope link
       valid_lft forever preferred_lft forever
9: eth0.2@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 2404:7a80:9621:7100::1/64 scope global dynamic mngtmpaddr
       valid_lft 2591885sec preferred_lft 604685sec
    inet6 fe80::1aa6:f7ff:fe8d:c0d4/64 scope link
       valid_lft forever preferred_lft forever
10: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 state UNKNOWN qlen 1000
    inet6 2404:7a80:9621:7100::9999:1/112 scope global
       valid_lft forever preferred_lft forever
12: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::1aa6:f7ff:fe8d:c0d2/64 scope link
       valid_lft forever preferred_lft forever
13: wlan1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::1aa6:f7ff:fe8d:c0d3/64 scope link
       valid_lft forever preferred_lft forever

It also actually works:

# curl -6 http://ifconfig.co

But it seems that all the associated software and tooling of OpenWrt are unaware of this. For example:

# ubus call network.interface dump | jsonfilter -e '@.interface[@.interface="wan6"]'
{ "interface": "wan6", "up": true, "pending": false, "available": true, "autostart": true, "dynamic": false, "uptime": 4716, "l3_device": "eth0.2", "proto": "static", "device": "eth0.2", "metric": 0, "dns_metric": 0, "delegation": true, "ipv4-address": [ ], "ipv6-address": [ ], "ipv6-prefix": [ ], "ipv6-prefix-assignment": [ ], "route": [ ], "dns-server": [ ], "dns-search": [ ], "neighbors": [ ], "inactive": { "ipv4-address": [ ], "ipv6-address": [ ], "route": [ ], "dns-server": [ ], "dns-search": [ ], "neighbors": [ ] }, "data": { } }

Or more cleanly

# ubus call network.interface dump | jsonfilter -e '@.interface[@.interface="wan6"]["ipv6-address"]'
[ ]

As you can see, ubus remains unaware that there is a working IPv6 address associated with the interface. This makes some Luci services not to work, as they use OpenWrt tools as their source of truth.

Any advice how to improve the situation?

1 Like

It looks like proto=dhcpv6 should support the option ifaceid:

Actually, you can try to specify both ifaceid and ip6ifaceid and check whether it works or not.


@vgaetera Wonderful, thanks! That does the job, so I don't have to rely on my non-idiomatic hacks. I had totally missed that setting. (Reflecting a bit, why I might have missed it: although I tend to edit the config files by hand, I often look what Luci has to say about the config, and here Luci doesn't actually expose that setting. Additionally, I thought that because the ISP router actually doesn't send DHCPv6, the actual DHCPv6 protocol settings would be irrelevant – but clearly they weren't in this case!)

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.