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。
优雅的做法是使用如下命令:
sudo modprobe br_netfilter
其作用是让桥直接在二层调用iptables,然后继续进行二层转发。可以通过抓包工具验证前者ttl比后者ttl小1,那是因为前者走了路由(必须启用ip_forward,否则无效),而后者直接通过br_forward转发(无需启用ip_forward)。
在同时启用上述两种方法的情形下,对于同一个数据包,如符合broute规则,就强制走路由,直接在三层调用iptables;如不符合broute规则,就通过br_netfilter在二层调用iptables。
顺便提一下,在二层调用iptables后,如二层数据包的最终目标MAC为多播MAC或网桥MAC(或网桥处于promisc模式),该数据包仍将移交三层处理。通过br_netfilter注册的ip_sabotage_in钩子函数,可以避免同一数据包再次遍历三层PREROUTING链(同一数据包不可能同时经过二层和三层的FORWARD或POSTROUTING链)。
二、桥接模式使用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工具。
有线网络也可以采用上述思路,启用桥对应有线网卡的hairpin_mode参数,并针对DNAT规则增加相应的SNAT规则引导响应包经过桥。但后一操作产生了新的问题,针对交换机环境,新增SNAT规则只修改了源IP为桥IP,以引导响应包返回桥,br_netfilter在二层调用iptables时并不去修改源MAC为桥MAC,导致交换机既在原发送设备对应的交换机端口学习到该源MAC,又在桥对应的交换机端口学习到同一个源MAC,产生MAC漂移,针对此现象,交换机也会主动丢包。为避免这种漂移,还需针对该SNAT规则(iptables规则)配套使用ebtables snat规则修改源MAC为桥MAC(来回两个方向都要修改),以消除MAC漂移。
针对有线网络,除采用上述思路(hairpin_mode+iptables SNAT+ebtables snat)外,还可采用前面强制路由的思路,即仅针对需要DNAT的包采用ebtables broute强制移交三层路由(必须启用ip_forward),同样可以绕开网桥同一端口无法二层转发的机制,然后增加相应的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功能。
上述命令的其他注意事项,一是使用-p ! arp参数(参见上述第二条命令),不对arp包强制路由。二是注意不能使用-j DROP取代-j redirect --redirect-target DROP做强制路由,当桥MAC和桥物理端口MAC不一致时,-j DROP实际上起不到强制路由作用(详见后文分析)。
四、broute之DROP简要分析
broute的DROP和ebtables、iptables的其他DROP,意义完全不同,按照官方资料,在broute中DROP表示将包移交三层去路由。broute的DROP有多种用法,虽然都是移交三层,但其行为各不相同,最稳妥的用法就是前述例子中的BROUTING -j redirect --redirect-target DROP,如直接使用-j DROP,有很大可能起不到强制路由作用,变成名副其实的DROP(被三层DROP)。下面结合源码分析和实际验证,阐述一下两者实质性差异。
Linux网桥由多个物理端口组成,网桥和物理端口都有各自MAC,大多数情况下,桥MAC和物理端口MAC并不一致。当两者不一致时,由于桥物理端口不参与ARP响应,导致该物理端口收包的目标MAC要么是桥MAC,要么是其他机器MAC,正常情况下,永远不会收到目标MAC与自身MAC一致的包,因此在调用eth_type_trans时总是将包类型设置为PACKET_OTHERHOST,并移交br_handle_frame处理,br_handle_frame则在调用BROUTING之后、调用PREROUTING之前将目标MAC和桥MAC一致的包修改为PACKET_HOST。
BROUTING -j DROP虽然被br_handle_frame调用,但调用完毕后直接退出br_handle_frame,并不修改包类型,在桥物理端口MAC和桥MAC不一致时,该物理端口收到的包类型全是PACKET_OTHERHOST,三层针对PACKET_OTHERHOST一律DROP,导致事实上起不到强制路由作用。而BROUTING -j redirect --redirect-target DROP调用和退出虽然和BROUTING -j DROP相同,但在进行redirect处理时,将数据包的目标MAC替换为桥物理端口的MAC,同时修改包类型为PACKET_HOST,可被三层处理或路由。
还有很少用到的BROUTING -j dnat -to-dst xx:xx:xx:xx:xx:xx --dnat-target DROP,仅当xx:xx:xx:xx:xx:xx设置为桥物理端口MAC时,方修改包类型为PACKET_HOST(其实质完全等价于前述redirect),如xx:xx:xx:xx:xx:xx和桥物理端口MAC不一致,包类型一律修改为PACKET_OTHERHOST(其实质完全等价于前述-j DROP)。较老的内核,bridge在处理dnat时压根不修改包类型,当收包的桥物理端口MAC和桥MAC不一致时,收包总是PACKET_OTHERHOST,如同-j DROP一样,起不到强制路由作用。