Commit 2628f495 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

net: add helpers for lookup and walking netdevs under netdev_lock()



Add helpers for accessing netdevs under netdev_lock().
There's some careful handling needed to find the device and lock it
safely, without it getting unregistered, and without taking rtnl_lock
(the latter being the whole point of the new locking, after all).

Reviewed-by: default avatarEric Dumazet <edumazet@google.com>
Reviewed-by: default avatarKuniyuki Iwashima <kuniyu@amazon.com>
Link: https://patch.msgid.link/20250115035319.559603-4-kuba@kernel.org


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 5fda3f35
Loading
Loading
Loading
Loading
+110 −0
Original line number Diff line number Diff line
@@ -784,6 +784,49 @@ struct napi_struct *netdev_napi_by_id(struct net *net, unsigned int napi_id)
	return napi;
}

/**
 *	netdev_napi_by_id_lock() - find a device by NAPI ID and lock it
 *	@net: the applicable net namespace
 *	@napi_id: ID of a NAPI of a target device
 *
 *	Find a NAPI instance with @napi_id. Lock its device.
 *	The device must be in %NETREG_REGISTERED state for lookup to succeed.
 *	netdev_unlock() must be called to release it.
 *
 *	Return: pointer to NAPI, its device with lock held, NULL if not found.
 */
struct napi_struct *
netdev_napi_by_id_lock(struct net *net, unsigned int napi_id)
{
	struct napi_struct *napi;
	struct net_device *dev;

	rcu_read_lock();
	napi = netdev_napi_by_id(net, napi_id);
	if (!napi || READ_ONCE(napi->dev->reg_state) != NETREG_REGISTERED) {
		rcu_read_unlock();
		return NULL;
	}

	dev = napi->dev;
	dev_hold(dev);
	rcu_read_unlock();

	dev = __netdev_put_lock(dev);
	if (!dev)
		return NULL;

	rcu_read_lock();
	napi = netdev_napi_by_id(net, napi_id);
	if (napi && napi->dev != dev)
		napi = NULL;
	rcu_read_unlock();

	if (!napi)
		netdev_unlock(dev);
	return napi;
}

/**
 *	__dev_get_by_name	- find a device by its name
 *	@net: the applicable net namespace
@@ -972,6 +1015,73 @@ struct net_device *dev_get_by_napi_id(unsigned int napi_id)
	return napi ? napi->dev : NULL;
}

/* Release the held reference on the net_device, and if the net_device
 * is still registered try to lock the instance lock. If device is being
 * unregistered NULL will be returned (but the reference has been released,
 * either way!)
 *
 * This helper is intended for locking net_device after it has been looked up
 * using a lockless lookup helper. Lock prevents the instance from going away.
 */
struct net_device *__netdev_put_lock(struct net_device *dev)
{
	netdev_lock(dev);
	if (dev->reg_state > NETREG_REGISTERED) {
		netdev_unlock(dev);
		dev_put(dev);
		return NULL;
	}
	dev_put(dev);
	return dev;
}

/**
 *	netdev_get_by_index_lock() - find a device by its ifindex
 *	@net: the applicable net namespace
 *	@ifindex: index of device
 *
 *	Search for an interface by index. If a valid device
 *	with @ifindex is found it will be returned with netdev->lock held.
 *	netdev_unlock() must be called to release it.
 *
 *	Return: pointer to a device with lock held, NULL if not found.
 */
struct net_device *netdev_get_by_index_lock(struct net *net, int ifindex)
{
	struct net_device *dev;

	dev = dev_get_by_index(net, ifindex);
	if (!dev)
		return NULL;

	return __netdev_put_lock(dev);
}

struct net_device *
netdev_xa_find_lock(struct net *net, struct net_device *dev,
		    unsigned long *index)
{
	if (dev)
		netdev_unlock(dev);

	do {
		rcu_read_lock();
		dev = xa_find(&net->dev_by_index, index, ULONG_MAX, XA_PRESENT);
		if (!dev) {
			rcu_read_unlock();
			return NULL;
		}
		dev_hold(dev);
		rcu_read_unlock();

		dev = __netdev_put_lock(dev);
		if (dev)
			return dev;

		(*index)++;
	} while (true);
}

static DEFINE_SEQLOCK(netdev_rename_lock);

void netdev_copy_name(struct net_device *dev, char *name)
+16 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@
#ifndef _NET_CORE_DEV_H
#define _NET_CORE_DEV_H

#include <linux/cleanup.h>
#include <linux/types.h>
#include <linux/rwsem.h>
#include <linux/netdevice.h>
@@ -23,8 +24,23 @@ struct sd_flow_limit {
extern int netdev_flow_limit_table_len;

struct napi_struct *netdev_napi_by_id(struct net *net, unsigned int napi_id);
struct napi_struct *
netdev_napi_by_id_lock(struct net *net, unsigned int napi_id);
struct net_device *dev_get_by_napi_id(unsigned int napi_id);

struct net_device *netdev_get_by_index_lock(struct net *net, int ifindex);
struct net_device *__netdev_put_lock(struct net_device *dev);
struct net_device *
netdev_xa_find_lock(struct net *net, struct net_device *dev,
		    unsigned long *index);

DEFINE_FREE(netdev_unlock, struct net_device *, if (_T) netdev_unlock(_T));

#define for_each_netdev_lock_scoped(net, var_name, ifindex)		\
	for (struct net_device *var_name __free(netdev_unlock) = NULL;	\
	     (var_name = netdev_xa_find_lock(net, var_name, &ifindex)); \
	     ifindex++)

#ifdef CONFIG_PROC_FS
int __init dev_proc_init(void);
#else