diff mbox series

net: add a debugfs files for showing netns refcount tracking info

Message ID 20250324-netns-debugfs-v1-1-c75e9d5a6266@kernel.org (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series net: add a debugfs files for showing netns refcount tracking info | expand

Checks

Context Check Description
netdev/series_format warning Single patches do not need cover letters; Target tree name not specified in the subject
netdev/tree_selection success Guessed tree name to be net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 1 maintainers not CCed: kuniyu@amazon.com
netdev/build_clang success Errors and warnings before: 0 this patch: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch warning WARNING: line length of 82 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 3 this patch: 3
netdev/source_inline success Was 0 now: 0
netdev/contest fail net-next-2025-03-24--21-00 (tests: 896)

Commit Message

Jeff Layton March 24, 2025, 8:24 p.m. UTC
CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display
its tracking info. Add a new net_ns directory in debugfs. Have a
directory in there for every net, with refcnt and notrefcnt files that
show the currently tracked active and passive references.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Recently, I had a need to track down some long-held netns references,
and discovered CONFIG_NET_NS_REFCNT_TRACKER. The main thing that seemed
to be missing from it though is a simple way to view the currently held
references on the netns. This adds files in debugfs for this.
---
 net/core/net_namespace.c | 151 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 151 insertions(+)


---
base-commit: 695caca9345a160ecd9645abab8e70cfe849e9ff
change-id: 20250324-netns-debugfs-df213b2ab9ce

Best regards,

Comments

Andrew Lunn March 24, 2025, 8:50 p.m. UTC | #1
On Mon, Mar 24, 2025 at 04:24:47PM -0400, Jeff Layton wrote:
> CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display
> its tracking info. Add a new net_ns directory in debugfs. Have a
> directory in there for every net, with refcnt and notrefcnt files that
> show the currently tracked active and passive references.

Hi Jeff

CONFIG_NET_NS_REFCNT_TRACKER is just an instance of
CONFIG_REF_TRACKER.

It would be good to explain why you are doing it at the netdev level,
rather than part of the generic CONFIG_REF_TRACKER level. Why would
other subsystems not benefit from having their reference trackers in
debugfs?

	Andrew
Jeff Layton March 25, 2025, 12:37 a.m. UTC | #2
On Mon, 2025-03-24 at 21:50 +0100, Andrew Lunn wrote:
> On Mon, Mar 24, 2025 at 04:24:47PM -0400, Jeff Layton wrote:
> > CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display
> > its tracking info. Add a new net_ns directory in debugfs. Have a
> > directory in there for every net, with refcnt and notrefcnt files that
> > show the currently tracked active and passive references.
> 
> Hi Jeff
> 
> CONFIG_NET_NS_REFCNT_TRACKER is just an instance of
> CONFIG_REF_TRACKER.
> 
> It would be good to explain why you are doing it at the netdev level,
> rather than part of the generic CONFIG_REF_TRACKER level. Why would
> other subsystems not benefit from having their reference trackers in
> debugfs?
> 
> 

Mostly because I just needed the NS_REFCNT_TRACKER at the time.

I'm OK with making this more general, but all of those subsystems using
refcount trackers would need to add the infrastructure to create
directories to track them.

To whit:

What would the directory structure look like for the more general case?
Eric Dumazet March 25, 2025, 5:21 a.m. UTC | #3
On Mon, Mar 24, 2025 at 9:24 PM Jeff Layton <jlayton@kernel.org> wrote:
>
> CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display
> its tracking info. Add a new net_ns directory in debugfs. Have a
> directory in there for every net, with refcnt and notrefcnt files that
> show the currently tracked active and passive references.
>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> ---
> Recently, I had a need to track down some long-held netns references,
> and discovered CONFIG_NET_NS_REFCNT_TRACKER. The main thing that seemed
> to be missing from it though is a simple way to view the currently held
> references on the netns. This adds files in debugfs for this.

Thanks for working on this, this is a very good idea.


> +#define MAX_NS_DEBUG_BUFSIZE   (32 * PAGE_SIZE)
> +
> +static int
> +ns_debug_tracker_show(struct seq_file *f, void *v)
> +{
> +       struct ref_tracker_dir *tracker = f->private;
> +       int len, bufsize = PAGE_SIZE;
> +       char *buf;
> +
> +       for (;;) {
> +               buf = kvmalloc(bufsize, GFP_KERNEL);
> +               if (!buf)
> +                       return -ENOMEM;
> +
> +               len = ref_tracker_dir_snprint(tracker, buf, bufsize);
> +               if (len < bufsize)
> +                       break;
> +
> +               kvfree(buf);
> +               bufsize *= 2;
> +               if (bufsize > MAX_NS_DEBUG_BUFSIZE)
> +                       return -ENOBUFS;
> +       }
> +       seq_write(f, buf, len);
> +       kvfree(buf);
> +       return 0;
> +}

I guess we could first change ref_tracker_dir_snprint(tracker, buf, bufsize)
to ref_tracker_dir_snprint(tracker, buf, bufsize, &needed) to avoid
too many tries in this loop.

Most of ref_tracker_dir_snprint() runs with hard irq being disabled...


diff --git a/drivers/gpu/drm/i915/intel_wakeref.c
b/drivers/gpu/drm/i915/intel_wakeref.c
index 87f2460473127af65a9a793c7f1934fafe41e79e..6650421b4f00c318adec72cd7c17a76832f14cce
100644
--- a/drivers/gpu/drm/i915/intel_wakeref.c
+++ b/drivers/gpu/drm/i915/intel_wakeref.c
@@ -208,7 +208,7 @@ void intel_ref_tracker_show(struct ref_tracker_dir *dir,
        if (!buf)
                return;

-       count = ref_tracker_dir_snprint(dir, buf, buf_size);
+       count = ref_tracker_dir_snprint(dir, buf, buf_size, NULL);
        if (!count)
                goto free;
        /* printk does not like big buffers, so we split it */
diff --git a/include/linux/ref_tracker.h b/include/linux/ref_tracker.h
index 8eac4f3d52547ccbaf9dcd09962ce80d26fbdff8..19bd42088434b661810082350a9d5afcbff6a88a
100644
--- a/include/linux/ref_tracker.h
+++ b/include/linux/ref_tracker.h
@@ -46,7 +46,7 @@ void ref_tracker_dir_print_locked(struct ref_tracker_dir *dir,
 void ref_tracker_dir_print(struct ref_tracker_dir *dir,
                           unsigned int display_limit);

-int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf,
size_t size);
+int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf,
size_t size, size_t *needed);

 int ref_tracker_alloc(struct ref_tracker_dir *dir,
                      struct ref_tracker **trackerp, gfp_t gfp);
@@ -77,7 +77,7 @@ static inline void ref_tracker_dir_print(struct
ref_tracker_dir *dir,
 }

 static inline int ref_tracker_dir_snprint(struct ref_tracker_dir *dir,
-                                         char *buf, size_t size)
+                                         char *buf, size_t size,
size_t *needed)
 {
        return 0;
 }
diff --git a/lib/ref_tracker.c b/lib/ref_tracker.c
index cf5609b1ca79361763abe5a3a98484a3ee591ff2..d8d02dab7ce67caf91ae22f9391abe2c92481c7f
100644
--- a/lib/ref_tracker.c
+++ b/lib/ref_tracker.c
@@ -65,6 +65,7 @@ ref_tracker_get_stats(struct ref_tracker_dir *dir,
unsigned int limit)
 struct ostream {
        char *buf;
        int size, used;
+       size_t needed;
 };

 #define pr_ostream(stream, fmt, args...) \
@@ -76,6 +77,7 @@ struct ostream {
        } else { \
                int ret, len = _s->size - _s->used; \
                ret = snprintf(_s->buf + _s->used, len, pr_fmt(fmt), ##args); \
+               _s->needed += ret; \
                _s->used += min(ret, len); \
        } \
 })
@@ -141,7 +143,7 @@ void ref_tracker_dir_print(struct ref_tracker_dir *dir,
 }
 EXPORT_SYMBOL(ref_tracker_dir_print);

-int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf,
size_t size)
+int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf,
size_t size, size_t *needed)
 {
        struct ostream os = { .buf = buf, .size = size };
        unsigned long flags;
@@ -150,6 +152,8 @@ int ref_tracker_dir_snprint(struct ref_tracker_dir
*dir, char *buf, size_t size)
        __ref_tracker_dir_pr_ostream(dir, 16, &os);
        spin_unlock_irqrestore(&dir->lock, flags);

+       if (needed)
+               *needed = os.needed;
        return os.used;
 }
 EXPORT_SYMBOL(ref_tracker_dir_snprint);
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index ce4b01cc7aca15ddf74f4160580871868e693fb8..61ce889ab29c2b726eab064b0ecb39838db30229
100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -1529,13 +1529,14 @@ struct ns_debug_net {
        struct dentry *notrefcnt;
 };

