35

I need to know if a process with a given PID has opened a port without using external commands. I must then use the /proc filesystem. I can read the /proc/$PID/net/tcp file for example and get information about TCP ports opened by the process. However, on a multithreaded process, the /proc/$PID/task/$TID directory will also contains a net/tcp file. My question is :

do I need to go over all the threads net/tcp files, or will the port opened by threads be written into the process net/tcp file.

6 Answers 6

41

I can read the /proc/$PID/net/tcp file for example and get information about TCP ports opened by the process.

That file is not a list of tcp ports opened by the process. It is a list of all open tcp ports in the current network namespace, and for processes running in the same network namespace is identical to the contents of /proc/net/tcp.

To find ports opened by your process, you would need to get a list of socket descriptors from /proc/<pid>/fd, and then match those descriptors to the inode field of /proc/net/tcp.

3
  • Thank you, for your answer. And if the process is multithreaded, do I need to go over all the fd directory of each thread ? Or does the /proc/pid/fd directory "inherit" the /proc/pid/task/tid/fd directories ? Commented Aug 29, 2015 at 14:54
  • I'm not sure, but that seems like an easy thing to test. Commented Aug 29, 2015 at 14:56
  • 9
    @rmonjo Threads can't open files, only processes can. The fd directory of a thread just repeats the fd directory of the process. Commented Aug 29, 2015 at 16:33
15

Please

cat /proc/$PID/net/tcp 

and you will get output like this

 sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 0: 00000000:01BB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 2891985097 1 0000000000000000 100 0 0 10 0 

The second column (local_address) of the output shows the port in hexadecimal. Use your Programming Calculator to convert the hex code to decimal.

For example over here, the port :01BB (in hex) is equal to 443 (in decimal) which is the HTTPS default port.

3

As a side note, the /proc/net/tcp has been superseded by the NETLINK_SOCK_DIAG, which essentially gives you the same data in a binary form and, from what the implementers say, is faster & safer than reading from the /proc/... folder.

There are libraries (libmnl is the base library) and the feature is available in many languages (C/C++ can directly access these, Go & Python have extensions, I think perl has too, and certainly many others).

Since you do not say which language you are using, it's difficult to say more at this point. I have a C sample implementation on Stackoverflow here: https://stackoverflow.com/questions/68425240/can-the-netlink-sock-diag-interface-be-used-to-listen-for-listen-and-close

2
  • 1
    "Since you do not say which language you are using, it's difficult to say more at this point." => it's been 6 years don't worry I'm fine 😂. I was looking for a language agnostic solution anyway Commented Aug 9, 2021 at 16:09
  • 2
    @rmonjo Yes. Others who end up on your question, though, may be interested in using the newer interface available to them. Commented Aug 9, 2021 at 23:36
3

I wrote a small Bash script as an example that outputs all processes that open connections without lsof or netstat.

https://github.com/rtulke/portcheck/blob/main/pcng.sh

#!/bin/bash # table headline printf "%-9s %-9s %-20s %-21s %-21s %s\n" "COMMAND" "PID" "USER" "LOCAL ADDRESS" "REMOTE ADDRESS" "STATE" # TCP-Status-Codes declare -A states=( [01]="ESTABLISHED" [02]="SYN_SENT" [03]="SYN_RECV" [04]="FIN_WAIT1" [05]="FIN_WAIT2" [06]="TIME_WAIT" [07]="CLOSE" [08]="CLOSE_WAIT" [09]="LAST_ACK" [0A]="LISTEN" [0B]="CLOSING" ) # Convert IP:Port from hex to readable format parse_addr() { ip_hex=${1%:*} port_hex=${1#*:} ip_dec=$(printf "%d.%d.%d.%d" 0x${ip_hex:6:2} 0x${ip_hex:4:2} 0x${ip_hex:2:2} 0x${ip_hex:0:2}) port_dec=$((16#$port_hex)) echo "$ip_dec:$port_dec" } # Mapping: Inode → "PID CMD USER" declare -A inode_map # Go through all processes for pid_dir in /proc/[0-9]*; do pid=${pid_dir##*/} # command and user cmd=$(cat "$pid_dir/comm" 2>/dev/null) || continue uid=$(awk '/Uid:/ {print $2}' "$pid_dir/status" 2>/dev/null) || continue user=$(getent passwd "$uid" | cut -d: -f1) [ -z "$cmd" ] && continue # Go through all file descriptors of the process. for fd in "$pid_dir"/fd/*; do [ -L "$fd" ] || continue link=$(readlink "$fd" 2>/dev/null) || continue echo "$link" | grep -qE '^socket:\[[0-9]+\]$' || continue inode=$(echo "$link" | sed -n 's/^socket:\[\([0-9]\+\)\]$/\1/p') [ -n "$inode" ] || continue inode_map["$inode"]="$pid $cmd $user" done done # Debug: # echo "Inodes found: ${#inode_map[@]}" # Go through TCP connections tail -n +2 /proc/net/tcp | while read -r line; do fields=($line) local_addr_parsed=$(parse_addr "${fields[1]}") rem_addr_parsed=$(parse_addr "${fields[2]}") state_code="${fields[3]}" inode="${fields[9]}" state=${states[$state_code]} if [[ -n "${inode_map[$inode]}" ]]; then read -r pid cmd user <<< "${inode_map[$inode]}" printf "%-9s %-9s %-20s %-21s %-21s %s\n" "$cmd" "$pid" "$user" "$local_addr_parsed" "$rem_addr_parsed" "$state" fi 
0
2

Here is sample script which discovers inode of socket descriptor and displays PID of running process which has opened this socket.

port=9090; hex_port=$(python -c 'print(hex('$port')[2:])'); inode=$(cat /proc/net/tcp | grep ":$hex_port" | awk '{print $10}'); for i in $(ps axo pid); do ls -l /proc/$i/fd 2> /dev/null | grep -q ":\[$inode\]" && echo $i; done 
3
  • that's super useful, thank you! Commented Dec 14, 2022 at 13:08
  • 1
    hex_port=$(printf %x $port) Commented Feb 26, 2024 at 22:40
  • hex_port="$(printf '%04X' "${port}")" Commented Apr 7 at 0:38
0

Improving on https://unix.stackexchange.com/a/718108/610026

  • don't use python
  • force 4-character representation of hex value with leading zeros, so it works well with ports below 1024
  • handle multiple matches in /proc/net/tcp
port=9090; hex_port=$(printf '%04x' $port); inodes=$(grep ":$hex_port " /proc/net/tcp | awk '{print $10}'); string=':\['$(echo $inodes | sed 's/ /\\]\\|:\\[/g')'\]'; for i in $(ps axo pid); do ls -l /proc/$i/fd 2> /dev/null | grep -q "$string" && echo $i; done 

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.