Home How to get process socket information from /proc
Post
Cancel

How to get process socket information from /proc

When I want to see the connections opened by some process I normally use commands like lsof and ss. But these commands are not always available. In these scenarios, I found that it’s not that difficult to obtain the same information from the /proc filesystem.

I will show here how this can be done by first checking the output from lsof and ss, then finding the same information from the proc filesystem.

Test setup

For this test, I will start a Jenkins container. The container will have TCP port 8080 open, and I will access it from my browser to generate connections. The socket inodes and the port numbers will change during the tests, because the connections are not kept open for more than a few seconds.

We will be looking at the connections as seen from the host and from inside the container. We will see that network information fro inside the container is not directly available to commands running on the host. Also we will see that lsof is not available inside the container. Finally we will examine connection information by looking in the /proc filesystem inside the container.

Connections as seen by the host

First let’s find the id of the process as seen by the host:

1
2
3
4
5
root@leo-ubuntu-20:~# ps awwwxf | grep jenkins
  26863 ?        Ss     0:00  |       \_ /sbin/tini -- /usr/local/bin/jenkins.sh
  26890 ?        Sl     0:56  |           \_ java -Duser.home=/var/jenkins_home -Djenkins.model.Jenkins.slaveAgentPort=50000 -jar /usr/share/jenkins/jenkins.war
  27312 pts/2    S+     0:00                          \_ grep --color=auto jenkins
root@leo-ubuntu-20:~#

We see that the PID is 26890. Now let’s check the open files as listed by lsof. I’m filtering by sock to avoid the huge list of open files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
root@leo-ubuntu-20:~# lsof -p 26890 | grep sock
lsof: WARNING: can't stat() fuse.gvfsd-fuse file system /run/user/1000/gvfs
      Output information may be incomplete.
lsof: WARNING: can't stat() fuse file system /run/user/1000/doc
      Output information may be incomplete.
java    26890  leo   18u     sock    0,8      0t0 606974 protocol: UNIX
java    26890  leo  137u     sock    0,8      0t0 607014 protocol: TCP
java    26890  leo  138u     sock    0,8      0t0 607015 protocol: UNIX
java    26890  leo  142u     sock    0,8      0t0 618040 protocol: TCP
java    26890  leo  145u     sock    0,8      0t0 618049 protocol: TCP
java    26890  leo  146u     sock    0,8      0t0 617834 protocol: TCP
java    26890  leo  147u     sock    0,8      0t0 608390 protocol: TCP
java    26890  leo  149u     sock    0,8      0t0 618055 protocol: TCP
java    26890  leo  150u     sock    0,8      0t0 618061 protocol: TCP
java    26890  leo  152u     sock    0,8      0t0 617295 protocol: TCP
root@leo-ubuntu-20:~#

We can see two UNIX sockets and a few TCP sockets. The TCP information is not available, as Jenkins is running in a different network namespace from the host.

If we try using ss to see the open connections, we hit the same limitation. The information is not available in the network namespace of the host:

1
2
root@leo-ubuntu-20:~# ss -p | grep 26890
root@leo-ubuntu-20:~#

But if we run the same command in the namespace of the process we can see detailed information about the connections:

1
2
3
4
5
6
7
8
9
10
root@leo-ubuntu-20:~# nsenter -t 26890 -n ss -p | grep 26890
u_str  ESTAB  0       0                      * 607015                  * 0       users:(("java",pid=26890,fd=138))
u_str  ESTAB  0       0                      * 606974                  * 0       users:(("java",pid=26890,fd=18))
tcp    ESTAB  0       0             172.17.0.2:http-alt       172.17.0.1:54584   users:(("java",pid=26890,fd=149))
tcp    ESTAB  0       0             172.17.0.2:http-alt       172.17.0.1:54590   users:(("java",pid=26890,fd=151))
tcp    ESTAB  0       0             172.17.0.2:http-alt       172.17.0.1:54586   users:(("java",pid=26890,fd=150))
tcp    ESTAB  0       0             172.17.0.2:http-alt       172.17.0.1:54580   users:(("java",pid=26890,fd=142))
tcp    ESTAB  0       0             172.17.0.2:http-alt       172.17.0.1:54570   users:(("java",pid=26890,fd=153))
tcp    ESTAB  0       0             172.17.0.2:http-alt       172.17.0.1:54582   users:(("java",pid=26890,fd=146))
root@leo-ubuntu-20:~#

