diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-03 16:21:11 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-03 16:21:11 +0100 |
| commit | e1c68a43919d23be3c16bd28e080b381f769eadf (patch) | |
| tree | bc927a25506f73d9d3cb4d0cf6c20c55a4cd8533 /net | |
| parent | 586fdefdc281411252ac7266aa4aeb69dd300dd9 (diff) | |
| parent | 110ef9f94f84d425ab55d09b2d70ed12ceffdf08 (diff) | |
| download | linux-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.c | 1 | ||||
| -rw-r--r-- | net/core/dev.c | 188 | ||||
| -rw-r--r-- | net/core/dst.c | 85 | ||||
| -rw-r--r-- | net/core/lock_debug.c | 1 | ||||
| -rw-r--r-- | net/socket.c | 32 |
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)); |
