从事运维工程师(兼公司网管😂)以来,遇到不少各式各样的网络故障,有的来自于路由器问题,有的则是客户端配置错误。而开发人员的反馈大多类似于:

  • 「我上不了网了!」
  • 「网络卡了吗?一直在加载…」

信息量稍多一点的例如:

  • ping 不通某某地址」
  • 「Chrome 显示某某错误」

等等诸如此类。

然而,这些对于网络工程师来说并没有任何价值。你表述的是「现象」,并不是「线索」,通过这寥寥无几的语句,他们无法快速地调查故障原因。就好像你开发的应用无法正常工作,而测试工程师的反馈并没有详细的报错信息,只有「状态码 500」一样。

而本文,就是想通过常见的场景,来引导大家快速地定位本地网络故障。在负责网络的同事还没有「空降」到你电脑之前,尽可能把可能的原因缩小到比较容易排查的区间。即便作为开发者可能无法解决这些问题,但也能给网络工程师们提供极大的帮助,说不定他们会对你刮目相看的。

浏览器与 CURL

通常来说,发现网络故障的「入口」大多是浏览器打不开某个网站了。

以 Chrome 为例,错误页面可能是这样的:

首先,你需要确认是对方(服务端)还是你的网络问题,你可以尝试访问百度、腾讯之类的网站,这些站点本身故障的概率非常低,如果连续访问几个站点都打不开,那么你可以继续往下看了。

浏览器访问网站最重要的步骤之一就是发起 HTTP 请求。但其实在输入地址按下回车后,直到网页展示在你的面前,这其中浏览器还做了大量的工作(有兴趣可自行搜索该经典面试题)。使用浏览器调试网络问题的不可控因素太多,诸如各类缓存机制、预加载等等。另外不同浏览器的行为或多或少存在差别,报错信息也不尽相同。因此,请使用 CURL 工具发起原始的 HTTP 请求,比如:

curl -v --noproxy "*" www.baidu.com

其中:

  • -v 表示开启啰嗦模式,尽可能多地输出信息以便调试。
  • --noproxy "*" 表示对所有站点禁用代理,如果你的命令行环境设置了诸如 http_proxysocks5_proxyall_proxy 等环境变量,该选项可以保证 curl 命令不使用任何代理,因此可以避免受到代理服务器的影响。

如果 CURL 正常运行,输出了一大段 HTML 之类的数据,那么问题有可能出在:

  • 浏览器问题。比如浏览器设置错误,或是装了某些存在 BUG 的插件,可以换个浏览器或使用隐身模式再尝试。该类问题不在本文讨论范围内。
  • 系统代理配置问题。以 macOS 为例,在「系统偏好设置」-「网络」-「高级」-「代理」标签页内,可能配置了错误的代理服务器。场景举例:在 macOS 强行重启后,像 ShadowsocksX-NG、Charles、Fiddler 等工具未正常退出,导致运行时修改了系统代理服务器但没有还原,因此无法上网。

如果 CURL 报错,我们大致可分为三类:

  1. HTTP 问题

    • 例如各类状态码 302 / 403 / 502 等。出现此类问题多数是由于服务端配置不当导致,除非你请求所有的网站都返回同样的错误状态码,否则几乎不可能是本地网络原因。唯一我能够想到的可能性,就是你的网络遭到了劫持或篡改(或许是网络工程师故意为之)。
  2. DNS 问题

    • Rebuilt URL to: ... 卡住:此时 CURL 可能在发送 DNS 请求,尝试将域名解析为 IP 地址,然而 DNS 服务并没有给予响应,需要更深入地排查。
    • curl: (6) Could not resolve host: ***:DNS 服务发回了响应,但无法解析这个域名(没有该记录)。场景举例:DNS 服务器的上游服务器配置错误,导致无法从上游服务器获取解析结果。
  3. TCP 问题

    • curl: (52) Empty reply from server:服务端接受了连接,但并没有发任何回复就断开了。场景举例:路由器科学上网,但密码配置错了;网关(科学上网的路由器)接到连接之后立即接受,由于密码错误导致无法与代理服务器正常建立连接,因此只好啥也不回复就断开。如果你 CURL 的地址是你自己的服务,那么有可能是协议不对,对面监听的或许并不是 HTTP 协议。
    • curl: (7) Failed to connect to *** port 80: Connection refused:服务端直接拒绝了连接。场景举例:防火墙。无论是电脑的防火墙还是路由器、交换机甚至服务器的防火墙,如果该目标端口被防火墙拦截,那么有可能出现此问题。
    • Trying xx.xx.xx.xxx... 卡住:服务端一直没有接受连接,也没有任何回应。场景举例:路由器断网。例如 PPPoE 拨号失败,光猫故障等。
    • curl: (7) Couldn't connect to server:无法连接到服务器,需要更深入地排查。

