Google Cloud 技術支援提供的有關 DNS 封包遺失的故事

來自Google部落格編輯器: 您是否想知道 Google Cloud 技術解決方案 (TSE) 工程師如何處理您的支援請求? TSE 技術支援工程師負責識別和糾正使用者報告的問題來源。 其中一些問題非常簡單,但有時您會遇到需要多個工程師同時關注的問題。 在這篇文章中,一位東證員工將向我們講述他最近實踐中遇到的一個非常棘手的問題—— DNS封包遺失的情況。 在這個故事中,我們將看到工程師如何設法解決這個問題,以及他們在修復錯誤時學到了哪些新東西。 我們希望這個故事不僅能讓您了解一個根深蒂固的錯誤,還能讓您深入了解向 Google Cloud 提交支援請求的流程。

Google Cloud 技術支援提供的有關 DNS 封包遺失的故事

故障排除既是一門科學,也是一門藝術。 這一切都從建立關於系統非標準行為原因的假設開始,然後對其進行強度測試。 然而,在提出假設之前,我們必須清楚定義並精確地表達問題。 如果問題聽起來太模糊,那麼你就必須仔細分析一切; 這就是故障排除的「藝術」。

在 Google Cloud 下,這類流程變得更加複雜,因為 Google Cloud 會盡力確保使用者的隱私。 因此,TSE 工程師無法編輯您的系統,也無法像使用者一樣廣泛地查看配置。 因此,為了檢驗我們的任何假設,我們(工程師)無法快速修改系統。

有些使用者認為我們會修復所有東西,就像汽車服務中的機械師一樣,只需向我們發送虛擬機的ID,而實際上該過程以對話形式進行:收集信息,形成和確認(或反駁)假設,最後,決策問題是基於與客戶的溝通。

有問題的問題

今天我們要講的故事有一個美好的結局。 所提出的案例得以成功解決的原因之一是對問題的描述非常詳細且準確。 您可以在下面看到第一張票的副本(經過編輯以隱藏機密資訊):
Google Cloud 技術支援提供的有關 DNS 封包遺失的故事
此訊息包含許多對我們有用的信息:

  • 指定的特定VM
  • 問題本身已表明 - DNS 不起作用
  • 它指出了問題出現的地方 - 虛擬機器和容器
  • 指示了使用者為識別問題所採取的步驟。

該請求被註冊為“P1:嚴重影響 - 服務在生產中無法使用”,這意味著根據“Follow the Sun”方案持續監控情況 24/7(您可以閱讀更多關於 用戶請求的優先權),隨著每個時區的變化,它從一個技術支援團隊轉移到另一個技術支援團隊。 事實上,當這個問題到達我們蘇黎世團隊時,它已經傳遍了全球。 此時,用戶已採取緩解措施,但由於尚未找到根本原因,擔心生產中的情況會重演。

當機票到達蘇黎世時,我們已經掌握了以下資訊:

  • 內容 /etc/hosts
  • 內容 /etc/resolv.conf
  • 產量 iptables-save
  • 由團隊組裝 ngrep .pcap 文件

有了這些數據,我們就可以開始「調查」和故障排除階段。

我們的第一步

首先,我們檢查了元資料伺服器的日誌和狀態,並確保它正常運作。 元資料伺服器回應 IP 位址 169.254.169.254,並負責控制網域名稱。 我們也仔細檢查了防火牆是否能夠與虛擬機器正常運作並且不會阻止封包。

這是某種奇怪的問題:nmap 檢查駁斥了我們關於 UDP 封包遺失的主要假設,因此我們在心裡想出了更多的選項和方法來檢查它們:

  • 資料包是否有選擇地丟棄? =>檢查iptables規則
  • 是不是太小了? MTU? => 檢查輸出 ip a show
  • 該問題僅影響 UDP 封包還是也影響 TCP? => 開車離開 dig +tcp
  • dig 產生的資料包是否回傳? => 開車離開 tcpdump
  • libdns 運作正常嗎? => 開車離開 strace 檢查雙向資料包的傳輸

這裡我們決定打電話給用戶現場排查問題。

在通話過程中,我們可以檢查幾件事:

  • 經過多次檢查後,我們從原因清單中排除了 iptables 規則
  • 我們檢查網路介面和路由表,並仔細檢查 MTU 是否正確
  • 我們發現 dig +tcp google.com (TCP)以其應有的方式工作,但是 dig google.com (UDP) 不起作用
  • 開車離開後 tcpdump 它仍在工作 dig,我們發現UDP封包正在返回
  • 我們開車離開 strace dig google.com 我們看到 dig 如何正確調用 sendmsg() и recvms(),但是第二個被超時中斷

不幸的是,輪班結束了,我們被迫將問題升級到下一個時區。 然而,這個請求引起了我們團隊的興趣,一位同事建議使用 scrapy Python 模組來建立初始 DNS 套件。

from scapy.all import *

answer = sr1(IP(dst="169.254.169.254")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="google.com")),verbose=0)
print ("169.254.169.254", answer[DNS].summary())

該片段會建立一個 DNS 封包並將請求傳送到元資料伺服器。

使用者運行程式碼,返回 DNS 回應,應用程式接收它,確認網路等級沒有問題。

又經過一次「環球旅行」後,請求回到我們團隊,我完全轉移給自己,認為請求不再到處循環,對使用者來說會更方便。

同時,使用者同意提供系統映像的快照。 這是一個非常好的消息:能夠自己測試系統使故障排除變得更快,因為我​​不再需要要求用戶運行命令、向我發送結果並進行分析,我可以自己完成所有事情!

我的同事開始有點羨慕我了。 午餐時我們討論了轉換,但沒有人知道發生了什麼。 幸運的是,用戶本人已經採取了減輕後果的措施,而且並不著急,所以我們有時間來剖析這個問題。 由於我們有了圖像,我們可以運行任何我們感興趣的測試。 偉大的!

退一步

系統工程師職位最常見的面試問題之一是:「當你 ping 時會發生什麼事? www.google.com? 這個問題很好,因為候選人需要描述從 shell 到用戶空間,到系統內核,然後到網路的所有內容。 我笑了:有時候面試問題在現實生活中很有用…

我決定將這個人力資源問題應用於當前的問題。 粗略地說,當您嘗試確定 DNS 名稱時,會發生以下情況:

  1. 應用程式呼叫系統庫,例如 libdns
  2. libdns 檢查它應該聯繫的 DNS 伺服器的系統配置(在圖中是 169.254.169.254,元資料伺服器)
  3. libdns 使用系統呼叫建立 UDP 套接字 (SOKET_DGRAM) 並雙向發送帶有 DNS 查詢的 UDP 封包
  4. 透過 sysctl 介面您可以在核心層級配置 UDP 堆疊
  5. 核心與硬體交互,透過網路介面在網路上傳輸資料包
  6. 管理程式在與元資料伺服器聯繫時捕獲資料包並將其傳輸到元資料伺服器
  7. 元資料伺服器透過其魔力確定 DNS 名稱並使用相同的方法回傳回應

Google Cloud 技術支援提供的有關 DNS 封包遺失的故事
讓我提醒您我們已經考慮過哪些假設:

假設:損壞的圖書館

  • 測試1:在系統中執行strace,檢查dig是否呼叫了正確的系統調用
  • 結果:調用了正確的系統調用
  • 測試 2:使用 srapy 檢查是否可以繞過系統庫確定名稱
  • 結果:我們可以
  • 測試 3:對 libdns 套件和 md5sum 庫檔案執行 rpm –V
  • 結果:庫程式碼與工作作業系統中的程式碼完全相同
  • 測試 4:在沒有此行為的 VM 上掛載使用者的根系統映像,執行 chroot,查看 DNS 是否運作
  • 結果:DNS 工作正常

根據測試得出的結論: 問題不在圖書館

假設:DNS 設定有錯誤

  • 測試1:運行dig後檢查tcpdump,看看DNS資料包是否正確發送和返回
  • 結果:資料包傳輸正確
  • 測試2:在伺服器上仔細檢查 /etc/nsswitch.conf и /etc/resolv.conf
  • 結果:一切正確

根據測試得出的結論: 問題不在於 DNS 配置

假設:核心損壞

  • 測試:安裝新內核,檢查簽名,重啟
  • 結果:類似的行為

根據測試得出的結論: 核心沒有損壞

假設:使用者網路(或管理程式網路介面)不正確的行為

  • 測試 1:檢查您的防火牆設置
  • 結果:防火牆在主機和 GCP 上傳遞 DNS 封包
  • 測試二:攔截流量並監控DNS請求的傳輸和回傳的正確性
  • 結果:tcpdump確認主機已收到回傳封包

根據測試得出的結論: 問題不在網路上

假設:元資料伺服器不工作

  • 測試1:檢查元資料伺服器日誌是否有異常
  • 結果:日誌沒有異常
  • 測試 2:透過繞過元資料伺服器 dig @8.8.8.8
  • 結果:即使不使用元資料伺服器,解析度也會被破壞

根據測試得出的結論: 問題不在於元資料伺服器

底線: 我們測試了所有子系統,除了 運行時設定!

深入了解內核運行時設定

若要設定核心執行環境,可以使用命令列選項 (grub) 或 sysctl 介面。 我調查了 /etc/sysctl.conf 想一想,我發現了幾個自訂設定。 感覺好像抓住了什麼,我放棄了所有非網絡或非 TCP 設置,保留了山設置 net.core。 然後我轉到虛擬機器中主機權限所在的位置,開始對損壞的虛擬機器進行一項又一項應用設置,直到找到罪魁禍首:

net.core.rmem_default = 2147483647

這就是一個破壞 DNS 的配置! 我找到凶器但為什麼會出現這種情況呢? 我還需要一個動機。

基本 DNS 封包緩衝區大小透過配置 net.core.rmem_default。 典型值約為 200KiB,但如果您的伺服器收到大量 DNS 封包,您可能需要增加緩衝區大小。 如果新資料包到達時緩衝區已滿,例如因為應用程式處理速度不夠快,那麼您將開始遺失資料包。 我們的客戶正確地增加了緩衝區大小,因為他擔心資料遺失,因為他正在使用透過 DNS 資料包收集指標的應用程式。 他設定的值是可能的最大值:231-1(如果設定為231,核心將傳回「INVALID ARGUMENT」)。

突然我意識到為什麼 nmap 和 scapy 可以正常工作:它們使用的是原始套接字! 原始套接字與常規套接字不同:它們繞過 iptables,並且不進行緩衝!

但為什麼「緩衝區太大」會導致問題呢? 它顯然沒有按預期工作。

此時我可以在多個核心和多個發行版上重現問題。 這個問題已經出現在 3.x 核心上,現在也出現在 5.x 核心上。

確實,啟動時

sysctl -w net.core.rmem_default=$((2**31-1))

DNS 停止工作。

我開始透過簡單的二分搜尋演算法尋找工作值,發現系統可以使用2147481343,但這個數字對我來說是一組毫無意義的數字。 我建議客戶嘗試這個號碼,他回答說系統可以在 google.com 上運行,但在其他網域上仍然出錯,所以我繼續調查。

我已經安裝了 水滴表,一個應該早先使用的工具:它準確地顯示了資料包在內核中的最終位置。 罪魁禍首是函數 udp_queue_rcv_skb。 我下載了內核原始碼並添加了一些 功能 printk 追蹤資料包的確切結束位置。 我很快就找到了合適的條件 if,只是盯著它看了一會兒,因為就在那時,一切終於組合成一幅完整的圖畫:231-1,一個無意義的數字,一個不起作用的域......它是一段代碼 __udp_enqueue_schedule_skb:

if (rmem > (size + sk->sk_rcvbuf))
		goto uncharge_drop;

請注意:

  • rmem 是 int 類型
  • size 類型為 u16(無符號 XNUMX 位元 int)並儲存資料包大小
  • sk->sk_rcybuf 是 int 類型並儲存緩衝區大小,根據定義,該大小等於中的值 net.core.rmem_default

何時 sk_rcvbuf 接近 231,對資料包大小求和可能會導致 整數溢出。 由於它是一個 int,它的值變為負數,因此當它應該為 false 時條件變為 true(您可以在以下位置閱讀更多相關資訊) 鏈接).

這個錯誤可以透過一種簡單的方式來修正:透過強制轉換 unsigned int。 我應用了修復程式並重新啟動了系統,DNS 再次工作。

勝利的滋味

我將我的發現轉發給客戶並發送 LKML 內核補丁。 我很高興:拼圖的每一塊都拼湊在一起,我可以準確地解釋為什麼我們觀察到我們所觀察到的現象,最重要的是,由於我們的團隊合作,我們能夠找到問題的解決方案!

值得注意的是,這種情況很少見,幸運的是我們很少收到用戶如此複雜的請求。

Google Cloud 技術支援提供的有關 DNS 封包遺失的故事


來源: www.habr.com

添加評論