mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	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
					
				
					 4 changed files with 63 additions and 0 deletions
				
			
		| 
						 | 
					@ -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 {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#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 {
 | 
				
			||||||
 *	@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 {
 | 
				
			||||||
#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);
 | 
				
			||||||
 *	@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)
 | 
				
			||||||
 *	@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)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
	  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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										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)) {
 | 
								       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,
 | 
				
			||||||
	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)
 | 
				
			||||||
	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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue