溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Linux下如何實(shí)現(xiàn)連接跟蹤

發(fā)布時(shí)間:2021-10-27 11:49:41 來源:億速云 閱讀:254 作者:小新 欄目:系統(tǒng)運(yùn)維

小編給大家分享一下Linux下如何實(shí)現(xiàn)連接跟蹤,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

1 引言

連接跟蹤是許多網(wǎng)絡(luò)應(yīng)用的基礎(chǔ)。例如,Kubernetes Service、ServiceMesh sidecar、 軟件四層負(fù)載均衡器 LVS/IPVS、Docker network、OVS、iptables 主機(jī)防火墻等等,都依賴 連接跟蹤功能。

1.1 概念

連接跟蹤(conntrack)

Linux下如何實(shí)現(xiàn)連接跟蹤

圖 1.1. 連接跟蹤及其內(nèi)核位置

連接跟蹤,顧名思義,就是跟蹤(并記錄)連接的狀態(tài)。

例如,圖 1.1 是一臺(tái) IP 地址為 10.1.1.2 的 Linux 機(jī)器,我們能看到這臺(tái)機(jī)器上有三條 連接:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.  機(jī)器訪問外部 HTTP 服務(wù)的連接(目的端口 80)

  3.  外部訪問機(jī)器內(nèi) FTP 服務(wù)的連接(目的端口 21)

  4.  機(jī)器訪問外部 DNS 服務(wù)的連接(目的端口 53)

連接跟蹤所做的事情就是發(fā)現(xiàn)并跟蹤這些連接的狀態(tài),具體包括:

  •  從數(shù)據(jù)包中提取元組(tuple)信息,辨別數(shù)據(jù)流(flow)和對(duì)應(yīng)的連接(connection)

  •  為所有連接維護(hù)一個(gè)狀態(tài)數(shù)據(jù)庫(conntrack table),例如連接的創(chuàng)建時(shí)間、發(fā)送 包數(shù)、發(fā)送字節(jié)數(shù)等等

  •  回收過期的連接(GC)

  •  為更上層的功能(例如 NAT)提供服務(wù)

需要注意的是,連接跟蹤中所說的“連接”,概念和 TCP/IP 協(xié)議中“面向連接”( connection oriented)的“連接”并不完全相同,簡單來說:

  •  TCP/IP 協(xié)議中,連接是一個(gè)四層(Layer 4)的概念。

    •  TCP 是有連接的,或稱面向連接的(connection oriented),發(fā)送出去的包都要求對(duì)端應(yīng)答(ACK),并且有重傳機(jī)制

    •  UDP 是無連接的,發(fā)送的包無需對(duì)端應(yīng)答,也沒有重傳機(jī)制

  •  CT 中,一個(gè)元組(tuple)定義的一條數(shù)據(jù)流(flow )就表示一條連接(connection)。

    •  后面會(huì)看到 UDP 甚至是 ICMP 這種三層協(xié)議在 CT 中也都是有連接記錄的

    •  但不是所有協(xié)議都會(huì)被連接跟蹤

本文中用到“連接”一詞時(shí),大部分情況下指的都是后者,即“連接跟蹤”中的“連接”。

網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT)

Linux下如何實(shí)現(xiàn)連接跟蹤

圖 1.2. NAT 及其內(nèi)核位置

網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT),意思也比較清楚:對(duì)(數(shù)據(jù)包的)網(wǎng)絡(luò)地址(IP + Port)進(jìn)行轉(zhuǎn)換。

例如,圖 1.2 中,機(jī)器自己的 IP 10.1.1.2 是能與外部正常通信的,但 192.168 網(wǎng)段是私有 IP 段,外界無法訪問,也就是說源 IP 地址是 192.168 的包,其應(yīng)答包是無 法回來的。

因此當(dāng)源地址為 192.168 網(wǎng)段的包要出去時(shí),機(jī)器會(huì)先將源 IP 換成機(jī)器自己的 10.1.1.2 再發(fā)送出去;收到應(yīng)答包時(shí),再進(jìn)行相反的轉(zhuǎn)換。這就是 NAT 的基本過程。

Docker 默認(rèn)的 bridge 網(wǎng)絡(luò)模式就是這個(gè)原理 [4]。每個(gè)容器會(huì)分一個(gè)私有網(wǎng)段的 IP 地址,這個(gè) IP 地址可以在宿主機(jī)內(nèi)的不同容器之間通信,但容器流量出宿主機(jī)時(shí)要進(jìn)行 NAT。

NAT 又可以細(xì)分為幾類:

  •  SNAT:對(duì)源地址(source)進(jìn)行轉(zhuǎn)換

  •  DNAT:對(duì)目的地址(destination)進(jìn)行轉(zhuǎn)換

  •  Full NAT:同時(shí)對(duì)源地址和目的地址進(jìn)行轉(zhuǎn)換

以上場(chǎng)景屬于 SNAT,將不同私有 IP 都映射成同一個(gè)“公有 IP”,以使其能訪問外部網(wǎng)絡(luò)服 務(wù)。這種場(chǎng)景也屬于正向代理。

NAT 依賴連接跟蹤的結(jié)果。連接跟蹤最重要的使用場(chǎng)景就是 NAT。

四層負(fù)載均衡(L4 LB)

Linux下如何實(shí)現(xiàn)連接跟蹤

圖 1.3. L4LB: Traffic path in NAT mode [3]

再將范圍稍微延伸一點(diǎn),討論一下 NAT 模式的四層負(fù)載均衡。

四層負(fù)載均衡是根據(jù)包的四層信息(例如 src/dst ip, src/dst port, proto)做流量分發(fā)。

VIP(Virtual IP)是四層負(fù)載均衡的一種實(shí)現(xiàn)方式:

  •  多個(gè)后端真實(shí) IP(Real IP)掛到同一個(gè)虛擬 IP(VIP)上

  •  客戶端過來的流量先到達(dá) VIP,再經(jīng)負(fù)載均衡算法轉(zhuǎn)發(fā)給某個(gè)特定的后端 IP

如果在 VIP 和 Real IP 節(jié)點(diǎn)之間使用的 NAT 技術(shù)(也可以使用其他技術(shù)),那客戶端訪 問服務(wù)端時(shí),L4LB 節(jié)點(diǎn)將做雙向 NAT(Full NAT),數(shù)據(jù)流如圖 1.3。

