网桥调用iptables规则的善后处理

本文主要讲述BR_NF_PRE_ROUTING点的相关处理。


Linux网桥在BR_NF_PRE_ROUTING hook点的处理上,有两个主要的执行流程:第一执行ebtables在此hook点添加的规则,第二如果PROC文件bridge-nf-call-iptables为真,执行iptables在hook点NF_INET_PRE_ROUTING配置的IPv4或IPv6协议相关规则。由于iptables的DNAT规则会改变数据包的目的IP地址(包括重定向到本机的报文),而网桥只会依据MAC转发表发送数据,目的IP地址改变将导致数据包不能送达正确的目的地。



所以,网桥代码在执行iptables规则返回之后,调用函数br_nf_pre_routing_finish进行了善后的处理。


DNAT规则判断

首先判断是否配置有DNAT的iptables规则,即调用br_nf_ipv4_daddr_was_changed函数检查数据包的目的IP地址是否改变:


static inline bool
br_nf_ipv4_daddr_was_changed(const struct sk_buff *skb, const struct nf_bridge_info *nf_bridge)
{
    return ip_hdr(skb)->daddr != nf_bridge->ipv4_daddr;
}


如已改变,则需要重新确定改变后目的IP地址所对应的二层MAC地址。MAC地址的确定分为两种情况,见代码中注释(简单翻译):

  * There are two cases to consider:
  * 1. The packet was DNAT'ed to a device in the same bridge
  *    port group as it was received on. We can still bridge
  *    the packet.
  * 2. The packet was DNAT'ed to a different device, either
  *    a non-bridged device or another bridge port group.
  *    The packet will need to be routed.

  考虑以下两种情况:
  1. 数据包DNAT后的目的出口设备与其入口设备在同一网桥上,数据包仍可通过二层桥转发。
  2. 数据包DNAT到一个不同的设备上,可能是一个非网桥设备,或者是另一网桥下的设备。此时数据包需要走路由转发。
  

所以,问题是如何从现有DNAT之后的目的IP地址,去获得出口设备呢?此时函数ip_route_input该上场了,通过路由查询可得到入口路由,进而得到路由设备即出口设备(注意虽然是查询入口路由,但是得到的仍然是出口设备,详见ip_route_input函数)。


出口路由查找

但是ip_route_input函数也有可能会失败,我们看一下失败的几种情况:

1)错误EHOSTUNREACH,入口设备没有打开转发功能;
2)错误EINVAL,非法的火星地址(源IP地址或者目的IP地址);或者没有到此目的IP的路由;

3)其它错误。

	if ((err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))) {
		if (err != -EHOSTUNREACH || !in_dev || IN_DEV_FORWARD(in_dev))
			goto free_skb;
	}


如上的判断代码,对于错误2)和错误3),即如果err错误码不等于EHOSTUNREACH,释放skb结束处理;对于错误1),即err等于EHOSTUNREACH,这里又有两种情况:

a)如果入口设备开启了转发(IN_DEV_FORWARD(in_dev)),释放skb结束处理;
b)如果是由于入口设备未开启转发导致的EHOSTUNREACH错误,继续查找出口设备。

在情况b)发生后是否要继续,还是也释放skb结束呢?由于内核中DNAT不需要入口设备打开转发功能,所以可继续查找出口设备。此时就需要ip_route_output函数帮忙了。因为出口路由的查找不会判断入口设备的转发是否打开,参见以下代码,查找函数参数中的源IP地址和入口设备都传空,ip_route_output会当做是一个本地主机产生的报文处理:

rt = ip_route_output(net, iph->daddr, 0, RT_TOS(iph->tos), 0);

如果还是找不到出口设备,只有释放skb结束处理了。如果找到的话,比较一下找到的路由所指向的出口设备是否与数据包入口设备所属的网桥相同:


I) 如果相同,DNAT后的出口设备在同一网桥下,此数据包可通过网桥转发(br_nf_pre_routing_finish_bridge),查询FDB表即可得到其MAC地址;
II)不相同,需要上层路由转发。将数据包的目的MAC地址更换为本机网桥的MAC地址,pkt_type设置为PACKET_HOST,最终由函数br_handle_frame_finish将数据包送往本机上层处理(br_pass_frame_up)。

ether_addr_copy(eth_hdr(skb)->h_dest, dev->dev_addr);

skb->pkt_type = PACKET_HOST;


入口路由查找

前面讲的都是ip_route_input函数查询失败的情况,来看一成功返回的处理。ip_route_input查询到路由,找到出口设备。接下来的处理与从ip_route_output一致,参见I)和II)。另外,对于重定向的报文,in_route_input返回路由指向本地loopback接口,同II)与网桥设备也不相同,送往上层协议栈处理。


处理流程图




内核版本

linux-4.14.4



猜你喜欢

转载自blog.csdn.net/sinat_20184565/article/details/80579276