netfilter中對多連接協(xié)議跟蹤和NAT實(shí)現
本文檔的Copyleft歸yfydz所有,使用GPL發(fā)布,可以自由拷貝,轉載,轉載時(shí)請保持文檔的完整性,嚴禁用于任何商業(yè)用途。
msn:
yfydz_no1@hotmail.com來(lái)源:
http://yfydz.cublog.cn1. 前言
對于多連接協(xié)議,netfilter需要對其進(jìn)行特殊的跟蹤和NAT以提供動(dòng)態(tài)的對子連接的支持,詳見(jiàn)“防火墻為什么要對多連接協(xié)議進(jìn)行特殊處理”一文。netfilter對這些多連接協(xié)議的跟蹤和NAT和匹配、目標或IP層協(xié)議那樣進(jìn)行模塊化的跟蹤和NAT處理。
以下內核代碼說(shuō)明都使用Linux-2.4.26版本的內核代碼。
2. 協(xié)議連接跟蹤
2.1 ip_conntrack_helper結構
netfilter中對每個(gè)要進(jìn)行跟蹤的多連接協(xié)議定義了以下的連接輔助結構,每個(gè)多連接協(xié)議的連接跟蹤處理就是要填寫(xiě)這樣一個(gè)結構:
include/linux/netfilter_ipv4/ip_conntrack_helper.h
struct ip_conntrack_helper
{
struct list_head list; /* Internal use. */
const char *name; /* name of the module */
unsigned char flags; /* Flags (see above) */
struct module *me; /* pointer to self */
unsigned int max_expected; /* Maximum number of concurrent
* expected connections */
unsigned int timeout; /* timeout for expecteds */
/* Mask of things we will help (compared against server response) */
struct ip_conntrack_tuple tuple;
struct ip_conntrack_tuple mask;
/* Function to call when data passes; return verdict, or -1 to
invalidate. */
int (*help)(const struct iphdr *, size_t len,
struct ip_conntrack *ct,
enum ip_conntrack_info conntrackinfo);
};
結構中包括以下參數:
struct list_head list:將該結構掛接到多連接協(xié)議跟蹤鏈表helpers中, helpers鏈表在ip_conntrack_core.c文件中定義:
static LIST_HEAD(helpers);
注意是static的,只在該文件范圍有效;
const char *name:協(xié)議名稱(chēng),字符串常量
unsigned char flags:關(guān)于本連接跟蹤模塊的一些標志;
struct module *me:指向模塊本身,統計模塊是否被使用
unsigned int max_expected:子連接的數量,這只是表示主連接在每個(gè)時(shí)刻所擁有的的子連接的數量,而不是主連接的整個(gè)生存期內總共生成的子連接的數量,如FTP,不論傳多少個(gè)文件,建立多少個(gè)子連接,每個(gè)時(shí)刻主連接最多只有一個(gè)子連接,一個(gè)子連接結束前按協(xié)議是不能再派生出第二個(gè)子連接的,所以該值為1;
unsigned int timeoout:超時(shí),指在多少時(shí)間范圍內子連接沒(méi)有建立的話(huà)子連接跟蹤失效
struct ip_conntrack_tuple tuple, mask:這兩個(gè)參數用來(lái)描述子連接,判斷一個(gè)新來(lái)的連接是否是主連接期待的子連接,之所以要有mask參數,是因為子連接的某些參數不能確定,如被動(dòng)模式的FTP傳輸,只能得到子連接的目的端口而不能確定源端口,所以源端口部分要用mask來(lái)進(jìn)行泛匹配;
結構中包括以下函數:
(*help):連接跟蹤基本函數,解析主連接的通信內容,提取出關(guān)于子連接的信息,將子連接信息填充到一個(gè)struct ip_conntrack_expect結構中,然后將此結構通過(guò)調用函數ip_conntrack_expect_related()把子連接的信息添加到系統的期待子連接鏈表ip_conntrack_expect_list中。返回值是NF_ACCEPT或NF_DROP,或-1表示協(xié)議數據非法。該函數在ip_conntrack_in()函數中調用。
2.2 期待的連接的結構
期待的連接結構用來(lái)描述所期待的連接,但該連接目前是不存在的,是一個(gè)虛構的連接,只是netfilter根據主連接的信息解析出來(lái)的可能的子連接的信息,struct ip_conntrack_helper結構中的(*help)成員函數的目的就是建立一個(gè)struct ip_conntrack_expect結構:
struct ip_conntrack_expect
{
/* Internal linked list (global expectation list) */
struct list_head list;
/* reference count */
atomic_t use;
/* expectation list for this master */
struct list_head expected_list;
/* The conntrack of the master connection */
struct ip_conntrack *expectant;
/* The conntrack of the sibling connection, set after
* expectation arrived */
struct ip_conntrack *sibling;
/* Tuple saved for conntrack */
struct ip_conntrack_tuple ct_tuple;
/* Timer function; deletes the expectation. */
struct timer_list timeout;
/* Data filled out by the conntrack helpers follow: */
/* We expect this tuple, with the following mask */
struct ip_conntrack_tuple tuple, mask;
/* Function to call after setup and insertion */
int (*expectfn)(struct ip_conntrack *new);
/* At which sequence number did this expectation occur */
u_int32_t seq;
union ip_conntrack_expect_proto proto;
union ip_conntrack_expect_help help;
};
結構中包括以下參數:
struct list_head list:將該結構掛接到期待連接鏈表
ip_conntrack_expect_list中;
atomic_t use:該期待連接結構的使用次數;
struct list_head expected_list:主連接的期待的子連接的鏈表;
struct ip_conntrack *expectant:期待連接對應的主連接;
struct ip_conntrack *sibling:期待連接對應的真實(shí)的子連接;
struct ip_conntrack_tuple ct_tuple:連接的tuple值
struct timer_list timeout:定時(shí)器
struct ip_conntrack_tuple tuple, mask:期待連接相關(guān)的tuple和mask;
u_int32_t seq:TCP協(xié)議時(shí),主連接中描述子連接的數據起始處對應的序列號值;
union ip_conntrack_expect_proto proto:跟蹤各個(gè)多連接IP層協(xié)議相關(guān)的數據;
union ip_conntrack_expect_help help:跟蹤各個(gè)多連接應用層協(xié)議相關(guān)的數據;
結構中包括以下函數:
int (*expectfn)(struct ip_conntrack *new):期待連接相關(guān)函數,在ip_conntrack_core.c文件的init_conntrack()函數中調用:
...
if (expected && expected->expectfn)
expected->expectfn(conntrack);
...
2.3 多連接協(xié)議的連接跟蹤過(guò)程
2.3.1 過(guò)程簡(jiǎn)述
新連接的數據包進(jìn)入netfilter系統后先要建立對應連接,檢查是否是期待的連接,如果與某期待連接符合,說(shuō)明該連接是子連接,將連接和主連接建立相關(guān)的聯(lián)系,并對連接設置相關(guān)標志,表明是RELATED的連接;否則,說(shuō)明是主連接,則查找與這個(gè)連接對應的協(xié)議的連接輔助模塊helper,如果找到,執行helper中的(*help)函數檢查是否能提取出相關(guān)的子連接信息,生成期待的連接信息添加到期待連接鏈表中。
2.3.2 連接helper查找
在ip_conntrack_core.c文件的init_conntrack()函數中先查找期待連接是否存在,然后調用ip_ct_find_helper()函數來(lái)找到與該協(xié)議對應的helper函數,注意是用反向的tuple來(lái)查找的:
...
// 查找是否期待連接
expected = LIST_FIND(&ip_conntrack_expect_list, expect_cmp,
struct ip_conntrack_expect *, tuple);
...
// 查找連接對應的helper
if (!expected)
conntrack->helper = ip_ct_find_helper(&repl_tuple);
...
在查找helper之前先檢查該連接是否存在對應的期待連接,如果是存在期待連接,說(shuō)明是子連接,子連接就不再去找helper了,這就避免了phrack63的那篇文章中描述的防火墻穿透方法。但這種限制也帶來(lái)一個(gè)問(wèn)題,比如H.323協(xié)議,主連接是H.225協(xié)議,會(huì )派生出一個(gè)H.245的連接,由H.245連接再派生出一個(gè)連接進(jìn)行數據傳輸,這種情況下H.245的helper就不能通過(guò)ip_ct_find_helper()函數來(lái)獲取了,為解決這個(gè)問(wèn)題,H.323跟蹤NAT模塊的作者使用了一個(gè)很巧妙的方法,就是通過(guò)期待連接結構的(*expectfn)函數來(lái)解決,在該函數中直接將H.245的helper直接賦值到該連接中。
2.3.3 (*help)函數執行
(*help)函數對主連接的每個(gè)合法數據包都會(huì )進(jìn)行檢查的,在ip_conntrack_core.c文件的ip_conntrack_in()函數中調用:
...
if (ret != NF_DROP && ct->helper) {
ret = ct->helper->help((*pskb)->nh.iph, (*pskb)->len,
ct, ctinfo);
if (ret == -1) {
// 協(xié)議數據格式錯誤時(shí)help函數返回-1,清空該包對應的nfct,則該包在狀態(tài)檢測時(shí)將被視為非法包
/* Invalid */
nf_conntrack_put((*pskb)->nfct);
(*pskb)->nfct = NULL;
return NF_ACCEPT;
}
}
...
3. 多連接協(xié)議NAT
3.1 ip_nat_helper結構
netfilter中對每個(gè)要進(jìn)行NAT的多連接協(xié)議定義了以下結構,每個(gè)多連接協(xié)議的連接跟蹤處理就是要填寫(xiě)這樣一個(gè)結構:
struct ip_nat_helper
{
struct list_head list; /* Internal use */
const char *name; /* name of the module */
unsigned char flags; /* Flags (see above) */
struct module *me; /* pointer to self */
/* Mask of things we will help: vs. tuple from server */
struct ip_conntrack_tuple tuple;
struct ip_conntrack_tuple mask;
/* Helper function: returns verdict */
unsigned int (*help)(struct ip_conntrack *ct,
struct ip_conntrack_expect *exp,
struct ip_nat_info *info,
enum ip_conntrack_info ctinfo,
unsigned int hooknum,
struct sk_buff **pskb);
/* Returns verdict and sets up NAT for this connection */
unsigned int (*expect)(struct sk_buff **pskb,
unsigned int hooknum,
struct ip_conntrack *ct,
struct ip_nat_info *info);
};
結構中包括以下參數:
struct list_head list:將該結構掛接到多連接協(xié)議NAT鏈表helpers中,helpers鏈表在ip_nat_core.c文件中定義:
LIST_HEAD(helpers);
注意該定義不是static的,而是全局有效的,連接跟蹤的helpers則是static的,只在該文件中起作用,而且屏蔽了ip_nat_core.c中的helpers,所以在此兩個(gè)文件外的helpers是指nat的helpers;
const char *name:協(xié)議名稱(chēng),字符串常量
unsigned char flags:關(guān)于本連接跟蹤模塊的一些標志;
struct module *me:指向模塊本身,統計模塊是否被使用
struct ip_conntrack_tuple tuple, mask:這兩個(gè)參數用來(lái)描述進(jìn)行了NAT后的子連接,同樣因為子連接的某些參數不能確定,要用mask來(lái)進(jìn)行泛匹配;
結構中包括以下函數:
(*help):多協(xié)議連接NAT操作基本函數,由于作NAT后要修改IP地址以及端口,因此原來(lái)的主連接中描述子連接的信息必須進(jìn)行修改,(*help)函數的功能就要要找到一個(gè)空閑的tuple對應新的子連接,修改期待的子連接,然后修改主連接的通信內容,修改關(guān)于IP地址和端口部分的描述信息為空閑tuple的信息,由于修改了應用層數據,數據的校驗和必須重新計算,而且如果數據長(cháng)度發(fā)生變化,會(huì )引起TCP序列號的變化,在連接的協(xié)議相關(guān)數據中會(huì )記錄這些變化,對后續的所有數據都要進(jìn)行相應的調整;該函數在do_bindings()函數中調用;
(*expect):該函數建立子連接對應的NAT相關(guān)信息,主連接的NAT相關(guān)信息是通過(guò)iptables的NAT規則建立的;該函數在ip_nat_statndalone.c的call_expect()函數中調用
在NAT的基本函數ip_nat_fn()中,先調用call_expect(),最后調用的do_bindings(),所以(*expect)函數先調用,(*help)函數后調用。
3.2 多連接協(xié)議的NAT過(guò)程
2.3.1 過(guò)程簡(jiǎn)述
不論是SNAT還是DNAT,其對應的netfilter的nf_hook_ops節點(diǎn)都要執行ip_nat_fn(),此時(shí)與數據包對應的連接已經(jīng)建立,對于連接的后續包,只是把連接的NAT信息綁定到該包(do_binding()函數);如果是新連接,如果是子連接,則調用協(xié)議相關(guān)nat_helper的(*expect)函數建立子連接的相關(guān)信息;否不論是SNAT還是DNAT都會(huì )調用ip_nat_setup_info()函數建立與該連接對應的NAT信息,所以在NAT規則中是不需要加狀態(tài)是NEW的匹配的,最后將連接的NAT信息綁定到數據包。
在NAT信息的綁定過(guò)程中,會(huì )檢查當前的數據包是否屬于期待的要進(jìn)行NAT修改的包,具體是用exp_for_packet()函數進(jìn)行檢查,該函數中調用相應傳輸層協(xié)議的(*exp_matches_pkt)函數,該函數只在TCP的協(xié)議中定義,但UDP沒(méi)有,沒(méi)有定義此函數時(shí)exp_for_packet()始終返回要求檢查;如果要修改,將調用相應nat_helper的(*help)函數來(lái)修改數據,修改期待的子連接。
2.3.2 nat helper查找
在ip_nat_core.c文件的ip_nat_setup_info()函數中
...
/* If there‘s a helper, assign it; based on new tuple. */
if (!conntrack->master)
info->helper = LIST_FIND(&helpers, helper_cmp, struct ip_nat_helper *,
&reply);
...
static inline int
helper_cmp(const struct ip_nat_helper *helper,
const struct ip_conntrack_tuple *tuple)
{
return ip_ct_tuple_mask_cmp(tuple, &helper->tuple, &helper->mask);
}
3.3.3 (*expect)函數執行
在ip_nat_standalone.c文件的ip_nat_fn()函數中調用call_expect()函數,在call_expect()函數中調用(*expect)函數:
static inline int call_expect(struct ip_conntrack *master,
struct sk_buff **pskb,
unsigned int hooknum,
struct ip_conntrack *ct,
struct ip_nat_info *info)
{
return master->nat.info.helper->expect(pskb, hooknum, ct, info);
}
static unsigned int
ip_nat_fn(unsigned int hooknum,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
...
case IP_CT_NEW:
...
if (ct->master
&& master_ct(ct)->nat.info.helper
&& master_ct(ct)->nat.info.helper->expect) {
ret = call_expect(master_ct(ct), pskb,
hooknum, ct, info);
} else {
#ifdef CONFIG_IP_NF_NAT_LOCAL
/* LOCAL_IN hook doesn‘t have a chain! */
if (hooknum == NF_IP_LOCAL_IN)
ret = alloc_null_binding(ct, info,
hooknum);
else
#endif
ret = ip_nat_rule_find(pskb, hooknum, in, out,
ct, info);
}
...
3.3.3 (*help)函數執行
在ip_nat_core.c文件的do_binding()函數中:
...
// 判斷是否是期待的修改點(diǎn)
if (exp_for_packet(exp, pskb)) {
/* FIXME: May be true multiple times in the
* case of UDP!! */
// 因為UDP沒(méi)有序列號,沒(méi)辦法指示修改點(diǎn),不象TCP可以用序列號表示,所以所有UDP包
// 都會(huì )執行help
DEBUGP("calling nat helper (exp=%p) for packet\n", exp);
// 修改數據參數
ret = helper->help(ct, exp, info, ctinfo,
hooknum, pskb);
if (ret != NF_ACCEPT) {
READ_UNLOCK(&ip_conntrack_lock);
return ret;
}
helper_called = 1;
}
}
/* Helper might want to manip the packet even when there is no
* matching expectation for this packet */
if (!helper_called && helper->flags & IP_NAT_HELPER_F_ALWAYS) {
DEBUGP("calling nat helper for packet without expectation\n");
ret = helper->help(ct, NULL, info, ctinfo,
hooknum, pskb);
if (ret != NF_ACCEPT) {
READ_UNLOCK(&ip_conntrack_lock);
return ret;
}
}
...
有兩處地方調用了(*help)函數,第一處(*help)執行了后面的(*help)就不會(huì )執行,但即使前面的(*help)沒(méi)有執行,由于很多helper的flags都設置為0,所以后面那次(*help)基本都不會(huì )執行。
4. 結論
netfilter對多連接協(xié)議跟蹤和NAT處理很好地實(shí)現了模塊化,只要按固定的格式編寫(xiě)跟蹤和NAT程序就能支持新的多連接協(xié)議,現在netfilter已經(jīng)可以支持很多多連接協(xié)議,如FTP、TFTP、IRC、TALK、H.323、SIP、MMS、QUAKE3等。