Connections as seen inside the container

We now enter inside the container. We will run all further commands from here:

1
2
3
4
5
root@leo-ubuntu-20:~# docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                               NAMES
54e9a2cefd94        jenkins/jenkins:lts   "/sbin/tini -- /usr/…"   34 minutes ago      Up 34 minutes       0.0.0.0:8080->8080/tcp, 50000/tcp   thirsty_albattani
root@leo-ubuntu-20:~# docker exec -ti 54e9 bash
jenkins@54e9a2cefd94:/$

Let’s check the process IDs here:

1
2
3
4
5
6
7
jenkins@54e9a2cefd94:/$ ps awwxf
    PID TTY      STAT   TIME COMMAND
     92 pts/0    Ss     0:00 bash
     97 pts/0    R+     0:00  \_ ps awwxf
      1 ?        Ss     0:00 /sbin/tini -- /usr/local/bin/jenkins.sh
      6 ?        Sl     1:00 java -Duser.home=/var/jenkins_home -Djenkins.model.Jenkins.slaveAgentPort=50000 -jar /usr/share/jenkins/jenkins.war
jenkins@54e9a2cefd94:/$

And let’s see what information we can collect with lsof and ss:

1
2
3
4
5
6
7
8
9
10
11
jenkins@54e9a2cefd94:/$ lsof -p 6
bash: lsof: command not found
jenkins@54e9a2cefd94:/$ ss -p
Netid State Recv-Q Send-Q   Local Address:Port       Peer Address:Port
u_str ESTAB 0      0                    * 607015                * 0      users:(("java",pid=6,fd=138))
u_str ESTAB 0      0                    * 606974                * 0      users:(("java",pid=6,fd=18))
tcp   ESTAB 0      0           172.17.0.2:http-alt     172.17.0.1:54730  users:(("java",pid=6,fd=146))
tcp   ESTAB 0      0           172.17.0.2:http-alt     172.17.0.1:54718  users:(("java",pid=6,fd=152))
tcp   ESTAB 0      0           172.17.0.2:http-alt     172.17.0.1:54722  users:(("java",pid=6,fd=142))
tcp   ESTAB 0      0           172.17.0.2:http-alt     172.17.0.1:54728  users:(("java",pid=6,fd=145))
jenkins@54e9a2cefd94:/$

We can see that lsof is not available and ss provides the full information. Now let’s take a look at the information from /proc.

Checking the /proc filesystem

In the /proc filesystem there is a folder for each process id. Each of these folders contain information about that specific process. We will be looking at the fd subfolder, which lists the file descriptors of that process. Again, I’m filtering on sock to avoid the huge list of files:

1
2
3
4
5
6
7
8
9
10
11
jenkins@54e9a2cefd94:/$ ls -l /proc/6/fd | grep sock
lrwx------ 1 jenkins jenkins 64 Aug 10 07:51 137 -> socket:[607014]
lrwx------ 1 jenkins jenkins 64 Aug 10 07:51 138 -> socket:[607015]
lrwx------ 1 jenkins jenkins 64 Aug 10 07:51 142 -> socket:[619101]
lrwx------ 1 jenkins jenkins 64 Aug 10 07:51 145 -> socket:[619107]
lrwx------ 1 jenkins jenkins 64 Aug 10 07:51 146 -> socket:[619108]
lrwx------ 1 jenkins jenkins 64 Aug 10 07:51 147 -> socket:[608390]
lrwx------ 1 jenkins jenkins 64 Aug 10 07:56 149 -> socket:[618922]
lrwx------ 1 jenkins jenkins 64 Aug 10 07:56 150 -> socket:[619110]
lrwx------ 1 jenkins jenkins 64 Aug 10 07:51 18 -> socket:[606974]
jenkins@54e9a2cefd94:/$

