Read /proc/net/tcp If ss, nc, netstat, telnet and curl Aren't Installed
If you want to see if a process is bound to a port there's a few tools you can use but if they are not there you can cat /proc/net/tcp.
Prefer video? Here it is on YouTube.
I found myself in a situation where ss
, nc
, netstat
, telnet
and curl
weren’t available and I wasn’t able to install them. In case you didn’t know,
curl can create telnet connections.
All of these tools let you see if an address is bound to a specific port. Maybe you’re troubleshooting something and you want to make sure that the process you care about is really running on a specific port and bound to a specific address like 0.0.0.0 or 127.0.0.1 depending on what you’re doing.
If you’re running things in a containerized world using Docker or Kubernetes, you could be in a super locked down container running as a non-root user with no user escalation permissions and very few utilities installed.
In the Kubernetes case you can always spawn a debug container to analyze the state of another container with custom tools but you might not be using Kubernetes or maybe the environment you’re in won’t allow you to spin up new containers.
This is where learning how to read cat /proc/net/tcp
or cat /proc/net/tcp6
could be useful.
# What is /proc/net/tcp[6]?
- On Linux systems a process will write its address:port info in its protocol file
- If you’re on macOS
/proc/net
won’t be available but if you happen to be running a Linux container, you can connect to it and find details about that container’s ports
- If you’re on macOS
- There’s
tcp
,udp
and many other network related files in/proc/net
- You’ll often see
tcp
andtcp6
depending on if a socket is bound to IPv4 or IPv6
- You’ll often see
Containerized Processes with Docker
If you’re using Docker and you have a container running with something like -p 8080:80
and you want to check the port on your host then you’ll want to use
cat /proc/net/tcp6
because as far as I know Docker will bind a port back to
your host over an IPv6 wildcard socket (::
) and that socket is able to handle
both IPv4 and IPv6 traffic for all addresses.
If instead you used -p 127.0.0.1:8080:80
then Docker will use an IPv4 socket
and you’ll find it in cat /proc/net/tcp
. That’s an important detail if
you’re wondering why it might be different depending on which projects you’re
working on.
# Parsing the Output of /proc/net/tcp[6]
The way to parse both files (tcp6
vs tcp
) is the same.
There’s a number of columns but we’re only going to focus on local_address
.
I’ve removed the other columns so it’s easier to read here.
Here’s the state of the system on my dev box. In a bit we’ll go over an example Docker command so you can follow along:
tcp6 vs tcp
$ cat /proc/net/tcp6
sl local_address
0: 00000000000000000000000000000000:0521
0
(first) entry is Hugo running and published on0.0.0.0:1313
through Docker
$ cat /proc/net/tcp
sl local_address
0: 0100007F:1F90
0
(first) entry is NGiNX running and published on127.0.0.1:8080
through Docker
16-Bit Hexadecimal Port
The local_address
ends with :XXXX
which is the port in 16-bit
hexadecimal format.
On the command line you can use printf
to convert this to decimal along with
many other tools. With most tools, make sure your hex number starts with 0x
:
$ printf "%d\n" 0x0521
1313
$ printf "%d\n" 0x1F90
8080
16-Bit Hexadecimal Address
The IPv6 and IPv4 address values for ::
and 0.0.0.0
are:
00000000000000000000000000000000
00000000
For these you probably won’t need to convert them to decimal but there’s quite a few details to be aware of when you do need to do a conversion.
For example 0100007F
is the IPv4 address for 127.0.0.1
.
Here’s a workflow to convert that into an IP address:
- Break it up into bytes which are chunks of 2 characters such as
01 00 00 7F
- Reverse it to
7F 00 00 01
- It’s in little-endian format (stores the least-significant byte at the smallest address)
- Convert each byte into decimal:
7F
is127
00
is0
00
is0
01
is1
Boom, we just arrived at 127.0.0.1
. Funny enough after seeing this enough
you’ll be able to pick out 0100007F
as 127.0.0.1
without going through
that process every time.
Converting Decimal into 16-bit Hexadecimal
If you have a bunch of entries in /proc/net/tcp
and you want to look for a
specific port you can always run grep ":XXXX" /proc/net/tcp
and replace
XXXX
with the hex value you want.
This is where knowing how to do the conversion helps because you might want to
convert 5432
(decimal) into the value you’ll be grepping for (hex):
$ printf "%04X\n" 5432
1538
You can verify this with the ports we used above (1313 / 8080):
$ printf "%04X\n" 1313
0521
$ printf "%04X\n" 8080
1F90
Pretty handy! Now you have enough information to parse this output and search for specific ports to see if your process is listening and running correctly.
With that said, if you have tools like ss
or netstat
available, it’s much
easier to get this information since it converts and aligns everything for you:
$ ss -ltpn
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:*
LISTEN 0 4096 *:1313 *:*
# Following Along With Docker
This will further drive home the above examples by showing different values depending on if we’re talking about the host’s machine or inside of the container and if you’re on macOS it will also let you follow along.
To produce the NGiNX output I used before, I ran this command:docker container run --rm --name procnet -p 127.0.0.1:8080:80 nginx:mainline-bookworm
Now you can run this in a 2nd terminal:
$ docker container exec -it procnet cat /proc/net/tcp
sl local_address
0: 00000000:0050
It’s interesting right?
On the host, we published this information:
- The address was bound to
127.0.0.1
but here we see0.0.0.0
for the address - The port was bound to
8080
but here we see0050
which converts to80
All of the above makes sense. We’re now running cat /proc/net/tcp
inside of
the container so the host and port information is related to that.
The NGiNX image we use binds to 0.0.0.0
and runs on port 80
. As a separate
step we’ve published that to 127.0.0.1:8080
on the host.
Technically you can run docker container exec procnet cat /proc/net/tcp6
too and see the IPv6 information because inside of the container NGiNX binds on
both IPv4 and IPv6.
I thought it would be helpful to include the above so you can see both sides of how an address:port works in a container having its address:port published back to the host.
The video below goes over all of the above.
# Demo Video
Timestamps
- 0:33 – Use cases
- 1:12 – The basics of /proc/net/tcp
- 2:58 – How Docker binds addresses
- 4:57 – What we’re going to cover and hexadecimal formats
- 6:48 – Parsing the port value
- 7:38 – Parsing the address value
- 9:29 – Converting decimal to hexadecimal for filtering
- 10:53 – Use ss, netstat, etc. if you have it
- 11:27 – Checking out /proc/net/tcp in a container
When was the last time you had to manually parse /proc/net? Let me know below.