DNS

上文中提到的 DNS 故障大概可以分为两种情况:

  1. DNS 服务发回响应,但解析失败。
  2. DNS 服务没有响应。

在继续阅读之前,建议你先去系统设置里看看 DNS 服务的 IP 地址是否配置正确,如果你是手动设置而非使用 DHCP 下发的 DNS 服务,那么请确保你设置的 DNS 服务器运行正常。

由于 DNS 协议基于 UDP,没有 TCP 建立连接的概念,因此也不存在 Connection refused 等错误,所以我们无法得知 DNS 服务此时的状态如何。不过,有个名叫 dig 的工具可以帮助我们主动发起 DNS 请求。例如:

dig www.baidu.com # 使用系统默认 DNS 服务器解析 www.baidu.com
dig www.baidu.com @114.144.114.114 # 使用 114.114.114.114 解析

如果系统默认的 DNS 服务器有问题,也可以试试 114.114.114.114180.76.76.76 等知名公共 DNS。同样,这些服务本身故障的概率非常低。

解析成功的输出类似:

www.baidu.com.          491     IN      CNAME   www.a.shifen.com.
www.a.shifen.com.       134     IN      A       182.61.200.7
www.a.shifen.com.       134     IN      A       182.61.200.6

发回响应但解析失败类似:

;www.baidu.com.                 IN      A

若没有收到响应,dig 将会继续等待。一般来说,DNS 服务大多能够在几百毫秒内发回响应,如果几秒钟后依然卡着,那可能是:

  • DNS 服务挂了。
  • UDP 数据包在某个环节被防火墙拦截或丢弃。
  • DNS 服务的 IP 在网络中不存在,数据包无法到达。

看到这里,如果能够确认你的 DNS 配置无误,那么你可以将你的 DNS 服务器 IP 地址告知网络工程师了。同时,最好直接将 dig 命令的输出原封不动的发给他们。

TCP 协议

如果你确定问题出在 TCP 协议层,那么有个工具推荐:telnet。使用 telnet 相比于 CURL 能够更方便地测试端口是否畅通。例如:

telnet 1.1.1.1 80

以上命令 telnet 将会尝试与 1.1.1.180 端口建立连接,此处不使用域名是为了避免受到 DNS 服务的影响。你可以多尝试几个地址和端口,例如常用的 80、443、22 等。

假设成功建立 TCP 连接,输出将会类似:

Trying 1.1.1.1...
Connected to one.one.one.one.
Escape character is '^]'.

然后 telnet 不会退出,而是等待你继续输入;你输入的内容将会被发送到服务端,而服务端的响应同样会显示在命令行中。

这里有个小把戏,你可以直接手动「编写」一个 HTTP 请求,例如:

GET / HTTP/1.1
Host: www.baidu.com
[换行]

[换行] 请留空,实际就是多按一下回车,表示 HTTP 请求结束,开始等待服务器响应。稍等片刻,你会看到 telnet 输出了 HTTP 响应头和一大堆 HTML。

如果你对 TCP 协议足够了解,可以尝试 tcpdumpTermshark 以及 Wireshark,更加深入地分析 TCP 握手的过程,在此不再展开。

若是无法建立连接或者建立连接后立刻断开,telnet 将会输出具体的信息。如果出现诸如 Connection refusedConnection closed by foreign host 等提示,说明至少我们收到了服务器发回的 TCP 响应(注意不是 HTTP),此时你可能需要召唤网络工程师,告诉他们你本机发出的 TCP 连接被远端拒绝或是主动关闭,让他们调查一下防火墙是否配置有误了。

不过这里有个特殊情况,如果你的报错中含有类似 Network is unreachable 的关键字,那么有两大可能性:

  1. 你的 Wi-Fi 或者网线断了。
  2. 你本地的 IP 或「网关(部分系统称作路由器)」配置有误。你需要确认你使用的是 DHCP 下发的网关,而不是自己手动配置的。另外,如果你的 IP 地址是 169.254 开头,那么多半是网络内的 DHCP 服务出现问题,或是压根不存在 DHCP 服务。请直接呼叫网管,告诉他们你的电脑无法获取 IP 地址吧。

如果一直卡在 Trying ... 而没有 Connected to ...,那么请继续往下看。

ICMP 协议

我们平时经常使用的 ping 命令就是基于 ICMP 协议的。相比于 TCP,它不存在端口的概念,可以用来调查更加底层的网络问题。例如:

ping 1.1.1.1

如果能够 ping 通,那么请呼叫网络工程师,告诉他们「能 ping 通某某地址,但无法 telnet 某某地址某某端口」,并附上 telnet 的原始输出。他们可能会检查网关上的 iptables 是否配置正确,说不定你的 TCP 数据包被直接 DROP 掉,或者重定向到某个「黑洞」里了。

如果 ping 不通,有两大可能的原因:

  1. IP 或网关配置有误。
  2. 在你本地到目标地址的途中,某个网关出现了故障,或是路由规则配置错误,或是被防火墙拦截。

第一种情况,一般在 telnet 的时候就会报错。比如 ping 提示 No route to host,对于运行在 TCP 层的 telnet 来说,错误信息就是 Network is unreachable

第二种情况,推荐使用 mtr 工具,例如:

sudo mtr -b 1.1.1.1

mtr 将会追踪 ICMP 数据包的路径,并持续向这些网关发出 ping 请求。样例输出如下:

                             My traceroute  [vUNKNOWN]
Suns-Laptop.local (172.20.10.2)                            2019-05-25T09:26:06+0800
Keys:  Help   Display mode   Restart statistics   Order of fields   quit
                                           Packets               Pings
 Host                                    Loss%   Snt   Last   Avg  Best  Wrst StDev
 1. bogon (172.20.10.1)                   0.0%     5   36.0  15.6   3.3  36.0  14.6
 2. ???
 3. bogon (10.255.255.97)                 0.0%     5   50.5  49.7  44.3  56.8   4.6
 4. ???
 5. 115.170.139.241 (115.170.139.241)     0.0%     5   87.2  67.5  48.0 101.7  25.2
 6. 36.112.252.5 (36.112.252.5)           0.0%     5   46.5  63.2  46.5 107.9  25.2
 7. 202.97.94.230 (202.97.94.230)         0.0%     5   80.8 128.3  80.8 171.5  43.9
 8. 202.97.85.58 (202.97.85.58)           0.0%     5   72.0  60.9  53.6  72.0   8.3
 9. 202.97.90.238 (202.97.90.238)         0.0%     5  358.0 326.4 312.2 358.0  21.6
10. 202.97.50.22 (202.97.50.22)           0.0%     5  336.9 336.1 323.5 342.3   8.8
11. 218.30.54.214 (218.30.54.214)         0.0%     4  228.7 227.6 217.7 243.9  11.9
12. one.one.one.one (1.1.1.1)             0.0%     4  311.5 288.4 247.4 322.2  34.7

首先,请确认最后一行是你的目标地址,如果不是,那么最后一行很有可能就是出问题的网关。

另外注意 Loss% 一列,如果某个网关丢包率很高,那么它可能也存在问题。

mtr 的信息通常来说不需要开发者分析,只需原封不动地把输出发给网络工程师即可,他们会读懂的。

ARP 协议

上文中提到的方法对于普通网络用户已经非常底层了,如果你有兴趣,也可以看看这一小节。

如果 mtr 的输出类似这样:

                                           Packets               Pings
 Host                                    Loss%   Snt   Last   Avg  Best  Wrst StDev
 1. bogon (172.20.10.1)                 100.0%     5   ...

这意味着你电脑的网关(通常是路由器)都 ping 不通。在 IP 和网关确认配置无误的情况下,你可以使用 arp-scan 工具向本地网络主动发出 ARP 请求,例如:

arp-scan 172.20.10.1/24

其中,172.20.10.1/24 是网关 IP + 子网掩码转换成 CIDR 的形式。通常来说,我们只需使用 网关IP/24 即可,/24 表示子网掩码为 255.255.255.0

如果网关正常响应了 ARP 请求,输出应当类似于:

Interface: en0, datalink type: EN10MB (Ethernet)
WARNING: host part of 172.20.10.1/24 is non-zero
Starting arp-scan 1.9.5 with 256 hosts (https://github.com/royhills/arp-scan)
172.20.10.1     fe:2a:9c:ee:16:64       (Unknown)

619 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.9.5: 256 hosts scanned in 1.866 seconds (137.19 hosts/sec). 1 responded

ping 不通网关(例如 No route to host),但是网络设备正常响应了 ARP 请求;除防火墙外,我能想到的唯一可能性,就是你的电脑还没有 IP 地址。

小结

可以看出,本文所述排查顺序从运行在七层的 HTTP 协议,到运行在二层的 ARP 协议,逐渐深入。

最后,我不是网络工程专业出身,本文或许还不够「专业」。如果你发现任何错误、疑问,或是更好的方案、更多的场景,欢迎留言补充。

链路层

最后的最后。

如果 ARP 协议都不能正常工作。

那么你寻求帮助的对象,也许不是网络工程师了。你可能需要叫硬件大佬来检查一下:

  • 你电脑的无线网卡是否正常工作
  • 周围无线电环境是否存在干扰
  • 某次电闪雷鸣是否击中了二层交换机
  • 以及网线是否被老鼠咬了。