Linux环境下如何让RAW Socket进程过滤同网卡UDP进程的收发数据包?
嗨,这个场景我太熟悉了——用ETH_P_ALL创建的RAW套接字确实会把本地进程收发的所有数据包都捞进来,要过滤掉进程2的UDP包,有几个实用的方案,我给你详细拆解下:
方案1:内核层面用BPF过滤器过滤(最推荐)
这是效率最高的方式,因为过滤逻辑在内核层完成,进程1根本不会收到那些要丢弃的包。你可以通过SO_ATTACH_FILTER套接字选项给RAW套接字绑定BPF规则,精准排除进程2相关的UDP包。
分两种场景处理:
场景A:进程2使用固定UDP端口
如果进程2的本地UDP端口(比如1234)或远程端口是固定的,直接用端口作为过滤条件就行。你可以用tcpdump生成对应的BPF字节码,然后嵌入到代码中。
比如要排除源端口为1234或目的端口为1234的UDP包,先执行这条命令生成BPF字节码:
tcpdump -dd 'not (udp and (src port 1234 or dst port 1234))'
把输出的结果直接用到代码里,示例如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <linux/filter.h> #include <linux/if_packet.h> #include <net/ethernet.h> #include <arpa/inet.h> #define PROCESS2_UDP_PORT 1234 int main() { // 创建RAW套接字 int raw_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (raw_sock < 0) { perror("Failed to create RAW socket"); exit(EXIT_FAILURE); } // 绑定BPF过滤器:排除进程2的UDP包 struct sock_filter filter[] = { { 0x28, 0, 0, 0x0000000c }, { 0x15, 0, 11, 0x00000800 }, { 0x30, 0, 0, 0x00000017 }, { 0x15, 0, 9, 0x00000011 }, { 0x28, 0, 0, 0x00000014 }, { 0x45, 7, 0, 0x00001fff }, { 0xb1, 0, 0, 0x0000000e }, { 0x28, 0, 0, 0x00000000 }, { 0x15, 2, 0, htons(PROCESS2_UDP_PORT) }, { 0x28, 0, 0, 0x00000002 }, { 0x15, 0, 1, htons(PROCESS2_UDP_PORT) }, { 0x6, 0, 0, 0x00040000 }, { 0x6, 0, 0, 0x00000000 }, }; struct sock_fprog fprog = { .len = sizeof(filter) / sizeof(filter[0]), .filter = filter, }; if (setsockopt(raw_sock, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) { perror("Failed to attach BPF filter"); close(raw_sock); exit(EXIT_FAILURE); } // 正常接收数据包即可,已过滤掉进程2的UDP包 unsigned char buf[65536]; while (1) { ssize_t recv_len = recvfrom(raw_sock, buf, sizeof(buf), 0, NULL, NULL); if (recv_len < 0) { perror("recvfrom failed"); break; } printf("Received valid packet (length: %zd)\n", recv_len); } close(raw_sock); return 0; }
场景B:进程2使用动态UDP端口
如果进程2的端口是随机分配的,你可以先通过/proc文件系统获取进程2的UDP端口:
# 替换成进程2的实际PID PROCESS2_PID=1234 # 提取进程2的UDP端口(十进制) PROCESS2_PORT=$(grep -w $PROCESS2_PID /proc/net/udp | awk '{print $2}' | cut -d: -f2 | xargs printf "%d\n")
然后把这个端口号传入进程1,动态生成BPF过滤规则(本质和场景A一样,只是端口是动态传入的)。
方案2:应用层手动过滤(快速验证首选)
如果不想折腾BPF,也可以在进程1收到数据包后,手动解析以太网帧、IP头和UDP头,判断是否是进程2的包,是的话直接丢弃。这种方式的缺点是内核还是会把所有包发给进程1,会浪费一些CPU资源,适合小流量场景。
示例代码片段:
#include <netinet/ip.h> #include <netinet/udp.h> #define PROCESS2_UDP_PORT 1234 void handle_packet(unsigned char *buf, ssize_t len) { // 解析以太网帧 struct ethhdr *eth_header = (struct ethhdr *)buf; if (ntohs(eth_header->h_proto) != ETH_P_IP) { return; // 不是IP包,跳过 } // 解析IP头 struct iphdr *ip_header = (struct iphdr *)(buf + sizeof(struct ethhdr)); if (ip_header->protocol != IPPROTO_UDP) { return; // 不是UDP包,跳过 } // 解析UDP头 int ip_header_len = ip_header->ihl * 4; struct udphdr *udp_header = (struct udphdr *)(buf + sizeof(struct ethhdr) + ip_header_len); uint16_t src_port = ntohs(udp_header->source); uint16_t dst_port = ntohs(udp_header->dest); // 过滤进程2的UDP包 if (src_port == PROCESS2_UDP_PORT || dst_port == PROCESS2_UDP_PORT) { return; } // 处理符合条件的数据包 printf("Processing non-process2 packet\n"); }
方案3:仅过滤进程2发出的本地包
如果你只需要过滤进程2发送出去的包(而不是发给进程2的包),可以通过recvfrom返回的struct sockaddr_ll结构体中的sll_pkttype字段判断:
PACKET_OUTGOING:本地生成、发往网卡的包PACKET_HOST:发往本地主机的包
结合UDP端口过滤的示例代码:
struct sockaddr_ll sa_ll; socklen_t sa_len = sizeof(sa_ll); unsigned char buf[65536]; ssize_t recv_len = recvfrom(raw_sock, buf, sizeof(buf), 0, (struct sockaddr *)&sa_ll, &sa_len); if (recv_len < 0) { perror("recvfrom failed"); return; } // 仅处理非本地发出的包,或者本地发出但不是进程2的包 if (sa_ll.sll_pkttype == PACKET_OUTGOING) { struct iphdr *ip_header = (struct iphdr *)(buf + sizeof(struct ethhdr)); if (ip_header->protocol == IPPROTO_UDP) { int ip_header_len = ip_header->ihl * 4; struct udphdr *udp_header = (struct udphdr *)(buf + sizeof(struct ethhdr) + ip_header_len); if (ntohs(udp_header->source) == PROCESS2_UDP_PORT) { return; // 丢弃进程2发出的包 } } } // 处理剩余数据包 handle_packet(buf, recv_len);
内容的提问来源于stack exchange,提问作者Alex Wu
相关产品推荐
相关产品推荐

