ip6_gre: Clear ign->fb_tunnel_dev in ip6gre_exit_rtnl_net().

ip6gre_dellink() ignores itn->fb_tunnel_dev, so the per-netns
fallback tunnel device cannot be removed by userspace.

This also makes default_device_exit_batch() impossible to remove
the device since it calls ->dellink().

So, ip6gre_exit_rtnl_net() has to iterate devices in the dying
netns and call unregister_netdevice_queue() directly, and
ip6gre_tunnel_unlink() must be called at ->ndo_uninit().

For per-netns device unregistration, we want to check if the
device is being removed in ->dellink(), for which we must call
ip6gre_tunnel_unlink() before unregister_netdevice_queue().

As a prep, let's set itn->fb_tunnel_dev to NULL and remove
remove for_each_netdev_safe() in ip6gre_exit_rtnl_net().

Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
Link: https://patch.msgid.link/20260916230353.367014-4-kuniyu@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Kuniyuki Iwashima 2026-09-16 23:02:22 +00:00 committed by Jakub Kicinski
parent 3b2813cc6b
commit 28328796e0

View File

@ -374,8 +374,6 @@ static void ip6gre_tunnel_uninit(struct net_device *dev)
ip6gre_tunnel_unlink_md(ign, t);
ip6gre_tunnel_unlink(ign, t);
if (ign->fb_tunnel_dev == dev)
WRITE_ONCE(ign->fb_tunnel_dev, NULL);
dst_cache_reset(&t->dst_cache);
netdev_put(dev, &t->dev_tracker);
}
@ -1552,29 +1550,20 @@ static void __net_exit ip6gre_exit_rtnl_net(struct net *net,
struct list_head *dev_kill_list)
{
struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);
struct net_device *dev, *aux;
int prio;
for_each_netdev_safe(net, dev, aux)
if (dev->rtnl_link_ops == &ip6gre_link_ops ||
dev->rtnl_link_ops == &ip6gre_tap_ops ||
dev->rtnl_link_ops == &ip6erspan_tap_ops)
unregister_netdevice_queue(dev, dev_kill_list);
WRITE_ONCE(ign->fb_tunnel_dev, NULL);
for (prio = 0; prio < 4; prio++) {
int h;
for (h = 0; h < IP6_GRE_HASH_SIZE; h++) {
struct hlist_head *head = &ign->tunnels[prio][h];
struct hlist_node *tmp;
struct ip6_tnl *t;
hlist_for_each_entry(t, head, hash_node) {
/* If dev is in the same netns, it has already
* been added to the list by the previous loop.
*/
if (!net_eq(dev_net(t->dev), net))
unregister_netdevice_queue(t->dev, dev_kill_list);
}
hlist_for_each_entry_safe(t, tmp, head, hash_node)
unregister_netdevice_queue(t->dev, dev_kill_list);
}
}
}