netstat and ss alternative to get the list of listening TCP ports

4 min read | by Jordi Prats

On docker containers we might not have neither netstat nor ss installed, yet we can still get the list of listening TCP ports by looking at the /proc filesystem

The files /proc/net/tcp and /proc/net/tcp6 contain information about currently active TCP connections and listening ports. But this information is not in a human friendly format:

$ cat /proc/net/tcp  sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode   0: 0100007F:DB73 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 2469436 1 0000000000000000 100 0 0 10 0   1: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 50509 1 0000000000000000 100 0 0 10 0   2: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 101 0 40528 1 0000000000000000 100 0 0 10 0   3: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 44230 1 0000000000000000 100 0 0 10 0   4: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 5452235 1 0000000000000000 100 0 0 10 0   5: 0100007F:DBB7 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 2235992 1 0000000000000000 100 0 0 10 0   6: 00000000:88B7 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 2418076 1 0000000000000000 100 0 0 10 0   7: 00000000:01BB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 44802 1 0000000000000000 100 0 0 10 0   8: 0100007F:D3A3 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 2234403 1 0000000000000000 100 0 0 10 0   9: 00000000:962B 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 2418083 1 0000000000000000 100 0 0 10 0   10: 0100007F:A0ED 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 2235993 1 0000000000000000 100 0 0 10 0   11: 00000000:9470 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 2418082 1 0000000000000000 100 0 0 10 0   (...) 

To be able to extract just the TCP listening ports, the forth column is the state of the connection. If it is "0A" means that it is listening, so we can get the list of listening addresses from this list using awk:

$ awk '$4 == "0A" { print $2 }' /proc/net/tcp /proc/net/tcp6 0100007F:DB73 017AA8C0:0035 3500007F:0035 00000000:0016 0100007F:0277 0100007F:DBB7 00000000:88B7 00000000:01BB 0100007F:D3A3 00000000:962B 0100007F:A0ED 00000000:9470 00000000000000000000000000000000:06B4 00000000000000000000000000000000:0016 00000000000000000000000001000000:0277 00000000000000000000000000000000:9858 00000000000000000000000000000000:01BB 

We can then transform the hexadecimal notation to human readable IPs an ports using:

awk ' function hextodec(str,ret,n,i,k,c){  ret = 0  n = length(str)  for (i = 1; i <= n; i++) {  c = tolower(substr(str, i, 1))  k = index("123456789abcdef", c)  ret = ret * 16 + k  }  return ret } function getIP(str,ret){  ret=hextodec(substr(str,index(str,":")-2,2));  for (i=5; i>0; i-=2) {  ret = ret"."hextodec(substr(str,i,2))  }  ret = ret":"hextodec(substr(str,index(str,":")+1,4))  return ret } $4 == "0A" { print getIP($2) }' /proc/net/tcp /proc/net/tcp6 

It's output will look like:

$ awk ' function hextodec(str,ret,n,i,k,c){  ret = 0  n = length(str)  for (i = 1; i <= n; i++) {  c = tolower(substr(str, i, 1))  k = index("123456789abcdef", c)  ret = ret * 16 + k  }  return ret } function getIP(str,ret){  ret=hextodec(substr(str,index(str,":")-2,2));  for (i=5; i>0; i-=2) {  ret = ret"."hextodec(substr(str,i,2))  }  ret = ret":"hextodec(substr(str,index(str,":")+1,4))  return ret } $4 == "0A" { print getIP($2) }' /proc/net/tcp /proc/net/tcp6 127.0.0.1:56179 192.168.122.1:53 127.0.0.53:53 0.0.0.0:22 127.0.0.1:631 127.0.0.1:56247 0.0.0.0:34999 0.0.0.0:443 127.0.0.1:54179 0.0.0.0:38443 127.0.0.1:41197 0.0.0.0:38000 0.0.0.0:1716 0.0.0.0:22 0.0.0.0:631 0.0.0.0:39000 0.0.0.0:443 

We'll need to take into account that for IPv6 it's output it's not quite right, but yet we are getting a similar output we would get using netstat or ss:

$ netstat -tln |tail +3 | awk '{print $4}' 127.0.0.1:56179 192.168.122.1:53 127.0.0.53:53 0.0.0.0:22 127.0.0.1:631 127.0.0.1:56247 0.0.0.0:34999 0.0.0.0:443 127.0.0.1:54179 0.0.0.0:38443 127.0.0.1:41197 0.0.0.0:38000 :::1716 :::22 ::1:631 :::39000 :::443 

Posted on 07/07/2021