1.2 原理

了解以上概念之后,我們來思考下連接跟蹤的技術(shù)原理。

要跟蹤一臺(tái)機(jī)器的所有連接狀態(tài),就需要

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.  攔截(或稱過濾)流經(jīng)這臺(tái)機(jī)器的每一個(gè)數(shù)據(jù)包,并進(jìn)行分析。

  3.  根據(jù)這些信息建立起這臺(tái)機(jī)器上的連接信息數(shù)據(jù)庫(conntrack table)。

  4.  根據(jù)攔截到的包信息,不斷更新數(shù)據(jù)庫

例如,

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.  攔截到一個(gè) TCP SYNC 包時(shí),說明正在嘗試建立 TCP 連接,需要?jiǎng)?chuàng)建一條新 conntrack entry 來記錄這條連接

  3.  攔截到一個(gè)屬于已有 conntrack entry 的包時(shí),需要更新這條 conntrack entry 的收發(fā)包數(shù)等統(tǒng)計(jì)信息

除了以上兩點(diǎn)功能需求,還要考慮性能問題,因?yàn)檫B接跟蹤要對(duì)每個(gè)包進(jìn)行過濾和分析 。性能問題非常重要,但不是本文重點(diǎn),后面介紹實(shí)現(xiàn)時(shí)會(huì)進(jìn)一步提及。

之外,這些功能最好還有配套的管理工具來更方便地使用。

1.3 設(shè)計(jì):Netfilter

Linux下如何實(shí)現(xiàn)連接跟蹤

圖 1.4. Netfilter architecture inside Linux kernel

Linux 的連接跟蹤是在 Netfilter 中實(shí)現(xiàn)的。

Netfilter 是 Linux 內(nèi)核中一個(gè)對(duì)數(shù)據(jù) 包進(jìn)行控制、修改和過濾(manipulation and filtering)的框架。它在內(nèi)核協(xié)議 棧中設(shè)置了若干hook 點(diǎn),以此對(duì)數(shù)據(jù)包進(jìn)行攔截、過濾或其他處理。

  “    說地更直白一些,hook 機(jī)制就是在數(shù)據(jù)包的必經(jīng)之路上設(shè)置若干檢測(cè)點(diǎn),所有到達(dá)這 些檢測(cè)點(diǎn)的包都必須接受檢測(cè),根據(jù)檢測(cè)的結(jié)果決定:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.      放行:不對(duì)包進(jìn)行任何修改,退出檢測(cè)邏輯,繼續(xù)后面正常的包處理

  3.      修改:例如修改 IP 地址進(jìn)行 NAT,然后將包放回正常的包處理邏輯

  4.      丟棄:安全策略或防火墻功能

    連接跟蹤模塊只是完成連接信息的采集和錄入功能,并不會(huì)修改或丟棄數(shù)據(jù)包,后者是其 他模塊(例如 NAT)基于 Netfilter hook 完成的。    ”

Netfilter 是最古老的內(nèi)核框架之一,1998 年開始開發(fā),2000 年合并到 2.4.x 內(nèi) 核主線版本 [5]。

1.4 設(shè)計(jì):進(jìn)一步思考

現(xiàn)在提到連接跟蹤(conntrack),可能首先都會(huì)想到 Netfilter。但由 1.2 節(jié)的討論可知, 連接跟蹤概念是獨(dú)立于 Netfilter 的,Netfilter 只是 Linux 內(nèi)核中的一種連接跟蹤實(shí)現(xiàn)。

換句話說,只要具備了 hook 能力,能攔截到進(jìn)出主機(jī)的每個(gè)包,完全可以在此基礎(chǔ)上自 己實(shí)現(xiàn)一套連接跟蹤。

Linux下如何實(shí)現(xiàn)連接跟蹤

圖 1.5. Cilium's conntrack and NAT architectrue

云原生網(wǎng)絡(luò)方案 Cilium 在 1.7.4+ 版本就實(shí)現(xiàn)了這樣一套獨(dú)立的連接跟蹤和 NAT 機(jī)制 (完備功能需要 Kernel 4.19+)。其基本原理是:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.  基于 BPF hook 實(shí)現(xiàn)數(shù)據(jù)包的攔截功能(等價(jià)于 netfilter 里面的 hook 機(jī)制)

  3.  在 BPF hook 的基礎(chǔ)上,實(shí)現(xiàn)一套全新的 conntrack 和 NAT

因此,即便卸載掉 Netfilter ,也不會(huì)影響 Cilium 對(duì) Kubernetes ClusterIP、NodePort、ExternalIPs 和 LoadBalancer 等功能的支持 [2]。

由于這套連接跟蹤機(jī)制是獨(dú)立于 Netfilter 的,因此它的 conntrack 和 NAT 信息也沒有 存儲(chǔ)在內(nèi)核的(也就是 Netfilter 的)conntrack table 和 NAT table。所以常規(guī)的 conntrack/netstats/ss/lsof 等工具是看不到的,要使用 Cilium 的命令,例如:

$ cilium bpf nat list  $ cilium bpf ct list global

配置也是獨(dú)立的,需要在 Cilium 里面配置,例如命令行選項(xiàng) --bpf-ct-tcp-max。

另外,本文會(huì)多次提到連接跟蹤模塊和 NAT 模塊獨(dú)立,但出于性能考慮,具體實(shí)現(xiàn)中 二者代碼可能是有耦合的。例如 Cilium 做 conntrack 的垃圾回收(GC)時(shí)就會(huì)順便把 NAT 里相應(yīng)的 entry 回收掉,而非為 NAT 做單獨(dú)的 GC。

以上是理論篇,接下來看一下內(nèi)核實(shí)現(xiàn)。

2 Netfilter hook 機(jī)制實(shí)現(xiàn)

Netfilter 由幾個(gè)模塊構(gòu)成,其中最主要的是連接跟蹤(CT) 模塊和網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT)模塊。

CT 模塊的主要職責(zé)是識(shí)別出可進(jìn)行連接跟蹤的包。CT 模塊獨(dú)立于 NAT 模塊,但主要目的是服務(wù)于后者。

2.1 Netfilter 框架

