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

如何利用iptables/ebtables钩子函数获取出站报文的二层信息

解决IP层与链路层之间捕获出站报文并获取二层信息的方案

嘿,这个需求我之前做流量监控工具时刚好踩过同款坑,给你捋几个可行的实操思路:

1. 用Netfilter的链路层钩子(PF_PACKET)

这是最贴合你需求的内核态方案。Netfilter不止支持IP层(PF_INET)的钩子,还提供了链路层的PF_PACKET协议族钩子,对应的出站钩子点是NF_PACKET_OUTGOING——这个钩子会在IP层处理完成、ARP地址解析完毕、报文准备发往物理接口时触发,刚好能拿到完整的二层信息。

核心实现要点:

  • 注册钩子时,把nf_hook_ops结构的pf字段设为PF_PACKET,hooknum设为NF_PACKET_OUTGOING,优先级可以设高一些(比如NF_IP_PRI_FIRST)确保先处理。
  • 在钩子函数中,通过struct sk_buff直接提取关键信息:
    • 输出接口:skb->dev,可以拿到接口名(dev->name)、接口MAC(dev->dev_addr)等。
    • 二层头部:用eth_hdr(skb)获取以太网头指针,直接读取源MAC(eth->h_source)和目的MAC(eth->h_dest)。

简化代码示例:

#include <linux/netfilter.h>
#include <linux/netfilter_packet.h>
#include <linux/if_ether.h>

static unsigned int packet_out_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) {
    struct ethhdr *eth;
    struct net_device *dev = skb->dev;

    if (!skb || !dev) return NF_ACCEPT;

    // 获取以太网头部
    eth = eth_hdr(skb);
    if (!eth) return NF_ACCEPT;

    // 打印接口名、源MAC、目的MAC
    pr_info("Outgoing interface: %s\n", dev->name);
    pr_info("Source MAC: %pM\n", eth->h_source);
    pr_info("Dest MAC: %pM\n", eth->h_dest);

    // 此处可添加过滤、修改逻辑,最后返回NF_ACCEPT/NF_DROP等动作
    return NF_ACCEPT;
}

static struct nf_hook_ops packet_out_ops = {
    .hook = packet_out_hook,
    .pf = PF_PACKET,
    .hooknum = NF_PACKET_OUTGOING,
    .priority = NF_IP_PRI_FIRST,
};

// 模块加载时注册钩子
static int __init my_module_init(void) {
    return nf_register_net_hook(&init_net, &packet_out_ops);
}

// 模块卸载时注销钩子
static void __exit my_module_exit(void) {
    nf_unregister_net_hook(&init_net, &packet_out_ops);
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

2. 用TC(Traffic Control)+ BPF(用户态方案)

如果不想写内核模块,这个方案更灵活易维护。TC可以把BPF程序挂载到链路层的egress方向,在报文离开IP层进入链路层时捕获,同样能拿到完整的二层信息。

操作步骤:

  • 用bpftool编写或加载BPF程序,在程序中通过skb获取接口、MAC等信息(逻辑和内核模块类似,但在用户态BPF中实现)。
  • 将BPF程序挂载到指定接口的egress队列:
    tc qdisc add dev eth0 clsact
    tc filter add dev eth0 egress bpf da obj my_bpf.o sec egress
    
  • BPF程序里可以通过直接访问skb->mac_header提取二层数据,还能把信息通过perf buffer输出到用户态做进一步分析。

3. Ebtables的扩展使用(适合网桥场景)

你之前觉得ebtables只和网桥有关,其实也能用它处理本机出站流量,但前提是把物理接口加入一个单接口网桥(即使只有一个接口)。此时可以用ebtables的OUTPUT钩子(对应NF_BRIDGE_LOCAL_OUT),这个钩子会拦截本机发往网桥接口的报文,能获取二层信息。

不过这个方案需要调整网络配置(比如把IP地址从物理接口移到网桥),不如前两种直接,更适合本身就是网桥设备的场景。

避坑提醒

  • 别再用NF_INET_POSTROUTING:正如你推测的,这个钩子在ip_output()中ARP解析前触发,此时还没有生成二层头部,自然拿不到MAC地址。
  • 注意VLAN报文:如果你的网络带VLAN标签,需要处理struct vlan_ethhdr而非普通ethhdr,可以通过skb->vlan_tci判断是否带VLAN标识。

内容的提问来源于stack exchange,提问作者gdb007

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:23:46