如何專業地表達「我上不了網了!」

Wi1dcard發表於2019-05-25

從事運維工程師(兼公司網管 :joy: )以來,遇到不少各式各樣的網路故障,有的來自於路由器問題,有的則是客戶端配置錯誤。而開發人員的反饋大多類似於:

  • 「我上不了網了!」
  • 「網路卡了嗎?一直在載入...」

資訊量稍多一點的例如:

  • 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.255

如果閘道器正常響應了 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 協議都不能正常工作。

那麼你尋求幫助的物件,也許不是網路工程師了。你可能需要叫硬體大佬來檢查一下:

  • 你電腦的無線網路卡是否正常工作
  • 周圍無線電環境是否存在干擾
  • 某次電閃雷鳴是否擊中了二層交換機
  • 以及網線是否被老鼠咬了。

我感謝自己平凡,敢愛敢恨沒負擔。
我感謝自己不凡,可愛可恨都包攬。

相關文章