5 個(gè) hook 點(diǎn)

Linux下如何實(shí)現(xiàn)連接跟蹤

圖 2.1. The 5 hook points in netfilter framework

如上圖所示,Netfilter 在內(nèi)核協(xié)議棧的包處理路徑上提供了 5 個(gè) hook 點(diǎn),分別是:

// include/uapi/linux/netfilter_ipv4.h  #define NF_IP_PRE_ROUTING    0 /* After promisc drops, checksum checks. */  #define NF_IP_LOCAL_IN       1 /* If the packet is destined for this box. */  #define NF_IP_FORWARD        2 /* If the packet is destined for another interface. */  #define NF_IP_LOCAL_OUT      3 /* Packets coming from a local process. */  #define NF_IP_POST_ROUTING   4 /* Packets about to hit the wire. */  #define NF_IP_NUMHOOKS       5

用戶可以在這些 hook 點(diǎn)注冊(cè)自己的處理函數(shù)(handlers)。當(dāng)有數(shù)據(jù)包經(jīng)過 hook 點(diǎn)時(shí), 就會(huì)調(diào)用相應(yīng)的 handlers。

  “    另外還有一套 NF_INET_ 開頭的定義,include/uapi/linux/netfilter.h。這兩套是等價(jià)的,從注釋看,NF_IP_ 開頭的定義可能是為了保持兼容性。

enum nf_inet_hooks {       NF_INET_PRE_ROUTING,       NF_INET_LOCAL_IN,       NF_INET_FORWARD,       NF_INET_LOCAL_OUT,       NF_INET_POST_ROUTING,       NF_INET_NUMHOOKS   };

    ”

hook 返回值類型

hook 函數(shù)對(duì)包進(jìn)行判斷或處理之后,需要返回一個(gè)判斷結(jié)果,指導(dǎo)接下來要對(duì)這個(gè)包做什 么。可能的結(jié)果有:

// include/uapi/linux/netfilter.h  #define NF_DROP   0  // 已丟棄這個(gè)包  #define NF_ACCEPT 1  // 接受這個(gè)包,繼續(xù)下一步處理  #define NF_STOLEN 2  // 當(dāng)前處理函數(shù)已經(jīng)消費(fèi)了這個(gè)包,后面的處理函數(shù)不用處理了  #define NF_QUEUE  3  // 應(yīng)當(dāng)將包放到隊(duì)列  #define NF_REPEAT 4  // 當(dāng)前處理函數(shù)應(yīng)當(dāng)被再次調(diào)用

hook 優(yōu)先級(jí)

每個(gè) hook 點(diǎn)可以注冊(cè)多個(gè)處理函數(shù)(handler)。在注冊(cè)時(shí)必須指定這些 handlers 的優(yōu)先級(jí),這樣觸發(fā) hook 時(shí)能夠根據(jù)優(yōu)先級(jí)依次調(diào)用處理函數(shù)。

2.2 過濾規(guī)則的組織

iptables 是配置 Netfilter 過濾功能的用戶空間工具。為便于管理, 過濾規(guī)則按功能分為若干 table:

  •  raw

  •  filter

  •  nat

  •  mangle

這不是本文重點(diǎn)。更多信息可參考 (譯) 深入理解 iptables 和 netfilter 架構(gòu)

3 Netfilter conntrack 實(shí)現(xiàn)

連接跟蹤模塊用于維護(hù)可跟蹤協(xié)議(trackable protocols)的連接狀態(tài)。也就是說, 連接跟蹤針對(duì)的是特定協(xié)議的包,而不是所有協(xié)議的包。稍后會(huì)看到它支持哪些協(xié)議。

3.1 重要結(jié)構(gòu)體和函數(shù)

重要結(jié)構(gòu)體:

  •    

  • struct nf_conntrack_tuple {}

     : 定義一個(gè) tuple。

  •         struct nf_conntrack_man_proto {}:manipulable part 中協(xié)議相關(guān)的部分。

  •        

  • struct nf_conntrack_man {}

      :tuple 的 manipulable part。

  •  struct nf_conntrack_l4proto {}: 支持連接跟蹤的協(xié)議需要實(shí)現(xiàn)的方法集(以及其他協(xié)議相關(guān)字段)。

  •  struct nf_conntrack_tuple_hash {}:哈希表(conntrack table)中的表項(xiàng)(entry)。

  •  struct nf_conn {}:定義一個(gè) flow。

重要函數(shù):

  •  hash_conntrack_raw():根據(jù) tuple 計(jì)算出一個(gè) 32 位的哈希值(hash key)。

  •  nf_conntrack_in():連接跟蹤模塊的核心,包進(jìn)入連接跟蹤的地方。

  •  resolve_normal_ct() -> init_conntrack() -> l4proto->new():創(chuàng)建一個(gè)新的連接記錄(conntrack entry)。

  •  nf_conntrack_confirm():確認(rèn)前面通過 nf_conntrack_in() 創(chuàng)建的新連接。

3.2 struct nf_conntrack_tuple {}:元組(Tuple)

Tuple 是連接跟蹤中最重要的概念之一。

一個(gè) tuple 定義一個(gè)單向(unidirectional)flow。內(nèi)核代碼中有如下注釋:

  “    //include/net/netfilter/nf_conntrack_tuple.h

  A tuple is a structure containing the information to uniquely identify a connection. ie. if two packets have the same tuple, they are in the same connection; if not, they are not.    ”

結(jié)構(gòu)體定義

//include/net/netfilter/nf_conntrack_tuple.h  // 為方便 NAT 的實(shí)現(xiàn),內(nèi)核將 tuple 結(jié)構(gòu)體拆分為 "manipulatable" 和 "non-manipulatable" 兩部分  // 下面結(jié)構(gòu)體中的 _man 是 manipulatable 的縮寫                                                 // ude/uapi/linux/netfilter.h                                                 union nf_inet_addr {                                                     __u32            all[4];                                                     __be32           ip;                                                     __be32           ip6[4];                                                     struct in_addr   in;                                                     struct in6_addr  in6;  /* manipulable part of the tuple */         /  };  struct nf_conntrack_man {                  /      union nf_inet_addr           u3; -->--/      union nf_conntrack_man_proto u;  -->--\                                             \   // include/uapi/linux/netfilter/nf_conntrack_tuple_common.h      u_int16_t l3num; // L3 proto            \  // 協(xié)議相關(guān)的部分  };                                            union nf_conntrack_man_proto {                                                    __be16 all;/* Add other protocols here. */                                                   struct { __be16 port; } tcp;                                                    struct { __be16 port; } udp;                                                    struct { __be16 id;   } icmp;                                                    struct { __be16 port; } dccp;                                                    struct { __be16 port; } sctp;                                                    struct { __be16 key;  } gre;                                                };  struct nf_conntrack_tuple { /* This contains the information to distinguish a connection. */      struct nf_conntrack_man src;  // 源地址信息,manipulable part      struct {          union nf_inet_addr u3;          union {              __be16 all; /* Add other protocols here. */              struct { __be16 port;         } tcp;              struct { __be16 port;         } udp;              struct { u_int8_t type, code; } icmp;              struct { __be16 port;         } dccp;              struct { __be16 port;         } sctp;              struct { __be16 key;          } gre;          } u;          u_int8_t protonum; /* The protocol. */          u_int8_t dir;      /* The direction (for tuplehash) */      } dst;                       // 目的地址信息  };

Tuple 結(jié)構(gòu)體中只有兩個(gè)字段 src 和 dst,分別保存源和目的信息。src 和 dst 自身也是結(jié)構(gòu)體,能保存不同類型協(xié)議的數(shù)據(jù)。以 IPv4 UDP 為例,五元組分別保存在如下字段:

  •  dst.protonum:協(xié)議類型

  •  src.u3.ip:源 IP 地址

  •  dst.u3.ip:目的 IP 地址

  •  src.u.udp.port:源端口號(hào)

  •  dst.u.udp.port:目的端口號(hào)

CT 支持的協(xié)議

從以上定義可以看到,連接跟蹤模塊目前只支持以下六種協(xié)議:TCP、UDP、ICMP、DCCP、SCTP、GRE。

注意其中的 ICMP 協(xié)議。大家可能會(huì)認(rèn)為,連接跟蹤模塊依據(jù)包的三層和四層信息做 哈希,而 ICMP 是三層協(xié)議,沒有四層信息,因此 ICMP 肯定不會(huì)被 CT 記錄。但實(shí)際上 是會(huì)的,上面代碼可以看到,ICMP 使用了其頭信息中的 ICMP type和 code 字段來 定義 tuple。

3.3 struct nf_conntrack_l4proto {}:協(xié)議需要實(shí)現(xiàn)的方法集合

支持連接跟蹤的協(xié)議都需要實(shí)現(xiàn) struct nf_conntrack_l4proto {} 結(jié)構(gòu)體 中定義的方法,例如 pkt_to_tuple()。

// include/net/netfilter/nf_conntrack_l4proto.h  struct nf_conntrack_l4proto {      u_int16_t l3proto; /* L3 Protocol number. */      u_int8_t  l4proto; /* L4 Protocol number. */      // 從包(skb)中提取 tuple      bool (*pkt_to_tuple)(struct sk_buff *skb, ... struct nf_conntrack_tuple *tuple);      // 對(duì)包進(jìn)行判決,返回判決結(jié)果(returns verdict for packet)      int (*packet)(struct nf_conn *ct, const struct sk_buff *skb ...);      // 創(chuàng)建一個(gè)新連接。如果成功返回 TRUE;如果返回的是 TRUE,接下來會(huì)調(diào)用 packet() 方法      bool (*new)(struct nf_conn *ct, const struct sk_buff *skb, unsigned int dataoff);      // 判斷當(dāng)前數(shù)據(jù)包能否被連接跟蹤。如果返回成功,接下來會(huì)調(diào)用 packet() 方法      int (*error)(struct net *net, struct nf_conn *tmpl, struct sk_buff *skb, ...);     ...  };

3.4 struct nf_conntrack_tuple_hash {}:哈希表項(xiàng)

conntrack 將活動(dòng)連接的狀態(tài)存儲(chǔ)在一張哈希表中(key: value)。

hash_conntrack_raw() 根據(jù) tuple 計(jì)算出一個(gè) 32 位的哈希值(key):

// net/netfilter/nf_conntrack_core.c  static u32 hash_conntrack_raw(struct nf_conntrack_tuple *tuple, struct net *net)  {      get_random_once(&nf_conntrack_hash_rnd, sizeof(nf_conntrack_hash_rnd));      /* The direction must be ignored, so we hash everything up to the       * destination ports (which is a multiple of 4) and treat the last three bytes manually.  */      u32 seed = nf_conntrack_hash_rnd ^ net_hash_mix(net);      unsigned int n = (sizeof(tuple->src) + sizeof(tuple->dst.u3)) / sizeof(u32);      return jhash3((u32 *)tuple, n, seed ^ ((tuple->dst.u.all << 16) | tuple->dst.protonum));  }

注意其中是如何利用 tuple 的不同字段來計(jì)算哈希的。

nf_conntrack_tuple_hash 是哈希表中的表項(xiàng)(value):

// include/net/netfilter/nf_conntrack_tuple.h  // 每條連接在哈希表中都對(duì)應(yīng)兩項(xiàng),分別對(duì)應(yīng)兩個(gè)方向(egress/ingress)  // Connections have two entries in the hash table: one for each way  struct nf_conntrack_tuple_hash {      struct hlist_nulls_node   hnnode;   // 指向該哈希對(duì)應(yīng)的連接 struct nf_conn,采用 list 形式是為了解決哈希沖突      struct nf_conntrack_tuple tuple;    // N 元組,前面詳細(xì)介紹過了  };

3.5 struct nf_conn {}:連接(connection)

Netfilter 中每個(gè) flow 都稱為一個(gè) connection,即使是對(duì)那些非面向連接的協(xié)議(例 如 UDP)。每個(gè) connection 用 struct nf_conn {} 表示,主要字段如下:

// include/net/netfilter/nf_conntrack.h                                                    // include/linux/skbuff.h                                          ------>   struct nf_conntrack {                                          |             atomic_t use;  // 連接引用計(jì)數(shù)?                                          |         };  struct nf_conn {                        |      struct nf_conntrack            ct_general;      struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX]; // 哈希表項(xiàng),數(shù)組是因?yàn)橐涗泝蓚€(gè)方向的 flow      unsigned long status; // 連接狀態(tài),見下文      u32 timeout;          // 連接狀態(tài)的定時(shí)器      possible_net_t ct_net;      struct hlist_node    nat_bysource;                                                          // per conntrack: protocol private data      struct nf_conn *master;                             union nf_conntrack_proto {                                                              /* insert conntrack proto private data here */      u_int32_t mark;    /* 對(duì) skb 進(jìn)行特殊標(biāo)記 */            struct nf_ct_dccp dccp;      u_int32_t secmark;                                      struct ip_ct_sctp sctp;                                                              struct ip_ct_tcp tcp;      union nf_conntrack_proto proto; ---------->----->       struct nf_ct_gre gre;  };                                                          unsigned int tmpl_padto;                                                          };

連接的狀態(tài)集合 enum ip_conntrack_status:

// include/uapi/linux/netfilter/nf_conntrack_common.h  enum ip_conntrack_status {      IPS_EXPECTED      = (1 << IPS_EXPECTED_BIT),      IPS_SEEN_REPLY    = (1 << IPS_SEEN_REPLY_BIT),      IPS_ASSURED       = (1 << IPS_ASSURED_BIT),      IPS_CONFIRMED     = (1 << IPS_CONFIRMED_BIT),      IPS_SRC_NAT       = (1 << IPS_SRC_NAT_BIT),      IPS_DST_NAT       = (1 << IPS_DST_NAT_BIT),      IPS_NAT_MASK      = (IPS_DST_NAT | IPS_SRC_NAT),      IPS_SEQ_ADJUST    = (1 << IPS_SEQ_ADJUST_BIT),      IPS_SRC_NAT_DONE  = (1 << IPS_SRC_NAT_DONE_BIT),      IPS_DST_NAT_DONE  = (1 << IPS_DST_NAT_DONE_BIT),      IPS_NAT_DONE_MASK = (IPS_DST_NAT_DONE | IPS_SRC_NAT_DONE),      IPS_DYING         = (1 << IPS_DYING_BIT),      IPS_FIXED_TIMEOUT = (1 << IPS_FIXED_TIMEOUT_BIT),      IPS_TEMPLATE      = (1 << IPS_TEMPLATE_BIT),      IPS_UNTRACKED     = (1 << IPS_UNTRACKED_BIT),      IPS_HELPER        = (1 << IPS_HELPER_BIT),      IPS_OFFLOAD       = (1 << IPS_OFFLOAD_BIT),      IPS_UNCHANGEABLE_MASK = (IPS_NAT_DONE_MASK | IPS_NAT_MASK |                   IPS_EXPECTED | IPS_CONFIRMED | IPS_DYING |                   IPS_SEQ_ADJUST | IPS_TEMPLATE | IPS_OFFLOAD),  };

3.6 nf_conntrack_in():進(jìn)入連接跟蹤

Linux下如何實(shí)現(xiàn)連接跟蹤

Fig. Netfilter 中的連接跟蹤點(diǎn)

如上圖所示,Netfilter 在四個(gè) Hook 點(diǎn)對(duì)包進(jìn)行跟蹤:

  1.  PRE_ROUTING 和 LOCAL_OUT:調(diào)用 nf_conntrack_in() 開始連接跟蹤,正常情況 下會(huì)創(chuàng)建一條新連接記錄,然后將 conntrack entry 放到 unconfirmed list。

  為什么是這兩個(gè) hook 點(diǎn)呢?因?yàn)樗鼈兌际切逻B接的第一個(gè)包最先達(dá)到的地方,

  • PRE_ROUTING 是外部主動(dòng)和本機(jī)建連時(shí)包最先到達(dá)的地方

  • LOCAL_OUT 是本機(jī)主動(dòng)和外部建連時(shí)包最先到達(dá)的地方

  2.  POST_ROUTING 和 LOCAL_IN:調(diào)用 nf_conntrack_confirm() 將 nf_conntrack_in() 創(chuàng)建的連接移到 confirmed list。

  同樣要問,為什么在這兩個(gè) hook 點(diǎn)呢?因?yàn)槿绻逻B接的第一個(gè)包沒有被丟棄,那這 是它們離開 netfilter 之前的最后 hook 點(diǎn):

  •  外部主動(dòng)和本機(jī)建連的包,如果在中間處理中沒有被丟棄,LOCAL_IN 是其被送到應(yīng)用(例如 nginx 服務(wù))之前的最后 hook 點(diǎn)

  •  本機(jī)主動(dòng)和外部建連的包,如果在中間處理中沒有被丟棄,POST_ROUTING 是其離開主機(jī)時(shí)的最后 hook 點(diǎn)

下面的代碼可以看到這些 handler 是如何注冊(cè)的:

// net/netfilter/nf_conntrack_proto.c  /* Connection tracking may drop packets, but never alters them, so make it the first hook.  */  static const struct nf_hook_ops ipv4_conntrack_ops[] = {   {    .hook  = ipv4_conntrack_in,       // 調(diào)用 nf_conntrack_in() 進(jìn)入連接跟蹤    .pf  = NFPROTO_IPV4,    .hooknum = NF_INET_PRE_ROUTING,     // PRE_ROUTING hook 點(diǎn)    .priority = NF_IP_PRI_CONNTRACK,   },   {    .hook  = ipv4_conntrack_local,    // 調(diào)用 nf_conntrack_in() 進(jìn)入連接跟蹤    .pf  = NFPROTO_IPV4,    .hooknum = NF_INET_LOCAL_OUT,       // LOCAL_OUT hook 點(diǎn)    .priority = NF_IP_PRI_CONNTRACK,   },   {    .hook  = ipv4_confirm,            // 調(diào)用 nf_conntrack_confirm()    .pf  = NFPROTO_IPV4,    .hooknum = NF_INET_POST_ROUTING,    // POST_ROUTING hook 點(diǎn)    .priority = NF_IP_PRI_CONNTRACK_CONFIRM,   },   {    .hook  = ipv4_confirm,            // 調(diào)用 nf_conntrack_confirm()    .pf  = NFPROTO_IPV4,    .hooknum = NF_INET_LOCAL_IN,        // LOCAL_IN hook 點(diǎn)    .priority = NF_IP_PRI_CONNTRACK_CONFIRM,   },  };

nf_conntrack_in 函數(shù)是連接跟蹤模塊的核心。

// net/netfilter/nf_conntrack_core.c  unsigned int  nf_conntrack_in(struct net *net, u_int8_t pf, unsigned int hooknum, struct sk_buff *skb)  {    struct nf_conn *tmpl = nf_ct_get(skb, &ctinfo); // 獲取 skb 對(duì)應(yīng)的 conntrack_info 和連接記錄    if (tmpl || ctinfo == IP_CT_UNTRACKED) {        // 如果記錄存在,或者是不需要跟蹤的類型        if ((tmpl && !nf_ct_is_template(tmpl)) || ctinfo == IP_CT_UNTRACKED) {            NF_CT_STAT_INC_ATOMIC(net, ignore);     // 無需跟蹤的類型,增加 ignore 計(jì)數(shù)            return NF_ACCEPT;                       // 返回 NF_ACCEPT,繼續(xù)后面的處理        }        skb->_nfct = 0;                             // 不屬于 ignore 類型,計(jì)數(shù)器置零,準(zhǔn)備后續(xù)處理    }    struct nf_conntrack_l4proto *l4proto = __nf_ct_l4proto_find(...);    // 提取協(xié)議相關(guān)的 L4 頭信息    if (l4proto->error != NULL) {                   // skb 的完整性和合法性驗(yàn)證        if (l4proto->error(net, tmpl, skb, dataoff, pf, hooknum) <= 0) {            NF_CT_STAT_INC_ATOMIC(net, error);            NF_CT_STAT_INC_ATOMIC(net, invalid);            goto out;        }    }  repeat:    // 開始連接跟蹤:提取 tuple;創(chuàng)建新連接記錄,或者更新已有連接的狀態(tài)    resolve_normal_ct(net, tmpl, skb, ... l4proto);    l4proto->packet(ct, skb, dataoff, ctinfo); // 進(jìn)行一些協(xié)議相關(guān)的處理,例如 UDP 會(huì)更新 timeout    if (ctinfo == IP_CT_ESTABLISHED_REPLY && !test_and_set_bit(IPS_SEEN_REPLY_BIT, &ct->status))        nf_conntrack_event_cache(IPCT_REPLY, ct);  out:    if (tmpl)        nf_ct_put(tmpl); // 解除對(duì)連接記錄 tmpl 的引用  }

大致流程:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.  嘗試獲取這個(gè) skb 對(duì)應(yīng)的連接跟蹤記錄

  3.  判斷是否需要對(duì)這個(gè)包做連接跟蹤,如果不需要,更新 ignore 計(jì)數(shù),返回 NF_ACCEPT;如果需要,就初始化這個(gè) skb 的引用計(jì)數(shù)。

  4.  從包的 L4 header 中提取信息,初始化協(xié)議相關(guān)的 struct nf_conntrack_l4proto {} 變量,其中包含了該協(xié)議的連接跟蹤相關(guān)的回調(diào)方法。

  5.  調(diào)用該協(xié)議的 error() 方法檢查包的完整性、校驗(yàn)和等信息。

  6.  調(diào)用 resolve_normal_ct() 開始連接跟蹤,它會(huì)創(chuàng)建新 tuple,新 conntrack entry,或者更新已有連接的狀態(tài)。

  7.  調(diào)用該協(xié)議的 packet() 方法進(jìn)行一些協(xié)議相關(guān)的處理,例如對(duì)于 UDP,如果 status bit 里面設(shè)置了 IPS_SEEN_REPLY 位,就會(huì)更新 timeout。timeout 大小和協(xié) 議相關(guān),越小越越可以防止 DoS 攻擊(DoS 的基本原理就是將機(jī)器的可用連接耗盡)

3.7 init_conntrack():創(chuàng)建新連接記錄

如果連接不存在(flow 的第一個(gè)包),resolve_normal_ct() 會(huì)調(diào)用 init_conntrack ,后者進(jìn)而會(huì)調(diào)用 new() 方法創(chuàng)建一個(gè)新的 conntrack entry。

// include/net/netfilter/nf_conntrack_core.c  // Allocate a new conntrack  static noinline struct nf_conntrack_tuple_hash *  init_conntrack(struct net *net, struct nf_conn *tmpl,          const struct nf_conntrack_tuple *tuple,          const struct nf_conntrack_l4proto *l4proto,          struct sk_buff *skb, unsigned int dataoff, u32 hash)  {   struct nf_conn *ct;   ct = __nf_conntrack_alloc(net, zone, tuple, &repl_tuple, GFP_ATOMIC, hash);   l4proto->new(ct, skb, dataoff); // 協(xié)議相關(guān)的方法   local_bh_disable();             // 關(guān)閉軟中斷   if (net->ct.expect_count) {    exp = nf_ct_find_expectation(net, zone, tuple);    if (exp) {     /* Welcome, Mr. Bond.  We've been expecting you... */     __set_bit(IPS_EXPECTED_BIT, &ct->status);     /* exp->master safe, refcnt bumped in nf_ct_find_expectation */     ct->master = exp->master;    ct->mark = exp->master->mark;     ct->secmark = exp->master->secmark;     NF_CT_STAT_INC(net, expect_new);    }   }   /* Now it is inserted into the unconfirmed list, bump refcount */   nf_conntrack_get(&ct->ct_general);   nf_ct_add_to_unconfirmed_list(ct);   local_bh_enable();              // 重新打開軟中斷   if (exp) {    if (exp->expectfn)     exp->expectfn(ct, exp);    nf_ct_expect_put(exp);   }   return &ct->tuplehash[IP_CT_DIR_ORIGINAL];  }

每種協(xié)議需要實(shí)現(xiàn)自己的 l4proto->new() 方法,代碼見:net/netfilter/nf_conntrack_proto_*.c。

如果當(dāng)前包會(huì)影響后面包的狀態(tài)判斷,init_conntrack() 會(huì)設(shè)置 struct nf_conn 的 master 字段。面向連接的協(xié)議會(huì)用到這個(gè)特性,例如 TCP。

3.8 nf_conntrack_confirm():確認(rèn)包沒有被丟棄

nf_conntrack_in() 創(chuàng)建的新 conntrack entry 會(huì)插入到一個(gè) 未確認(rèn)連接( unconfirmed connection)列表。

如果這個(gè)包之后沒有被丟棄,那它在經(jīng)過 POST_ROUTING 時(shí)會(huì)被 nf_conntrack_confirm() 方法處理,原理我們?cè)诜治鲞^了 3.6 節(jié)的開頭分析過了。nf_conntrack_confirm() 完成之后,狀態(tài)就變?yōu)榱?IPS_CONFIRMED,并且連接記錄從 未確認(rèn)列表移到正常的列表。

之所以要將創(chuàng)建一個(gè)合法的新 entry 的過程分為創(chuàng)建(new)和確認(rèn)(confirm)兩個(gè)階段 ,是因?yàn)榘诮?jīng)過 nf_conntrack_in() 之后,到達(dá) nf_conntrack_confirm() 之前 ,可能會(huì)被內(nèi)核丟棄。這樣會(huì)導(dǎo)致系統(tǒng)殘留大量的半連接狀態(tài)記錄,在性能和安全性上都 是很大問題。分為兩步之后,可以加快半連接狀態(tài) conntrack entry 的 GC。

// include/net/netfilter/nf_conntrack_core.h  /* Confirm a connection: returns NF_DROP if packet must be dropped. */  static inline int nf_conntrack_confirm(struct sk_buff *skb)  {   struct nf_conn *ct = (struct nf_conn *)skb_nfct(skb);   int ret = NF_ACCEPT;   if (ct) {    if (!nf_ct_is_confirmed(ct))     ret = __nf_conntrack_confirm(skb);    if (likely(ret == NF_ACCEPT))     nf_ct_deliver_cached_events(ct);   }   return ret;  }

confirm 邏輯,省略了各種錯(cuò)誤處理邏輯:

// net/netfilter/nf_conntrack_core.c  /* Confirm a connection given skb; places it in hash table */  int  __nf_conntrack_confirm(struct sk_buff *skb)  {   struct nf_conn *ct;   ct = nf_ct_get(skb, &ctinfo);   local_bh_disable();               // 關(guān)閉軟中斷   hash = *(unsigned long *)&ct->tuplehash[IP_CT_DIR_REPLY].hnnode.pprev;   reply_hash = hash_conntrack(net, &ct->tuplehash[IP_CT_DIR_REPLY].tuple);   ct->timeout += nfct_time_stamp;   // 更新連接超時(shí)時(shí)間,超時(shí)后會(huì)被 GC   atomic_inc(&ct->ct_general.use);  // 設(shè)置連接引用計(jì)數(shù)?   ct->status |= IPS_CONFIRMED;      // 設(shè)置連接狀態(tài)為 confirmed   __nf_conntrack_hash_insert(ct, hash, reply_hash);  // 插入到連接跟蹤哈希表   local_bh_enable();                // 重新打開軟中斷   nf_conntrack_event_cache(master_ct(ct) ? IPCT_RELATED : IPCT_NEW, ct);   return NF_ACCEPT;  }

可以看到,連接跟蹤的處理邏輯中需要頻繁關(guān)閉和打開軟中斷,此外還有各種鎖, 這是短連高并發(fā)場(chǎng)景下連接跟蹤性能損耗的主要原因?。

4 Netfilter NAT 實(shí)現(xiàn)

NAT 是與連接跟蹤獨(dú)立的模塊。

4.1 重要數(shù)據(jù)結(jié)構(gòu)和函數(shù)

重要數(shù)據(jù)結(jié)構(gòu):

支持 NAT 的協(xié)議需要實(shí)現(xiàn)其中的方法:

  •  struct nf_nat_l3proto {}

  •  struct nf_nat_l4proto {}

重要函數(shù):

  •  nf_nat_inet_fn():NAT 的核心函數(shù)是,在除 NF_INET_FORWARD 之外的其他 hook 點(diǎn)都會(huì)被調(diào)用。

4.2 NAT 模塊初始化

// net/netfilter/nf_nat_core.c  static struct nf_nat_hook nat_hook = {   .parse_nat_setup = nfnetlink_parse_nat_setup,   .decode_session  = __nf_nat_decode_session,   .manip_pkt  = nf_nat_manip_pkt,  }; static int __init nf_nat_init(void)  {   nf_nat_bysource = nf_ct_alloc_hashtable(&nf_nat_htable_size, 0);   nf_ct_helper_expectfn_register(&follow_master_nat);   RCU_INIT_POINTER(nf_nat_hook, &nat_hook);  }  MODULE_LICENSE("GPL");  module_init(nf_nat_init);

4.3 struct nf_nat_l3proto {}:協(xié)議相關(guān)的 NAT 方法集

// include/net/netfilter/nf_nat_l3proto.h  struct nf_nat_l3proto {      u8    l3proto; // 例如,AF_INET      u32     (*secure_port    )(const struct nf_conntrack_tuple *t, __be16);      bool    (*manip_pkt      )(struct sk_buff *skb, ...);      void    (*csum_update    )(struct sk_buff *skb, ...);      void    (*csum_recalc    )(struct sk_buff *skb, u8 proto, ...);      void    (*decode_session )(struct sk_buff *skb, ...);      int     (*nlattr_to_range)(struct nlattr *tb[], struct nf_nat_range2 *range);  };

4.4 struct nf_nat_l4proto {}:協(xié)議相關(guān)的 NAT 方法集

// include/net/netfilter/nf_nat_l4proto.h  struct nf_nat_l4proto {      u8 l4proto; // Protocol number,例如 IPPROTO_UDP, IPPROTO_TCP      // 根據(jù)傳入的 tuple 和 NAT 類型(SNAT/DNAT)修改包的 L3/L4 頭      bool (*manip_pkt)(struct sk_buff *skb, *l3proto, *tuple, maniptype);      // 創(chuàng)建一個(gè)唯一的 tuple      // 例如對(duì)于 UDP,會(huì)根據(jù) src_ip, dst_ip, src_port 加一個(gè)隨機(jī)數(shù)生成一個(gè) 16bit 的 dst_port      void (*unique_tuple)(*l3proto, tuple, struct nf_nat_range2 *range, maniptype, struct nf_conn *ct);      // If the address range is exhausted the NAT modules will begin to drop packets.      int (*nlattr_to_range)(struct nlattr *tb[], struct nf_nat_range2 *range);  };

