暗无天日

=============>DarkSun的个人博客

用 /proc/pid/fd 追踪进程网络连接

这篇文章 介绍了如何用 /proc 文件系统追踪进程的网络连接。不需要安装任何工具,任何 Linux 系统都能用。

原理

/proc 是 Linux 内核暴露的虚拟文件系统,每个进程的网络连接都会以 socket 文件形式出现在 /proc/<pid>/fd 下。

场景一:已知端口,找进程

  1. /proc/net/tcp 找到端口对应的 inode 号
  2. 用 inode 号反查是哪个进程持有这个 socket

完整的脚本如下:

#!/bin/bash
# 用法: ./find_pid_by_port.sh <十六进制端口>
# 示例: ./find_pid_by_port.sh 2328  (查找端口 9000)

HEX_PORT=$1

if [ -z "$HEX_PORT" ]; then
  echo "用法: $0 <十六进制端口>"
  echo "示例: $0 2328  # 查找端口 9000"
  exit 1
fi

# 步骤1: 从 /proc/net/tcp 找到端口对应的 inode(只找 LISTEN 状态)
INODE=$(awk -v port="$HEX_PORT" '$2 ~ ":"port"$" && $4 == "0A" {print $10}' /proc/net/tcp)

if [ -z "$INODE" ]; then
  echo "未找到端口 $((16#$HEX_PORT)) 的监听连接"
  exit 1
fi

echo "端口 $((16#$HEX_PORT)) 对应 inode: $INODE"

# 步骤2: 遍历所有进程的 fd,找到指向该 inode 的 socket
for pid_dir in /proc/[0-9]*/; do
  pid=$(basename "$pid_dir")
  for fd in "$pid_dir"fd/*; do
    link=$(readlink "$fd" 2>/dev/null)
    if [ "$link" = "socket:[$INODE]" ]; then
      comm=$(cat "$pid_dir/comm" 2>/dev/null || echo "unknown")
      echo "PID: $pid ($comm)"
      break
    fi
  done
done

场景二:已知 PID,查看所有连接

#!/bin/bash
# 用法: ./show_pid_connections.sh <PID>

PID=$1

if [ -z "$PID" ]; then
  echo "用法: $0 <PID>"
  exit 1
fi

if [ ! -d "/proc/$PID" ]; then
  echo "进程 $PID 不存在"
  exit 1
fi

echo "=== 进程 $PID 的所有网络连接 ==="
echo ""

# 收集该进程所有 socket 的 inode
INODES=$(ls -la /proc/$PID/fd 2>/dev/null | grep -oP 'socket:\[\K[0-9]+' | sort -u)

if [ -z "$INODES" ]; then
  echo "未找到网络连接"
  exit 0
fi

# 从 /proc/net/tcp 和 tcp6 中查找这些 inode 的详细信息
for inode in $INODES; do
  # TCP4
  LINE=$(awk -v ino="$inode" '$10 == ino {print $0}' /proc/net/tcp 2>/dev/null)
  if [ -n "$LINE" ]; then
    LOCAL=$(echo "$LINE" | awk '{print $2}')
    REMOTE=$(echo "$LINE" | awk '{print $3}')
    STATE=$(echo "$LINE" | awk '{print $4}')
    
    # 解析十六进制地址
    L_IP=$(echo "$LOCAL" | cut -d: -f1 | sed 's/\(.\{2\}\)/0x\1 /g' | xargs printf "%d.%d.%d.%d")
    L_PORT=$((16#$(echo "$LOCAL" | cut -d: -f2)))
    R_IP=$(echo "$REMOTE" | cut -d: -f1 | sed 's/\(.\{2\}\)/0x\1 /g' | xargs printf "%d.%d.%d.%d")
    R_PORT=$((16#$(echo "$REMOTE" | cut -d: -f2)))
    
    echo "TCP4  $L_IP:$L_PORT -> $R_IP:$R_PORT  state=$STATE"
  fi
done

输出示例:

=== 进程 12345 的所有网络连接 ===

TCP4  0.0.0.0:80 -> 0.0.0.0:0  state=0A
TCP4  10.0.2.15:22 -> 192.168.1.5:54322  state=01
TCP4  10.0.2.15:443 -> 203.0.113.45:9000  state=01

state 值含义:=0A= 是 LISTEN,=01= 是 ESTABLISHED,=06= 是 TIME_WAIT。

> 为什么要用 inode? 因为 /proc/<pid>/net/tcp 是系统全局视图,每个进程都能看到所有 TCP 连接。如果直接 grep 每个进程的 net/tcp,会全部命中。正确方法是:先从全局 /proc/net/tcp 找到 inode(内核给 socket 的唯一编号),再遍历进程的 /proc/<pid>/fd 找到哪个文件描述符指向这个 inode。

常见端口的十六进制对照

十进制 十六进制
80 0050
443 01BB
8080 1F90
3000 0BB8
5000 1388
9000 2328
Linux : 网络 : /proc : 调试