网桥调用IP层netfilter的HOOK函数

网桥调用IP层netfilter的HOOK函数
原创redwingz 最后发布于2018-05-24 16:49:04 阅读数 1355 收藏
展开
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
————————————————
版权声明:本文为CSDN博主「redwingz」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sinat_20184565/article/details/80436878

原文地址:https://www.cnblogs.com/heboxiang/p/12199998.html