shixudong@163.com
疫情期间,在研究《iptables DNAT实现broadcast与unicast之间相互映射》关系时,对ebtables与iptables之间的交互进行了深入学习。网上ebtables的中文资料大多是关于man ebtables的翻译性文献,而ebtables官网的《ebtables/iptables interaction on a Linux-based bridge》一文也基本是概念性的内容,缺乏深入的分析。通过查阅源码,本文对ebtables与iptables之间的交互操作进行了稍许分析,并对网上流行的个别错误观点进行了一些剖析,原本一些模糊的概念和原理均得以厘清。特此记录,以备今后随时查询,如能有益于他人,善莫大焉。
一、桥接模式使用iptables
默认情况下,经过网桥的流量不会被iptables处理。为了让网桥上的流量经过iptables处理,比较粗暴的做法是使用如下命令:
sudo ebtables -t broute -A BROUTING -p IPv4 --ip-proto tcp -j redirect --redirect-target DROP
其作用是强制桥上所有tcp包走路由而非桥,不足是broute表的redirect将数据包的接收设备设置成了桥物理端口而非桥设备自己,导致后续iptables nat表PREROUTING链只能使用DNAT取代REDIRECT。鉴于ebtables的nat表的redirect将数据包的接收设备设置成桥设备自己,稍微优雅一点的做法是改用如下命令:
sudo ebtables -t nat -A PREROUTING -p IPv4 --ip-proto tcp -j redirect
其作用是引导桥上所有tcp包走路由而非桥,且后续iptables nat表PREROUTING链照样可以使用REDIRECT规则。
更为优雅的做法是使用如下命令:
sudo modprobe br_netfilter
其作用是让桥直接在二层调用iptables,然后继续返回二层进行桥转发。可以通过抓包工具验证前两种方法的ttl值都比后一种方法的ttl值小1,那是因为前两者走了路由,采用三层转发(必须启用ip_forward,否则前两种方法无效),而后者直接通过br_forward二层转发(无需启用ip_forward)。
网上流行一种错误观点:为了在桥接模式下使用iptables,需要同时启用方法一和方法三。显然是没有深入理解相关原理,进入桥的包首先经过broute表,而broute的DROP规则强制桥上符合过滤条件的包直接走路由,所以启用方法一后,压根就跳过了br_netfilter机制,直接在三层调用iptables,此时再启用方法三的话,纯粹是画蛇添足多此一举,其实质仍是方法一。
方法二虽然也是让桥上符合条件的包走路由,但那是通过redirect将包引导到桥去走路由,而没有采用强制手段。此时如再启用方法三的话,br_netfilter机制使得这些拟redirect包仍然需要在二层遍历iptables的PREROUTING链(包括raw、mangle和nat),然后包才会真正redirect到三层去做路由决策,并继续在三层遍历iptables的后续表和链。但此时若在iptables nat表PREROUTING链做了DNAT,那些拟redirect包的目标MAC(桥MAC)被再次修改为DNAT后目标的MAC,继续在二层转发,而非移交三层路由,实际上没有起到redirect的效果。也就是说,如果同时启用方法二和方法三的话,由于方法三在后面执行,并没有被跳过,在路由决策前,其实质仍是方法三;如不存在DNAT后否决方法二redirect效果的情形,路由决策后,其实质则是方法二。
二、桥接模式使用iptables后何时需要ebtables
如前所说,一般情况下,桥接模式使用iptables后,只需启用br_netfilter模块,即可引导桥流量遍历iptables规则,无需额外使用ebtables工具。但若需要在iptables nat表PREROUTING链中做DNAT,且DNAT后数据包目的地与初始进入包都位于网桥同一物理端口时,由于两个原因,该DNAT最终是无法成功的。一个原因是由于桥转发本身的内在机制,网桥同一物理端口的包不允许二层转发;第二个原因是如DNAT映射后期望的响应包后续返回时没有经过桥,就无法对该响应包结合原DNAT映射进行反转处理,导致原发送设备会因为收到响应包的源IP和期望的IP不一致而丢弃会话。
针对上述问题,无线网络和有线网络分别有着不同的解决思路,下面展开分析一下:
无线网络下,首先启用桥对应无线物理端口的hairpin_mode参数,重新允许网桥同一物理端口的包在二层转发。其次启用hostapd的ap_isolate功能,使得DNAT后期望的响应包必须经过桥,或者针对DNAT规则增加相应的SNAT规则引导响应包经过桥。也就是说,无线网络解决该问题无需另行引入ebtables工具。
有线网络下,理论上也可以采用上述思路,只是后者只能用增加SNAT规则取代hostapd的ap_isolate功能,之所以说理论上是因为又产生了两个新问题。一是有线网络启用hairpin_mode参数易引发二层环路,二是针对交换机环境,采用上述思路还是无法彻底解决。那是因为新增SNAT规则只修改了源IP为桥IP,以引导响应包返回桥,并未修改源MAC为桥MAC,导致交换机既在原发送设备对应的交换机端口学习到该源MAC,又在桥对应的交换机端口学习到同一个源MAC,产生MAC漂移,针对此现象,交换机也会主动丢包。为避免这种漂移,还需针对该SNAT规则(iptables规则)配套使用ebtables snat规则修改源MAC为桥MAC(来回两个方向都要修改),以消除MAC漂移。
显然可见,采用无线网络思路解决有线网络的上述问题既显繁琐,又增加了二层环路隐患。因此,针对有线网络,可以采用前面方法一的思路,即仅针对需要DNAT的包采用ebtables broute强制走路由在三层转发,同样可以绕开网桥同一端口无法二层转发的机制,然后增加相应的SNAT规则引导响应包经过桥。当SNAT规则(iptables规则)在三层修改源IP为桥IP时,会自动将二层源MAC同时修改为桥MAC,无需再使用ebtables单独修改。
需要注意的是,桥接模式下通过br_netfilter直接在二层调用iptables,仍然能保证iptables的连接跟踪机制。但ebtables工具不支持连接跟踪特性,在使用ebtables broute规则针对需要DNAT的包强制走路由时,要分别考虑进出两个方向的包,而不像iptables下返程包可以通过连接跟踪自动匹配。
三、ebtables对MAC地址做snat/dnat
在一些数据中心,网络设施采用了SDN架构,能识别并限制接入设备的MAC地址。在日常环境中,很容易模拟这一现象,在只有无线网卡的笔记本上使用VirtualBox并使用Bridged Networking连网时,需要对虚拟机的MAC做“nat”转换为主机无线网卡的MAC,以便连到外部网络,此时Bridged Networking也能识别和限制虚拟机的MAC地址。对于VirtualBox这种情况,在多个虚拟机利用linux桥和“内部网络”连接方式组网测试网络功能时,Bridged Networking只能识别到采用“桥接网卡”虚拟机的MAC,无法识别采用“内部网络”虚拟机的MAC,此时可在“桥接网卡”虚拟机上使用ebtables的snat规则将其他虚拟机的MAC地址转换为Bridged Networking能识别的MAC地址,使得其他虚拟机最终也能通过Bridged Networking连到外部网络。下面两条命令是必不可少的:
sudo ebtables -t nat -A POSTROUTING -o eth0 -j snat --to-src `cat /sys/class/net/br0/address` --snat-arp
sudo ebtables -t nat -A PREROUTING -p arp -i eth0 --arp-op Reply -j dnat --to-dst ff:ff:ff:ff:ff:ff
第一条命令将其他虚拟机的源MAC通过snat转换为“桥接网卡”虚拟机的桥MAC,此桥MAC可被Bridged Networking识别,后续就能被Bridged Networking再次“nat”转换为主机无线网卡的MAC。该条命令同时将其他虚拟机arp请求包里的Sender MAC也转换为桥MAC,同样以便通过后续的再次“nat”。
第二条命令将外部回来的单播arp响应包的目标MAC通过dnat转换为二层广播地址后向其他虚拟机广播。
前面提到过,ebtables没有连接跟踪机制,对于外部回来的响应包,还需针对每个内部IP添加一条相应的dnat规则(同时也适用于外部进来的初始包)。
sudo ebtables -t nat -A PREROUTING -p IPv4 -i eth0 --ip-dst $IP -j dnat --to-dst $MAC
以上三条命令均是利用二层机制对数据包的MAC进行nat转换,如嫌最后一类命令繁琐(需要对每个内部IP添加一条),可采用三层机制优化。考虑执行第一条命令后,外部设备看到的其他虚拟机MAC地址全部是“桥接网卡”虚拟机的桥MAC,如不执行最后一类命令,外来响应包或初始包都会自然移交到“桥接网卡”虚拟机的三层处理,此时只需启用“桥接网卡”虚拟机的路由功能(启用ip_forward),无需上述第三类命令即可将外来数据包路由到相应的其他虚拟机。
采用路由处理后,其他虚拟机向外部发出的包走二层出去,外来响应包或初始包通过三层进来,此时“桥接网卡”虚拟机如发现外来数据包的源IP、目标IP(即其他虚拟机IP)和自身IP都属于同一网段,会向同网段外部设备不停发送Redirect Host包,可在“桥接网卡”虚拟机上关掉send_redirects功能,或者使用如下命令防止路由功能发送Redirect Host包。
sudo ebtables -t broute -A BROUTING -p ! arp -i eth0 -d `cat /sys/class/net/br0/address` -j redirect --redirect-target DROP
使用该命令后,将数据包的接收设备设置为桥物理端口,一般情况下桥物理端口不配置IP,所以不满足外来数据包的源IP、目标IP和接口自身IP都属于同一网段这一规则,故路由功能也不会发送Redirect Host包又不影响其他情形下的send_redirects功能。而ebtables+nat+redirect将数据包的接收设备设置成桥设备自己(一般配有桥IP),故无法防止路由功能发送Redirect Host包。
上述命令的其他注意事项,一是使用-p ! arp参数(参见上述第二条命令),不对arp包强制路由。二是注意不能使用-j DROP取代-j redirect --redirect-target DROP做强制路由,这是因为当桥MAC和桥物理端口MAC不一致时(较新内核linux默认采用MACAddressPolicy=persistent策略,导致桥MAC和桥物理端口MAC默认就不一致),-j DROP会将目标MAC为桥MAC的包类型设置为PACKET_OTHERHOST,后续被三层ip_input做丢包处理,导致实际上起不到强制路由作用。



