iptable 簡析

  • filter表的實現

        filter表的實現函數實際上就是模塊iptable_filter.oinit函數,位於net/ipv4/netfilter/iptable_filter.cLine128。其主要工作是首先通過ipt_register_table()函數進行表的註冊,然後用nf_register_hook()函數註冊表所監聽的各個HOOK

    其中,對HOOK進行註冊時,是通過對數據結構nf_hook_ops的一個實例ipt_ops進行操作來實現的,這個實例的定義及初始化位於net/ipv4/netfilter/iptable_filter.cLine117

static struct nf_hook_ops ipt_ops[]

= { { { NULL, NULL }, ipt_hook, PF_INET, NF_IP_LOCAL_IN, NF_IP_PRI_FILTER },

{ { NULL, NULL }, ipt_hook, PF_INET, NF_IP_FORWARD, NF_IP_PRI_FILTER },

{ { NULL, NULL }, ipt_local_out_hook, PF_INET, NF_IP_LOCAL_OUT,

NF_IP_PRI_FILTER }

};

    對應前面所分析nf_hook_ops的各個成員,不難確定這些初始化值的意義。

    其中,對應INFORWARD的處理函數均爲ipt_hookOUT的處理函數則爲ipt_local_out_hook,下面依次分析之:

  • ipt_hook,定義於net/ipv4/netfilter/iptable_filter.cLine89

static unsigned int

ipt_hook(unsigned int hook,

struct sk_buff **pskb,

const struct net_device *in,

const struct net_device *out,

int (*okfn)(struct sk_buff *))

{

return ipt_do_table(pskb, hook, in, out, &packet_filter, NULL);

}

    實際上它就是調用了ipt_do_table()函數,也就是說,註冊時首先註冊一個ipt_hook()函數,然後ipt_hook()通過調用ipt_do_table()函數對傳入的數據進行真正的處理。下面我們來看一下ipt_do_table()這個函數:

    它位於net/ipv4/netfilter/ip_tables.cLine254,是一個很長的函數,其主要功能是對數據報進行各種匹配、過濾(包括基本規則、matches以及target),具體些說,其工作大致爲:

  1. 初始化各種變量,如IP頭、數據區、輸入輸出設備、段偏移、規則入口及偏移量等等;

  2. 進行規則的匹配,首先調用ip_packet_match()函數(位於net/ipv4/netfilter/ip_tables.cLine121)確定IP數據報是否匹配規則,若不匹配則跳到下一條規則(這個函數的主要工作大致爲:依次處理源/目的IP地址、輸入輸出接口,然後對基本的規則進行匹配);

  3. 如果數據報匹配,則下面開始繼續匹配matchestarget,首先利用宏IPT_MATCH_ITERATE調用do_match()函數(下面單獨分析)對擴展的match進行匹配,若不匹配則跳到下一條規則;

  4. 擴展match匹配後,首先調用ipt_get_target()獲得target的偏移地址,然後對target進行匹配,這個匹配的過程要比match的匹配過程複雜一些,同樣在下面單獨分析。

 

    下面首先來分析do_match()函數,它位於net/ipv4/netfilter/ip_tables.cLine229,它的實現只有一個if語句:

if (!m->u.kernel.match->match(skb, in, out, m->data, offset, hdr, datalen, hotdrop))

return 1;

else

return 0;

    其中的`m->u.kernel.match->match(skb, in, out, m->data, offset, hdr, datalen, hotdrop)`是用來定位match的。

    因爲如果僅僅是根據match的名字遍歷鏈表來進行查找的話,效率會非常低下。Netfilter源碼中採用的方法是在進行match的檢測之前,也就是在ipt_register_table()函數中通過translate_table()函數由宏IPT_ENTRY_ITERATE調用函數check_entry()時,在check_entry()中通過宏IPT_MATCH_ITERATE調用了check_match()函數(位於net/ipv4/netfilter/ip_tables.cLine640),在這個函數中,有一個對m->u.kernel.match的賦值:

