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

如何使用PF_PACKET阻止数据包进入内核协议栈

PF_PACKET用户态TCP栈:数据包过滤与内核拦截方案

一、仅捕获目标TCP 8000端口的数据包

PF_PACKET套接字默认会接收所有指定协议(此处为ETH_P_IP)的二层帧,为避免不必要的数据包进入用户态,推荐使用内核态BPF过滤器,效率远高于用户态过滤。

实现步骤

  1. 构造BPF过滤规则,匹配目标TCP端口为8000的IP数据包:
    规则逻辑:仅保留IP协议为TCP(0x06)且TCP目标端口为8000的数据包。
  2. 通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 21:37:05