All of lore.kernel.org
 help / color / mirror / Atom feed
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


  parent reply	other threads:[~2026-07-01 21:43 UTC|newest]

Thread overview: 17+ 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
2026-07-02 21:59   ` Kuniyuki Iwashima

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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.