如何监控TCP/UDP端口活动(不绑定)以实现进程资源优化?
解决方案:监控进程网络活动实现智能暂停/恢复
核心思路
因为要支持UDP(无连接,无法通过连接状态判断活动),必须实时感知数据包到达进程的事件,而非定期轮询。同时要避免中间人方案,所以直接追踪进程的网络接收行为或内核层面的包交付事件是最优路径。
方案1:eBPF(推荐,Linux平台,低性能开销)
eBPF可以在内核层面追踪UDP数据包交付到目标进程的事件,完全不干扰进程正常通信,能获取源IP,且性能损耗极低。
实现步骤
- 编写eBPF程序,追踪
udp_recvmsg内核函数(对应用户态的recvmsg/recvfrom调用),过滤目标端口对应的套接字,记录数据包到达的进程PID。 - 用户态程序监听eBPF的事件输出:
- 当收到数据包事件时,若进程处于暂停状态,发送
SIGCONT恢复;同时重置该进程的无活动计时器。 - 计时器超时(比如300秒)时,向进程发送
SIGSTOP,或调用Docker API执行docker pause。
- 当收到数据包事件时,若进程处于暂停状态,发送
代码示例(Python + BCC)
from bcc import BPF import time from datetime import datetime import os import signal # 配置目标端口和超时时间 TARGET_PORT = 12345 INACTIVE_TIMEOUT = 300 # 秒 # 存储进程的最后活动时间 last_activity = {} # eBPF程序:追踪UDP数据包接收 bpf_text = """ #include <uapi/linux/ptrace.h> #include <linux/udp.h> #include <linux/socket.h> BPF_PERF_OUTPUT(events); struct event { u32 pid; u16 dport; }; int trace_udp_recvmsg(struct pt_regs *ctx) { struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx); u16 dport = sk->__sk_common.skc_dport; dport = ntohs(dport); if (dport != %d) { return 0; } struct event e = {}; e.pid = bpf_get_current_pid_tgid() >> 32; e.dport = dport; events.perf_submit(ctx, &e, sizeof(e)); return 0; } """ % TARGET_PORT b = BPF(text=bpf_text) b.attach_kprobe(event="udp_recvmsg", fn_name="trace_udp_recvmsg") def handle_event(cpu, data, size): event = b["events"].event(data) pid = event.pid # 更新最后活动时间 last_activity[pid] = datetime.now() # 恢复进程(如果已暂停) try: os.kill(pid, signal.SIGCONT) except OSError: # 进程未暂停或不存在,忽略 pass b["events"].open_perf_buffer(handle_event) # 定时检查超时 while True: b.perf_buffer_poll(timeout=1000) now = datetime.now() for pid, last_time in list(last_activity.items()): if (now - last_time).total_seconds() >= INACTIVE_TIMEOUT: try: # 暂停进程 os.kill(pid, signal.SIGSTOP) print(f"Paused process {pid} due to inactivity") except OSError: # 进程已结束或无法暂停,从字典移除 del last_activity[pid]
优缺点
- 优点:性能损耗极小,不干扰进程通信,能获取源IP,支持UDP;若配合Docker API,可直接替换
kill调用为容器暂停/恢复命令。 - 缺点:需要root权限,仅支持Linux平台;需要安装BCC库(
apt install bcc)。
方案2:系统调用追踪(Ptrace,无需特殊内核支持)
通过ptrace追踪目标进程的recvfrom/recvmsg系统调用,当这些调用被触发时,说明有UDP数据包到达,以此触发恢复和计时器重置。
实现步骤
- 附加到目标进程(需知道PID或通过端口查找PID,用
ss -uap | grep :<port>)。 - 追踪
recvfrom和recvmsg系统调用的返回事件,每次触发时重置计时器,恢复进程。 - 计时器超时则发送
SIGSTOP。
代码示例(C语言简化版)
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <sys/ptrace.h> #include <sys/wait.h> #include <time.h> #define INACTIVE_TIMEOUT 300 // 秒 int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <pid>\n", argv[0]); exit(1); } pid_t pid = atoi(argv[1]); time_t last_active = time(NULL); // 附加到进程 if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) { perror("ptrace attach"); exit(1); } waitpid(pid, NULL, 0); while (1) { // 追踪系统调用进入 ptrace(PTRACE_SYSCALL, pid, NULL, NULL); waitpid(pid, NULL, 0); int syscall = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL); // 检查是否是recvfrom(2)或recvmsg(2)(x86_64系统调用号) if (syscall == 22 || syscall == 23) { // 等待系统调用返回 ptrace(PTRACE_SYSCALL, pid, NULL, NULL); waitpid(pid, NULL, 0); // 更新活动时间 last_active = time(NULL); // 恢复进程 kill(pid, SIGCONT); } else { // 继续执行 ptrace(PTRACE_CONT, pid, NULL, NULL); waitpid(pid, NULL, 0); } // 检查超时 if (time(NULL) - last_active >= INACTIVE_TIMEOUT) { kill(pid, SIGSTOP); printf("Paused process %d\n", pid); } sleep(1); } ptrace(PTRACE_DETACH, pid, NULL, NULL); return 0; }
优缺点
- 优点:无需特殊内核功能,可跨Linux发行版;若监控自己启动的进程,无需root权限(监控其他进程需要root)。
- 缺点:
ptrace会增加进程的性能开销;需要提前知道目标进程PID,或额外编写端口转PID的逻辑;跨平台性差(仅Linux)。
方案3:跨平台思路(Windows/macOS兼容)
对于非Linux平台,可结合平台原生API:
- macOS:使用
DTrace替代eBPF,追踪udp_recvmsg或用户态的recvfrom调用,逻辑与eBPF方案一致。 - Windows:使用
ETW(事件跟踪)追踪UdpReceive事件,或通过Win32 API的GetUdpTable结合进程套接字关联,但UDP无连接需结合计时器和进程的IO事件。
不过跨平台方案普遍存在性能损耗更高、实现复杂度大的问题,优先推荐Linux的eBPF方案。
Docker容器适配
若目标是Docker容器,可将上述方案与Docker API结合:
- 替代
os.kill调用,使用Docker SDK(比如Python的docker库)执行container.pause()和container.unpause()。 - 通过
docker inspect获取容器内进程的PID,或直接监控容器的网络端口对应的事件。
内容的提问来源于stack exchange,提问作者SugoiDogo
相关产品推荐
相关产品推荐