m->u.kernel.match = match;

    這樣,每條規則的u.kernel.match 就與內核模塊中的struct ipt_match鏈表關聯了起來,也就是說,這樣一來,根據match的名字,其對應的match函數就與鏈表中對應的函數關聯了起來。於是,上面的那條定位match的語句的意義也就開始明瞭了:

    利用宏IPT_MATCH_ITERATE來遍歷規則中的所有mach,然後直接調用`m->u.kernel.match->match`來進行對數據報的匹配工作——這樣的效率顯然要比簡單的遍歷要高許多。

 

    然後我們來看一下對target的匹配,從數據結構的實現上看,似乎這個過程與match的匹配應該是相似的,但實際上target存在標準的和非標準的兩種,其中標準的target與非標準的target的處理是不一樣的。在這裏我遇到了問題,如下:

    首先,在Netfilter的源碼中,存在兩個ipt_standard_target,其中一個是一個struct,位於include/linux/netfilter_ipv4/ip_tables.hLine94;另一個是`struct ipt_target`的一個實例,位於net/ipv4/netfilter/IPtables.cLine1684,而在target的匹配過程中,它是這樣處理的(ipt_do_tables()net/ipv4/netfilter/ip_tables.cLine329):

 

if (!t->u.kernel.target->target) {……}

    從這裏看來,它應該是當 t->u.kernel.targettarget函數爲空時,表明其爲標準的target。那麼結合上述兩者的定義,似乎用的是後者,因爲後者的定義及初始化如下:

 

static struct ipt_target ipt_standard_target

= { { NULL, NULL }, IPT_STANDARD_TARGET, NULL, NULL, NULL };

    但是問題出現在:初始化中的IPT_STANDARD_TARGET被定義爲””!!並且在整個源碼中,用到實例化的ipt_standard_target的地方僅有兩處,即上面的這個定義以及ip_tables.c中將ipt_standard_target加入到target鏈表之中。也就是說這個實例的名字一直爲空,這一點如何理解?

  • ipt_local_out_hook,定義於net/ipv4/netfilter/iptable_filter.cLine99其功能與ipt_hook()相似,只不過爲了防止DOS攻擊而增加了對ratelimit的檢查。

    這樣,到這裏,filter表的實現已經分析完畢,至於具體的過濾功能如何實現,那就是每個HOOK處理函數的問題了。

六、連接跟蹤模塊(Conntrack

1. 概述

    連接跟蹤模塊是NAT的基礎,但作爲一個單獨的模塊實現。它用於對包過濾功能的一個擴展,管理單個連接(特別是TCP連接),並負責爲現有的連接分配輸入、輸出和轉發IP數據報,從而基於連接跟蹤來實現一個“基於狀態”的防火牆。

    當連接跟蹤模塊註冊一個連接建立包之後,將生成一個新的連接記錄。此後,所有屬於此連接的數據報都被唯一地分配給這個連接。如果一段時間內沒有流量而超時,連接將被刪除,然後其他需要使用連接跟蹤的模塊就可以重新使用這個連接所釋放的資源了。

    如果需要用於比傳輸協議更上層的應用協議,連接跟蹤模塊還必須能夠將建立的數據連接與現有的控制連接相關聯。

        ConntrackNetfilter中是基於下列HOOK的:

  • NF_IP_PRE_ROUTING

  • NF_IP_LOCAL_OUT

  • 同時當使用NAT時,Conntrack也會有基於NF_IP_LOCAL_INNF_IP_POST_ROUTING的,只不過優先級很小。

    在所有的HOOK上,NF_IP_PRI_CONNTRACK的優先級是最高的(-200),這意味着每個數據報在進入和發出之前都首先要經過Conntrack模塊,然後纔會被傳到鉤在HOOK上的其它模塊。

        Conntrack模塊的接口位於文件net/ipv4/netfilter/ip_conntrack_standalone.c中。

2. 連接狀態的管理

  1. 多元組

    在連接跟蹤模塊中,使用所謂的“tuple”,也就是多元組,來小巧銳利地描述連接記錄的關鍵部分,主要是方便連接記錄的管理。其對應的數據結構是ip_conntrack_tuple(位於include/linux/netfilter_ipv4/ip_conntrack_tuple.hLine38):

struct ip_conntrack_tuple

{

struct ip_conntrack_manip src;

struct {

u_int32_t ip;

union {

u_int16_t all;

struct { u_int16_t port; } tcp;

struct { u_int16_t port; } udp;

struct { u_int8_t type, code; } icmp;

} u;

u_int16_t protonum;

} dst;

};

    從它的定義可以看出,一個多元組實際上包括兩個部分:一是所謂的“unfixed”部分,也就是源地址以及端口;二是所謂的“fixed”部分,也就是目的地址、端口以及所用的協議。這樣,連接的兩端分別用地址+端口,再加上所使用的協議,一個tuple就可以唯一地標識一個連接了(對於沒有端口的icmp協議,則用其它東東標識)。

  1. 連接記錄

    那麼真正的完整連接記錄則是由數據結構ip_conntrack(位於include/linux/netfilter_ipv4/ip_conntrack.hLine160)來描述的,其成員有:

  • `struct nf_conntrack ct_general;`nf_conntrack結構定義於include/linux/skbuff.hLine89,其中包括一個計數器use和一個destroy函數。計數器use對本連接記錄的公開引用次數進行計數

  • `struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];`:其中的IP_CT_DIR_MAX是一個枚舉類型ip_conntrack_dir(位於include/linux/netfilter_ipv4/ip_conntrack_tuple.hLine65)的第3個成員,從這個結構實例在源碼中的使用看來,實際上這是定義了兩個tuple多元組的hash表項tuplehash[IP_CT_DIR_ORIGINAL/0]tuplehash[IP_CT_DIR_REPLY/1],利用兩個不同方向的tuple定位一個連接,同時也可以方便地對ORIGINAL以及REPLY兩個方向進行追溯

  • `unsigned long status;`:這是一個位圖,是一個狀態域。在實際的使用中,它通常與一個枚舉類型ip_conntrack_status(位於include/linux/netfilter_ipv4/ip_conntrack.hLine33)進行位運算來判斷連接的狀態。其中主要的狀態包括:

    • IPS_EXPECTED(_BIT),表示一個預期的連接

    • IPS_SEEN_REPLY(_BIT),表示一個雙向的連接

    • IPS_ASSURED(_BIT),表示這個連接即使發生超時也不能提早被刪除

    • IPS_CONFIRMED(_BIT),表示這個連接已經被確認(初始包已經發出)

  • `struct timer_list timeout;`:其類型timer_list位於include/linux/timer.hLine11,其核心是一個處理函數。這個成員表示當發生連接超時時,將調用此處理函數

  • `struct list_head sibling_list;`:所謂“預期的連接”的鏈表,其中存放的是我們所期望的其它相關連接

  • `unsigned int expecting;`:目前的預期連接數量

  • `struct ip_conntrack_expect *master;`:結構ip_conntrack_expect位於include/linux/netfilter_ipv4/ip_conntrack.hLine119,這個結構用於將一個預期的連接分配給現有的連接,也就是說本連接是這個master的一個預期連接

  • `struct ip_conntrack_helper *helper;`helper模塊。這個結構定義於include/linux/netfilter_ipv4/ip_conntrack_helper.hLine11,這個模塊提供了一個可以用於擴展Conntrack功能的接口。經過連接跟蹤HOOK的每個數據報都將被髮給每個已經註冊的helper模塊(註冊以及卸載函數分別爲ip_conntrack_helper_register()以及ip_conntrack_helper_unregister(),分別位於net/ipv4/netfilter/ip_conntrack_core.cLine11361159)。這樣我們就可以進行一些動態的連接管理了

  • `struct nf_ct_info infos[IP_CT_NUMBER];`:一系列的nf_ct_info類型(定義於include/linux/skbuff.h ,Line92,實際上就是nf_conntrack結構)的結構,每個結構對應於某種狀態的連接。這一系列的結構會被sk_buff結構的nfct指針所引用,描述了所有與此連接有關係的數據報。其狀態由枚舉類型ip_conntrack_info定義(位於include/linux/netfilter_ipv4/ip_conntrack.hLine12),共有5個成員:

    • IP_CT_ESTABLISHED:數據報屬於已經完全建立的連接

    • IP_CT_RELATED: 數據報屬於一個新的連接,但此連接與一個現有連接相關(預期連接);或者是ICMP錯誤

    • IP_CT_NEW:數據報屬於一個新的連接

    • IP_CT_IS_REPLY:數據報屬於一個連接的回覆

    • IP_CT_NUMBER:不同IP_CT類型的數量,這裏爲7NEW僅存於一個方向上

  • NAT模塊設置的信息(在條件編譯中)

  1. hash

        Netfilter使用一個hash表來對連接記錄進行管理,這個hash表的初始指針爲*ip_conntrack_hash,位於net/ipv4/netfilter/ip_conntrack_core.cLine65,這樣我們就可以使用ip_conntrack_hash[num]的方式來直接定位某個連接記錄了。

    而hash表的每個表項則由數據結構ip_conntrack_tuple_hash(位於include/linux/netfilter_ipv4/ip_conntrack_tuple.hLine86)描述 :

struct ip_conntrack_tuple_hash

{

struct list_head list;

struct ip_conntrack_tuple tuple;

struct ip_conntrack *ctrack;

};

    可見,一個hash表項中實質性的內容就是一個多元組ip_conntrack_tuple;同時還有一個指向連接的ip_conntrack結構的指針;以及一個鏈表頭(這個鏈表頭不知是幹嘛的)。

 

3. 連接跟蹤的實現

    有了以上的數據結構,連接跟蹤的具體實現其實就非常簡單而常規了,無非就是初始化連接記錄的創建在連接跟蹤hash表中搜索並定位數據報將數據報轉換爲一個多元組判斷連接的狀態以及方向超時處理協議的添加、查找和註銷對不同協議的不同處理、以及在兩個連接跟蹤相關的HOOK上對數據報的處理

    下面重點說明一下我在分析中遇到的幾個比較重要或者比較難理解的地方:

  • 所謂“預期鏈接”
        可以將預期連接看作父子關係來理解,如圖  http://blog.chinaunix.net/photo/24896_061206192612.jpg 
  • ip_conntrack的狀態轉換
        ip_conntrack的狀態轉換分兩種,同樣用圖來描述。首先是正常的狀態轉換,如圖http://blog.chinaunix.net/photo/24896_061206192631.jpg,
        然後是
ICMP error時的狀態轉換(由函數icmp_error_track()來判斷,位於net/ipv4/netfilter/ip_conntrack_core.cLine495),
        如圖
http://blog.chinaunix.net/photo/24896_061206192648.jpg 
  • 在NF_IP_PRE_ROUTING上對分片IP數據報的處理
    在經過HOOK中的NF_IP_PRE_ROUTING時(函數ip_conntrack_in(),位於net/ipv4/netfilter/ip_conntrack_core.cLine796),由於外來的數據報有可能是經過分片的,所以必須對分片的情形進行處理,將IP數據報組裝後才能分配給連接。
    具體的操作是首先由函數ip_ct_gather_frags()對分片的數據報進行收集,然後調用ip_defrag()函數(位於net/ipv4/ip_fragment.cLine632)組裝之

4. 協議的擴展

    由於我們可能要添加新的協議,所以單獨對協議的擴展進行分析。

    各種協議使用一個全局的協議列表存放,即protocol_list(位於include/linux/netfilter_ipv4/ip_conntrack_core.hLine21),使用結構ip_conntrack_protocol(位於include/linux/netfilter_ipv4/ip_conntrack_protocol.hLine6)來表示:

struct ip_conntrack_protocol

{

struct list_head list;

u_int8_t proto; //協議號

const char *name;

int (*pkt_to_tuple)(const void *datah, size_t datalen,

struct ip_conntrack_tuple *tuple);

int (*invert_tuple)(struct ip_conntrack_tuple *inverse,

const struct ip_conntrack_tuple *orig);

unsigned int (*print_tuple)(char *buffer,

const struct ip_conntrack_tuple *);

unsigned int (*print_conntrack)(char *buffer,

const struct ip_conntrack *);

int (*packet)(struct ip_conntrack *conntrack,

struct iphdr *iph, size_t len,

enum ip_conntrack_info ctinfo);

int (*new)(struct ip_conntrack *conntrack, struct iphdr *iph,

size_t len);

void (*destroy)(struct ip_conntrack *conntrack);

int (*exp_matches_pkt)(struct ip_conntrack_expect *exp,

struct sk_buff **pskb);

struct module *me;

};

    其中重要成員:

  • `int (*pkt_to_tuple)(……)`:其指向函數的作用是將協議加入到ip_conntrack_tupledst子結構中

  • `int (*invert_tuple)(……)`:其指向函數的作用是將源和目的多元組中協議部分的值進行互換,包括IP地址、端口等

  • `unsigned int (*print_tuple)(……)`:其指向函數的作用是打印多元組中的協議信息

  • `unsigned int (*print_conntrack)(……)`:其指向函數的作用是打印整個連接記錄

  • `int (*packet)(……)`:其指向函數的作用是返回數據報的verdict

  • `int (*new)(……)`:當此協議的一個新連接發生時,調用其指向的這個函數,調用返回true時再繼續調用packet()函數

  • `int (*exp_matches_pkt)(……)`:其指向函數的作用是判斷是否有數據報匹配預期的連接

    添加/刪除協議使用的是函數ip_conntrack_protocol_register()以及ip_conntrack_protocol_unregister(),分別位於net/ipv4/netfilter/ip_conntrack_standalone.cLine298 & 320,其工作就是將ip_conntrack_protocol添加到全局協議列表protocol_list

七、網絡地址轉換模塊(Network Address Translation

1. 概述

    網絡地址轉換的機制一般用於處理IP地址轉換,在Netfilter中,可以支持多種NAT類型,而其實現的基礎是連接跟蹤。

        NAT可以分爲SNATDNAT,即源NAT和目的NAT,在Netfilter中分別基於以下HOOK

  • NF_IP_PRE_ROUTING:可以在這裏定義DNAT的規則,因爲路由器進行路由時只檢查數據報的目的IP地址,所以爲了使數據報得以正確路由,我們必須在路由之前就進行DNAT

  • NF_IP_POST_ROUTING:可以在這裏定義SNAT的規則,系統在決定了數據報的路由以後在執行該HOOK上的規則

  • NF_IP_LOCAL_OUT:定義對本地產生的數據報的DNAT規則

  • CONFIG_IP_NF_NAT_LOCAL定義後,NF_IP_LOCAL_IN上也可以定義DNAT規則。

    同時,MASQUERADE(僞裝)是SNAT的一種特例,它與SNAT幾乎一樣,只有一點不同:如果連接斷開,所有的連接跟蹤信息將被丟棄,而去使用重新連接以後的IP地址進行IP僞裝;而REDIRECT(重定向)是DNAT的一種特例,這時候就相當於將符合條件的數據報的目的IP地址改爲數據報進入系統時的網絡接口的IP地址。

2. 基於連接跟蹤的相關數據結構

    NAT是基於連接跟蹤實現的,NAT中所有的連接都由連接跟蹤模塊來管理,NAT模塊的主要任務是維護nat表和進行實際的地址轉換。這樣,我們來回頭重新審視一下連接跟蹤模塊中由條件編譯決定的部分。

    首先,是連接的描述ip_conntrack,在連接跟蹤模塊部分中提到,這個結構的最後有“NAT模塊設置的信息”,即:

#ifdef CONFIG_IP_NF_NAT_NEEDED

struct {

struct ip_nat_info info;

union ip_conntrack_nat_help help;

#if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || \

defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)

int masq_index;

#endif

} nat;

#endif

這是一個叫做nat的子結構,其中有3個成員:

  • 一個ip_nat_info結構,這個結構下面會具體分析

  • 一個ip_conntrack_nat_help結構,是一個空結構,爲擴展功能而設

  • 一個爲僞裝功能而設的index,從源碼中對這個變量的使用看來,是對應所僞裝網絡接口的ID,也就是net_device中的ifindex成員

 

    好,下面我們來看一下這個ip_nat_info結構,這個結構存儲了連接中的地址綁定信息,其定義位於include/linux/netfilter_ipv4/ip_nat.hLine98

struct ip_nat_info

{

int initialized;

unsigned int num_manips;

struct ip_nat_info_manip manips[IP_NAT_MAX_MANIPS];

const struct ip_nat_mapping_type *mtype;

struct ip_nat_hash bysource, byipsproto;

struct ip_nat_helper *helper;

struct ip_nat_seq seq[IP_CT_DIR_MAX];

};

  • `int initialized;`:這是一個位圖,表明源地址以及目的地址的地址綁定是否已被初始化

  • `unsigned int num_manips;`:這個成員指定了存放在下面的manip數組中的可執行操作的編號,在不同的HOOK以及不同的方向上,可執行操作是分別進行計數的。

  • `struct ip_nat_info_manip manips[IP_NAT_MAX_MANIPS];`ip_nat_info_manip結構定義於include/linux/netfilter_ipv4/ip_nat.hLine66,一個ip_nat_info_manip結構對應着一個可執行操作或地址綁定,其成員包括方向(ORIGINAL以及REPLY)、HOOK號、操作類型(由一個枚舉類型ip_nat_manip_type定義,有IP_NAT_MANIP_SRCIP_NAT_MANIP_DST兩種)以及一個ip_conntrack_manip結構

  • `const struct ip_nat_mapping_type *mtype;`ip_nat_mapping_type這個結構在整個內核源碼中都沒有定義,根據註釋來看應該也是一個預留的擴展,一般就是NULL

  • `struct ip_nat_hash bysource, byipsproto;`ip_nat_hash結構定義於include/linux/netfilter_ipv4/ip_nat.hLine89,實際上就是一個帶表頭的ip_conntrack結構,跟連接跟蹤中hash表的實現類似。其中,

    • bysource表是用來管理現有連接的

    • byipsproto表則管理已經完成的轉換映射,以保證同一個IP不會同時有兩個映射,避免地址轉換衝突

  • `struct ip_nat_helper *helper;`:擴展用

  • `struct ip_nat_seq seq[IP_CT_DIR_MAX];`:這是爲每一個方向(其實就兩個方向)記錄一個序列號。ip_nat_seq結構定義於include/linux/netfilter_ipv4/ip_nat.hLine33,這個結構用得並不多,應該是用於TCP連接的計數和對涉及TCP的修改的定位


3. nat表的實現

        nat表的初始化和實現與filter極爲相似。

    在初始化上,其初始化位於net/ipv4/netfilter/ip_nat_rule.cLine104,初始化所用模板則位於net/ipv4/netfilter/ip_nat_rule.cLine50

    在實現上,其實現函數就是NAT模塊的初始化函數init_or_cleanup()(位於net/ipv4/netfilter/ip_nat_standalone.cLine278)。其工作主要是依次調用ip_nat_rule_init()ip_nat_init()以及nf_register_hook()

  1. 首先ip_nat_rule_init()(位於net/ipv4/netfilter/ip_nat_rule.cLine278)調用ipt_register_table()來初始化並註冊nat表,然後利用ipt_register_target()來初始化並註冊SNATDNAT,在這個註冊過程中,關鍵的函數是ip_nat_setup_info(位於net/ipv4/netfilter/ip_nat_core.cLine511),其工作是:

  • 首先調用invert_tupler()net/ipv4/netfilter/ip_conntrack_core.cLine879),將記錄反轉

  • 然後調用get_unique_tuple()net/ipv4/netfilter/ip_nat_core.cLine393,在指定的地址範圍(ip_nat_multi_range結構)中查找空閒的地址),如果沒有空閒地址可用則會返回NF_DROP

  • 判斷源和目的是否改變,如果改變,則更新ip_nat_info

  1. 然後ip_nat_init()(位於net/ipv4/netfilter/ip_nat_core.cLine953)會給nat所用的兩個hash表(bysourcebyipsproto)分配空間並初始化各種協議

  2. 最後會通過nf_register_hook()註冊相應HOOK的函數ip_nat_fn()ip_nat_local_fn()ip_nat_out(),並增加連接跟蹤的計數器。

    在具體的HOOK函數實現上,後兩者其實都是基於ip_nat_fn()的,而這其中最重要的處理函數,也就是實際的網絡地址轉換函數是do_bindings(),下面將對其進行分析:

        do_bindings()位於net/ipv4/netfilter/ip_nat_core.cLine747,其主要工作是將ip_nat_info中的地址綁定應用於數據報:

  1. 它首先在ip_nat_info->manip數組中查找能夠匹配的綁定

  2. 然後調用manip_pkt()函數(位於net/ipv4/netfilter/ip_nat_core.cLine701)進行相應的地址轉換:

  • manip_pkt()這個函數會根據不同的方向進行轉換,並且對校驗和進行處理

  • 同時,它是遞歸調用自己以處理不同協議的情況

  1. 最後調用helper模塊進行執行(當然,如果有的話),特別是避免在同一個數據報上執行多次同一個helper模塊


        nat表與filter表還有一個較大的不同:在一個連接中,只有第一個數據報纔會經過nat表,而其轉換的結果會作用於此連接中的其它所有數據報。

