You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:57:51