* [PATCH wireguard] wireguard: device: allow userspace to enable IPv6 link-local addresses
@ 2026-10-22 11:00 Roman Khromenok
2026-11-10 16:47 ` Jason A. Donenfeld
0 siblings, 1 reply; 3+ messages in thread
From: Roman Khromenok @ 2026-10-22 11:00 UTC (permalink / raw)
To: Jason A . Donenfeld
Cc: wireguard, netdev, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Andrew Lunn, Shuah Khan, linux-kselftest
WireGuard interfaces do not get an IPv6 link-local address by default,
which wg_open() achieves by setting addr_gen_mode to
IN6_ADDR_GEN_MODE_NONE on every ndo_open. A side effect is that any mode
chosen by userspace, via sysctl or IFLA_INET6_ADDR_GEN_MODE, is silently
reset on the next down/up cycle. Users running routing protocols over
WireGuard, such as Babel or OSPFv3, need fe80::/64 addresses and today
have to set the mode again after every ifup, which network managers do
not expect to have to do. For example, systemd-networkd cannot honor
LinkLocalAddressing=ipv6 on WireGuard interfaces because of this.
Instead, set the default once, when the inet6_dev is created. That
happens on NETDEV_REGISTER, both when the device is created and every
time it is moved to another network namespace, since addrconf tears down
the inet6_dev on NETDEV_UNREGISTER. So register a netdevice notifier
with a priority just below addrconf's, so that it runs after the
inet6_dev exists. The default behavior is unchanged, but userspace can
now opt in and the setting persists across down/up.
An alternative approach using IFF_NO_ADDRCONF was proposed in [1], but
it removes the ability to have link-local addresses altogether, and it
was reverted because it broke the selftests.
Add a selftest checking that link-local addresses are off by default,
that they can be enabled and survive down/up, and that the default is
restored after moving the device to another namespace.
Link: https://lists.zx2c4.com/pipermail/wireguard/2026-February/009496.html [1]
Link: https://github.com/systemd/systemd/issues/44005
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
Notes:
One pre-existing corner case is not addressed: if the MTU of a running
WireGuard interface drops below IPV6_MIN_MTU and is raised again, addrconf
destroys the inet6_dev and allocates a new one on NETDEV_CHANGEMTU, with the
default addr_gen_mode, and generates a link-local address in the same
notifier call, before any driver notifier gets a chance to run. Today that
address goes away on the next down/up, because wg_open() resets the mode;
with this patch it stays until userspace changes the mode. I don't see a
clean way to handle this from the driver, so I left it alone, but I'm happy
to look into it if you think it matters.
Tested with the wireguard selftests in QEMU: the new checks pass with this
patch, and the down/up check fails without it.
drivers/net/wireguard/device.c | 37 +++++++++++++++++++---
tools/testing/selftests/wireguard/netns.sh | 14 ++++++++
2 files changed, 47 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireguard/device.c b/drivers/net/wireguard/device.c
index 67b07ee2d660..50f108a82a53 100644
--- a/drivers/net/wireguard/device.c
+++ b/drivers/net/wireguard/device.c
@@ -31,7 +31,6 @@ static LIST_HEAD(device_list);
static int wg_open(struct net_device *dev)
{
struct in_device *dev_v4 = __in_dev_get_rtnl(dev);
- struct inet6_dev *dev_v6 = __in6_dev_get(dev);
struct wg_device *wg = netdev_priv(dev);
struct wg_peer *peer;
int ret;
@@ -44,8 +43,6 @@ static int wg_open(struct net_device *dev)
IN_DEV_CONF_SET(dev_v4, SEND_REDIRECTS, false);
IPV4_DEVCONF_ALL(dev_net(dev), SEND_REDIRECTS) = false;
}
- if (dev_v6)
- dev_v6->cnf.addr_gen_mode = IN6_ADDR_GEN_MODE_NONE;
mutex_lock(&wg->device_update_lock);
ret = wg_socket_init(wg, wg->incoming_port);
@@ -434,6 +431,31 @@ static struct pernet_operations pernet_ops __read_mostly = {
.exit_rtnl = wg_netns_exit_rtnl
};
+static int wg_netdevice_notification(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(data);
+ struct inet6_dev *dev_v6;
+
+ if (action != NETDEV_REGISTER || dev->netdev_ops != &netdev_ops)
+ return NOTIFY_DONE;
+
+ /* Disable IPv6 link-local address generation by default, but only
+ * when the inet6_dev is created, which happens on device creation and
+ * on every netns change, so that userspace may still opt in.
+ */
+ dev_v6 = __in6_dev_get(dev);
+ if (dev_v6)
+ WRITE_ONCE(dev_v6->cnf.addr_gen_mode, IN6_ADDR_GEN_MODE_NONE);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block netdevice_notifier = {
+ .notifier_call = wg_netdevice_notification,
+ /* Run after addrconf has created the inet6_dev. */
+ .priority = ADDRCONF_NOTIFY_PRIORITY - 1
+};
+
int __init wg_device_init(void)
{
int ret;
@@ -450,12 +472,18 @@ int __init wg_device_init(void)
if (ret)
goto error_vm;
- ret = rtnl_link_register(&link_ops);
+ ret = register_netdevice_notifier(&netdevice_notifier);
if (ret)
goto error_pernet;
+ ret = rtnl_link_register(&link_ops);
+ if (ret)
+ goto error_netdevice;
+
return 0;
+error_netdevice:
+ unregister_netdevice_notifier(&netdevice_notifier);
error_pernet:
unregister_pernet_device(&pernet_ops);
error_vm:
@@ -468,6 +496,7 @@ int __init wg_device_init(void)
void wg_device_uninit(void)
{
rtnl_link_unregister(&link_ops);
+ unregister_netdevice_notifier(&netdevice_notifier);
unregister_pernet_device(&pernet_ops);
unregister_random_vmfork_notifier(&vm_notifier);
unregister_pm_notifier(&pm_notifier);
diff --git a/tools/testing/selftests/wireguard/netns.sh b/tools/testing/selftests/wireguard/netns.sh
index a8f550aecb35..747a7e23b5fa 100755
--- a/tools/testing/selftests/wireguard/netns.sh
+++ b/tools/testing/selftests/wireguard/netns.sh
@@ -699,6 +699,20 @@ pp ip netns delete $netns2
pp ip netns add $netns1
pp ip netns add $netns2
+# Ensure that IPv6 link-local addresses are off by default, but can be enabled
+ip1 link add dev wg3 type wireguard
+ip1 link set up dev wg3
+[[ -z $(ip1 -6 addr show dev wg3 scope link) ]]
+n1 bash -c 'printf 3 > /proc/sys/net/ipv6/conf/wg3/addr_gen_mode'
+[[ $(ip1 -6 addr show dev wg3 scope link) == *fe80::* ]]
+ip1 link set down dev wg3
+ip1 link set up dev wg3
+[[ $(ip1 -6 addr show dev wg3 scope link) == *fe80::* ]]
+ip1 link set wg3 netns $netns2
+ip2 link set up dev wg3
+[[ -z $(ip2 -6 addr show dev wg3 scope link) ]]
+ip2 link del wg3
+
# Ensure there aren't circular reference loops
ip1 link add wg1 type wireguard
ip2 link add wg2 type wireguard
--
2.47.3
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH wireguard] wireguard: device: allow userspace to enable IPv6 link-local addresses
2026-10-22 11:00 [PATCH wireguard] wireguard: device: allow userspace to enable IPv6 link-local addresses Roman Khromenok
@ 2026-11-10 16:47 ` Jason A. Donenfeld
2026-11-10 18:19 ` Roman Khromenok
0 siblings, 1 reply; 3+ messages in thread
From: Jason A. Donenfeld @ 2026-11-10 16:47 UTC (permalink / raw)
To: Roman Khromenok
Cc: wireguard, netdev, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Andrew Lunn, Shuah Khan, linux-kselftest
On Thu, Oct 22, 2026 at 01:00:39PM +0200, Roman Khromenok wrote:
> WireGuard interfaces do not get an IPv6 link-local address by default,
> which wg_open() achieves by setting addr_gen_mode to
> IN6_ADDR_GEN_MODE_NONE on every ndo_open. A side effect is that any mode
> chosen by userspace, via sysctl or IFLA_INET6_ADDR_GEN_MODE, is silently
> reset on the next down/up cycle. Users running routing protocols over
> WireGuard, such as Babel or OSPFv3, need fe80::/64 addresses and today
> have to set the mode again after every ifup, which network managers do
> not expect to have to do. For example, systemd-networkd cannot honor
> LinkLocalAddressing=ipv6 on WireGuard interfaces because of this.
>
> Instead, set the default once, when the inet6_dev is created. That
> happens on NETDEV_REGISTER, both when the device is created and every
> time it is moved to another network namespace, since addrconf tears down
> the inet6_dev on NETDEV_UNREGISTER. So register a netdevice notifier
> with a priority just below addrconf's, so that it runs after the
> inet6_dev exists. The default behavior is unchanged, but userspace can
> now opt in and the setting persists across down/up.
>
> An alternative approach using IFF_NO_ADDRCONF was proposed in [1], but
> it removes the ability to have link-local addresses altogether, and it
> was reverted because it broke the selftests.
>
> Add a selftest checking that link-local addresses are off by default,
> that they can be enabled and survive down/up, and that the default is
> restored after moving the device to another namespace.
This is an old decision, but one I think I'm inclined to stick with. You
can't use WireGuard with LL generated addresses, because AllowedIPs
requires explicitly binding to public keys. So you wind up having to do
coordination between the chosen IP address and the distribution of keys.
The solution for Babel and the like is to add explicitly WireGuard
support and use the neat mesh qualities of Babel for key distribution
too. There has been some talk about implementing this for a long time.
That's the direction I'd like this to go in, not breaking the whole
allowedips model, which is what this moves toward. I hope that's
understandable.
Jason
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH wireguard] wireguard: device: allow userspace to enable IPv6 link-local addresses
2026-11-10 16:47 ` Jason A. Donenfeld
@ 2026-11-10 18:19 ` Roman Khromenok
0 siblings, 0 replies; 3+ messages in thread
From: Roman Khromenok @ 2026-11-10 18:19 UTC (permalink / raw)
To: Jason A . Donenfeld
Cc: wireguard, netdev, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Andrew Lunn, Shuah Khan, linux-kselftest
On Tue, Nov 10, 2026 at 04:47:35PM +0000, Jason A. Donenfeld wrote:
> This is an old decision, but one I think I'm inclined to stick with. You
> can't use WireGuard with LL generated addresses, because AllowedIPs
> requires explicitly binding to public keys. So you wind up having to do
> coordination between the chosen IP address and the distribution of keys.
Thanks for the explanation, understood. Just to note, the use case I had
in mind is one peer per interface with AllowedIPs ::/0, as commonly used
for Babel/BIRD meshes, where no address/key coordination is needed.
> That's the direction I'd like this to go in, not breaking the whole
> allowedips model, which is what this moves toward. I hope that's
> understandable.
It is. I see the direction you prefer, so I'll drop this patch.
Roman
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-10 18:22 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-22 11:00 [PATCH wireguard] wireguard: device: allow userspace to enable IPv6 link-local addresses Roman Khromenok
2026-11-10 16:47 ` Jason A. Donenfeld
2026-11-10 18:19 ` Roman Khromenok
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox