Commit 4d92b95f authored by Eric Dumazet's avatar Eric Dumazet Committed by Jakub Kicinski

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: default avatarEric Dumazet <edumazet@google.com>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 914a7b50
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
#include <uapi/linux/pkt_cls.h> #include <uapi/linux/pkt_cls.h>
#include <linux/hashtable.h> #include <linux/hashtable.h>
#include <linux/rbtree.h> #include <linux/rbtree.h>
#include <linux/ref_tracker.h>
struct netpoll_info; struct netpoll_info;
struct device; struct device;
...@@ -300,6 +301,12 @@ enum netdev_state_t { ...@@ -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 gro_list {
struct list_head list; struct list_head list;
int count; int count;
...@@ -1865,6 +1872,7 @@ enum netdev_ml_priv_type { ...@@ -1865,6 +1872,7 @@ enum netdev_ml_priv_type {
* @proto_down_reason: reason a netdev interface is held down * @proto_down_reason: reason a netdev interface is held down
* @pcpu_refcnt: Number of references to this device * @pcpu_refcnt: Number of references to this device
* @dev_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 * @todo_list: Delayed register/unregister
* @link_watch_list: XXX: need comments on this one * @link_watch_list: XXX: need comments on this one
* *
...@@ -2178,6 +2186,7 @@ struct net_device { ...@@ -2178,6 +2186,7 @@ struct net_device {
#else #else
refcount_t dev_refcnt; refcount_t dev_refcnt;
#endif #endif
struct ref_tracker_dir refcnt_tracker;
struct list_head link_watch_list; struct list_head link_watch_list;
...@@ -3805,6 +3814,7 @@ void netdev_run_todo(void); ...@@ -3805,6 +3814,7 @@ void netdev_run_todo(void);
* @dev: network device * @dev: network device
* *
* Release reference to device to allow it to be freed. * 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) static inline void dev_put(struct net_device *dev)
{ {
...@@ -3822,6 +3832,7 @@ 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 * @dev: network device
* *
* Hold reference to device to keep it from being freed. * 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) static inline void dev_hold(struct net_device *dev)
{ {
...@@ -3834,6 +3845,40 @@ 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 /* Carrier loss detection, dial on demand. The functions netif_carrier_on
* and _off may be called from IRQ context, but it is caller * and _off may be called from IRQ context, but it is caller
* who is responsible for serialization of these calls. * who is responsible for serialization of these calls.
......
...@@ -598,6 +598,11 @@ config DEBUG_MISC ...@@ -598,6 +598,11 @@ config DEBUG_MISC
Say Y here if you need to enable miscellaneous debug code that should Say Y here if you need to enable miscellaneous debug code that should
be under a more specific debug option but isn't. be under a more specific debug option but isn't.
menu "Networking Debugging"
source "net/Kconfig.debug"
endmenu # "Networking Debugging"
menu "Memory Debugging" menu "Memory Debugging"
......
# 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) ...@@ -9864,6 +9864,7 @@ static void netdev_wait_allrefs(struct net_device *dev)
netdev_unregister_timeout_secs * HZ)) { netdev_unregister_timeout_secs * HZ)) {
pr_emerg("unregister_netdevice: waiting for %s to become free. Usage count = %d\n", pr_emerg("unregister_netdevice: waiting for %s to become free. Usage count = %d\n",
dev->name, refcnt); dev->name, refcnt);
ref_tracker_dir_print(&dev->refcnt_tracker, 10);
warning_time = jiffies; warning_time = jiffies;
} }
} }
...@@ -10154,6 +10155,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, ...@@ -10154,6 +10155,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
dev = PTR_ALIGN(p, NETDEV_ALIGN); dev = PTR_ALIGN(p, NETDEV_ALIGN);
dev->padded = (char *)dev - (char *)p; dev->padded = (char *)dev - (char *)p;
ref_tracker_dir_init(&dev->refcnt_tracker, 128);
#ifdef CONFIG_PCPU_DEV_REFCNT #ifdef CONFIG_PCPU_DEV_REFCNT
dev->pcpu_refcnt = alloc_percpu(int); dev->pcpu_refcnt = alloc_percpu(int);
if (!dev->pcpu_refcnt) if (!dev->pcpu_refcnt)
...@@ -10270,6 +10272,7 @@ void free_netdev(struct net_device *dev) ...@@ -10270,6 +10272,7 @@ void free_netdev(struct net_device *dev)
list_for_each_entry_safe(p, n, &dev->napi_list, dev_list) list_for_each_entry_safe(p, n, &dev->napi_list, dev_list)
netif_napi_del(p); netif_napi_del(p);
ref_tracker_dir_exit(&dev->refcnt_tracker);
#ifdef CONFIG_PCPU_DEV_REFCNT #ifdef CONFIG_PCPU_DEV_REFCNT
free_percpu(dev->pcpu_refcnt); free_percpu(dev->pcpu_refcnt);
dev->pcpu_refcnt = NULL; dev->pcpu_refcnt = NULL;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment