From nobody Mon Feb 9 01:01:03 2026 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0597E2E7F27; Mon, 15 Dec 2025 16:56:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.176.79.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1765817802; cv=none; b=Gb3pHQ8Pg7+V7szWUHwZODcnCqQIJsVywwpoMrMBQl8DL8FW3B+oWtWY8J8UguwWSQZdupW5UpiX1i6xCB+UkneMA/pc2pCnIAm1eASj7PWO4iPmH3uiFbLiA8CqqYeXkEdoGrHrtqYnzG2RgA2mKP6Y+4bnCXLPr3VDfPMoqic= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1765817802; c=relaxed/simple; bh=+uja6Ajxs+eNWyLshdphoXTFDrhj7cDFAql14wuwdio=; h=From:To:CC:Subject:Date:Message-ID:MIME-Version:Content-Type; b=f58mhY5kOtC/kOBgVdQOr5FHBiHcqXXpxRs9F7lfgGF5hgAfDGF06zuBfj5N91F9ZcydoJtDYlbr28nXY6vKo940KRvgp9ny95O1JIQeNTuFpCW/AO3Uiz8wSfJdTjxXI39FTVGe4mjL4ukoald1/ksgNRYwC+Ae3/ViIW7pHhw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; arc=none smtp.client-ip=185.176.79.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Received: from mail.maildlp.com (unknown [172.18.224.150]) by frasgout.his.huawei.com (SkyGuard) with ESMTPS id 4dVR671ChBzHnH5c; Tue, 16 Dec 2025 00:56:15 +0800 (CST) Received: from mscpeml500004.china.huawei.com (unknown [7.188.26.250]) by mail.maildlp.com (Postfix) with ESMTPS id BC97240539; Tue, 16 Dec 2025 00:56:36 +0800 (CST) Received: from huawei-ThinkCentre-M920t.huawei.com (10.123.122.223) by mscpeml500004.china.huawei.com (7.188.26.250) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.11; Mon, 15 Dec 2025 19:56:36 +0300 From: Dmitry Skorodumov To: , Dmitry Skorodumov , Xiao Liang , Jakub Kicinski , Kuniyuki Iwashima , Guillaume Nault , Julian Vetter , Eric Dumazet , Stanislav Fomichev , Etienne Champetier , "David S. Miller" , Paolo Abeni , CC: Andrew Lunn Subject: [PATCH net] ipvlan: Make the addrs_lock be per port Date: Mon, 15 Dec 2025 19:54:46 +0300 Message-ID: <20251215165457.752634-1-skorodumov.dmitry@huawei.com> X-Mailer: git-send-email 2.25.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: mscpeml500003.china.huawei.com (7.188.49.51) To mscpeml500004.china.huawei.com (7.188.26.250) Content-Type: text/plain; charset="utf-8" Make the addrs_lock be per port, not per ipvlan dev. Initial code seems to be written in the assumption, that any address change must occur under RTNL. But it is not so for the case of IPv6. So 1) Introduce per-port addrs_lock. 2) It was needed to fix places where it was forgotten to take lock (ipvlan_open/ipvlan_close) 3) Fix places, where list_for_each_entry_rcu() was used to iterate the list while holding a lock This appears to be a very minor problem though. Since it's highly unlikely that ipvlan_add_addr() will be called on 2 CPU simultaneously. But nevertheless, this could cause: 1) False-negative of ipvlan_addr_busy(): one interface iterated through all port->ipvlans + ipvlan->addrs under some ipvlan spinlock, and another added IP under its own lock. Though this is only possible for IPv6, since looks like only ipvlan_addr6_event() can be called without rtnl_lock. 2) Race since ipvlan_ht_addr_add(port) is called under different ipvlan->addrs_lock locks This should not affect performance, since add/remove IP is a rare situation and spinlock is not taken on fast paths. Fixes: 8230819494b3 ("ipvlan: use per device spinlock to protect addrs list= updates") Signed-off-by: Dmitry Skorodumov CC: Paolo Abeni Signed-off-by: Dmitry Skorodumov --- drivers/net/ipvlan/ipvlan.h | 2 +- drivers/net/ipvlan/ipvlan_core.c | 12 ++++---- drivers/net/ipvlan/ipvlan_main.c | 52 ++++++++++++++++++-------------- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h index 50de3ee204db..80f84fc87008 100644 --- a/drivers/net/ipvlan/ipvlan.h +++ b/drivers/net/ipvlan/ipvlan.h @@ -69,7 +69,6 @@ struct ipvl_dev { DECLARE_BITMAP(mac_filters, IPVLAN_MAC_FILTER_SIZE); netdev_features_t sfeatures; u32 msg_enable; - spinlock_t addrs_lock; }; =20 struct ipvl_addr { @@ -90,6 +89,7 @@ struct ipvl_port { struct net_device *dev; possible_net_t pnet; struct hlist_head hlhead[IPVLAN_HASH_SIZE]; + spinlock_t addrs_lock; /* guards hash-table and addrs */ struct list_head ipvlans; u16 mode; u16 flags; diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_c= ore.c index 2efa3ba148aa..22cb5ee7a231 100644 --- a/drivers/net/ipvlan/ipvlan_core.c +++ b/drivers/net/ipvlan/ipvlan_core.c @@ -109,14 +109,14 @@ struct ipvl_addr *ipvlan_find_addr(const struct ipvl_= dev *ipvlan, { struct ipvl_addr *addr, *ret =3D NULL; =20 - rcu_read_lock(); - list_for_each_entry_rcu(addr, &ipvlan->addrs, anode) { + assert_spin_locked(&ipvlan->port->addrs_lock); + + list_for_each_entry(addr, &ipvlan->addrs, anode) { if (addr_equal(is_v6, addr, iaddr)) { ret =3D addr; break; } } - rcu_read_unlock(); return ret; } =20 @@ -125,14 +125,14 @@ bool ipvlan_addr_busy(struct ipvl_port *port, void *i= addr, bool is_v6) struct ipvl_dev *ipvlan; bool ret =3D false; =20 - rcu_read_lock(); - list_for_each_entry_rcu(ipvlan, &port->ipvlans, pnode) { + assert_spin_locked(&port->addrs_lock); + + list_for_each_entry(ipvlan, &port->ipvlans, pnode) { if (ipvlan_find_addr(ipvlan, iaddr, is_v6)) { ret =3D true; break; } } - rcu_read_unlock(); return ret; } =20 diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_m= ain.c index 660f3db11766..b0b4f747f162 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -75,6 +75,7 @@ static int ipvlan_port_create(struct net_device *dev) for (idx =3D 0; idx < IPVLAN_HASH_SIZE; idx++) INIT_HLIST_HEAD(&port->hlhead[idx]); =20 + spin_lock_init(&port->addrs_lock); skb_queue_head_init(&port->backlog); INIT_WORK(&port->wq, ipvlan_process_multicast); ida_init(&port->ida); @@ -181,18 +182,18 @@ static void ipvlan_uninit(struct net_device *dev) static int ipvlan_open(struct net_device *dev) { struct ipvl_dev *ipvlan =3D netdev_priv(dev); + struct ipvl_port *port =3D ipvlan->port; struct ipvl_addr *addr; =20 - if (ipvlan->port->mode =3D=3D IPVLAN_MODE_L3 || - ipvlan->port->mode =3D=3D IPVLAN_MODE_L3S) + if (port->mode =3D=3D IPVLAN_MODE_L3 || port->mode =3D=3D IPVLAN_MODE_L3S) dev->flags |=3D IFF_NOARP; else dev->flags &=3D ~IFF_NOARP; =20 - rcu_read_lock(); - list_for_each_entry_rcu(addr, &ipvlan->addrs, anode) + spin_lock_bh(&port->addrs_lock); + list_for_each_entry(addr, &ipvlan->addrs, anode) ipvlan_ht_addr_add(ipvlan, addr); - rcu_read_unlock(); + spin_unlock_bh(&port->addrs_lock); =20 return 0; } @@ -206,10 +207,10 @@ static int ipvlan_stop(struct net_device *dev) dev_uc_unsync(phy_dev, dev); dev_mc_unsync(phy_dev, dev); =20 - rcu_read_lock(); - list_for_each_entry_rcu(addr, &ipvlan->addrs, anode) + spin_lock_bh(&ipvlan->port->addrs_lock); + list_for_each_entry(addr, &ipvlan->addrs, anode) ipvlan_ht_addr_del(addr); - rcu_read_unlock(); + spin_unlock_bh(&ipvlan->port->addrs_lock); =20 return 0; } @@ -579,7 +580,6 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl= _newlink_params *params, if (!tb[IFLA_MTU]) ipvlan_adjust_mtu(ipvlan, phy_dev); INIT_LIST_HEAD(&ipvlan->addrs); - spin_lock_init(&ipvlan->addrs_lock); =20 /* TODO Probably put random address here to be presented to the * world but keep using the physical-dev address for the outgoing @@ -657,13 +657,13 @@ void ipvlan_link_delete(struct net_device *dev, struc= t list_head *head) struct ipvl_dev *ipvlan =3D netdev_priv(dev); struct ipvl_addr *addr, *next; =20 - spin_lock_bh(&ipvlan->addrs_lock); + spin_lock_bh(&ipvlan->port->addrs_lock); list_for_each_entry_safe(addr, next, &ipvlan->addrs, anode) { ipvlan_ht_addr_del(addr); list_del_rcu(&addr->anode); kfree_rcu(addr, rcu); } - spin_unlock_bh(&ipvlan->addrs_lock); + spin_unlock_bh(&ipvlan->port->addrs_lock); =20 ida_free(&ipvlan->port->ida, dev->dev_id); list_del_rcu(&ipvlan->pnode); @@ -817,6 +817,8 @@ static int ipvlan_add_addr(struct ipvl_dev *ipvlan, voi= d *iaddr, bool is_v6) { struct ipvl_addr *addr; =20 + assert_spin_locked(&ipvlan->port->addrs_lock); + addr =3D kzalloc(sizeof(struct ipvl_addr), GFP_ATOMIC); if (!addr) return -ENOMEM; @@ -847,16 +849,16 @@ static void ipvlan_del_addr(struct ipvl_dev *ipvlan, = void *iaddr, bool is_v6) { struct ipvl_addr *addr; =20 - spin_lock_bh(&ipvlan->addrs_lock); + spin_lock_bh(&ipvlan->port->addrs_lock); addr =3D ipvlan_find_addr(ipvlan, iaddr, is_v6); if (!addr) { - spin_unlock_bh(&ipvlan->addrs_lock); + spin_unlock_bh(&ipvlan->port->addrs_lock); return; } =20 ipvlan_ht_addr_del(addr); list_del_rcu(&addr->anode); - spin_unlock_bh(&ipvlan->addrs_lock); + spin_unlock_bh(&ipvlan->port->addrs_lock); kfree_rcu(addr, rcu); } =20 @@ -878,14 +880,14 @@ static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, = struct in6_addr *ip6_addr) { int ret =3D -EINVAL; =20 - spin_lock_bh(&ipvlan->addrs_lock); + spin_lock_bh(&ipvlan->port->addrs_lock); if (ipvlan_addr_busy(ipvlan->port, ip6_addr, true)) netif_err(ipvlan, ifup, ipvlan->dev, "Failed to add IPv6=3D%pI6c addr for %s intf\n", ip6_addr, ipvlan->dev->name); else ret =3D ipvlan_add_addr(ipvlan, ip6_addr, true); - spin_unlock_bh(&ipvlan->addrs_lock); + spin_unlock_bh(&ipvlan->port->addrs_lock); return ret; } =20 @@ -924,21 +926,24 @@ static int ipvlan_addr6_validator_event(struct notifi= er_block *unused, struct in6_validator_info *i6vi =3D (struct in6_validator_info *)ptr; struct net_device *dev =3D (struct net_device *)i6vi->i6vi_dev->dev; struct ipvl_dev *ipvlan =3D netdev_priv(dev); + int ret =3D NOTIFY_OK; =20 if (!ipvlan_is_valid_dev(dev)) return NOTIFY_DONE; =20 switch (event) { case NETDEV_UP: + spin_lock_bh(&ipvlan->port->addrs_lock); if (ipvlan_addr_busy(ipvlan->port, &i6vi->i6vi_addr, true)) { NL_SET_ERR_MSG(i6vi->extack, "Address already assigned to an ipvlan device"); - return notifier_from_errno(-EADDRINUSE); + ret =3D notifier_from_errno(-EADDRINUSE); } + spin_unlock_bh(&ipvlan->port->addrs_lock); break; } =20 - return NOTIFY_OK; + return ret; } #endif =20 @@ -946,14 +951,14 @@ static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, = struct in_addr *ip4_addr) { int ret =3D -EINVAL; =20 - spin_lock_bh(&ipvlan->addrs_lock); + spin_lock_bh(&ipvlan->port->addrs_lock); if (ipvlan_addr_busy(ipvlan->port, ip4_addr, false)) netif_err(ipvlan, ifup, ipvlan->dev, "Failed to add IPv4=3D%pI4 on %s intf.\n", ip4_addr, ipvlan->dev->name); else ret =3D ipvlan_add_addr(ipvlan, ip4_addr, false); - spin_unlock_bh(&ipvlan->addrs_lock); + spin_unlock_bh(&ipvlan->port->addrs_lock); return ret; } =20 @@ -995,21 +1000,24 @@ static int ipvlan_addr4_validator_event(struct notif= ier_block *unused, struct in_validator_info *ivi =3D (struct in_validator_info *)ptr; struct net_device *dev =3D (struct net_device *)ivi->ivi_dev->dev; struct ipvl_dev *ipvlan =3D netdev_priv(dev); + int ret =3D NOTIFY_OK; =20 if (!ipvlan_is_valid_dev(dev)) return NOTIFY_DONE; =20 switch (event) { case NETDEV_UP: + spin_lock_bh(&ipvlan->port->addrs_lock); if (ipvlan_addr_busy(ipvlan->port, &ivi->ivi_addr, false)) { NL_SET_ERR_MSG(ivi->extack, "Address already assigned to an ipvlan device"); - return notifier_from_errno(-EADDRINUSE); + ret =3D notifier_from_errno(-EADDRINUSE); } + spin_unlock_bh(&ipvlan->port->addrs_lock); break; } =20 - return NOTIFY_OK; + return ret; } =20 static struct notifier_block ipvlan_addr4_notifier_block __read_mostly =3D= { --=20 2.25.1