summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-03 16:21:11 +0100
committerMark Brown <broonie@kernel.org>2026-07-03 16:21:11 +0100
commite1c68a43919d23be3c16bd28e080b381f769eadf (patch)
treebc927a25506f73d9d3cb4d0cf6c20c55a4cd8533 /net
parent586fdefdc281411252ac7266aa4aeb69dd300dd9 (diff)
parent110ef9f94f84d425ab55d09b2d70ed12ceffdf08 (diff)
downloadlinux-next-e1c68a43919d23be3c16bd28e080b381f769eadf.tar.gz
linux-next-e1c68a43919d23be3c16bd28e080b381f769eadf.zip
Merge branch 'master' of git://git.code.sf.net/p/tomoyo/tomoyo.git
Diffstat (limited to 'net')
-rw-r--r--net/bridge/br_nf_core.c1
-rw-r--r--net/core/dev.c188
-rw-r--r--net/core/dst.c85
-rw-r--r--net/core/lock_debug.c1
-rw-r--r--net/socket.c32
5 files changed, 300 insertions, 7 deletions
diff --git a/net/bridge/br_nf_core.c b/net/bridge/br_nf_core.c
index a8c67035e23c..5b3a85b4be8f 100644
--- a/net/bridge/br_nf_core.c
+++ b/net/bridge/br_nf_core.c
@@ -70,6 +70,7 @@ void br_netfilter_rtable_init(struct net_bridge *br)
struct rtable *rt = &br->fake_rtable;
rcuref_init(&rt->dst.__rcuref, 1);
+ save_dst_trace_buffer(&rt->dst, 1);
rt->dst.dev = br->dev;
dst_init_metrics(&rt->dst, br->metrics, false);
dst_metric_set(&rt->dst, RTAX_MTU, br->dev->mtu);
diff --git a/net/core/dev.c b/net/core/dev.c
index 5933c5dab09e..547e10516258 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1875,6 +1875,7 @@ const char *netdev_cmd_to_name(enum netdev_cmd cmd)
N(PRE_CHANGEADDR) N(OFFLOAD_XSTATS_ENABLE) N(OFFLOAD_XSTATS_DISABLE)
N(OFFLOAD_XSTATS_REPORT_USED) N(OFFLOAD_XSTATS_REPORT_DELTA)
N(XDP_FEAT_CHANGE)
+ N(DEBUG_UNREGISTER)
}
#undef N
return "UNKNOWN_NETDEV_EVENT";
@@ -11581,6 +11582,14 @@ int netdev_refcnt_read(const struct net_device *dev)
}
EXPORT_SYMBOL(netdev_refcnt_read);
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+static void dump_netdev_trace_buffer(const struct net_device *dev);
+static void erase_netdev_trace_buffer(const struct net_device *dev);
+#else
+static inline void dump_netdev_trace_buffer(const struct net_device *dev) { }
+static inline void erase_netdev_trace_buffer(const struct net_device *dev) { }
+#endif
+
int netdev_unregister_timeout_secs __read_mostly = 10;
#define WAIT_REFS_MIN_MSECS 1
@@ -11654,11 +11663,16 @@ static struct net_device *netdev_wait_allrefs_any(struct list_head *list)
if (time_after(jiffies, warning_time +
READ_ONCE(netdev_unregister_timeout_secs) * HZ)) {
+ rtnl_lock();
list_for_each_entry(dev, list, todo_list) {
pr_emerg("unregister_netdevice: waiting for %s to become free. Usage count = %d\n",
dev->name, netdev_refcnt_read(dev));
ref_tracker_dir_print(&dev->refcnt_tracker, 10);
+ call_netdevice_notifiers(NETDEV_DEBUG_UNREGISTER, dev);
+ dump_netdev_trace_buffer(dev);
}
+ __rtnl_unlock();
+ rcu_barrier();
warning_time = jiffies;
}
@@ -12058,6 +12072,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
dev->priv_len = sizeof_priv;
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+ INIT_LIST_HEAD(&dev->netdev_trace_buffer_list);
+#endif
ref_tracker_dir_init(&dev->refcnt_tracker, 128, "netdev");
#ifdef CONFIG_PCPU_DEV_REFCNT
dev->pcpu_refcnt = alloc_percpu(int);
@@ -12234,6 +12251,8 @@ void free_netdev(struct net_device *dev)
mutex_destroy(&dev->lock);
+ erase_netdev_trace_buffer(dev);
+
/* Compatibility with error handling in drivers */
if (dev->reg_state == NETREG_UNINITIALIZED ||
dev->reg_state == NETREG_DUMMY) {
@@ -13338,3 +13357,172 @@ out:
}
subsys_initcall(net_dev_init);
+
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+
+#define NETDEV_TRACE_BUFFER_SIZE 32768
+static struct netdev_trace_buffer {
+ struct list_head list;
+ int prev_count;
+ atomic_t count;
+ int nr_entries;
+ unsigned long entries[20];
+} netdev_trace_buffer[NETDEV_TRACE_BUFFER_SIZE];
+static LIST_HEAD(netdev_trace_buffer_list);
+static DEFINE_SPINLOCK(netdev_trace_buffer_lock);
+static bool netdev_trace_buffer_exhausted;
+
+static int netdev_trace_buffer_init(void)
+{
+ int i;
+
+ for (i = 0; i < NETDEV_TRACE_BUFFER_SIZE; i++)
+ list_add_tail(&netdev_trace_buffer[i].list, &netdev_trace_buffer_list);
+ return 0;
+}
+pure_initcall(netdev_trace_buffer_init);
+
+static void dump_netdev_trace_buffer(const struct net_device *dev)
+{
+ struct netdev_trace_buffer *ptr;
+ int count, balance = 0, pos = 0;
+
+ dump_dst_trace_buffer(dev);
+ list_for_each_entry_rcu(ptr, &dev->netdev_trace_buffer_list, list,
+ /* list elements can't go away. */ 1) {
+ pos++;
+ count = atomic_read(&ptr->count);
+ balance += count;
+ if (ptr->prev_count == count)
+ continue;
+ ptr->prev_count = count;
+ pr_info("Call trace for %s[%d] %+d at\n", dev->name, pos, count);
+ stack_trace_print(ptr->entries, ptr->nr_entries, 4);
+ cond_resched();
+ }
+ if (!netdev_trace_buffer_exhausted)
+ pr_info("balance as of %s[%d] is %d\n", dev->name, pos, balance);
+}
+
+static void erase_netdev_trace_buffer(const struct net_device *dev)
+{
+ struct netdev_trace_buffer *ptr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&netdev_trace_buffer_lock, flags);
+ while (!list_empty(&dev->netdev_trace_buffer_list)) {
+ ptr = list_first_entry(&dev->netdev_trace_buffer_list, typeof(*ptr), list);
+ list_del(&ptr->list);
+ list_add_tail(&ptr->list, &netdev_trace_buffer_list);
+ }
+ spin_unlock_irqrestore(&netdev_trace_buffer_lock, flags);
+}
+
+int trim_netdev_trace(unsigned long *entries, int nr_entries)
+{
+#ifdef CONFIG_KALLSYMS
+ char buffer[32] = { };
+ char *cp;
+ int i;
+
+ if (in_softirq()) {
+ static unsigned long __data_racy caller;
+
+ if (!caller) {
+ for (i = 0; i < nr_entries; i++) {
+ snprintf(buffer, sizeof(buffer) - 1, "%ps", (void *)entries[i]);
+ cp = strchr(buffer, ' ');
+ if (cp)
+ *cp = '\0';
+ if (!strcmp(buffer, "handle_softirqs")) {
+ caller = entries[i];
+ break;
+ }
+ }
+ }
+ for (i = 0; i < nr_entries; i++)
+ if (entries[i] == caller)
+ return i + 1;
+ } else if (current->flags & PF_WQ_WORKER) {
+ static unsigned long __data_racy caller;
+
+ if (!caller) {
+ for (i = 0; i < nr_entries; i++) {
+ snprintf(buffer, sizeof(buffer) - 1, "%ps", (void *)entries[i]);
+ cp = strchr(buffer, ' ');
+ if (cp)
+ *cp = '\0';
+ if (!strcmp(buffer, "process_one_work")) {
+ caller = entries[i];
+ break;
+ }
+ }
+ }
+ for (i = 0; i < nr_entries; i++)
+ if (entries[i] == caller)
+ return i + 1;
+ } else {
+ for (i = 0; i < nr_entries; i++) {
+ snprintf(buffer, sizeof(buffer) - 1, "%ps", (void *)entries[i]);
+ cp = strchr(buffer, ' ');
+ if (cp)
+ *cp = '\0';
+ if (buffer[0] == 'k') {
+ if (!strcmp(buffer, "ksys_unshare"))
+ return i + 1;
+ } else if (buffer[0] == 's') {
+ if (!strcmp(buffer, "sock_sendmsg_nosec") ||
+ !strcmp(buffer, "sock_recvmsg_nosec"))
+ return i + 1;
+ } else if (buffer[0] == '_') {
+ if (!strcmp(buffer, "__sys_bind") ||
+ !strcmp(buffer, "__sock_release") ||
+ !strcmp(buffer, "__sys_bpf"))
+ return i + 1;
+ } else {
+ if (!strcmp(buffer, "do_sock_setsockopt"))
+ return i + 1;
+ }
+ }
+ }
+#endif
+ return nr_entries;
+}
+EXPORT_SYMBOL(trim_netdev_trace);
+
+void save_netdev_trace_buffer(struct net_device *dev, int delta)
+{
+ struct netdev_trace_buffer *ptr;
+ unsigned long entries[ARRAY_SIZE(ptr->entries)];
+ unsigned long nr_entries;
+ unsigned long flags;
+
+ if (in_nmi())
+ return;
+ nr_entries = stack_trace_save(entries, ARRAY_SIZE(ptr->entries), 1);
+ nr_entries = trim_netdev_trace(entries, nr_entries);
+ list_for_each_entry_rcu(ptr, &dev->netdev_trace_buffer_list, list,
+ /* list elements can't go away. */ 1) {
+ if (ptr->nr_entries == nr_entries &&
+ !memcmp(ptr->entries, entries, nr_entries * sizeof(unsigned long))) {
+ atomic_add(delta, &ptr->count);
+ return;
+ }
+ }
+ spin_lock_irqsave(&netdev_trace_buffer_lock, flags);
+ if (!list_empty(&netdev_trace_buffer_list)) {
+ ptr = list_first_entry(&netdev_trace_buffer_list, typeof(*ptr), list);
+ list_del(&ptr->list);
+ ptr->prev_count = 0;
+ atomic_set(&ptr->count, delta);
+ ptr->nr_entries = nr_entries;
+ memmove(ptr->entries, entries, nr_entries * sizeof(unsigned long));
+ list_add_tail_rcu(&ptr->list, &dev->netdev_trace_buffer_list);
+ } else {
+ netdev_trace_buffer_exhausted = true;
+ }
+ spin_unlock_irqrestore(&netdev_trace_buffer_lock, flags);
+}
+EXPORT_SYMBOL(save_netdev_trace_buffer);
+
+#endif
diff --git a/net/core/dst.c b/net/core/dst.c
index 092861133023..45452fa62e24 100644
--- a/net/core/dst.c
+++ b/net/core/dst.c
@@ -44,6 +44,82 @@ const struct dst_metrics dst_default_metrics = {
};
EXPORT_SYMBOL(dst_default_metrics);
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+
+#define DST_TRACE_BUFFER_SIZE 8192
+static struct dst_trace_buffer {
+ struct dst_entry *dst; // no-ref
+ struct net_device *ndev; // no-ref
+ int seq;
+ int delta;
+ u32 caller_id;
+ int nr_entries;
+ unsigned long entries[20];
+} dst_trace_buffer[DST_TRACE_BUFFER_SIZE];
+static bool dst_trace_buffer_exhausted;
+
+void dump_dst_trace_buffer(const struct net_device *ndev)
+{
+ struct dst_trace_buffer *ptr;
+ int count, balance = 0;
+ int i;
+
+ for (i = 0; i < DST_TRACE_BUFFER_SIZE; i++) {
+ ptr = &dst_trace_buffer[i];
+ if (!ptr->dst || ptr->ndev != ndev)
+ continue;
+ count = ptr->delta;
+ balance += count;
+ pr_info("Call trace for %s@%p[%d] %+d %c%u at\n", ndev->name, ptr->dst, ptr->seq,
+ count, ptr->caller_id & 0x80000000 ? 'C' : 'T',
+ ptr->caller_id & ~0x80000000);
+ stack_trace_print(ptr->entries, ptr->nr_entries, 4);
+ }
+ if (!dst_trace_buffer_exhausted)
+ pr_info("balance for %s@dst_entry is %d\n", ndev->name, balance);
+ else
+ pr_info("balance for %s@dst_entry is unknown\n", ndev->name);
+}
+
+static void erase_dst_trace_buffer(struct dst_entry *dst)
+{
+ int i;
+
+ for (i = 0; i < DST_TRACE_BUFFER_SIZE; i++)
+ if (dst_trace_buffer[i].dst == dst)
+ dst_trace_buffer[i].dst = NULL;
+}
+
+void save_dst_trace_buffer(struct dst_entry *dst, int delta)
+{
+ struct dst_trace_buffer *ptr;
+ unsigned long entries[ARRAY_SIZE(ptr->entries)];
+ unsigned long nr_entries;
+ int i;
+
+ if (!dst->dev)
+ return;
+ nr_entries = stack_trace_save(entries, ARRAY_SIZE(ptr->entries), 1);
+ nr_entries = trim_netdev_trace(entries, nr_entries);
+ for (i = 0; i < DST_TRACE_BUFFER_SIZE; i++) {
+ ptr = &dst_trace_buffer[i];
+ if (!ptr->dst && !cmpxchg(&ptr->dst, NULL, dst)) {
+ ptr->ndev = dst->dev;
+ ptr->seq = atomic_inc_return(&dst->dst_trace_seq);
+ ptr->delta = delta;
+ ptr->caller_id = in_task() ? task_pid_nr(current) :
+ 0x80000000 + raw_smp_processor_id();
+ ptr->nr_entries = nr_entries;
+ memmove(ptr->entries, entries, nr_entries * sizeof(unsigned long));
+ return;
+ }
+ }
+ dst_trace_buffer_exhausted = true;
+}
+EXPORT_SYMBOL(save_dst_trace_buffer);
+
+#endif
+
void dst_init(struct dst_entry *dst, struct dst_ops *ops,
struct net_device *dev, int initial_obsolete,
unsigned short flags)
@@ -67,6 +143,7 @@ void dst_init(struct dst_entry *dst, struct dst_ops *ops,
#endif
dst->lwtstate = NULL;
rcuref_init(&dst->__rcuref, 1);
+ save_dst_trace_buffer(dst, 1);
INIT_LIST_HEAD(&dst->rt_uncached);
dst->rt_uncached_list = NULL;
dst->__use = 0;
@@ -116,6 +193,10 @@ static void dst_destroy(struct dst_entry *dst)
lwtstate_put(dst->lwtstate);
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+ erase_dst_trace_buffer(dst);
+#endif
+
if (dst->flags & DST_METADATA)
metadata_dst_free((struct metadata_dst *)dst);
else
@@ -165,6 +246,8 @@ static void dst_count_dec(struct dst_entry *dst)
void dst_release(struct dst_entry *dst)
{
+ if (dst)
+ save_dst_trace_buffer(dst, -1);
if (dst && rcuref_put(&dst->__rcuref)) {
#ifdef CONFIG_DST_CACHE
if (dst->flags & DST_METADATA) {
@@ -182,6 +265,8 @@ EXPORT_SYMBOL(dst_release);
void dst_release_immediate(struct dst_entry *dst)
{
+ if (dst)
+ save_dst_trace_buffer(dst, -1);
if (dst && rcuref_put(&dst->__rcuref)) {
dst_count_dec(dst);
dst_destroy(dst);
diff --git a/net/core/lock_debug.c b/net/core/lock_debug.c
index 8a81c5430705..c1a44594c492 100644
--- a/net/core/lock_debug.c
+++ b/net/core/lock_debug.c
@@ -29,6 +29,7 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event,
case NETDEV_DOWN:
case NETDEV_REBOOT:
case NETDEV_UNREGISTER:
+ case NETDEV_DEBUG_UNREGISTER:
case NETDEV_CHANGEMTU:
case NETDEV_CHANGEADDR:
case NETDEV_PRE_CHANGEADDR:
diff --git a/net/socket.c b/net/socket.c
index b0256cd222f8..068cd8a7705b 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -723,7 +723,11 @@ struct socket *sock_alloc(void)
}
EXPORT_SYMBOL(sock_alloc);
-static void __sock_release(struct socket *sock, struct inode *inode)
+static
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+noinline
+#endif
+void __sock_release(struct socket *sock, struct inode *inode)
{
const struct proto_ops *ops = READ_ONCE(sock->ops);
@@ -795,7 +799,13 @@ static noinline void call_trace_sock_send_length(struct sock *sk, int ret,
trace_sock_send_length(sk, ret, 0);
}
-static inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg)
+static
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+noinline
+#else
+inline
+#endif
+int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg)
{
int ret = INDIRECT_CALL_INET(READ_ONCE(sock->ops)->sendmsg, inet6_sendmsg,
inet_sendmsg, sock, msg,
@@ -1145,8 +1155,13 @@ static noinline void call_trace_sock_recv_length(struct sock *sk, int ret, int f
trace_sock_recv_length(sk, ret, flags);
}
-static inline int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg,
- int flags)
+static
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+noinline
+#else
+inline
+#endif
+int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg, int flags)
{
int ret = INDIRECT_CALL_INET(READ_ONCE(sock->ops)->recvmsg,
inet6_recvmsg,
@@ -2649,9 +2664,12 @@ static int copy_msghdr_from_user(struct msghdr *kmsg,
return err < 0 ? err : 0;
}
-static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys,
- unsigned int flags, struct used_address *used_address,
- unsigned int allowed_msghdr_flags)
+static
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+noinline
+#endif
+int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys, unsigned int flags,
+ struct used_address *used_address, unsigned int allowed_msghdr_flags)
{
unsigned char ctl[sizeof(struct cmsghdr) + 20]
__aligned(sizeof(__kernel_size_t));