最近发现kolla安装的openstack(Ocata版本)环境中,某虚拟网络上的虚拟机对外访问异常。经过调查,发现虚拟机外发数据包经过安全组的网桥后源地址被修改为宿主机的IP。
简化的网络拓扑如图:
在虚拟机的网络接口上抓包观察,数据包正常:
1 | [root@control01 ~]# tcpdump -i tap812bd281-f5 -nn icmp |
而在安全组的veth端抓包观察,数据包源IP已被修改为宿主机IP:
1 | [root@control01 ~]# tcpdump -iqvb812bd281-f5 -nn icmp |
docker的默认网段为172.17.0.1/16, 而该虚拟网络网段恰好和docker网段重叠, 因而怀疑是网络冲突造成。
1 | [root@control01 ~]# ip addr show docker0 |
查看系统上的iptables规则,发现docker会创建一条MASQUERADE规则, 用于对docker容器访问外部IP的数据包进行NAT操作:
但实际上,该条规则写的过于宽泛,认为只要源IP网段是docker接口所配置的网段,并且出口不是docker接口的数据都认为是docker容器内外出的数据包。
在kolla部署的openstack场景下,因为宿主机上net.bridge.bridge-nf-call-iptables参数是开启的,从虚拟机通过安全组网桥时也会进行iptables规则匹配,由于网段重叠,同样会命中该条规则,因而被错误地执行MASQUERADE操作。
该问题从根本上看,是由于docker的这条规则写的过于宽泛,应该更精确一些,明确针对来自于docker容器的数据包。因为在POSTROUTING链上不允许使用-i来指定来源设备,可以将条件拆成两个阶段。在PREROUTING阶段对来自于docker容器的数据包设置mark值,而在POSTROUTING阶段的MASQUERADE规则额外添加上mark值的匹配条件。
这需要修改docker的源码,具体修改本文不详述,这里简单手动修改规则进行验证:
1 | iptables -t nat -I PREROUTING 1 -i docker0 -j MARK --set-mark 0x12345678 |
添加后的iptables规则如图:
这时再次从安全组网桥的veth端抓包观察, 可以看到数据包恢复正常:
1 | [root@control01 ~]# tcpdump -iqvb812bd281-f5 -nn icmp |
还有另一种解决方法。因为openstack所添加的安全组网桥,名称是有特征性的, 都以qbr为前缀,可以在MASQUERADE规则前,添加iptables规则跳过安全组网桥的流量:
1 | iptables -t nat -I POSTROUTING 2 -o qbr+ -j RETURN |
修改后的iptables规则如图:
此时再次在安全组网桥的veth端抓包,确认数据包也恢复正常:
1 | [root@control01 ~]# tcpdump -iqvb812bd281-f5 -nn icmp |
很久没有研究过openstack,可能现在新版本的openstack已经不存在这种网桥形式的安全组实现了,因而也就不会存在这个问题。