Weird bugs - 4

奇怪的 bug 系列 4 -- Linux 网卡混杂模式

Posted by Di Chen on October 7, 2019

前言

这次记录的 bug 是主要是涉及网络知识比较多,排查中主要用到了 tcpdump 以及一些运气。


问题以及排查过程

问题表象

在一台 vmware 部署的 Redhat 系统上安装 docker,使用的是阿里云的镜像源,安装命令如下

yum remove  docker \
            docker-client \
            docker-client-latest \
            docker-common \
            docker-latest \
            docker-latest-logrotate \
            docker-logrotate \
            docker-selinux \
            docker-engine-selinux \
            docker-engine
 
 
# Set up repository
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
  
# Use Aliyun Docker
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
 
 
# install the latest version docker
yum install docker-ce

在安装完成后发现 docker 可以正常启动,未报错,但是启动后对应的 http 端口无法连通,具体表现为错误 “curl: (56) Recv Failure: Connection reset by peer.”

问题排查和解决

定位错误组件

首先尝试进行横向分段排查,定位具体是哪个组件为正常运行。

docker 所在的主机 ip 为 10.0.10.1 docker 建立的 NAT 网段为 172.17.0.1/16 启动的 docker 对外暴露的端口为 9200,启动命令类似:docker run -p 9200:9200 mydocker 启动后的 docker 在 docker 的网段内 ip 为 172.17.0.2

首先尝试在宿主主机访问对应端口: $ curl 10.0.10.1:9200,报 “No route to host” 错误。 同时尝试使用 telnet 连接该端口 $ telnet 10.0.10.1:9200,同样报 “No route to host” 错误。我们知道 tcp 连接的第一步是三次握手建立连接,报 “No route to host” 说明连握手都无法完成,问题应该出在网络的第4层以下。

在宿主主机上尝试 ping 对应的 docker NAT 网关内的 ip:$ping 172.17.0.2,发现ping不通。尝试反向ping,从 docker 内 ping 172.17.0.1 发现也无法 ping 通,说明 docker 的网桥可能未正常工作。

docker 的网络包是通过 iptables 进行配置转发的,所以首先排查宿主主机上的 iptables 配置是否正常。 $ iptables --list 查看所有 iptables 规则,iptables 内 DOCKER-USER 和 DOCKER 是检验所有发到 docker 容器的规则,这两部分在主机的 iptables 配置里都是空的,或 all anywhere,所以理论上不会对 tcp 包进行拦截。这部分没发现有异常。

接下来尝试使用 tcpdump 抓流量包,查看在链路的哪个部分无法连通。还是使用分段排查的方式,先排查 docker0 网卡的流量包,看是否包含 telnet 的握手请求。使用 $tcpdump -w docker.pcap -i docker0 在一个 ssh 窗口中进行抓包,然后使用 $telnet 172.17.0.1 9200 发起 tcp 连接。此时发现了一个奇怪的现象,在 tcpdump 运行的时候,所有网络请求都能通了! telnet 可以正常连接,curl 命令也能返回预期的信息。关闭 tcpdump 后,又变回了原来无法连通的情况。

解决方法

根据之前发现的情况,Google 搜索 “docker tcpdump 抓包后通了”,发现了一篇类似情况的排查博客:从混杂模式开始说起

但前几天BJ机房掉电,重启后发现宿主机无法登陆,网络不通。ipmi登陆上去,检查team0状态、br0状态都正常,tcpdump抓包发现,报文能够到达team0的子接口(eno0),但无法送到br0,因此ping宿主机不通。

偶然发现,从外面ping宿主机网络,如果在team0口、eno0口都执行tcpdump,宿主机、docker容器,网络均可达。

从现象上看,他遇到的问题和我的表现是一样的。在该博客中也提到了该问题的根源:物理网卡进入了混杂模式,但是子接口并没有进入,导致子接口无法获取对应的网络包。我在问题宿主主机上做了验证:

查看系统日志,并计算进入混杂模式但是未退出的网卡:

$ cat /var/log/messages | grep promisc | grep enter | cut -d " " -f 8 | sort | uniq -c | sort -n > card_list_enter
$ cat /var/log/messages | grep promisc | grep enter | cut -d " " -f 8 | sort | uniq -c | sort -n > card_list_left
$ diff card_list_enter card_list_left
...

上面的 shell 先统计了所有网卡进入混杂模式的次数,然后统计了所有网卡退出混杂模式的次数,再 diff 找到进入了混杂模式,但是未退出的网卡。然后再去 $cat /sys/class/net/docker0/flags, 看具体的标志位是否打开,发现除了 docker0 网卡之外,其他有若干网卡打开了混杂模式。而同时,查看 /var/log/messages 也发现每次 tcpdump 的时候,docker0 网卡都会进入混杂模式。

尝试使用命令 $ifconfig docker0 PROMISC 手动打开 docker0 的混杂模式后,docker 就能正常运行了。

原理

混杂模式是什么

混杂模式是网卡在链路层工作的一个工作模式。简单来说,一般网卡网卡只接受来自网络端口的目的地址指向自己的数据,但是混杂模式下会接受所有数据。判断标准就是数据的 MAC 地址是否相同。

为什么tcpdump需要打开对应网卡的混杂模式

tcpdump 需要监听特定网络接口或者进程的网络请求,这些网络请求并不一定是发给自己的,所以需要打开网卡的混杂模式,从而获取到所有相关数据。

为什么不混杂模式无法连接主机

这个原因仍然没有得到100%的确认和复现,因为出问题的主机是在客户的环境中,我们能访问的时间非常有限。应该是由于数据包的 MAC 地址在 iptables 或者虚拟机的网卡转发时被修改了,修改后的值与 docker0 的 MAC 地址不一致,所以 docker0 在没有打开混杂模式前无法获取到对应数据包。如果要从根本上解决这个问题,还需要 RedHat 以及 Vmware 的工程师介入,在这个 case 中我们没有相关的权限要求,只能先解决问题的表现了。

推荐阅读

  1. Docker and iptables

  2. 从混杂模式开始说起


如果你看到这里,一定是真爱!欢迎看看我的其他 blog。O(∩_∩)O