4. 協議的擴展

    要想擴展NAT的協議,那麼必須寫入兩個模塊,一個用於連接跟蹤,一個用於NAT實現。

    與連接跟蹤類似,nat中協議也由一個列表protos存放,位於include/linux/netfilter_ipv4/ip_nat_core.hLine17。協議的註冊和註銷分別是由函數ip_nat_protocol_register()ip_nat_protocol_unregister()實現的,位於net/ipv4/netfilter/ip_nat_standalone.cLine242

        nat的協議是由結構ip_nat_protocol描述的,其定義位於include/linux/netfilter_ipv4/ip_nat_protocol.hLine10

struct ip_nat_protocol

{

struct list_head list;

const char *name;

unsigned int protonum;

void (*manip_pkt)(struct iphdr *iph, size_t len,

const struct ip_conntrack_manip *manip,

enum ip_nat_manip_type maniptype);

int (*in_range)(const struct ip_conntrack_tuple *tuple,

enum ip_nat_manip_type maniptype,

const union ip_conntrack_manip_proto *min,

const union ip_conntrack_manip_proto *max);

int (*unique_tuple)(struct ip_conntrack_tuple *tuple,

const struct ip_nat_range *range,

enum ip_nat_manip_type maniptype,

const struct ip_conntrack *conntrack);

unsigned int (*print)(char *buffer,

const struct ip_conntrack_tuple *match,

const struct ip_conntrack_tuple *mask);

unsigned int (*print_range)(char *buffer,

const struct ip_nat_range *range);

};

    其中重要成員:

  • `void (*manip_pkt)(……)`:其指向的函數會根據ip_nat_info->manip參數進行數據報的轉換,即do_bindings()中調用的manip_pkt()函數

  • `int (*in_range)(……)`:其指向的函數檢查多元組的協議部分值是否在指定的區間之內

  • `int (*unique_tuple)(……)`:其指向函數的作用是根據manip的類型修改多元組中的協議部分,以獲得一個唯一的地址,多元組的協議部分被初始化爲ORIGINAL方向

八、數據報修改模塊──mangle

1. 概述

        mangle這個詞的原意是撕裂、破壞,這裏所謂“packet mangling”是指對packet的一些傳輸特性進行修改。mangle表被用來真正地對數據報進行修改,它可以在所有的5個HOOK上進行操作。

    從源碼看來,mangle表中所允許修改的傳輸特性目前有:

  • TOS(服務類型):修改IP數據報頭的TOS字段值

  • TTL(生存時間):修改IP數據報頭的TTL字段值

  • MARK:修改skbnfmark域設置的nfmark字段值。

    • nfmark是數據報的元數據之一,是一個用戶定義的數據報的標記,可以是unsigned long範圍內的任何值。該標記值用於基於策略的路由,通知ipqmpd(運行在用戶空間的隊列分揀器守護進程)將該數據報排隊給哪個進程等信息。

  • TCP MSS(最大數據段長度):修改TCP數據報頭的MSS字段值

2. mangle表的實現

    遍歷整個源碼,沒有發現mangle表的獨有數據結構。

        mangle表的初始化和實現與filter極爲相似。在初始化上,其初始化位於net/ipv4/netfilter/iptable_mangle.cLine117,初始化所用模板則位於net/ipv4/netfilter/iptable_mangle.cLine43

    在實現上,其實現函數就是mangle模塊的初始化函數init()(位於net/ipv4/netfilter/iptable_mangle.cLine183)。其工作就是依次註冊packet_mangler表以及5HOOK。其中NF_IP_LOCAL_OUT的處理函數爲ipt_local_hook(),其餘均爲ipt_route_hook(),分別位於net/ipv4/netfilter/iptable_mangle.cLine132 & 122,二者的關鍵實現都是通過調用ipt_do_table()實現的。