We now have the list of sockets but we need to match these to network addresses and ports. This information can be found in files from /proc/net/, for example proc/net/tcp for tcp connections.

Let’s take a socket inode from the output above and look for it in all files from /proc/net while discarding any error messages:

1
2
3
jenkins@54e9a2cefd94:/$ grep 606974 /proc/net/* 2>/dev/null
/proc/net/unix:0000000000000000: 00000002 00000000 00000000 0001 03 606974
jenkins@54e9a2cefd94:/$

We can see that this is a unix socket. Now let’s get the same info for all the sockets.

I will use a longer command to check the file descriptors for process with id 6, select the lines matching socket, then selecting the text that comes after [ and before ]. For each inode obtained in this way, I will grep the files in /proc/net looking for that specific inode.

1
2
3
4
5
6
7
8
9
10
11
12
jenkins@54e9a2cefd94:/$ for s in `ls -l /proc/6/fd | grep sock | cut -d'[' -f2 | cut -d ']' -f1` ; do grep $s /proc/net/* 2>/dev/null ; done
/proc/net/tcp:   1: 00000000:1F90 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 607014 1 0000000000000000 100 0 0 10 0
/proc/net/unix:0000000000000000: 00000002 00000000 00000000 0001 03 607015
/proc/net/tcp:   8: 020011AC:1F90 010011AC:D63E 01 00000000:00000000 00:00000000 00000000  1000        0 621793 1 0000000000000000 20 4 20 10 -1
/proc/net/tcp:   4: 020011AC:1F90 010011AC:D63A 01 00000000:00000000 00:00000000 00000000  1000        0 621699 1 0000000000000000 20 4 3 10 -1
/proc/net/tcp:   6: 020011AC:1F90 010011AC:D642 01 00000000:00000000 00:00000000 00000000  1000        0 621807 1 0000000000000000 20 4 21 10 -1
/proc/net/tcp:   0: 00000000:C350 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 608390 1 0000000000000000 100 0 0 10 0
/proc/net/tcp:   3: 020011AC:1F90 010011AC:D646 01 00000000:00000000 00:00000000 00000000  1000        0 621808 1 0000000000000000 20 4 19 10 -1
/proc/net/tcp:   5: 020011AC:1F90 010011AC:D64A 01 00000000:00000000 00:00000000 00000000  1000        0 621809 1 0000000000000000 20 4 21 10 -1
/proc/net/tcp:   9: 020011AC:1F90 010011AC:D64E 01 00000000:00000000 00:00000000 00000000  1000        0 621810 1 0000000000000000 20 4 20 10 -1
/proc/net/unix:0000000000000000: 00000002 00000000 00000000 0001 03 606974
jenkins@54e9a2cefd94:/$

The information may seem difficult to read because it’s written in hex, but it’s just what we needed. Let’s check one of the TCP connections:

1
/proc/net/tcp:   8: 020011AC:1F90 010011AC:D63E 01 00000000:00000000 00:00000000 00000000  1000        0 621793 1 0000000000000000 20 4 20 10 -1

We can see two fields that look like IP and port information: 020011AC:1F90 where 02 00 11 AC is 2, 0, 17, 172 so the IP address is 172.17.0.2. Port 1F90 is 8080 in decimal, so the information here is 172.17.0.2:8080 which is the local address. By converting the second field 010011AC:D63E we get 172.17.0.1:54846 which is the other end of this connection.

Now we know that this line shows a connection between 172.17.0.2:8080 and 172.17.0.1:54846.

There is another line in the output that looks interesting:

1
/proc/net/tcp:   1: 00000000:1F90 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 607014 1 0000000000000000 100 0 0 10 0

Here, the connection looks as if it were from 0.0.0.0:0 to 0.0.0.0:8080. This is showing that the process is listening on all local IPs (0.0.0.0) on port 8080.

So, by looking only at the /proc filesystem we found the same information that was provided by lsof and ss. This can be useful when these tools are not available, or maybe even just for fun.

References

For more details about the /proc/ filesystem and the other commands used here, please check:

This post is licensed under CC BY 4.0 by the author.
Contents