ip netns list will only list the networks namespaces which were configured via the ip-netns(8) utility.
The lsns(1) program from the util-linux package is also highly deficient: it will list only those namespaces which are accessible via the /proc/<pid>/ns/* files, omitting all the per-thread namespaces and those kept alive by a bind mount or an open file descriptor.
The following demo script tries to do better: it will look for bind mounts through the /proc/<pid>/task/<tid>/mountinfo files, and for open fds through /proc/<pid>/task/<tid>/fd files.
For each namespace, it will print a path through which it is accessible:
# perl ./lsnsx.pl
...
mnt 3
4026531840 /proc/1/ns/mnt
4026531860 /proc/30/ns/mnt
4026532374 /proc/3119/ns/mnt
net 6
4026531992 /proc/1/ns/net
4026532376 /proc/25781/fd/9
4026532465 /proc/28373/fd/7
...
You can then use that path with nsenter(1), eg.
nsenter --net=/proc/28373/fd/7 ip link
The script could be easily changed to do that itself, or show other info, like the whole list of processes which use a namespace.
If the path is not accessible, it will follow it by the parent/mount ids and the /proc/<pid>/mountinfo file where it was found. Escaped newlines, tabs and spaces will be left as they are:
net 9
...
4026532732 /v/net\040ns /proc/3119/mountinfo 60 41
Since it has to read all those /proc/*/task/* files, this may get slow on any machine where heavily threaded programs are used; unfortunately I wasn't able to find any quick way to check if two threads/tasks share the same namespace(s): kcmp(2) will only tell if they're sharing the same address space, file descriptor table, etc; not anything namespace-related.
lsnsx.pl:
#! /usr/bin/perl
use strict;
my %t2n = (
# the CLONE_NEW* from sched.h
0x02000000 => "cgroup", 0x04000000 => "uts", 0x08000000 => "ipc",
0x10000000 => "user", 0x20000000 => "pid", 0x40000000 => "net",
0x00020000 => "mnt" # CLONE_NEWNS
);
my (%ns);
my $nsfs_dev = (stat "/proc/self/ns/mnt")[0];
my $type = shift || qr/\w+/;
sub unescape { $_[0] =~ s/\\([0-7]{3})/chr oct $1/ger }
# NS_GET_NSTYPE = 0xb7 << 8 | 3
sub nstype { my $h; open $h, $_[0] and ioctl $h, 0xb7 << 8 | 3, 0 }
for(</proc/[0-9]*/task/[0-9]*/{ns/*,fd/*,mountinfo}>){
s{/([0-9]*)/task/\1/}{/$1/};
if(my ($procpid) = m{(.*)/mountinfo$}){
open my $h, $_ or next;
LOOP: while(<$h>){
next unless (my @s = split)[2] eq "0:$nsfs_dev";
if(my($t, $i) = $s[3] =~ /^($type):\[(\d+)\]$/){
next if exists $ns{$t}{$i};
for ("", "$procpid/root"){
my ($d, $i1) =
(stat $_.unescape $s[4])[0, 1];
$ns{$t}{$i} = $_.$s[4], next LOOP
if $d == $nsfs_dev && $i == $i1;
}
$ns{$t}{$i} = "@s[4, 0, 1] $procpid/mountinfo"
}
}
}elsif(m{/ns/}){
$ns{$1}{$2} //= $_ if readlink =~ /^($type):\[(\d+)\]$/;
}else{
next unless my ($dev, $ino) = stat $_;
next unless $dev == $nsfs_dev;
next unless my $t = nstype $_;
next if ($t = $t2n{$t}) and $t !~ $type;
$ns{$t // '???'}{$ino} //= $_;
}
}
for my $type (sort keys %ns){
my $h = $ns{$type}; my @i = sort {$a<=>$b} keys %$h;
printf "%-8s %d\n", $type, scalar @i;
printf " %-11d %s\n", $_, $$h{$_} for @i;
}