net: add net device refcount tracker infrastructure
net device are refcounted. Over the years we had numerous bugs caused by imbalanced dev_hold() and dev_put() calls. The general idea is to be able to precisely pair each decrement with a corresponding prior increment. Both share a cookie, basically a pointer to private data storing stack traces. This patch adds dev_hold_track() and dev_put_track(). To use these helpers, each data structure owning a refcount should also use a "netdevice_tracker" to pair the hold and put. netdevice_tracker dev_tracker; ... dev_hold_track(dev, &dev_tracker, GFP_ATOMIC); ... dev_put_track(dev, &dev_tracker); Whenever a leak happens, we will get precise stack traces of the point dev_hold_track() happened, at device dismantle phase. We will also get a stack trace if too many dev_put_track() for the same netdevice_tracker are attempted. This is guarded by CONFIG_NET_DEV_REFCNT_TRACKER option. Signed-off-by: Eric Dumazet <edumazet@google.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
914a7b5000
commit
4d92b95ff2
@ -48,6 +48,7 @@
|
||||
#include <uapi/linux/pkt_cls.h>
|
||||
#include <linux/hashtable.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/ref_tracker.h>
|
||||
|
||||
struct netpoll_info;
|
||||
struct device;
|
||||
@ -300,6 +301,12 @@ enum netdev_state_t {
|
||||
};
|
||||
|
||||
|
||||
#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
|
||||
typedef struct ref_tracker *netdevice_tracker;
|
||||
#else
|
||||
typedef struct {} netdevice_tracker;
|
||||
#endif
|
||||
|
||||
struct gro_list {
|
||||
struct list_head list;
|
||||
int count;
|
||||
@ -1865,6 +1872,7 @@ enum netdev_ml_priv_type {
|
||||
* @proto_down_reason: reason a netdev interface is held down
|
||||
* @pcpu_refcnt: Number of references to this device
|
||||
* @dev_refcnt: Number of references to this device
|
||||
* @refcnt_tracker: Tracker directory for tracked references to this device
|
||||
* @todo_list: Delayed register/unregister
|
||||
* @link_watch_list: XXX: need comments on this one
|
||||
*
|
||||
@ -2178,6 +2186,7 @@ struct net_device {
|
||||
#else
|
||||
refcount_t dev_refcnt;
|
||||
#endif
|
||||
struct ref_tracker_dir refcnt_tracker;
|
||||
|
||||
struct list_head link_watch_list;
|
||||
|
||||
@ -3805,6 +3814,7 @@ void netdev_run_todo(void);
|
||||
* @dev: network device
|
||||
*
|
||||
* Release reference to device to allow it to be freed.
|
||||
* Try using dev_put_track() instead.
|
||||
*/
|
||||
static inline void dev_put(struct net_device *dev)
|
||||
{
|
||||
@ -3822,6 +3832,7 @@ static inline void dev_put(struct net_device *dev)
|
||||
* @dev: network device
|
||||
*
|
||||
* Hold reference to device to keep it from being freed.
|
||||
* Try using dev_hold_track() instead.
|
||||
*/
|
||||
static inline void dev_hold(struct net_device *dev)
|
||||
{
|
||||
@ -3834,6 +3845,40 @@ static inline void dev_hold(struct net_device *dev)
|
||||
}
|
||||
}
|
||||
|
||||
static inline void netdev_tracker_alloc(struct net_device *dev,
|
||||
netdevice_tracker *tracker, gfp_t gfp)
|
||||
{
|
||||
#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
|
||||
ref_tracker_alloc(&dev->refcnt_tracker, tracker, gfp);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void netdev_tracker_free(struct net_device *dev,
|
||||
netdevice_tracker *tracker)
|
||||
{
|
||||
#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
|
||||
ref_tracker_free(&dev->refcnt_tracker, tracker);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void dev_hold_track(struct net_device *dev,
|
||||
netdevice_tracker *tracker, gfp_t gfp)
|
||||
{
|
||||
if (dev) {
|
||||
dev_hold(dev);
|
||||
netdev_tracker_alloc(dev, tracker, gfp);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dev_put_track(struct net_device *dev,
|
||||
netdevice_tracker *tracker)
|
||||
{
|
||||
if (dev) {
|
||||
netdev_tracker_free(dev, tracker);
|
||||
dev_put(dev);
|
||||
}
|
||||
}
|
||||
|
||||
/* Carrier loss detection, dial on demand. The functions netif_carrier_on
|
||||
* and _off may be called from IRQ context, but it is caller
|
||||
* who is responsible for serialization of these calls.
|
||||
|
@ -598,6 +598,11 @@ config DEBUG_MISC
|
||||
Say Y here if you need to enable miscellaneous debug code that should
|
||||
be under a more specific debug option but isn't.
|
||||
|
||||
menu "Networking Debugging"
|
||||
|
||||
source "net/Kconfig.debug"
|
||||
|
||||
endmenu # "Networking Debugging"
|
||||
|
||||
menu "Memory Debugging"
|
||||
|
||||
|
10
net/Kconfig.debug
Normal file
10
net/Kconfig.debug
Normal file
@ -0,0 +1,10 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
config NET_DEV_REFCNT_TRACKER
|
||||
bool "Enable net device refcount tracking"
|
||||
depends on DEBUG_KERNEL && STACKTRACE_SUPPORT
|
||||
select REF_TRACKER
|
||||
default n
|
||||
help
|
||||
Enable debugging feature to track device references.
|
||||
This adds memory and cpu costs.
|
@ -9864,6 +9864,7 @@ static void netdev_wait_allrefs(struct net_device *dev)
|
||||
netdev_unregister_timeout_secs * HZ)) {
|
||||
pr_emerg("unregister_netdevice: waiting for %s to become free. Usage count = %d\n",
|
||||
dev->name, refcnt);
|
||||
ref_tracker_dir_print(&dev->refcnt_tracker, 10);
|
||||
warning_time = jiffies;
|
||||
}
|
||||
}
|
||||
@ -10154,6 +10155,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
|
||||
dev = PTR_ALIGN(p, NETDEV_ALIGN);
|
||||
dev->padded = (char *)dev - (char *)p;
|
||||
|
||||
ref_tracker_dir_init(&dev->refcnt_tracker, 128);
|
||||
#ifdef CONFIG_PCPU_DEV_REFCNT
|
||||
dev->pcpu_refcnt = alloc_percpu(int);
|
||||
if (!dev->pcpu_refcnt)
|
||||
@ -10270,6 +10272,7 @@ void free_netdev(struct net_device *dev)
|
||||
list_for_each_entry_safe(p, n, &dev->napi_list, dev_list)
|
||||
netif_napi_del(p);
|
||||
|
||||
ref_tracker_dir_exit(&dev->refcnt_tracker);
|
||||
#ifdef CONFIG_PCPU_DEV_REFCNT
|
||||
free_percpu(dev->pcpu_refcnt);
|
||||
dev->pcpu_refcnt = NULL;
|
||||
|
Loading…
x
Reference in New Issue
Block a user