如何使用PF_PACKET阻止数据包进入内核协议栈
PF_PACKET用户态TCP栈:数据包过滤与内核拦截方案
一、仅捕获目标TCP 8000端口的数据包
PF_PACKET套接字默认会接收所有指定协议(此处为ETH_P_IP)的二层帧,为避免不必要的数据包进入用户态,推荐使用内核态BPF过滤器,效率远高于用户态过滤。
实现步骤
- 构造BPF过滤规则,匹配目标TCP端口为8000的IP数据包:
规则逻辑:仅保留IP协议为TCP(0x06)且TCP目标端口为8000的数据包。 - 通过
setsockopt给PF_PACKET套接字绑定该过滤器。
修改后的代码示例(添加BPF过滤)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <linux/if_packet.h> #include <linux/filter.h> void fatal_error(const char *msg) { perror(msg); exit(EXIT_FAILURE); } int main() { int sock; struct sockaddr_ll srv_addr; struct ifreq s_ifr; sock = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); if (sock == -1) fatal_error("socket()"); char *str_devname = "lo"; strncpy(s_ifr.ifr_name, str_devname, sizeof(s_ifr.ifr_name)); int rc = ioctl(sock, SIOCGIFINDEX, &s_ifr); if (rc == -1) { fatal_error("ioctl()"); } memset(&srv_addr, 0, sizeof(srv_addr)); srv_addr.sll_family = AF_PACKET; srv_addr.sll_protocol = htons(ETH_P_IP); srv_addr.sll_ifindex = s_ifr.ifr_ifindex; if (bind(sock, (const struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) fatal_error("bind()"); // 绑定BPF过滤器:仅接收目标TCP 8000端口的数据包 struct sock_filter filter[] = { { 0x28, 0, 0, 0x0000000c }, // 加载IP协议字段偏移 { 0x15, 0, 11, 0x00000006 }, // 非TCP协议则丢弃 { 0x30, 0, 0, 0x00000017 }, // 加载TCP头偏移 { 0x15, 0, 9, 0x00001f90 }, // 目标端口非8000(0x1f90)则丢弃 { 0x28, 0, 0, 0x00000014 }, // 加载IP总长度 { 0x05, 0, 0, 0x00000000 }, // 跳转至规则结束 { 0x06, 0, 0, 0x0000ffff }, // 保留符合条件的数据包 { 0x06, 0, 0, 0x00000000 }, // 丢弃其他数据包 }; struct sock_fprog bpf_prog = { .len = sizeof(filter) / sizeof(struct sock_filter), .filter = filter, }; if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf_prog, sizeof(bpf_prog)) < 0) { fatal_error("setsockopt(SO_ATTACH_FILTER)"); } // 数据包接收逻辑 unsigned char buf[4096]; ssize_t len; while ((len = recvfrom(sock, buf, sizeof(buf), 0, NULL, NULL)) > 0) { printf("Received packet size: %zd bytes\n", len); // 此处添加IP/TCP头解析、用户态TCP栈处理逻辑 } close(sock); return 0; }
注:你也可以通过tcpdump -dd 'tcp dst port 8000'命令生成对应的BPF字节码,替换上述filter数组内容,确保规则准确性。
二、阻止内核处理目标TCP 8000端口的数据包
方法1:iptables拦截(简单易用)
直接在INPUT链中DROP目标TCP 8000端口的数据包,让内核协议栈无法接收这些流量:
# 拦截所有接口的TCP 8000端口流量 iptables -A INPUT -p tcp --dport 8000 -j DROP # 仅拦截回环接口的流量(若仅测试本地回环) iptables -A INPUT -i lo -p tcp --dport 8000 -j DROP
如需恢复规则,执行:
iptables -D INPUT -p tcp --dport 8000 -j DROP
方法2:XDP内核态拦截(高性能)
如果需要处理高流量场景,可使用XDP(eXpress Data Path)程序在网卡驱动层面直接丢弃目标数据包,避免数据包进入内核协议栈。该方法实现相对复杂,但性能远高于iptables。
内容的提问来源于stack exchange,提问作者samofoz
相关产品推荐
相关产品推荐

