網路地址轉換(NAT)的報文跟蹤

大雄45發表於2021-07-01
導讀 這是有關網路地址轉換network address translation(NAT)的系列文章中的第一篇。這一部分將展示如何使用 iptables/nftables 報文跟蹤功能來定位 NAT 相關的連線問題。

網路地址轉換(NAT)的報文跟蹤網路地址轉換(NAT)的報文跟蹤

這是有關網路地址轉換network address translation(NAT)的系列文章中的第一篇。這一部分將展示如何使用 iptables/nftables 報文跟蹤功能來定位 NAT 相關的連線問題。

引言

網路地址轉換(NAT)是一種將容器或虛擬機器暴露在網際網路中的一種方式。傳入的連線請求將其目標地址改寫為另一個地址,隨後被路由到容器或虛擬機器。相同的技術也可用於負載均衡,即傳入的連線被分散到不同的伺服器上去。

當網路地址轉換沒有按預期工作時,連線請求將失敗,會暴露錯誤的服務,連線最終出現在錯誤的容器中,或者請求超時,等等。除錯此類問題的一種方法是檢查傳入請求是否與預期或已配置的轉換相匹配。

連線跟蹤

NAT 不僅僅是修改 IP 地址或埠號。例如,在將地址 X 對映到 Y 時,無需新增新規則來執行反向轉換。一個被稱為 “conntrack” 的 netfilter 系統可以識別已有連線的回覆報文。每個連線都在 conntrack 系統中有自己的 NAT 狀態。反向轉換是自動完成的。

規則匹配跟蹤

nftables 工具(以及在較小的程度上,iptables)允許針對某個報文檢查其處理方式以及該報文匹配規則集合中的哪條規則。為了使用這項特殊的功能,可在合適的位置插入“跟蹤規則”。這些規則會選擇被跟蹤的報文。假設一個來自 IP 地址 C 的主機正在訪問一個 IP 地址是 S 以及埠是 P 的服務。我們想知道報文匹配了哪條 NAT 轉換規則,系統檢查了哪些規則,以及報文是否在哪裡被丟棄了。

由於我們要處理的是傳入連線,所以我們將規則新增到 prerouting 鉤子上。prerouting 意味著核心尚未決定將報文發往何處。修改目標地址通常會使報文被系統轉發,而不是由主機自身處理。

初始配置
# nft 'add table inet trace_debug'
# nft 'add chain inet trace_debug trace_pre { type filter hook prerouting priority -200000; }'
# nft "insert rule inet trace_debug trace_pre ip saddr $C ip daddr $S tcp dport $P tcp flags syn limit rate 1/second meta nftrace set 1"

第一條規則新增了一張新的規則表,這使得將來刪除和除錯規則可以更輕鬆。一句 nft delete table inet trace_debug  就可以刪除除錯期間臨時加入表中的所有規則和鏈。

第二條規則在系統進行路由選擇之前(prerouting 鉤子)建立了一個基本鉤子,並將其優先順序設定為負數,以保證它在連線跟蹤流程和 NAT 規則匹配之前被執行。

然而,唯一最重要的部分是第三條規則的最後一段:meta nftrace set 1。這條規則會使系統記錄所有匹配這條規則的報文所關聯的事件。為了儘可能高效地檢視跟蹤資訊(提高訊雜比),考慮對跟蹤的事件增加一個速率限制,以保證其數量處於可管理的範圍。一個好的選擇是限制每秒鐘最多一個報文或一分鐘最多一個報文。上述案例記錄了所有來自終端 $C 且去往終端 $S 的埠 $P 的所有 SYN 報文和 SYN/ACK 報文。限制速率的配置語句可以防範事件過多導致的洪泛風險。事實上,大多數情況下只記錄一個報文就足夠了。

對於 iptables 使用者來講,配置流程是類似的。等價的配置規則類似於:

# iptables -t raw -I PREROUTING -s $C -d $S -p tcp --tcp-flags SYN SYN  --dport $P  -m limit --limit 1/s -j TRACE
獲取跟蹤事件

原生 nft 工具的使用者可以直接執行 nft 進入 nft 跟蹤模式:

# nft monitor trace

這條 會將收到的報文以及所有匹配該報文的規則列印出來(用 CTRL-C 來停止輸出):

trace id f0f627 ip raw prerouting  packet: iif "veth0" ether saddr ..

我們將在下一章詳細分析該結果。如果你用的是 iptables,首先透過 iptables –version 命令檢查一下已安裝的版本。例如:

# iptables --version
iptables v1.8.5 (legacy)

(legacy) 意味著被跟蹤的事件會被記錄到核心的環形緩衝區中。你可以用 dmesg 或 journalctl 命令來檢視這些事件。這些除錯輸出缺少一些資訊,但和新工具提供的輸出從概念上來講很類似。你將需要首先檢視規則被記錄下來的行號,並與活躍的 iptables 規則集合手動關聯。如果輸出顯示 (nf_tables),你可以使用 xtables-monitor 工具:

# xtables-monitor --trace

如果上述命令僅顯示版本號,你仍然需要檢視 dmesg/journalctl 的輸出。xtables-monitor 工具和 nft 監控跟蹤工具使用相同的核心介面。它們之間唯一的不同點就是,xtables-monitor 工具會用 iptables 的語法列印事件,且如果你同時使用了 iptables-nft 和 nft,它將不能列印那些使用了 maps/sets 或其他只有 nftables 才支援的功能的規則。

示例

我們假設需要除錯一個到虛擬機器/容器的埠不通的問題。ssh -p 1222 10.1.2.3 命令應該可以遠端連線那臺伺服器上的某個容器,但連線請求超時了。

你擁有執行那臺容器的主機的登入許可權。現在登入該機器並增加一條跟蹤規則。可透過前述案例檢視如何增加一個臨時的除錯規則表。跟蹤規則類似於這樣:

nft "insert rule inet trace_debug trace_pre ip daddr 10.1.2.3 tcp dport 1222 tcp flags syn limit rate 6/minute meta nftrace set 1"

在新增完上述規則後,執行 nft monitor trace,在跟蹤模式下啟動 nft,然後重試剛才失敗的 ssh 命令。如果規則集較大,會出現大量的輸出。不用擔心這些輸出,下一節我們會做逐行分析。

trace id 9c01f8 inet trace_debug trace_pre packet: iif "enp0" ether saddr .. ip saddr 10.2.1.2 ip daddr 10.1.2.3 ip protocol tcp tcp dport 1222 tcp flags == syn
trace id 9c01f8 inet trace_debug trace_pre rule ip daddr 10.2.1.2 tcp dport 1222 tcp flags syn limit rate 6/minute meta nftrace set 1 (verdict continue)
trace id 9c01f8 inet trace_debug trace_pre verdict continue
trace id 9c01f8 inet trace_debug trace_pre policy accept
trace id 9c01f8 inet nat prerouting packet: iif "enp0" ether saddr .. ip saddr 10.2.1.2 ip daddr 10.1.2.3 ip protocol tcp  tcp dport 1222 tcp flags == syn
trace id 9c01f8 inet nat prerouting rule ip daddr 10.1.2.3  tcp dport 1222 dnat ip to 192.168.70.10:22 (verdict accept)
trace id 9c01f8 inet filter forward packet: iif "enp0" oif "veth21" ether saddr .. ip daddr 192.168.70.10 .. tcp dport 22 tcp flags == syn tcp window 29200
trace id 9c01f8 inet filter forward rule ct status dnat jump allowed_dnats (verdict jump allowed_dnats)
trace id 9c01f8 inet filter allowed_dnats rule drop (verdict drop)
trace id 20a4ef inet trace_debug trace_pre packet: iif "enp0" ether saddr .. ip saddr 10.2.1.2 ip daddr 10.1.2.3 ip protocol tcp tcp dport 1222 tcp flags == syn
對跟蹤結果作逐行分析

輸出結果的第一行是觸發後續輸出的報文編號。這一行的語法與 nft 規則語法相同,同時還包括了接收報文的首部欄位資訊。你也可以在這一行找到接收報文的介面名稱(此處為 enp0)、報文的源和目的 MAC 地址、報文的源 IP 地址(可能很重要 - 報告問題的人可能選擇了一個錯誤的或非預期的主機),以及 TCP 的源和目的埠。同時你也可以在這一行的開頭看到一個“跟蹤編號”。該編號標識了匹配跟蹤規則的特定報文。第二行包括了該報文匹配的第一條跟蹤規則:

trace id 9c01f8 inet trace_debug trace_pre rule ip daddr 10.2.1.2 tcp dport 1222 tcp flags syn limit rate 6/minute meta nftrace set 1 (verdict continue)

這就是剛新增的跟蹤規則。這裡顯示的第一條規則總是啟用報文跟蹤的規則。如果在這之前還有其他規則,它們將不會在這裡顯示。如果沒有任何跟蹤輸出結果,說明沒有抵達這條跟蹤規則,或者沒有匹配成功。下面的兩行表明沒有後續的匹配規則,且 trace_pre 鉤子允許報文繼續傳輸(判定為接受)。

下一條匹配規則是:

trace id 9c01f8 inet nat prerouting rule ip daddr 10.1.2.3  tcp dport 1222 dnat ip to 192.168.70.10:22 (verdict accept)

這條 DNAT 規則設定了一個到其他地址和埠的對映。規則中的引數 192.168.70.10 是需要收包的虛擬機器的地址,目前為止沒有問題。如果它不是正確的虛擬機器地址,說明地址輸入錯誤,或者匹配了錯誤的 NAT 規則。

IP 轉發

透過下面的輸出我們可以看到,IP 路由引擎告訴 IP 協議棧,該報文需要被轉發到另一個主機:

trace id 9c01f8 inet filter forward packet: iif "enp0" oif "veth21" ether saddr .. ip daddr 192.168.70.10 .. tcp dport 22 tcp flags == syn tcp window 29200

這是接收到的報文的另一種呈現形式,但和之前相比有一些有趣的不同。現在的結果有了一個輸出介面集合。這在之前不存在的,因為之前的規則是在路由決策之前(prerouting 鉤子)。跟蹤編號和之前一樣,因此仍然是相同的報文,但目標地址和埠已經被修改。假設現在還有匹配 tcp dport 1222 的規則,它們將不會對現階段的報文產生任何影響了。

如果該行不包含輸出介面(oif),說明路由決策將報文路由到了本機。對路由過程的除錯屬於另外一個主題,本文不再涉及。

trace id 9c01f8 inet filter forward rule ct status dnat jump allowed_dnats (verdict jump allowed_dnats)

這條輸出表明,報文匹配到了一個跳轉到 allowed_dnats 鏈的規則。下一行則說明了連線失敗的根本原因:

trace id 9c01f8 inet filter allowed_dnats rule drop (verdict drop)

這條規則無條件地將報文丟棄,因此後續沒有關於該報文的日誌輸出。下一行則是另一個報文的輸出結果了:

trace id 20a4ef inet trace_debug trace_pre packet: iif "enp0" ether saddr .. ip saddr 10.2.1.2 ip daddr 10.1.2.3 ip protocol tcp tcp dport 1222 tcp flags == syn

跟蹤編號已經和之前不一樣,然後報文的內容卻和之前是一樣的。這是一個重傳嘗試:第一個報文被丟棄了,因此 TCP 嘗試了重傳。可以忽略掉剩餘的輸出結果了,因為它並沒有提供新的資訊。現在是時候檢查那條鏈了。

規則集合分析

上一節我們發現報文在 inet 過濾表中的一個名叫 allowed_dnats 的鏈中被丟棄。現在我們來檢視它:

# nft list chain inet filter allowed_dnats
table inet filter {
 chain allowed_dnats {
  meta nfproto ipv4 ip daddr . tcp dport @allow_in accept
  drop
   }
}

接受 @allow_in 集的資料包的規則沒有顯示在跟蹤日誌中。我們透過列出元素的方式,再次檢查上述報文的目標地址是否在 @allow_in 集中:

# nft "get element inet filter allow_in { 192.168.70.10 . 22 }"
Error: Could not process rule: No such file or directory

不出所料,地址-服務對並沒有出現在集合中。我們將其新增到集合中。

# nft "add element inet filter allow_in { 192.168.70.10 . 22 }"

現在執行查詢命令,它將返回新新增的元素。

# nft "get element inet filter allow_in { 192.168.70.10 . 22 }"
table inet filter {
   set allow_in {
      type ipv4_addr . inet_service
      elements = { 192.168.70.10 . 22 }
   }
}

ssh 命令現在應該可以工作,且跟蹤結果可以反映出該變化:

trace id 497abf58 inet filter forward rule ct status dnat jump allowed_dnats (verdict jump allowed_dnats)
 
trace id 497abf58 inet filter allowed_dnats rule meta nfproto ipv4 ip daddr . tcp dport @allow_in accept (verdict accept)
 
trace id 497abf58 ip postrouting packet: iif "enp0" oif "veth21" ether .. trace id 497abf58 ip postrouting policy accept

這表明報文透過了轉發路徑中的最後一個鉤子 - postrouting。

如果現在仍然無法連線,問題可能處在報文流程的後續階段,有可能並不在 nftables 的規則集合範圍之內。

總結

本文介紹瞭如何透過 nftables 的跟蹤機制檢查丟包或其他型別的連線問題。本系列的下一篇文章將展示如何檢查連線跟蹤系統和可能與連線跟蹤流相關的 NAT 資訊。

原文來自:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2779241/,如需轉載,請註明出處,否則將追究法律責任。

相關文章