3. 數據報的修改

    對數據報不同位的修改都是通過單獨的模塊實現的,也就是說由ipt_tos.oipt_ttl.oipt_mark.oipt_tcpmss.o實現上面所說的四種傳輸特性的修改。

    以TOS爲例,其涉及的文件爲net/ipv4/netfilter/ipt_tos.c以及include/linux/netfilter_ipv4/ipt_tos.h

    其專有數據結構爲ipt_tos_info,定義於ipt_tos.h中:

struct ipt_tos_info {

u_int8_t tos;

u_int8_t invert;

};

    其模塊的添加/卸載函數很簡單,其實就是添加/刪除TOSMATCHtos_match(定義並初始化於ipt_tos.cLine37):

static struct ipt_match tos_match

= { { NULL, NULL }, "tos", &match, &checkentry, NULL, THIS_MODULE };

    而在tos_match的處理函數match()中,已經完成了對相應位的賦值,這似乎是違反match僅僅匹配而不修改的一個特例。

九、其它高級功能模塊

        Netfilter中還有一些其它的高級功能模塊,基本是爲了用戶操作方便的,沒有對它們進行分析,如:

  • REJECT,丟棄包並通知包的發送者,同時返回給發送者一個可配置的ICMP錯誤信息,由ipt_REJECT.o完成

  • MIRROR,互換源和目的地址以後並重新發送,由ipt_MIRROR.o完成

  • LOG, 將匹配的數據報傳遞給系統的syslog()進行記錄,由ipt_LOG.o完成

  • ULOGUserspace logging,將數據報排隊轉發到用戶空間中,將匹配的數據適用用戶空間的log進程進行記錄,由ip_ULOG.o完成。這是Netfilter的一個關鍵技術,可以使用戶進程可以進行復雜的數據報操作,從而減輕內核空間中的複雜度

  • Queuing,這是上面ULOG技術的基礎,由ip_queue.o完成,提供可靠的異步包處理以及性能兩號的libipq庫來進行用戶空間數據報操作的開發。

  • 等等……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章