-#define MAX_NS_DEBUG_BUFSIZE   (32 * PAGE_SIZE)
+#define MAX_NS_DEBUG_BUFSIZE   (1 << 20)

 static int
 ns_debug_tracker_show(struct seq_file *f, void *v)
 {
        struct ref_tracker_dir *tracker = f->private;
        int len, bufsize = PAGE_SIZE;
+       size_t needed;
        char *buf;

        for (;;) {
@@ -1543,12 +1544,12 @@ ns_debug_tracker_show(struct seq_file *f, void *v)
                if (!buf)
                        return -ENOMEM;

-               len = ref_tracker_dir_snprint(tracker, buf, bufsize);
+               len = ref_tracker_dir_snprint(tracker, buf, bufsize, &needed);
                if (len < bufsize)
                        break;

                kvfree(buf);
-               bufsize *= 2;
+               bufsize = round_up(needed, PAGE_SIZE);
                if (bufsize > MAX_NS_DEBUG_BUFSIZE)
                        return -ENOBUFS;
        }
Jeff Layton March 25, 2025, 11:47 a.m. UTC | #4
On Tue, 2025-03-25 at 06:21 +0100, Eric Dumazet wrote:
> On Mon, Mar 24, 2025 at 9:24 PM Jeff Layton <jlayton@kernel.org> wrote:
> > 
> > CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display
> > its tracking info. Add a new net_ns directory in debugfs. Have a
> > directory in there for every net, with refcnt and notrefcnt files that
> > show the currently tracked active and passive references.
> > 
> > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > ---
> > Recently, I had a need to track down some long-held netns references,
> > and discovered CONFIG_NET_NS_REFCNT_TRACKER. The main thing that seemed
> > to be missing from it though is a simple way to view the currently held
> > references on the netns. This adds files in debugfs for this.
> 
> Thanks for working on this, this is a very good idea.
> 

Thanks. I needed it when I was tracking down refs held by NFS and RPC
clients. My thinking was that we could add the netns specific dirs to
debugfs with the refcnt and notrefcnt files in it for now, and could
add new files to it later as needed for other netns-related things.


> 
> > +#define MAX_NS_DEBUG_BUFSIZE   (32 * PAGE_SIZE)
> > +
> > +static int
> > +ns_debug_tracker_show(struct seq_file *f, void *v)
> > +{
> > +       struct ref_tracker_dir *tracker = f->private;
> > +       int len, bufsize = PAGE_SIZE;
> > +       char *buf;
> > +
> > +       for (;;) {
> > +               buf = kvmalloc(bufsize, GFP_KERNEL);
> > +               if (!buf)
> > +                       return -ENOMEM;
> > +
> > +               len = ref_tracker_dir_snprint(tracker, buf, bufsize);
> > +               if (len < bufsize)
> > +                       break;
> > +
> > +               kvfree(buf);
> > +               bufsize *= 2;
> > +               if (bufsize > MAX_NS_DEBUG_BUFSIZE)
> > +                       return -ENOBUFS;
> > +       }
> > +       seq_write(f, buf, len);
> > +       kvfree(buf);
> > +       return 0;
> > +}
> 
> I guess we could first change ref_tracker_dir_snprint(tracker, buf, bufsize)
> to ref_tracker_dir_snprint(tracker, buf, bufsize, &needed) to avoid
> too many tries in this loop.
> 
> Most of ref_tracker_dir_snprint() runs with hard irq being disabled...
> 

Ouch, yeah that sounds like a good idea.

> 
> diff --git a/drivers/gpu/drm/i915/intel_wakeref.c
> b/drivers/gpu/drm/i915/intel_wakeref.c
> index 87f2460473127af65a9a793c7f1934fafe41e79e..6650421b4f00c318adec72cd7c17a76832f14cce
> 100644
> --- a/drivers/gpu/drm/i915/intel_wakeref.c
> +++ b/drivers/gpu/drm/i915/intel_wakeref.c
> @@ -208,7 +208,7 @@ void intel_ref_tracker_show(struct ref_tracker_dir *dir,
>         if (!buf)
>                 return;
> 
> -       count = ref_tracker_dir_snprint(dir, buf, buf_size);
> +       count = ref_tracker_dir_snprint(dir, buf, buf_size, NULL);
>         if (!count)
>                 goto free;
>         /* printk does not like big buffers, so we split it */
> diff --git a/include/linux/ref_tracker.h b/include/linux/ref_tracker.h
> index 8eac4f3d52547ccbaf9dcd09962ce80d26fbdff8..19bd42088434b661810082350a9d5afcbff6a88a
> 100644
> --- a/include/linux/ref_tracker.h
> +++ b/include/linux/ref_tracker.h
> @@ -46,7 +46,7 @@ void ref_tracker_dir_print_locked(struct ref_tracker_dir *dir,
>  void ref_tracker_dir_print(struct ref_tracker_dir *dir,
>                            unsigned int display_limit);
> 
> -int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf,
> size_t size);
> +int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf,
> size_t size, size_t *needed);
> 
>  int ref_tracker_alloc(struct ref_tracker_dir *dir,
>                       struct ref_tracker **trackerp, gfp_t gfp);
> @@ -77,7 +77,7 @@ static inline void ref_tracker_dir_print(struct
> ref_tracker_dir *dir,
>  }
> 
>  static inline int ref_tracker_dir_snprint(struct ref_tracker_dir *dir,
> -                                         char *buf, size_t size)
> +                                         char *buf, size_t size,
> size_t *needed)
>  {
>         return 0;
>  }
> diff --git a/lib/ref_tracker.c b/lib/ref_tracker.c
> index cf5609b1ca79361763abe5a3a98484a3ee591ff2..d8d02dab7ce67caf91ae22f9391abe2c92481c7f
> 100644
> --- a/lib/ref_tracker.c
> +++ b/lib/ref_tracker.c
> @@ -65,6 +65,7 @@ ref_tracker_get_stats(struct ref_tracker_dir *dir,
> unsigned int limit)
>  struct ostream {
>         char *buf;
>         int size, used;
> +       size_t needed;
>  };
> 
>  #define pr_ostream(stream, fmt, args...) \
> @@ -76,6 +77,7 @@ struct ostream {
>         } else { \
>                 int ret, len = _s->size - _s->used; \
>                 ret = snprintf(_s->buf + _s->used, len, pr_fmt(fmt), ##args); \
> +               _s->needed += ret; \
>                 _s->used += min(ret, len); \
>         } \
>  })
> @@ -141,7 +143,7 @@ void ref_tracker_dir_print(struct ref_tracker_dir *dir,
>  }
>  EXPORT_SYMBOL(ref_tracker_dir_print);
> 
> -int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf,
> size_t size)
> +int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf,
> size_t size, size_t *needed)
>  {
>         struct ostream os = { .buf = buf, .size = size };
>         unsigned long flags;
> @@ -150,6 +152,8 @@ int ref_tracker_dir_snprint(struct ref_tracker_dir
> *dir, char *buf, size_t size)
>         __ref_tracker_dir_pr_ostream(dir, 16, &os);
>         spin_unlock_irqrestore(&dir->lock, flags);
> 
> +       if (needed)
> +               *needed = os.needed;
>         return os.used;
>  }
>  EXPORT_SYMBOL(ref_tracker_dir_snprint);
> diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
> index ce4b01cc7aca15ddf74f4160580871868e693fb8..61ce889ab29c2b726eab064b0ecb39838db30229
> 100644
> --- a/net/core/net_namespace.c
> +++ b/net/core/net_namespace.c
> @@ -1529,13 +1529,14 @@ struct ns_debug_net {
>         struct dentry *notrefcnt;
>  };
> 
> -#define MAX_NS_DEBUG_BUFSIZE   (32 * PAGE_SIZE)
> +#define MAX_NS_DEBUG_BUFSIZE   (1 << 20)
> 
>  static int
>  ns_debug_tracker_show(struct seq_file *f, void *v)
>  {
>         struct ref_tracker_dir *tracker = f->private;
>         int len, bufsize = PAGE_SIZE;
> +       size_t needed;
>         char *buf;
> 
>         for (;;) {
> @@ -1543,12 +1544,12 @@ ns_debug_tracker_show(struct seq_file *f, void *v)
>                 if (!buf)
>                         return -ENOMEM;
> 
> -               len = ref_tracker_dir_snprint(tracker, buf, bufsize);
> +               len = ref_tracker_dir_snprint(tracker, buf, bufsize, &needed);
>                 if (len < bufsize)
>                         break;
> 
>                 kvfree(buf);
> -               bufsize *= 2;
> +               bufsize = round_up(needed, PAGE_SIZE);
>                 if (bufsize > MAX_NS_DEBUG_BUFSIZE)
>                         return -ENOBUFS;
>         }
Andrew Lunn March 25, 2025, 1 p.m. UTC | #5
On Mon, Mar 24, 2025 at 08:37:57PM -0400, Jeff Layton wrote:
> On Mon, 2025-03-24 at 21:50 +0100, Andrew Lunn wrote:
> > On Mon, Mar 24, 2025 at 04:24:47PM -0400, Jeff Layton wrote:
> > > CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display
> > > its tracking info. Add a new net_ns directory in debugfs. Have a
> > > directory in there for every net, with refcnt and notrefcnt files that
> > > show the currently tracked active and passive references.
> > 
> > Hi Jeff
> > 
> > CONFIG_NET_NS_REFCNT_TRACKER is just an instance of
> > CONFIG_REF_TRACKER.
> > 
> > It would be good to explain why you are doing it at the netdev level,
> > rather than part of the generic CONFIG_REF_TRACKER level. Why would
> > other subsystems not benefit from having their reference trackers in
> > debugfs?
> > 
> > 
> 
> Mostly because I just needed the NS_REFCNT_TRACKER at the time.
> 
> I'm OK with making this more general, but all of those subsystems using
> refcount trackers would need to add the infrastructure to create
> directories to track them.

The base directory can be created by ref_tracker itself.

> To whit:
> 
> What would the directory structure look like for the more general case?

 ref_tracker_dir_init(struct ref_tracker_dir *dir,
					unsigned int quarantine_count,
					const char *name)

and then each tracker use 'name' as the debugfs filename. You would
need to check it is unique before creating it, since uniqueness was
probably not a requirement before.

	Andrew
diff mbox series

Patch

diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index 4303f2a4926243e2c0ff0c0387383cd8e0658019..b7ce8c7621bdf6055fa4aaa5cbfce111ca86b047 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -1512,3 +1512,154 @@  const struct proc_ns_operations netns_operations = {
 	.owner		= netns_owner,
 };
 #endif
+
+#ifdef CONFIG_DEBUG_FS
+#ifdef CONFIG_NET_NS_REFCNT_TRACKER
+
+#include <linux/debugfs.h>
+
+static struct dentry *ns_debug_dir;
+static unsigned int ns_debug_net_id;
+
+struct ns_debug_net {
+	struct dentry *netdir;
+	struct dentry *refcnt;
+	struct dentry *notrefcnt;
+};
+
+#define MAX_NS_DEBUG_BUFSIZE	(32 * PAGE_SIZE)
+
+static int
+ns_debug_tracker_show(struct seq_file *f, void *v)
+{
+	struct ref_tracker_dir *tracker = f->private;
+	int len, bufsize = PAGE_SIZE;
+	char *buf;
+
+	for (;;) {
+		buf = kvmalloc(bufsize, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+
+		len = ref_tracker_dir_snprint(tracker, buf, bufsize);
+		if (len < bufsize)
+			break;
+
+		kvfree(buf);
+		bufsize *= 2;
+		if (bufsize > MAX_NS_DEBUG_BUFSIZE)
+			return -ENOBUFS;
+	}
+	seq_write(f, buf, len);
+	kvfree(buf);
+	return 0;
+}
+
+static int
+ns_debug_ref_open(struct inode *inode, struct file *filp)
+{
+	int ret;
+	struct net *net = inode->i_private;
+
+	ret = single_open(filp, ns_debug_tracker_show, &net->refcnt_tracker);
+	if (!ret)
+		net_passive_inc(net);
+	return ret;
+}
+
+static int
+ns_debug_notref_open(struct inode *inode, struct file *filp)
+{
+	int ret;
+	struct net *net = inode->i_private;
+
+	ret = single_open(filp, ns_debug_tracker_show, &net->notrefcnt_tracker);
+	if (!ret)
+		net_passive_inc(net);
+	return ret;
+}
+
+static int
+ns_debug_ref_release(struct inode *inode, struct file *filp)
+{
+	struct net *net = inode->i_private;
+
+	net_passive_dec(net);
+	return single_release(inode, filp);
+}
+
+static const struct file_operations ns_debug_ref_fops = {
+	.owner		= THIS_MODULE,
+	.open		= ns_debug_ref_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= ns_debug_ref_release,
+};
+
+static const struct file_operations ns_debug_notref_fops = {
+	.owner		= THIS_MODULE,
+	.open		= ns_debug_notref_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= ns_debug_ref_release,
+};
+
+static int
+ns_debug_init_net(struct net *net)
+{
+	struct ns_debug_net *dnet = net_generic(net, ns_debug_net_id);
+	char name[11]; /* 10 decimal digits + NULL term */
+	int len;
+
+	len = snprintf(name, sizeof(name), "%u", net->ns.inum);
+	if (len >= sizeof(name))
+		return -EOVERFLOW;
+
+	dnet->netdir = debugfs_create_dir(name, ns_debug_dir);
+	if (IS_ERR(dnet->netdir))
+		return PTR_ERR(dnet->netdir);
+
+	dnet->refcnt = debugfs_create_file("refcnt", S_IFREG | 0400, dnet->netdir,
+					   net, &ns_debug_ref_fops);
+	if (IS_ERR(dnet->refcnt)) {
+		debugfs_remove(dnet->netdir);
+		return PTR_ERR(dnet->refcnt);
+	}
+
+	dnet->notrefcnt = debugfs_create_file("notrefcnt", S_IFREG | 0400, dnet->netdir,
+					      net, &ns_debug_notref_fops);
+	if (IS_ERR(dnet->notrefcnt)) {
+		debugfs_remove_recursive(dnet->netdir);
+		return PTR_ERR(dnet->notrefcnt);
+	}
+
+	return 0;
+}
+
+static void
+ns_debug_exit_net(struct net *net)
+{
+	struct ns_debug_net *dnet = net_generic(net, ns_debug_net_id);
+
+	debugfs_remove_recursive(dnet->netdir);
+}
+
+static struct pernet_operations ns_debug_net_ops = {
+	.init = ns_debug_init_net,
+	.exit = ns_debug_exit_net,
+	.id = &ns_debug_net_id,
+	.size = sizeof(struct ns_debug_net),
+};
+
+static int __init ns_debug_init(void)
+{
+	ns_debug_dir = debugfs_create_dir("net_ns", NULL);
+	if (IS_ERR(ns_debug_dir))
+		return PTR_ERR(ns_debug_dir);
+
+	register_pernet_subsys(&ns_debug_net_ops);
+	return 0;
+}
+late_initcall(ns_debug_init);
+#endif /* CONFIG_NET_NS_REFCNT_TRACKER */
+#endif /* CONFIG_DEBUG_FS */