linux內(nèi)核netfilter之ip_conntrack模塊的作用舉例--ftp為例 收藏
很多協(xié)議的控制信息在應(yīng)用層數(shù)據(jù)中被包含,這些信息直接影響到了鏈路的建立,比如ftp協(xié)議就是這樣,ftp分為port模式和pass模式,port模式中,起初client連接server的21端口,然后當(dāng)需要傳輸data的時(shí)候,client發(fā)送一個(gè)控制包給server,包中包含client端開啟的端口和自己的ip地址,server收到之后用自己的20端口去連接client控制包中建議的ip和端口,在這種情況下,如果client在nat后面使用私網(wǎng)地址,那么server將無(wú)法連接client,因此nat網(wǎng)關(guān)必須要處理這種情況,處理方式就是修改client發(fā)給server的控制包(如果加密將不可能修改,還好ftp是不加密的);在pass模式下,client連接server的21端口后,如果要傳輸data,client還要連接server的另一個(gè)隨機(jī)端口,該端口是由server發(fā)送的控制包傳給client的,如果client或者server端所在的防火墻禁止了任意非熟知端口,那么數(shù)據(jù)將被防火墻攔截;不管是port模式還是pass模式,防火墻都要處理“第二個(gè)”數(shù)據(jù)連接通路的放行問(wèn)題,在linux中是通過(guò)RELATED狀態(tài)來(lái)放行的,正如前文所述,只需配置一條--state RELATED -j ACCEPT規(guī)則即可,但是具體這個(gè)規(guī)則如何實(shí)現(xiàn),linux的連接追蹤模塊又是怎樣處理ftp的nat問(wèn)題的,本文詳述之。
首先從ip_conntrack的HOOK函數(shù)說(shuō)起:
unsigned int ip_conntrack_in(...)
{
...
proto = ip_ct_find_proto((*pskb)->nh.iph->protocol); //從數(shù)據(jù)包中取出協(xié)議號(hào)
...//resolve_normal_ct會(huì)試圖在已建立的連接中尋找剛進(jìn)入的包屬于的連接,如果找不到則新建立一個(gè)狀態(tài)為NEW的連接,同時(shí)還要初始化該連接相關(guān)的數(shù)據(jù),比如helper
ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo);//此中調(diào)用的init_conntrack函數(shù)是一個(gè)做了很多事的函數(shù)
...
if (ret != NF_DROP && ct->helper) { //如果有helper則調(diào)用其help函數(shù)
ret = ct->helper->help(*pskb, ct, ctinfo);
...
}
...
}
init_conntrack中有如下邏輯:
...//從鏈表中查找該連接,如果找到說(shuō)明這是一個(gè)“預(yù)測(cè)”的連接
expected = LIST_FIND(&ip_conntrack_expect_list, expect_cmp,
struct ip_conntrack_expect *, tuple);
...
if (expected) {
__set_bit(IPS_EXPECTED_BIT, &conntrack->status); //預(yù)測(cè)的連接到了,設(shè)置一個(gè)標(biāo)志,在resolve_normal_ct得到已有連接的情況下會(huì)判斷如果有了這個(gè)標(biāo)志,則設(shè)置IP_CT_RELATED狀態(tài),該狀態(tài)可用于filter的判斷
expected->sibling = conntrack; //預(yù)測(cè)的連接已經(jīng)到來(lái)并且初始化了。expected->sibling在預(yù)測(cè)的時(shí)候是NULL,因?yàn)槟菚r(shí)僅僅是預(yù)測(cè),連接還沒有真的到來(lái),后面可以看到,ip_conntrak預(yù)測(cè)之后,ip_nat會(huì)使用預(yù)測(cè)結(jié)果,然后調(diào)用helper的help修改應(yīng)用層的和連接相關(guān)的控制數(shù)據(jù),比如ip地址和端口信息,在遍歷一個(gè)已有連接的所有預(yù)測(cè)到的連接從而決定是否調(diào)用ip_nat的helper時(shí),如果一個(gè)預(yù)測(cè)即一個(gè)ip_conntrack_expect的sibling字段非NULL,ip_nat將跳過(guò)此預(yù)測(cè)結(jié)果,因?yàn)樗呀?jīng)是真實(shí)的連接了,說(shuō)明已經(jīng)在它還是預(yù)測(cè)的連接的時(shí)候就已經(jīng)被help過(guò)了。
...
}
...
每一個(gè)ip_conntrack都可以擁有多個(gè)helper,用于幫助處理連接相關(guān)的信息,比如ftp協(xié)議穿越防火墻就需要處理nat和副連接(data連接)問(wèn)題,因此就有必要用一個(gè)helper模塊來(lái)處理這一類情況,處理ftp nat的helper和處理副連接的helper其實(shí)不是一類helper,前者是ip_nat_ftp結(jié)構(gòu)體,后者是ip_conntrack_ftp結(jié)構(gòu)體,雖然不同,但是它們的處理邏輯和注冊(cè)邏輯都是一樣的,因此到后面說(shuō)ftp nat的時(shí)候再統(tǒng)一說(shuō)明。下面是ip_conntrack_ftp注冊(cè)的help函數(shù)的實(shí)現(xiàn)邏輯
static int help(...)
{
...//操作skb,取出我們需要的一切信息
skb_copy_bits(skb, dataoff, ftp_buffer, skb->len - dataoff);
...
array[0] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 24) & 0xFF;
array[1] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 16) & 0xFF;
array[2] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 8) & 0xFF;
array[3] = ntohl(ct->tuplehash[dir].tuple.src.ip) & 0xFF;
//以上的這個(gè)array就是server需要連接的ip地址
for (i = 0; i < ARRAY_SIZE(search); i++) {
if (search[i].dir != dir) continue;
found = find_pattern(...);//在ftp_buffer中尋找search字符,如果找到了,則說(shuō)明本次數(shù)據(jù)包需要help,其中有個(gè)參數(shù)是個(gè)數(shù)組,數(shù)組的每一個(gè)元素都是一個(gè)匹配鍵,此謂search,是一個(gè)ftp_search結(jié)構(gòu)體類型的數(shù)組
if (found) break;
}
...//如果找不到則返回,說(shuō)明本次到來(lái)的數(shù)據(jù)不需要help
exp = ip_conntrack_expect_alloc();
... //初始化一個(gè)ip_conntrack_expect,可以用于描述一個(gè)將要建立的連接
exp->expectfn = NULL;
ip_conntrack_expect_related(exp, ct); //準(zhǔn)備添加一個(gè)RELATED的連接,如果用戶在iptables規(guī)則中配置RELATED連接可以通過(guò),那么ftp的port模式數(shù)據(jù)連接就可以暢行無(wú)阻了。iptables的RELATED連接就是在這里被“預(yù)料”到的,然后加入進(jìn)已有的連接。
ret = NF_ACCEPT;
out:
UNLOCK_BH(&ip_ftp_lock);
return ret;
}
最終會(huì)在ip_conntrack_expect_insert函數(shù)中將“預(yù)料”到的連接加入與此“預(yù)料”的連接相關(guān)聯(lián)的已有連接的鏈表中,同時(shí)還將這個(gè)預(yù)料到的連接加入一個(gè)系統(tǒng)全局的鏈表中,并且如果已有的連接需要限制“預(yù)料”連接的建立連接時(shí)間,則需要啟動(dòng)一個(gè)定時(shí)器,定時(shí)器超時(shí)連接還不到的話,就會(huì)刪除該預(yù)料的連接。這個(gè)related連接會(huì)被netfilter的state模塊使用,比如你使用--state NEW/ESTABLISHED/...的話,在state模塊中的match回調(diào)函數(shù)中,系統(tǒng)會(huì)取出該數(shù)據(jù)包屬于的連接,然后取出該連接的state,將之與參數(shù)的state比較,然后返回進(jìn)入target抉擇。
以上是數(shù)據(jù)在ip_conntrack模塊中的流程,出了ip_conntrack就該進(jìn)入ip_nat了,還是從其HOOK說(shuō)起:
static unsigned int ip_nat_fn(...)
{
...
ct = ip_conntrack_get(*pskb, &ctinfo); //得到連接,如果沒有得到則返回NULL
... //如果沒有得到既有連接則返回ACCEPT(注意有ICMP重定向的特殊情況),由后續(xù)的鏈來(lái)抉擇,不管怎樣nat總在conntrack之后起作用,因此只要有連接,conntrack就會(huì)將之加入hash
switch (ctinfo) {
...
case IP_CT_NEW: //如果是一個(gè)連接的第一個(gè)包,那么就要初始化一系列結(jié)構(gòu)體,包括兩個(gè)方向的nat轉(zhuǎn)換表,ftp等等需要help的協(xié)議的相關(guān)結(jié)構(gòu)體等等
info = &ct->nat.info;
WRITE_LOCK(&ip_nat_lock);
if (!(info->initialized & (1 << maniptype))) {
...
ret = ip_nat_rule_find(pskb, hooknum, in, out, ct, info);
...
return do_bindings(ct, ctinfo, info, hooknum, pskb);
}
unsigned int do_bindings(...)
{
...
int proto = (*pskb)->nh.iph->protocol;
...//實(shí)施地址/端口轉(zhuǎn)換,省略。就是在兩個(gè)方向的轉(zhuǎn)換表中根據(jù)方向和地址/端口信息來(lái)修改數(shù)據(jù)包的協(xié)議頭
helper = info->helper; //info在ip_nat_setup_info也就是初始化連接的時(shí)候就會(huì)被建立,這里只是取出來(lái)
if (helper) {
...//一個(gè)主連接可以有多個(gè)與之RELATED的副連接,因此下面就遍歷這些副連接
list_for_each_prev(cur_item, &ct->sibling_list) {
...//如果已經(jīng)是established的連接了,則說(shuō)明下面將要做的工作已經(jīng)作過(guò)了,就不再做了。
if (exp_for_packet(exp, *pskb)) { //包合理則調(diào)用help函數(shù),在help函數(shù)中處理特殊的nat轉(zhuǎn)換,比如ftp的port模式相關(guān)的nat轉(zhuǎn)換
ret = helper->help(ct, exp, info, ctinfo, hooknum, pskb);
...
}
對(duì)于ip_nat_ftp幫助模塊而言,其help函數(shù)的執(zhí)行邏輯如下:
static unsigned int help(...)
{
...
ct_ftp_info = &exp->help.exp_ftp_info;
...
ftp_data_fixup(ct_ftp_info, ct, pskb, ctinfo, exp);
...
}
最終ftp_data_fixup調(diào)用了mangle[ct_ftp_info->ftptype](...)函數(shù),顯然最后的函數(shù)完成了對(duì)數(shù)據(jù)包的修改,對(duì)數(shù)據(jù)包進(jìn)行修改就是為了ftp服務(wù)器可以成功連接到客戶端。由于客戶端很多時(shí)候在具有nat功能的防火墻后,并且都是用私網(wǎng)地址,而在ftp的port模式下,如果客戶端將一個(gè)私有地址建議給了ftp服務(wù)器用于連接,服務(wù)器是連接不到的,這個(gè)建議的ip地址在ftp的數(shù)據(jù)包中,因此必須修改數(shù)據(jù)包,將建議的地址和端口修改為nat后的地址和端口,同時(shí)再鋪設(shè)一條nat,用于服務(wù)器連接客戶端時(shí)將請(qǐng)求真正轉(zhuǎn)到內(nèi)網(wǎng)的客戶端。ip_nat_mangle_tcp_packet是ip_nat_helper.c中的一個(gè)很重要的函數(shù),就是它完成了對(duì)應(yīng)用層數(shù)據(jù)包的修改。
ip_conntrack和ip_nat處理ftp總的過(guò)程就是,ip_conntrack模塊得到了連接的信息,然后根據(jù)連接信息可以得到一個(gè)helper,需要說(shuō)明,一個(gè)連接完全可以沒有helper,而且大多數(shù)的都沒有helper,是否需要helper是根據(jù)連接的類型決定的,一般觸及應(yīng)用層控制數(shù)據(jù)的修改時(shí)才會(huì)使用helper,比如ftp的控制命令是在應(yīng)用層數(shù)據(jù)中被傳輸?shù)模B接的類型是可以從數(shù)據(jù)包以及協(xié)議頭中得到的,因此ip_conntrack模塊需要數(shù)據(jù)包不能分段,也就是說(shuō)需要完整的ip數(shù)據(jù)包。得到helper之后開始調(diào)用其help函數(shù),然后判斷當(dāng)前數(shù)據(jù)包是否需要help,比如判斷是否是ftp的特殊命令,該命令可以建立一條新的連接,如果是這樣的話,那么help函數(shù)則“預(yù)測(cè)”到一條即將建立的連接并將之和當(dāng)前連接關(guān)聯(lián),然后ip_conntrack基本就沒有什么做的了,數(shù)據(jù)包繼續(xù)在netfilter中流動(dòng),進(jìn)入nat,同樣的,nat也如ip_conntrack判斷是否需要help,如果是則調(diào)用helper的help函數(shù),判斷是否需要help的依據(jù)一般就是是否在ip_conntrack模塊中“預(yù)測(cè)”到了即將建立的連接,如果預(yù)測(cè)到了,那么就調(diào)用nat的helper的help函數(shù),并且將預(yù)測(cè)到的連接參數(shù)傳入,在ip_nat_ftp的help函數(shù)中根據(jù)預(yù)測(cè)連接的信息對(duì)應(yīng)用層控制數(shù)據(jù)進(jìn)行修改。
兩類helper的注冊(cè)都是在模塊初始化的時(shí)候進(jìn)行的,而helper與連接或者nat的綁定則是在連接初始化的時(shí)候進(jìn)行的。ip_nat_fn是nat的HOOK,其中對(duì)于IP_CT_NEW包來(lái)講需要調(diào)用call_expect,而后者最終調(diào)用下面的函數(shù)實(shí)現(xiàn)ftp相關(guān)的ip_nat_helper結(jié)構(gòu)體的指定,該結(jié)構(gòu)體在模塊初始化時(shí)被注冊(cè):
unsigned int ip_nat_setup_info(...)
{
...
info->helper = LIST_FIND(&helpers, helper_cmp, struct ip_nat_helper *, &reply);
//尋找ip_nat_ftp的helper,在ip_nat_ftp的init中,會(huì)調(diào)用ip_conntrack_helper_register將ftp相關(guān)的信息注冊(cè)進(jìn)內(nèi)核,這些信息包含在ip_nat_helper結(jié)構(gòu)體中,其中有很多靜態(tài)數(shù)據(jù)是用于匹配helper的,比如新建一個(gè)連接,當(dāng)數(shù)據(jù)越過(guò)conntrack而進(jìn)入nat時(shí)會(huì)調(diào)用ip_nat_setup_info,在該函數(shù)中,如上述調(diào)用LIST_FIND,其實(shí)就是使用當(dāng)前的addr,port等信息和注冊(cè)的helper逐個(gè)進(jìn)行比較,一旦有命中的則將此helper取出留作后用,ip_nat_helper中最重要的就是help函數(shù)了。
...
}
ip_nat_ftp模塊的初始化函數(shù)如下:
static int __init init(void)
{
...
for (i = 0; (i < MAX_PORTS) && ports[i]; i++) {
ftp[i].tuple.src.u.tcp.port = htons(ports[i]);
ftp[i].tuple.dst.protonum = IPPROTO_TCP;
ftp[i].mask.src.u.tcp.port = 0xFFFF;
ftp[i].mask.dst.protonum = 0xFFFF;
ftp[i].max_expected = 1;
ftp[i].timeout = 0;
ftp[i].flags = IP_CT_HELPER_F_REUSE_EXPECT;
ftp[i].me = ip_conntrack_ftp;
ftp[i].help = help;
...
ret = ip_conntrack_helper_register(&ftp[i]);
...
}
return 0;
}
類似的ip_conntrack的helper也是在模塊初始化時(shí)注冊(cè),在連接初始化時(shí)被指定特定的連接的,道理和nat是一樣的。
總之,helper模塊一般是對(duì)需要在應(yīng)用層數(shù)據(jù)中傳輸控制數(shù)據(jù)的協(xié)議進(jìn)行幫助的,因?yàn)镺S實(shí)現(xiàn)的協(xié)議棧并不包含應(yīng)用層,但是有的時(shí)候必須對(duì)應(yīng)用層控制數(shù)據(jù)進(jìn)行修改,這時(shí)就不得不需要一個(gè)額外的幫助模塊了,注意,一般helper修改的都是控制數(shù)據(jù),而不是業(yè)務(wù)數(shù)據(jù),所謂控制數(shù)據(jù)就是和業(yè)務(wù)無(wú)關(guān)的,僅僅影響到連接本身的數(shù)據(jù)。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)
點(diǎn)擊舉報(bào)。