各協(xié)議實(shí)現(xiàn)的方法,見:net/netfilter/nf_nat_proto_*.c。例如 TCP 的實(shí)現(xiàn):

// net/netfilter/nf_nat_proto_tcp.c  const struct nf_nat_l4proto nf_nat_l4proto_tcp = {   .l4proto  = IPPROTO_TCP,   .manip_pkt  = tcp_manip_pkt,   .in_range  = nf_nat_l4proto_in_range,   .unique_tuple  = tcp_unique_tuple,   .nlattr_to_range = nf_nat_l4proto_nlattr_to_range,  };

4.5 nf_nat_inet_fn():進(jìn)入 NAT

NAT 的核心函數(shù)是 nf_nat_inet_fn(),它會(huì)在以下 hook 點(diǎn)被調(diào)用:

  •  NF_INET_PRE_ROUTING

  •  NF_INET_POST_ROUTING

  •  NF_INET_LOCAL_OUT

  •  NF_INET_LOCAL_IN

也就是除了 NF_INET_FORWARD 之外其他 hook 點(diǎn)都會(huì)被調(diào)用。

在這些 hook 點(diǎn)的優(yōu)先級(jí):Conntrack > NAT > Packet Filtering。連接跟蹤的優(yōu)先 級(jí)高于 NAT 是因?yàn)?NAT 依賴連接跟蹤的結(jié)果。

Linux下如何實(shí)現(xiàn)連接跟蹤

Fig. NAT

unsigned int  nf_nat_inet_fn(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)  {      ct = nf_ct_get(skb, &ctinfo);      if (!ct)    // conntrack 不存在就做不了 NAT,直接返回,這也是為什么說 NAT 依賴 conntrack 的結(jié)果          return NF_ACCEPT;      nat = nfct_nat(ct);      switch (ctinfo) {      case IP_CT_RELATED:      case IP_CT_RELATED_REPLY: /* Only ICMPs can be IP_CT_IS_REPLY.  Fallthrough */      case IP_CT_NEW: /* Seen it before? This can happen for loopback, retrans, or local packets. */          if (!nf_nat_initialized(ct, maniptype)) {              struct nf_hook_entries *e = rcu_dereference(lpriv->entries); // 獲取所有 NAT 規(guī)則              if (!e)                  goto null_bind;              for (i = 0; i < e->num_hook_entries; i++) { // 依次執(zhí)行 NAT 規(guī)則                  if (e->hooks[i].hook(e->hooks[i].priv, skb, state) != NF_ACCEPT )                      return ret;                         // 任何規(guī)則返回非 NF_ACCEPT,就停止當(dāng)前處理                  if (nf_nat_initialized(ct, maniptype))                      goto do_nat;              }  null_bind:              nf_nat_alloc_null_binding(ct, state->hook);          } else { // Already setup manip              if (nf_nat_oif_changed(state->hook, ctinfo, nat, state->out))                  goto oif_changed;          }          break;      default: /* ESTABLISHED */          if (nf_nat_oif_changed(state->hook, ctinfo, nat, state->out))              goto oif_changed;      }  do_nat:      return nf_nat_packet(ct, ctinfo, state->hook, skb);  oif_changed:      nf_ct_kill_acct(ct, ctinfo, skb);      return NF_DROP;  }

首先查詢 conntrack 記錄,如果不存在,就意味著無法跟蹤這個(gè)連接,那就更不可能做 NAT 了,因此直接返回。

如果找到了 conntrack 記錄,并且是 IP_CT_RELATED、IP_CT_RELATED_REPLY 或 IP_CT_NEW 狀態(tài),就去獲取 NAT 規(guī)則。如果沒有規(guī)則,直接返回 NF_ACCEPT,對(duì)包不 做任何改動(dòng);如果有規(guī)則,最后執(zhí)行 nf_nat_packet,這個(gè)函數(shù)會(huì)進(jìn)一步調(diào)用 manip_pkt 完成對(duì)包的修改,如果失敗,包將被丟棄。

Masquerade

NAT 模塊一般配置方式:Change IP1 to IP2 if matching XXX。

此次還支持一種更靈活的 NAT 配置,稱為 Masquerade:Change IP1 to dev1's IP if matching XXX。與前面的區(qū)別在于,當(dāng)設(shè)備(網(wǎng)卡)的 IP 地址發(fā)生變化時(shí),這種方式無 需做任何修改。缺點(diǎn)是性能比第一種方式要差。

4.6 nf_nat_packet():執(zhí)行 NAT

// net/netfilter/nf_nat_core.c  /* Do packet manipulations according to nf_nat_setup_info. */  unsigned int nf_nat_packet(struct nf_conn *ct, enum ip_conntrack_info ctinfo,        unsigned int hooknum, struct sk_buff *skb)  {   enum nf_nat_manip_type mtype = HOOK2MANIP(hooknum);   enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);   unsigned int verdict = NF_ACCEPT;   statusbit = (mtype == NF_NAT_MANIP_SRC? IPS_SRC_NAT : IPS_DST_NAT)   if (dir == IP_CT_DIR_REPLY)     // Invert if this is reply dir    statusbit ^= IPS_NAT_MASK;   if (ct->status & statusbit)     // Non-atomic: these bits don't change. */    verdict = nf_nat_manip_pkt(skb, ct, mtype, dir);   return verdict;  }  static unsigned int nf_nat_manip_pkt(struct sk_buff *skb, struct nf_conn *ct,           enum nf_nat_manip_type mtype, enum ip_conntrack_dir dir)  {   struct nf_conntrack_tuple target;   /* We are aiming to look like inverse of other direction. */   nf_ct_invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);  l3proto = __nf_nat_l3proto_find(target.src.l3num);   l4proto = __nf_nat_l4proto_find(target.src.l3num, target.dst.protonum);   if (!l3proto->manip_pkt(skb, 0, l4proto, &target, mtype)) // 協(xié)議相關(guān)處理    return NF_DROP;   return NF_ACCEPT;  }

以上是“Linux下如何實(shí)現(xiàn)連接跟蹤”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI