From: Kuniyuki Iwashima <kuniyu@google.com>
To: "David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>,
Paolo Abeni <pabeni@redhat.com>,
Andrew Lunn <andrew+netdev@lunn.ch>
Cc: Simon Horman <horms@kernel.org>,
Kuniyuki Iwashima <kuniyu@google.com>,
Kuniyuki Iwashima <kuni1840@gmail.com>,
netdev@vger.kernel.org
Subject: [PATCH v1 net-next 13/14] ipvlan: Protect ipvl_port.ipvlans with mutex.
Date: Wed, 1 Jul 2026 21:41:51 +0000 [thread overview]
Message-ID: <20260701214334.266991-14-kuniyu@google.com> (raw)
In-Reply-To: <20260701214334.266991-1-kuniyu@google.com>
struct ipvl_port is shared between a lower device and its upper
ipvlan devices.
All upper devices are linked to ipvl_port.ipvlans.
Once RTNL is removed, the list can be modified concurrently from
different netns due to device removal.
Let's protect it with a per-port mutex.
NETDEV_PRECHANGEUPPER and NETDEV_CHANGEUPPER are explicitly
skipped to avoid deadlock for netdev_upper_dev_unlink() called
from NETDEV_UNREGISTER.
Note that __ipvtap_dellink() and struct ipvtap_dev is moved to
ipvlan.c/h for CONFIG_IPVLAN=y but CONFIG_IPVTAP=m.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
drivers/net/ipvlan/ipvlan.h | 14 ++++++++-
drivers/net/ipvlan/ipvlan_main.c | 54 +++++++++++++++++++++++++++++---
drivers/net/ipvlan/ipvtap.c | 15 +++------
3 files changed, 67 insertions(+), 16 deletions(-)
diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h
index 78f9107fa752..a0736f5c89f6 100644
--- a/drivers/net/ipvlan/ipvlan.h
+++ b/drivers/net/ipvlan/ipvlan.h
@@ -16,6 +16,9 @@
#include <linux/if_arp.h>
#include <linux/if_link.h>
#include <linux/if_vlan.h>
+#if IS_ENABLED(CONFIG_IPVTAP)
+#include <linux/if_tap.h>
+#endif
#include <linux/ip.h>
#include <linux/inetdevice.h>
#include <linux/netfilter.h>
@@ -91,6 +94,7 @@ struct ipvl_port {
struct hlist_head hlhead[IPVLAN_HASH_SIZE];
spinlock_t addrs_lock; /* guards hash-table and addrs */
struct list_head ipvlans;
+ struct mutex pnodes_lock;
u16 mode;
u16 flags;
u16 dev_id_start;
@@ -168,7 +172,6 @@ void ipvlan_count_rx(const struct ipvl_dev *ipvlan,
unsigned int len, bool success, bool mcast);
int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
struct netlink_ext_ack *extack);
-void ipvlan_link_delete(struct net_device *dev, struct list_head *head);
void ipvlan_link_setup(struct net_device *dev);
int ipvlan_link_register(struct rtnl_link_ops *ops);
#ifdef CONFIG_IPVLAN_L3S
@@ -207,4 +210,13 @@ static inline bool netif_is_ipvlan_port(const struct net_device *dev)
return rcu_access_pointer(dev->rx_handler) == ipvlan_handle_frame;
}
+#if IS_ENABLED(CONFIG_IPVTAP)
+struct ipvtap_dev {
+ struct ipvl_dev vlan;
+ struct tap_dev tap;
+};
+
+void __ipvtap_dellink(struct net_device *dev, struct list_head *head);
+#endif
+
#endif /* __IPVLAN_H */
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c
index 7adad781e9b5..41024fe27b78 100644
--- a/drivers/net/ipvlan/ipvlan_main.c
+++ b/drivers/net/ipvlan/ipvlan_main.c
@@ -16,6 +16,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
ASSERT_RTNL();
if (port->mode != nval) {
+ mutex_lock(&port->pnodes_lock);
+
list_for_each_entry(ipvlan, &port->ipvlans, pnode) {
flags = ipvlan->dev->flags;
if (nval == IPVLAN_MODE_L3 || nval == IPVLAN_MODE_L3S) {
@@ -40,6 +42,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
ipvlan_l3s_unregister(port);
}
port->mode = nval;
+
+ mutex_unlock(&port->pnodes_lock);
}
return 0;
@@ -56,6 +60,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
NULL);
}
+ mutex_unlock(&port->pnodes_lock);
+
return err;
}
@@ -76,6 +82,7 @@ static int ipvlan_port_create(struct net_device *dev)
INIT_HLIST_HEAD(&port->hlhead[idx]);
spin_lock_init(&port->addrs_lock);
+ mutex_init(&port->pnodes_lock);
skb_queue_head_init(&port->backlog);
INIT_WORK(&port->wq, ipvlan_process_multicast);
ida_init(&port->ida);
@@ -676,7 +683,10 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
if (err)
goto unlink_netdev;
+ mutex_lock(&port->pnodes_lock);
list_add_tail_rcu(&ipvlan->pnode, &port->ipvlans);
+ mutex_unlock(&port->pnodes_lock);
+
netif_stacked_transfer_operstate(phy_dev, dev);
return 0;
@@ -690,7 +700,7 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
}
EXPORT_SYMBOL_GPL(ipvlan_link_new);
-void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
+static void __ipvlan_link_delete(struct net_device *dev, struct list_head *head)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
struct ipvl_addr *addr, *next;
@@ -708,7 +718,27 @@ void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
unregister_netdevice_queue(dev, head);
netdev_upper_dev_unlink(ipvlan->phy_dev, dev);
}
-EXPORT_SYMBOL_GPL(ipvlan_link_delete);
+
+static void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
+{
+ struct ipvl_dev *ipvlan = netdev_priv(dev);
+
+ mutex_lock(&ipvlan->port->pnodes_lock);
+ __ipvlan_link_delete(dev, head);
+ mutex_unlock(&ipvlan->port->pnodes_lock);
+}
+
+#if IS_ENABLED(CONFIG_IPVTAP)
+void __ipvtap_dellink(struct net_device *dev, struct list_head *head)
+{
+ struct ipvtap_dev *vlantap = netdev_priv(dev);
+
+ netdev_rx_handler_unregister(dev);
+ tap_del_queues(&vlantap->tap);
+ __ipvlan_link_delete(dev, head);
+}
+EXPORT_SYMBOL_GPL(__ipvtap_dellink);
+#endif
void ipvlan_link_setup(struct net_device *dev)
{
@@ -770,10 +800,16 @@ static int ipvlan_device_event(struct notifier_block *unused,
struct ipvl_port *port;
LIST_HEAD(lst_kill);
+ if (event == NETDEV_PRECHANGEUPPER ||
+ event == NETDEV_CHANGEUPPER)
+ return ret;
+
port = ipvlan_port_get(dev);
if (!port)
return ret;
+ mutex_lock(&port->pnodes_lock);
+
switch (event) {
case NETDEV_UP:
case NETDEV_DOWN:
@@ -800,9 +836,15 @@ static int ipvlan_device_event(struct notifier_block *unused,
if (dev->reg_state != NETREG_UNREGISTERING)
break;
- list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode)
- ipvlan->dev->rtnl_link_ops->dellink(ipvlan->dev,
- &lst_kill);
+ list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode) {
+#if IS_ENABLED(CONFIG_IPVTAP)
+ if (ipvlan->dev->rtnl_link_ops != &ipvlan_link_ops)
+ __ipvtap_dellink(ipvlan->dev, &lst_kill);
+ else
+#endif
+ __ipvlan_link_delete(ipvlan->dev, &lst_kill);
+ }
+
unregister_netdevice_many(&lst_kill);
break;
@@ -850,6 +892,8 @@ static int ipvlan_device_event(struct notifier_block *unused,
call_netdevice_notifiers(event, ipvlan->dev);
}
+ mutex_unlock(&port->pnodes_lock);
+
ipvlan_port_put(port);
return ret;
diff --git a/drivers/net/ipvlan/ipvtap.c b/drivers/net/ipvlan/ipvtap.c
index 2d6bbddd1edd..17b0dd7cf73b 100644
--- a/drivers/net/ipvlan/ipvtap.c
+++ b/drivers/net/ipvlan/ipvtap.c
@@ -2,7 +2,6 @@
#include <linux/etherdevice.h>
#include "ipvlan.h"
#include <linux/if_vlan.h>
-#include <linux/if_tap.h>
#include <linux/interrupt.h>
#include <linux/nsproxy.h>
#include <linux/compat.h>
@@ -43,11 +42,6 @@ static struct class ipvtap_class = {
.namespace = ipvtap_net_namespace,
};
-struct ipvtap_dev {
- struct ipvl_dev vlan;
- struct tap_dev tap;
-};
-
static void ipvtap_count_tx_dropped(struct tap_dev *tap)
{
struct ipvtap_dev *vlantap = container_of(tap, struct ipvtap_dev, tap);
@@ -112,11 +106,12 @@ static int ipvtap_newlink(struct net_device *dev,
static void ipvtap_dellink(struct net_device *dev,
struct list_head *head)
{
- struct ipvtap_dev *vlan = netdev_priv(dev);
+ struct ipvtap_dev *vlantap = netdev_priv(dev);
+ struct ipvl_port *port = vlantap->vlan.port;
- netdev_rx_handler_unregister(dev);
- tap_del_queues(&vlan->tap);
- ipvlan_link_delete(dev, head);
+ mutex_lock(&port->pnodes_lock);
+ __ipvtap_dellink(dev, head);
+ mutex_unlock(&port->pnodes_lock);
}
static void ipvtap_setup(struct net_device *dev)
--
2.55.0.rc0.799.gd6f94ed593-goog
next prev parent reply other threads:[~2026-07-01 21:43 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-01 21:41 [PATCH v1 net-next 00/14] net: Support per-netns device unregistration Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 01/14] rtnetlink: Lock sock_net(skb->sk) in rtnl_newlink() Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 02/14] rtnetlink: Call unregister_netdevice_many() only once in rtnl_link_unregister() Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 03/14] rtnetlink: Add per-netns rtnl_work Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 04/14] net: Wrap default_device_exit_net() with __rtnl_net_lock() Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 05/14] net: Hold __rtnl_net_lock() in netdev_wait_allrefs_any() Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 06/14] net: Add per-netns netdev unregistration infra Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 07/14] net: Call unregister_netdevice_many() per netns Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 08/14] veth: Support per-netns device unregistration Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 09/14] bareudp: Protect bareudp_list with mutex Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 10/14] bareudp: Support per-netns netdev unregistration Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 11/14] ipvlan: Convert ipvl_port.count to refcount_t Kuniyuki Iwashima
2026-07-01 21:41 ` [PATCH v1 net-next 12/14] ipvlan: Synchronise ipvlan_init() and ipvlan_uninit() for the same lower dev Kuniyuki Iwashima
2026-07-01 21:41 ` Kuniyuki Iwashima [this message]
2026-07-01 21:41 ` [PATCH v1 net-next 14/14] ipvlan: Support per-netns netdev unregistration Kuniyuki Iwashima
2026-07-02 7:45 ` [syzbot ci] Re: net: Support per-netns device unregistration syzbot ci
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260701214334.266991-14-kuniyu@google.com \
--to=kuniyu@google.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=kuni1840@gmail.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox