Linux連接跟蹤Conntrack:原理、應用與內核實現
從我們日常使用的手機 APP,到企業復雜的網絡架構,連接跟蹤(conntrack)都在背后默默發揮著關鍵作用。它就像是網絡世界的 “大管家”,幫助眾多網絡應用維持著高效、穩定的運行。以 Kubernetes Service 為例,在容器編排的復雜環境中,Kubernetes 需要將外部的網絡請求準確無誤地轉發到對應的容器服務上。這一過程中,連接跟蹤就像是導航儀,它記錄著每個服務的連接狀態,確保請求能夠被正確路由,讓各個容器服務之間的通信有條不紊地進行 。
再看 Docker network,當我們在容器中運行各種應用時,容器之間以及容器與外部網絡之間的通信管理十分重要。連接跟蹤能夠精準地識別和跟蹤這些連接,讓容器里的應用如同在一個有序的網絡社區中,彼此可以順暢交流,互不干擾。還有 iptables 主機防火墻,它守護著主機網絡的安全。
連接跟蹤則為它提供了關鍵信息,幫助防火墻判斷哪些連接是合法的,哪些可能存在風險,從而對網絡流量進行有效的過濾和控制,保障主機的網絡安全。可以說,連接跟蹤就像網絡世界的基石,雖然平時不太容易被我們察覺,但一旦缺失,許多網絡應用都將陷入混亂。接下來,讓我們深入了解連接跟蹤的原理、應用以及它在 Linux 內核中的實現機制,揭開它神秘的面紗。
Part1.連接跟蹤(Conntrack)概述
顧名思義,連接跟蹤是保存連接狀態的一種機制。為什么要保存連接狀態呢? 舉個例子,當你通過瀏覽器訪問一個網站(連接網站的80
端口)時,預期會收到服務器發送的源端口為80
的報文回應,防火墻自然應該放行這些回應報文。那是不是所有源端口為80
端口的報文都應該放行呢?顯然不是,我們只應該放行源IP為服務器地址,源端口為80
的報文,而應該阻止源地址不符的報文,即使它的源端口也是80
。總結一下這種情況就是,我們只應該讓主動發起的連接產生的雙向報文通過。
圖片
另一個例子是NAT。我們可以使用iptables配置nat表進行地址或者端口轉換的規則。如果每一個報文都去查詢規則,這樣效率太低了,因為同一個連接的轉換方式是不變的!連接跟蹤提供了一種緩存解決方案:當一條連接的第一個數據包通過時查詢nat表時,連接跟蹤將轉換方法保存下來,后續的報文只需要根據連接跟蹤里保存的轉換方法就可以了。
1.1連接跟蹤發生在哪里?
連接跟蹤需要拿到報文的第一手資料,因此它們的入口是以高優先級存在于LOCAL_OUT(本機發送)和PRE_ROUTING(報文接收)這兩個鏈。
既然有入口,自然就有出口。連接跟蹤采用的方案是在入口記錄,在出口確認(confirm)。以IPv4為例:
圖片
當連接的第一個skb通過入口時,連接跟蹤會將連接跟蹤信息保存在skb->nfctinfo,而在出口處,連接跟蹤會從skb上取下連接跟蹤信息,保存在自己的hash表中。當然,如果這個數據包在中途其他HOOK點被丟棄了,也就不存在最后的confirm過程了。
1.2連接跟蹤信息是什么?
連接跟蹤信息會在入口處進行計算,保存在skb上,信息具體包括tuple信息(地址、端口、協議號等)、擴展信息以及各協議的私有信息。
圖片
- tuple信息包括發送和接收兩個方向,對TCP和UDP來說,是IP加Port;對ICMP來說是IP加Type和Code,等等;
- 擴展信息比較復雜,本文暫時略過;
- 各協議的私有信息,比如對TCP就是序號、重傳次數、縮放因子等。
報文的連接跟蹤狀態
途徑Netfilter框架的每一個報文總是會在入口處(PRE ROUTING或者LOCAL OUT)被賦予一個連接跟蹤狀態。這個狀態存儲在skb->nfctinfo,有以下常見的取值:
- IP_CT_ESTABLISHED:這是一個屬于已經建立連接的報文,Netfilter目擊過兩個方向都互通過報文了
- IP_CT_RELATED:這個狀態的報文所處的連接與另一個IP_CT_ESTABLISHED狀態的連接是有聯系的。比如典型的ftp,ftp-data的連接就是ftp-control派生出來的,它就是RELATED狀態
- IP_CT_NEW:這是連接的第一個包,常見的就是TCP中的SYN包,UDP、ICMP中第一個包,
- IP_CT_ESTABLISHED + IP_CT_IS_REPLY:與IP_CT_ESTABLISHED類似,但是是在回復方向
- IP_CT_RELATED + IP_CT_IS_REPLY:與IP_CT_RELATED類似,但是是在回復方向
Part2.連接跟蹤(Conntrack)原理剖析
2.1 底層工作機制
連接跟蹤的底層工作機制,就像是一場精心編排的 “數據包處理交響樂”,每一個環節都緊密相扣,有條不紊。
當數據包進入網絡節點時,連接跟蹤系統就像一個敏銳的 “攔截者”,迅速對其進行攔截。它會仔細分析數據包的頭部信息,從中提取出關鍵的五元組信息,即源 IP 地址、目的 IP 地址、源端口、目的端口和協議類型。這些信息就像是數據包的 “身份密碼”,連接跟蹤系統憑借它們來判斷這個數據包是否屬于一個已有的連接,或者是否需要建立一個新的連接。
一旦識別出數據包的 “身份”,連接跟蹤系統就會開始建立連接追蹤記錄。如果這個數據包是一個新連接的開始,比如 TCP 協議中的 SYN 包,連接跟蹤系統會在連接跟蹤表中創建一個新的記錄項。這個記錄項就像是一個新的 “檔案”,記錄著這個連接的初始狀態和相關信息。在這個 “檔案” 里,會包含連接的五元組信息、創建時間、當前狀態等內容,為后續對這個連接的跟蹤和管理提供了基礎 。
隨著數據的傳輸,連接跟蹤系統會持續關注連接的狀態變化。當接收到屬于已有連接的數據包時,它會像一個嚴謹的 “檔案管理員”,及時更新連接記錄中的各種統計信息,比如收發包數、字節數等。同時,它還會根據數據包的類型和內容,更新連接的狀態。例如,在 TCP 連接中,當接收到 SYN + ACK 包時,連接狀態就會從 “SYN_SENT” 更新為 “ESTABLISHED”,標志著連接已經成功建立 。
當連接結束時,無論是正常的結束,還是因為超時、錯誤等原因導致的異常結束,連接跟蹤系統都會及時刪除連接記錄。它會從連接跟蹤表中移除對應的記錄項,釋放相關的系統資源,就像清理不再使用的 “檔案” 一樣,確保連接跟蹤表的高效運行,為新的連接記錄騰出空間 。
2.2與 Netfilter 的淵源
在 Linux 內核的網絡世界里,Netfilter 就像是一個強大的 “交通樞紐”,而連接跟蹤則是這個 “交通樞紐” 中不可或缺的一部分。Netfilter 是 Linux 內核中一個對數據包進行控制、修改和過濾的框架,它在內核協議棧中精心設置了若干 hook 點,這些 hook 點就像是交通要道上的 “檢查站”,所有流經的數據包都必須在這里接受檢查和處理 。
連接跟蹤正是借助 Netfilter 的 hook 點來實現其功能的。當數據包到達 Netfilter 設置的 hook 點時,連接跟蹤模塊就會被觸發。它會對數據包進行分析和處理,提取出連接相關的信息,并將這些信息記錄到連接跟蹤表中。在 NF_INET_PRE_ROUTING 這個 hook 點,連接跟蹤模塊會對進入系統的數據包進行檢查,判斷是否需要建立新的連接跟蹤記錄;而在 NF_INET_POST_ROUTING 這個 hook 點,它會對離開系統的數據包進行處理,更新連接的狀態信息 。
不過,隨著技術的不斷發展,云原生網絡方案 Cilium 帶來了一種全新的思路。Cilium 在 1.7.4 + 版本中,基于 BPF hook 實現了一套獨立的連接跟蹤和 NAT 機制。BPF hook 就像是 Cilium 自己搭建的 “檢查站”,它能夠實現與 Netfilter 中 hook 機制類似的數據包攔截功能 。
在這個基礎上,Cilium 構建了一套全新的連接跟蹤和 NAT 系統,這使得它即便在卸載 Netfilter 的情況下,也能正常支持 Kubernetes 的 ClusterIP、NodePort、ExternalIPs 和 LoadBalancer 等功能 。由于這套連接跟蹤機制是獨立于 Netfilter 的,它的連接跟蹤和 NAT 信息不會存儲在內核中 Netfilter 的連接跟蹤表和 NAT 表中,而是有自己獨立的存儲和管理方式,這也為網絡管理帶來了更多的靈活性和創新性 。
Part3.連接跟蹤(Conntrack)的多元應用
3.1 在 NAT 中的關鍵角色
NAT,全稱 Network Address Translation,即網絡地址轉換,它就像是網絡世界里的 “地址翻譯官”。在 IPv4 地址資源日益緊張的今天,NAT 技術應運而生,它允許一個機構或網絡以一個公用 IP 地址出現在 Internet 上 。簡單來說,它能夠把內部私有網絡地址翻譯成合法網絡 IP 地址 。
在一個企業內部網絡中,眾多員工的電腦都分配有私有 IP 地址,如 192.168.1.x 段的地址。當這些電腦需要訪問互聯網時,NAT 設備就會發揮作用。它會將數據包中的源 IP 地址(私有 IP)轉換為一個合法的公網 IP 地址,然后將數據包發送出去。當外部服務器返回響應數據包時,NAT 設備又能根據之前的轉換記錄,把目的 IP 地址(公網 IP)轉換回對應的私有 IP 地址,確保數據包能準確無誤地回到員工的電腦上 。
在這個過程中,連接跟蹤起著至關重要的輔助作用。當NAT設備進行地址轉換時,連接跟蹤系統會記錄下每個連接的相關信息,包括源IP、目的 IP、源端口、目的端口以及協議類型等 。這些信息就像是一個個 “連接檔案”,當后續的數據包到來時,NAT設備可以根據連接跟蹤表中的記錄,快速準確地進行地址轉換,確保通信的順暢 。而且,對于那些經過NAT轉換的連接,連接跟蹤系統能夠識別出回復報文,自動完成反向轉換,無需額外添加規則,大大提高了網絡通信的效率 。
3.2 助力狀態包過濾
狀態包過濾,是一種比傳統包過濾更為智能和高效的網絡安全技術 。傳統包過濾只基于每個數據包的源和目的地址以及端口號進行檢查,對每個數據包獨立處理,就像是一個只看表面信息的 “檢查員” 。而狀態包過濾則像是一個經驗豐富的 “偵探”,它會根據網絡連接的狀態,對數據包進行過濾和管理 。
狀態包過濾器能夠檢測網絡連接的狀態,比如TCP連接的三次握手過程,它都了如指掌 。當一個TCP SYN包到達時,狀態包過濾器會知道這是一個新連接的開始;當后續的SYN + ACK 包和 ACK包到來時,它能根據之前的記錄,判斷這些數據包是否屬于同一個連接 。通過這種方式,狀態包過濾器可以判斷是否允許特定的數據包通過網絡,從而有效地防范會話劫持、拒絕服務攻擊等網絡安全威脅 。
連接跟蹤為狀態包過濾提供了關鍵的連接狀態信息。連接跟蹤系統會維護一個連接狀態表,記錄著每個連接的詳細信息,包括連接的建立、數據傳輸以及關閉等各個階段 。狀態包過濾器在處理數據包時,會參考連接跟蹤表中的信息,判斷該數據包是否屬于一個已建立的合法連接 。如果是,就允許數據包通過;如果不是,就會根據安全策略進行處理,比如丟棄數據包 。在一個企業網絡中,連接跟蹤表記錄著內部員工與外部服務器建立的 HTTP 連接信息。當外部服務器返回的 HTTP 響應數據包到達時,狀態包過濾器會根據連接跟蹤表,確認該數據包屬于合法連接,從而允許其進入企業內部網絡 。
3.3 應用層網關擴展
在網絡協議的世界里,大多數協議在 IP 層和傳輸層頭部進行轉換處理就可以正常工作,但有些應用層協議比較特殊,它們在協議數據報文中包含了地址信息 。為了讓這些應用也能順利完成 NAT 轉換,就需要連接跟蹤通過擴展組件來跟蹤應用層協議,這就是應用層網關(ALG)技術 。
以 FTP 協議為例,它在數據傳輸過程中會包含地址和端口信息。當一個 FTP 客戶端連接到 FTP 服務器時,首先會建立控制連接,用于傳輸命令和響應 。在這個過程中,連接跟蹤系統會記錄下控制連接的相關信息 。當客戶端需要傳輸數據時,會通過 PORT 或 PASV 命令來建立數據連接 。這些命令中包含了客戶端或服務器的數據傳輸地址和端口信息 。
連接跟蹤的擴展組件會對這些信息進行分析和處理,確保在 NAT 環境下,數據連接能夠正確建立 。如果客戶端使用 PORT 命令,連接跟蹤組件會根據 NAT 轉換規則,修改命令中的地址信息,使其與轉換后的公網地址一致 。這樣,FTP 服務器就能正確地與客戶端建立數據連接,實現文件的傳輸 。
Part4.內核中連接跟蹤的實現
4.1 模塊初始化流程
在 Linux 內核中,連接跟蹤的模塊初始化是一個嚴謹且關鍵的過程,就像是搭建一座大廈前的奠基工作,為后續的連接跟蹤功能奠定了堅實的基礎。這一過程主要由 nf_conntrack_init_init_net () 函數和 nf_conntrack_init_net () 函數協同完成 。
圖片
這張圖乍一看比較亂,但是這張圖非常好的說明了數據流在內核中的過程;在圖中的灰色框標記了conntrack的位置,這個是連接跟蹤的起始點,其實在netfilter的所有HOOK點都可以更改流(flow)跟蹤的信息。
nf_conntrack_init_init_net () 函數首先會根據系統內存的大小來確定連接跟蹤表的關鍵參數。它會給全局變量 nf_conntrack_htable_size 賦值,這個值指定了存放連接跟蹤條目的哈希表的大小 。同時,它還會計算出系統可以創建的連接跟蹤條目的最大數量,并賦值給 nf_conntrack_max 。這兩個參數就像是連接跟蹤表的 “容量規劃師”,合理地規劃了連接跟蹤表的存儲能力 。接著,該函數會為 nf_conn 結構申請 slab cache,這個緩存就像是一個專門為 nf_conn 結構打造的 “倉庫”,用于高效地存儲和管理 nf_conn 結構 。
每個連接的狀態都由一個 nf_conn 結構體實例來描述,每個連接又分為 original 和 reply 兩個方向,每個方向都用一個元組(tuple)表示,tuple 中包含了這個方向上數據包的關鍵信息,如源 IP、目的 IP、源 port、目的 port 等 。隨后,它會給全局的 nf_ct_l3protos [] 數組賦上默認值,這個數組中的每個元素都被賦值為 nf_conntrack_l3proto_generic,這是一個不區分 L3 協議的處理函數,后續的初始化會根據不同的 L3 協議為其賦上相應的值 。最后,它還會給全局的 hash 表 nf_ct_helper_hash 分配一個頁大小的空間,這個 hash 表用于存放 helper 類型的 conntrack extension 。
nf_conntrack_init_net () 函數則主要負責初始化 net->ct 成員 。net 是本地 CPU 的網絡命名空間,在單 CPU 系統中就是全局變量 init_net 。它會將 net->ct.count 計數器設置為 0,就像是給連接跟蹤的 “計數器” 清零,準備開始記錄連接數量 。然后初始化 unconfirmed 鏈表和 dying 鏈表,這兩個鏈表就像是連接跟蹤的 “臨時存放區”,分別用于存放未確認的連接和即將死亡的連接 。
接著,它會給 net->ct.stat 分配空間并清零,net->ct.stat 用于統計連接跟蹤的相關數據,就像是一個 “數據統計員” 。之后,它會初始化 conntrack hash table,根據之前計算好的 nf_conntrack_htable_size 來分配相應的內存空間 。最后,它還會初始化 net->ct.expect_hash 及緩存,并在 /proc 中創建相應文件,完成一系列的初始化工作 。
4.2 連接跟蹤表的數據結構
連接跟蹤表的數據結構是連接跟蹤功能實現的核心部分,它就像是一個精心設計的 “信息倉庫”,高效地存儲和管理著網絡連接的各種信息 。在這個 “倉庫” 中,nf_conn 結構體是描述連接狀態的關鍵 “單元” 。每個 nf_conn 結構體實例都對應著一個網絡連接,它包含了連接的各種詳細信息,如連接的兩個方向(original 和 reply)的元組信息,這些元組信息就像是連接的 “身份標識”,包含了源 IP、目的 IP、源端口、目的端口和協議類型等關鍵內容 。
同時,nf_conn 結構體還記錄了連接的狀態,比如是新建連接(NEW)、已建立連接(ESTABLISHED)還是其他狀態,這些狀態信息就像是連接的 “狀態標簽”,方便系統對連接進行管理和處理 。此外,它還包含了一些與連接相關的統計信息,如收發包數、字節數等,這些統計信息就像是連接的 “運行數據記錄”,有助于系統了解連接的運行情況 。
連接跟蹤表采用哈希表(hash table)來實現,這是一種高效的數據結構,能夠快速地定位和查找連接信息 。哈希表就像是一個大型的 “索引庫”,每個連接的元組信息通過特定的哈希函數計算出一個哈希值,這個哈希值就像是連接在 “索引庫” 中的 “索引編號”,通過這個編號可以快速地找到對應的連接記錄 。
在哈希表中,每個哈希桶(bucket)是一個鏈表,當多個連接的哈希值相同時,這些連接的記錄就會以鏈表的形式存儲在同一個哈希桶中 。這種設計既利用了哈希表的快速查找特性,又解決了哈希沖突的問題 。在一個高并發的網絡環境中,可能會有大量的連接同時存在,哈希表的這種設計能夠保證系統在處理這些連接時,依然能夠快速地進行查找和匹配操作,大大提高了連接跟蹤的效率 。
(1)重要結構體
- struct nf_hook_ops {}: 在HOOK點上注冊的連接跟蹤信息,通過nf_register_hooks()注冊
- struct nf_conntrack_tuple {}: 連接跟蹤的基本元素,表示特定方向的流。
- struct nf_conn {}:連接跟蹤條目,定義一個 flow。
在nf_conn中有重要成員:如ct_general、status、master、tuplehash、timeout等。
struct nf_conn {
struct nf_conntrack ct_general;
spinlock_t lock;
u16 cpu;
/* XXX should I move this to the tail ? - Y.K */
/* These are my tuples; original and reply */
struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
/* Have we seen traffic both ways yet? (bitset) */
unsigned long status;
/* Timer function; drops refcnt when it goes off. */
struct timer_list timeout;
possible_net_t ct_net;
/* all members below initialized via memset */
u8 __nfct_init_offset[0];
/* If we were expected by an expectation, this will be it */
struct nf_conn *master;
#if defined(CONFIG_NF_CONNTRACK_MARK)
u_int32_t mark;
#endif
#ifdef CONFIG_NF_CONNTRACK_SECMARK
u_int32_t secmark;
#endif
/* Extensions */
struct nf_ct_ext *ext;
/* Storage reserved for other modules, must be the last member */
union nf_conntrack_proto proto;
};
(2)重要函數
- hash_conntrack_raw():根據 tuple 計算出一個 32 位的哈希值(hash key)。
- nf_conntrack_in():連接跟蹤模塊的核心,包進入連接跟蹤的地方。在此函數中包含下邊的步驟:resolve_normal_ct() -> nf_ct_timeout_lookup()在resolve_normal_ct() 中會計算元組的散列值,進行匹配,沒有就創建nf_conntrack_tuple_hash,將其加入未確認tuplehash列表中,已經創建則判斷狀態是否超時。
- nf_conntrack_confirm():確認前面通過 nf_conntrack_in() 創建的新連接(是否被丟棄),將元組從未確認tuplehash列表中刪除。
- nf_ct_get(skb, ctinfo):獲取連接跟蹤數據,沒有建立返回null
具體細節可以在內核代碼中查看代碼路徑官方網站:https://elixir.bootlin.com/linux/v4.4.155/source/net/netfilter
4.3 連接查找與匹配策略
當一個數據包到達時,連接查找的流程就像是在一個龐大的圖書館中查找一本特定的書籍 。首先,系統會從數據包中提取出關鍵的元組信息,這些元組信息就像是書籍的 “關鍵詞” 。然后,根據這些元組信息,通過哈希函數計算出一個哈希值,這個哈希值就像是圖書館中的 “書架編號” 。系統會根據這個哈希值快速定位到哈希表中的相應哈希桶,就像是找到了對應的書架 。
如果哈希桶中只有一個連接記錄,那么就可以直接找到對應的連接,這就像是在書架上只有一本書時,直接就能拿到想要的書 。但如果哈希桶中存在多個連接記錄(即發生了哈希沖突),系統就會沿著鏈表依次查找,對比每個連接記錄的元組信息與數據包的元組信息是否匹配,這就像是在書架上有很多本書時,需要逐一查看書籍的內容來找到目標書籍 。
匹配策略的實現主要基于數據包的元組信息 。系統會嚴格對比數據包的源 IP、目的 IP、源端口、目的端口和協議類型等元組信息與連接跟蹤表中記錄的元組信息 。只有當這些信息完全一致時,才認為找到了匹配的連接 。在一個企業網絡中,當內部員工的電腦向外部服務器發送數據包時,系統會根據數據包的元組信息在連接跟蹤表中進行查找和匹配 。如果找到了匹配的連接,就說明這是一個已建立連接的后續數據包,系統會根據連接的狀態進行相應的處理,比如更新連接的統計信息等 。如果沒有找到匹配的連接,就說明這可能是一個新的連接請求,系統會按照新連接的處理流程進行處理,比如創建新的連接跟蹤記錄 。
4.4 連接生命周期管理
連接的生命周期管理就像是一場精心安排的 “旅程”,涵蓋了連接的創建、確認、更新和刪除等多個重要階段,每個階段都有其特定的內核處理邏輯和狀態轉換 。
當一個新的連接請求到來時,比如 TCP 協議中的 SYN 包到達,內核會創建一個新的連接跟蹤記錄 。內核會從數據包中提取出五元組信息,然后為這個連接分配一個 nf_conn 結構體實例 。在這個結構體中,會初始化連接的兩個方向的元組信息,將連接狀態設置為初始狀態,比如 TCP 連接的 SYN_SENT 狀態 。同時,還會將這個連接的原始方向的 tuple_hash 添加到 unconfirmed 鏈表中,等待進一步的確認 。
當連接收到對方的確認信息時,比如 TCP 協議中的 SYN + ACK 包到達,內核會對連接進行確認操作 。它會將連接從 unconfirmed 鏈表中移除,并將其添加到連接跟蹤表的哈希表中,標志著連接已被確認 。同時,會更新連接的狀態,將 TCP 連接的狀態更新為 ESTABLISHED,表明連接已成功建立 。
在連接的數據傳輸過程中,內核會持續關注連接的狀態變化,并及時更新連接的相關信息 。當接收到屬于已有連接的數據包時,內核會更新連接記錄中的收發包數、字節數等統計信息 。如果連接的狀態發生了變化,比如 TCP 連接進入了 FIN_WAIT_1 狀態,內核也會及時更新連接的狀態信息 。
當連接結束時,無論是正常結束還是異常結束,內核都會執行刪除操作 。對于正常結束的連接,比如 TCP 連接完成了四次揮手過程,內核會將連接從連接跟蹤表的哈希表中移除,并釋放 nf_conn 結構體實例所占用的內存空間 。對于異常結束的連接,比如連接超時,內核也會同樣進行刪除操作,確保連接跟蹤表的高效運行,為新的連接記錄騰出空間 。 。