记一次网络诊断 - 切换 wifi 后主机无法访问服务器

背景

公司研发的硬件,运行自行定制的 Android ROM(基于 AOSP 4.4),偶尔会发生一个怪异的网络问题:

  • 主机 wifi 首先接入当前网络 A,一切没问题(可以访问 IDC 服务器);切换接入到另一个网络 B,无法访问 IDC 服务器,但此时在网络 B 的主机访问其他的 Internet 服务并无问题
  • 主机 wifi 首先接入网络 B,一切没问题;切换接入到一个网络 A,一切没问题;再切回到网络 B,无法访问 IDC 服务器,访问其他 Internet 服务并无问题

这个问题在办公室环境下发生频率较高(但并非 100%),也有部分客户会抱怨切换网络后不能访问服务器。

公司网络 A 的结构不同于一般家用网络,默认网关和对外的出口路由器并不是同一台设备,结构示意如下:

DHCP
Network: 192.168.0.0/24
Gateway: 192.168.0.1,负责公司内部各网络间的数据交换
DNS: 192.168.0.201
Router: 192.168.0.254,负责提供 Internet 接入

先说原因/结论:主机的 ROM 不能正确清理由 ICMP Redirect 报文生成的路由缓存

下文是分析过程的流水记录。

初次排查

刚发生这个问题的时候,大家都很困惑,因为除了主机外,其他的设备全部没有这个问题(手机、平板、PC),自然会认为问题是出在主机上。不过因为在客户手上的机器并没怎么出现过这个问题,所以公司内部测试的时候会避免这个问题。

随着业务规模扩大,主机遭遇的网络环境各种各样,有更多的客户反馈这个问题,于是在一个周末,几个同事一起分析这个问题:

  1. 起初,我们怀疑是 DNS 被劫持,但在不同网络下 ping IDC 服务器可以看得到 DNS 解析没问题
  2. 奇怪的是,在 A 网络下直接 ping IDC 的 IP 可以 ping 通,切换到 B 网络下却一直提示 unreachable
  3. 而同样的,在 A 网络下 ping baidu.com 可以 ping 通,切换到 B 网络下也仍能 ping 通 baidu.com,仔细观察了一下发现,baidu.com 做了 DNS balance,所以测试中先后两次其实 ping 的是不同的 IP
  4. A 网络下选定一个 baidu 的 IP 直接 ping 可以 ping 通,切换到 B 网络下也仍能 ping 通,这下就很头疼了,因为就 IDC 的 IP 无法 ping 通
  5. 重复了一次第 4 步,这次发现 baidu 的 IP 和 IDC 的 IP 一个表现,B 网络下也不能 ping 通
  6. 翻看 Linux 的路由表,并没有什么特别
  7. 大家都陷入了沉思……
  8. 重复测试了几遍,结果和第一次没什么差别,IDC 的 IP 表现一直一致,baidu 的 IP 一会正常一会儿不正常
  9. 引入新的网络 C,在网络 B 和网络 C 之间反复切换多次对比测试,一切都正常
  10. 有十几年经验的 IT 经理做了一个较合理的猜想:Linux 自己控制着一张隐藏的路由表,主机因为某个 bug,在切换网络时,隐藏的路由表没有同步更新,导致同样的 IP 在不同的网络下不能正确寻路
  11. 最后的结论是:公司的网络架构较一般环境复杂,通常情况下不会有客户会处在这么复杂的架构下,如果发生了这种情况,尽可能帮助客户简化网络复杂度

虽然这次分析并没有找到真正的原因,但是 IT 经理的猜想对我产生挺大冲击的。

因为在我看来,操作系统该做的是维护和管理好资源,对用户提供抽象的高级管理接口

就路由而言,理当遵照我们所看到的路由表那样去工作,怎么会偷偷搞一张用户无法控制的隐藏路由表呢?

可这又是当时最能解释现象的说法,我也只能信,同时也因此感到困惑。

不过后来的某一天,在一篇讲述 Linux 下策略路由的文章中了解到,Linux 支持最多 255 张策略路由表,普通的路由指令显示的只是其中一张较低优先级的默认路由表而已。这一刻我几乎就信了,只要查出出问题的那张路由表不就可以解决这个网络问题吗!

这时,我突然挺佩服 IT 经理,能大胆地做出这么一个方向上让人豁然开朗的猜想。

不过,进展仍然止步于此,因为之后我们没有再去讨论这个问题,我也没再去分析出问题的主机网络状况。

问题再现

最近仍有客户反馈这个问题,在上周,硬件部和 IT 部一起重新搭建一个网络环境,用来调查这个问题,因为硬件部担心问题可能是出在 wifi 芯片上。

当时 IT 的同事找到正在码代码的我(我并不清楚他们在做什么测试),向我咨询一个奇怪的问题,描述如下:

主机接入新网络 X,一切正常,切换到公司网络 A,一切正常,切换回网络 X,不能 ping 通网络 X 中的网关。而此时,接入网络 X 的手机并没有异常,可以 ping 通网关,诡异的是,居然也能 ping 通主机。

一开始听到这个描述,我直接摇头否认这个问题的描述,因为我认为,同在一个简单链路内,没有理由不能相互 ping 通(没有防火墙),更何况问题发生时,主机和手机之间是可以相互 ping 通的;而 wifi 环境下所有数据包都需要经过 AP 中转,AP 同时也是路由器,没理由数据包能被路由转发到其他设备,但路由自己却收不到。

可是,当我坐在主机前,做了一些验证后,我惊呆了,居然真的不能 ping 通网关。要知道,在同一个链路内,数据的传递只靠各自的网卡就能完成。

最终分析

查看路由表,切换网络前后的路由表都看不出异常,这时我突然想起来查一下”隐藏的路由表”,可是最后翻遍所有的路由表,发现没有任何异常,看来上一次的分析结论和 Linux 的 255 张路由表没有关系。

没有什么头绪,抓包吧,一边 ping 网关,一边 tcpdump 抓包,终于!露出马脚了!

网络 X 的网关是 192.168.1.1,网络 A 的网关是 192.168.0.1,网络 A 的出口路由是 192.168.0.204。

当我接入在网络 X 下,ping 着网络 X 的网关时,tcpdump 抓包却看到系统一直在广播 ARP 查找 192.168.0.204 这个 IP,明明切换了网络,192.168.0.204 根本就不再出现网络 X 上,而这个 IP 又正是网络 A 的出口路由(非网关),这一定不是巧合!

重启主机,从没接入任何网络时就开始抓包,抓住整个测试过程中的所有包,看能否找出原因:

  1. 在初次接入网络 X 的情况下,没有发现异常
  2. 切换到网络 A 下,果然,频繁收到 ICMP 的重定向包,指示相关的 IP 访问可以由 192.168.0.204 转发完成。注意,这里的重定向不仅仅只有公司的 API 服务器 IP,还有网络 X 的网关 192.168.1.1(由于接入过网络 X,某些应用可能抓取了 X 下的网关做网络连通性测试用途)
  3. 切换回网络 X,重新 ping 网关,真的 ping 不通了,而且抓到的全部都是探寻 IP 地址 192.168.0.204 的 ARP 数据包

不过,找来更多的机器试图重现这个问题,却发现这问题并不能完全重现。继续分析了一下,原因在于路由器发送 ICMP 重定向包存在一个不污染网络的策略,有一个频率限制,外部看起来呈现一定的随机性。针对这个小问题,在网络 A 的网关上人为伪造 ICMP 重定向包每 3 秒发送一次,最终做到了所有机器全部可以重现问题。

解决方案

很简单,调整内核参数,忽略所有 ICMP 重定向包:

1
2
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.accept_redirects = 0

PS:尽管问题出现在 ROM 本身,但是最终没能查到原因。