How to find namespaces in a Linux system

Namespaces in Linux are heavily used by many applications, e.g. LXC, Docker and Openstack.
Question: How to find all existing namespaces in a Linux system?

The answer is quite difficult, because it’s easy to hide a namespace or more exactly make it difficult to find them.

Exploring the system

In the basic/default setup Ubuntu 12.04 and higher provide namespaces for

  • ipc for IPC objects and POSIX message queues
  • mnt for filesystem mountpoints
  • net for network abstraction (VRF)
  • pid to provide a separated, isolated process ID number space
  • uts to isolate two system identifiers — nodename and domainname – to be used by uname

These namespaces are shown for every process in the system. if you execute as root

ls -lai /proc/1/ns

60073292 dr-x--x--x 2 root root 0 Dec 15 18:23 .
   10395 dr-xr-xr-x 9 root root 0 Dec  4 11:07 ..
60073293 lrwxrwxrwx 1 root root 0 Dec 15 18:23 ipc -> ipc:[4026531839]
60073294 lrwxrwxrwx 1 root root 0 Dec 15 18:23 mnt -> mnt:[4026531840]
60073295 lrwxrwxrwx 1 root root 0 Dec 15 18:23 net -> net:[4026531968]
60073296 lrwxrwxrwx 1 root root 0 Dec 15 18:23 pid -> pid:[4026531836]
60073297 lrwxrwxrwx 1 root root 0 Dec 15 18:23 uts -> uts:[4026531838]

you get the list of attached namespaces of the init process using PID=1. Even this process has attached namespaces. These are the default namespaces for ipc, mnt, net, pid and uts. For example, the default net namespace is using the ID net:[4026531968]. The number in the brackets is a inode number.

In order to find other namespaces with attached processes in the system, we use these entries of the PID=1 as a reference. Any process or thread in the system, which has not the same namespace ID as PID=1 is not belonging to the DEFAULT namespace.

Additionally, you find the namespaces created by „ip netns add <NAME>“ by default in /var/run/netns/ .

The python code

The python code below is listing all non default namespaces in a system. The program flow is

  • Get the reference namespaces from the init process (PID=1). Assumption: PID=1 is assigned to the default namespaces supported by the system
  • Loop through /var/run/netns/ and add the entries to the list
  • Loop through /proc/ over all PIDs and look for entries in /proc/<PID>/ns/ which are not the same as for PID=1 and add then to the list
  • Print the result
#!/usr/bin/python
#
# List all Namespaces (works for Ubuntu 12.04 and higher)
#
# (C) Ralf Trezeciak    2013-2014
#
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
import os
import fnmatch

if os.geteuid() != 0:
    print "This script must be run as root\nBye"
    exit(1)

def getinode( pid , type):
    link = '/proc/' + pid + '/ns/' + type
    ret = ''
    try:
        ret = os.readlink( link )
    except OSError as e:
        ret = ''
        pass
    return ret

#
# get the running command
def getcmd( p ):
    try:
        cmd = open(os.path.join('/proc', p, 'cmdline'), 'rb').read()
        if cmd == '':
            cmd = open(os.path.join('/proc', p, 'comm'), 'rb').read()
        cmd = cmd.replace('\x00' , ' ')
        cmd = cmd.replace('\n' , ' ')
        return cmd
    except:
        return ''
#
# look for docker parents
def getpcmd( p ):
    try:
        f = '/proc/' + p + '/stat'
        arr = open( f, 'rb').read().split()
        cmd = getcmd( arr[3] )
        if cmd.startswith( '/usr/bin/docker' ):
            return 'docker'
    except:
        pass
    return ''
#
# get the namespaces of PID=1
# assumption: these are the namespaces supported by the system
#
nslist = os.listdir('/proc/1/ns/')
if len(nslist) == 0:
    print 'No Namespaces found for PID=1'
    exit(1)
#print nslist
#
# get the inodes used for PID=1
#
baseinode = []
for x in nslist:
    baseinode.append( getinode( '1' , x ) )
#print "Default namespaces: " , baseinode
err = 0
ns = []
ipnlist = []
#
# loop over the network namespaces created using "ip"
#
try:
    netns = os.listdir('/var/run/netns/')
    for p in netns:
        fd = os.open( '/var/run/netns/' + p, os.O_RDONLY )
        info = os.fstat(fd)
        os.close( fd)
        ns.append( '-- net:[' + str(info.st_ino) + '] created by ip netns add ' + p )
        ipnlist.append( 'net:[' + str(info.st_ino) + ']' )
except:
    # might fail if no network namespaces are existing
    pass
#
# walk through all pids and list diffs
#
pidlist = fnmatch.filter(os.listdir('/proc/'), '[0123456789]*')
#print pidlist
for p in pidlist:
    try:
        pnslist = os.listdir('/proc/' + p + '/ns/')
        for x in pnslist:
            i = getinode ( p , x )
            if i != '' and i not in baseinode:
                cmd = getcmd( p )
                pcmd = getpcmd( p )
                if pcmd != '':
                    cmd = '[' + pcmd + '] ' + cmd
                tag = ''
                if i in ipnlist:
                    tag='**' 
                ns.append( p + ' ' + i + tag + ' ' + cmd)
    except:
        # might happen if a pid is destroyed during list processing
        pass
#
# print the stuff
#
print '{0:>10}  {1:20}  {2}'.format('PID','Namespace','Thread/Command')
for e in ns:
    x = e.split( ' ' , 2 )
    print '{0:>10}  {1:20}  {2}'.format(x[0],x[1],x[2][:60])
#

Copy the script to your system as listns.py , and run it as root using python listns.py

       PID  Namespace             Thread/Command
        --  net:[4026533172]      created by ip netns add qrouter-c33ffc14-dbc2-4730-b787-4747
        --  net:[4026533112]      created by ip netns add qrouter-5a691ed3-f6d3-4346-891a-3b59
        --  net:[4026533050]      created by ip netns add qdhcp-02e848cb-72d0-49df-8592-2f7a03
        --  net:[4026532992]      created by ip netns add qdhcp-47cfcdef-2b34-43b8-a504-6720e5
       297  mnt:[4026531856]      kdevtmpfs 
      3429  net:[4026533050]**    dnsmasq --no-hosts --no-resolv --strict-order --bind-interfa
      3429  mnt:[4026533108]      dnsmasq --no-hosts --no-resolv --strict-order --bind-interfa
      3446  net:[4026532992]**    dnsmasq --no-hosts --no-resolv --strict-order --bind-interfa
      3446  mnt:[4026533109]      dnsmasq --no-hosts --no-resolv --strict-order --bind-interfa
      3486  net:[4026533050]**    /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
      3486  mnt:[4026533107]      /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
      3499  net:[4026532992]**    /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
      3499  mnt:[4026533110]      /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
      4117  net:[4026533112]**    /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
      4117  mnt:[4026533169]      /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
     41998  net:[4026533172]**    /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil
     41998  mnt:[4026533229]      /usr/bin/python /usr/bin/neutron-ns-metadata-proxy --pid_fil

The example above is from an Openstack network node. The first four entries are entries created using the command ip. The entry PID=297 is a kernel thread and no user process. All other processes listed, are started by Openstack agents. These process are using network and mount namespaces. PID entries marked with ‚**‘ have a corresponding entry created with the ip command.

When a docker command is started, the output is:

       PID  Namespace             Thread/Command
        --  net:[4026532676]      created by ip netns add test
        35  mnt:[4026531856]      kdevtmpfs 
      6189  net:[4026532585]      [docker] /bin/bash 
      6189  uts:[4026532581]      [docker] /bin/bash 
      6189  ipc:[4026532582]      [docker] /bin/bash 
      6189  pid:[4026532583]      [docker] /bin/bash 
      6189  mnt:[4026532580]      [docker] /bin/bash 

The docker child running in the namespaces is marked using [docker].

On a node running mininet and a simple network setup the output looks like:

       PID  Namespace             Thread/Command
        14  mnt:[4026531856]      kdevtmpfs 
      1198  net:[4026532150]      bash -ms mininet:h1 
      1199  net:[4026532201]      bash -ms mininet:h2 
      1202  net:[4026532252]      bash -ms mininet:h3 
      1203  net:[4026532303]      bash -ms mininet:h4

Googles Chrome Browser

Googles Chrome Browser makes extensive use of the linux namespaces. Start Chrome and run the python script. The output looks like:

       PID  Namespace             Thread/Command
        63  mnt:[4026531856]      kdevtmpfs 
     30747  net:[4026532344]      /opt/google/chrome/chrome --type=zygote 
     30747  pid:[4026532337]      /opt/google/chrome/chrome --type=zygote 
     30753  net:[4026532344]      /opt/google/chrome/nacl_helper 
     30753  pid:[4026532337]      /opt/google/chrome/nacl_helper 
     30754  net:[4026532344]      /opt/google/chrome/chrome --type=zygote 
     30754  pid:[4026532337]      /opt/google/chrome/chrome --type=zygote 
     30801  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30801  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30807  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30807  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30813  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30813  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30820  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30820  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30829  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30829  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30835  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30835  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30841  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30841  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30887  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30887  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30893  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30893  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30901  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30901  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30910  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30910  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30915  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30915  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30923  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30923  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30933  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30933  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30938  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30938  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30944  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     30944  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     31271  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     31271  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     31538  net:[4026532344]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for
     31538  pid:[4026532337]      /opt/google/chrome/chrome --type=renderer --lang=en-US --for

Chrome makes use of pid and network namespaces to restrict the access of subcomponents. The network namespace does not have a link in /var/run/netns/.

Conclusion

It’s quite hard to explore the Linux namespace. There is a lot of documentation flowing around. I did not find any simple program to look for namespaces in the system. So I wrote one.

The script cannot find a network namespace, which do not have any process attached to AND which has no reference in /var/run/netns/. If root creates the reference inode somewhere else in the filesystem, you may only detect network ports (ovs port, veth port on one side), which are not attached to a known network namespace –> an unknown guest might be on your system using a „hidden“ (not so easy to find) network namespace.

And — Linux namespaces can be stacked.

Updated: 17/01/2021 — 13:18