网桥调用IP层netfilter的HOOK函数

匿名 (未验证) 提交于 2019-12-03 00:19:01
Linux中的网桥属于二层转发设备,可利用ebtables工具根据数据包的ethernet头等信息,配置规则,如下,将目的MAC地址为00:01:02:03:04:05的数据包,转发到目的MAC地址为00:06:07:08:09:0a的主机上:

ebtables -t nat -A PREROUTING -d 00:01:02:03:04:05 -j dnat --to-destination 00:06:07:08:09:0a

但是,网桥不仅可以支持ebtables配置的二层规则,也可支持iptables配置的三层规则。可通过PROC文件系统实现动态控制,控制文件位于/proc/sys/net/bridge目录下的bridge-nf-call-iptables和bridge-nf-call-ip6tables。


初始化

内核中网桥代码,通过调用br_netfilter_init函数,注册二层netfilter的三个hook点,分别是NF_BR_PRE_ROUTING,NF_BR_FORWARD和NF_BR_POST_ROUTING。这三个hook点,分别对应了三层netfilter的三个相同的hook点(准确说,是三层的6个hook点,IPv4与IPv6各三个点),这样在数据包经过网桥的这三个hook点时,就可以调用三层的iptables规则了。

三层netfilter的另外两个hook,NF_INET_LOCAL_IN和NF_INET_LOCAL_OUT,为发往主机和从主机发出的数据包所经过的hook,其本身自会经过三层协议栈的处理而被调用到,不需要在二层网桥上调用。


static struct nf_hook_ops br_nf_ops[] __read_mostly = {     {         .hook = br_nf_pre_routing,         .pf = NFPROTO_BRIDGE,         .hooknum = NF_BR_PRE_ROUTING,     },     {         .hook = br_nf_forward_ip,         .pf = NFPROTO_BRIDGE,         .hooknum = NF_BR_FORWARD,     },     {         .hook = br_nf_post_routing,         .pf = NFPROTO_BRIDGE,         .hooknum = NF_BR_POST_ROUTING,     }, }  static int __init br_netfilter_init(void) {     ret = nf_register_hooks(br_nf_ops, ARRAY_SIZE(br_nf_ops)); }


网桥PRE_ROUTING HOOK

数据包在进入网桥(br_handle_frame)后,调用其NF_BR_PRE_ROUTING hook点上注册的函数。其中,会调用到之前注册的br_nf_pre_routing函数。其首先检查proc文件bridge-nf-call-iptables是否允许数据包到达三层netfilter的hook点。之后调用setup_pre_routing函数准备调用NF_INET_PRE_ROUTING hook点注册的函数,两点准备工作:第一skb中pkt_type需要由PACKET_OTHERHOST修改为PACKET_HOST,否则将会被三层丢弃;第二skb中的dev由物理口设备修改为其父设备(网桥设备),因为像REDIRECT等的iptables规则需要使用设备的IP地址(子设备无三层地址),所以作此修改。注意,最后在br_nf_pre_routing_finish函数中,会将此两项修改还原回去。

IPv6相关的NF_INET_PRE_ROUTING调用也在br_nf_pre_routing函数中处理,此处略去。


struct net_device *setup_pre_routing(struct sk_buff *skb) {     if (skb->pkt_type == PACKET_OTHERHOST) {         skb->pkt_type = PACKET_HOST;         nf_bridge->pkt_otherhost = true;     }     nf_bridge->physindev = skb->dev;     skb->dev = brnf_get_logical_dev(skb, skb->dev); }  static unsigned int br_nf_pre_routing(...) {     if (!brnf_call_iptables && !br->nf_call_iptables)         return NF_ACCEPT;     if (!setup_pre_routing(skb))         return NF_DROP;      NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, state->sk, skb, skb->dev, NULL,         br_nf_pre_routing_finish); }


网桥转发HOOK


在网桥转发(NF_BR_FORWARD)hook点上,除了要修改skb的pkt_type到PACKET_HOST外,还要在调用三层NF_INET_FORWARD hook点函数之前,将入口设备和出口设备修改为对应的父设备(网桥设备)。skb成员dev中指向的为真实的出口设备(非网桥)。最后br_nf_forward_finish函数会还原skb->pkt_type的值。

另外,只有PROC文件bridge-nf-call-iptables为真,nf_bridge才会在br_nf_pre_routing函数中分配,此时检查nf_bridge就可以了。


static unsigned int br_nf_forward_ip(...) {     if (!skb->nf_bridge)         return NF_ACCEPT;      parent = bridge_parent(state->out);     if (!parent)         return NF_DROP;      if (skb->pkt_type == PACKET_OTHERHOST) {         skb->pkt_type = PACKET_HOST;         nf_bridge->pkt_otherhost = true;     }      NF_HOOK(pf, NF_INET_FORWARD, NULL, skb,         brnf_get_logical_dev(skb, state->in), parent, br_nf_forward_finish); }


网桥POST_ROUTING


网桥的NF_BR_POST_ROUTING hook点调用三层的NF_INET_POST_ROUTING hook点,但与前两个HOOK点不同,主机本身发出的报文也会调用NF_INET_POST_ROUTING hook点的函数,所以需要再此判断physoutdev是否为空,为空说明主机已经在IP三层处理中调用了此hook点,直接返回NF_ACCEPT。


static unsigned int br_nf_post_routing(...) {     if (!nf_bridge || !nf_bridge->physoutdev)         return NF_ACCEPT;      if (skb->pkt_type == PACKET_OTHERHOST) {         skb->pkt_type = PACKET_HOST;         nf_bridge->pkt_otherhost = true;     }      NF_HOOK(pf, NF_INET_POST_ROUTING, state->sk, skb,         NULL, realoutdev, br_nf_dev_queue_xmit); }

内核版本

linux-3.10.0



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!