linux內核netfilter之ip conntrack模塊的作用舉例--ftp為例
很多協議的控制信息在應用層數據中被包含,這些信息直接影響到了鏈路的建立,比如ftp協議就是這樣,ftp分為port模式和pass模式,port模式中,起初client連接server的21端口,然後當需要傳輸data的時候,client發送一個控制包給server,包中包含client端開啟的端口和自己的ip地址,server收到之後用自己的20端口去連接client控制包中建議的ip和端口,在這種情況下,如果client在nat後面使用私網地址,那麽server將無法連接client,因此nat網關必須要處理這種情況,處理方式就是修改client發給server的控制包(如果加密將不可能修改,還好ftp是不加密的);在pass模式下,client連接server的21端口後,如果要傳輸data,client還要連接server的另一個隨機端口,該端口是由server發送的控制包傳給client的,如果client或者server端所在的防火墻禁止了任意非熟知端口,那麽數據將被防火墻攔截;不管是port模式還是pass模式,防火墻都要處理“第二個”數據連接通路的放行問題,在linux中是通過RELATED狀態來放行的,正如前文所述,只需配置一條--state RELATED -j ACCEPT規則即可,但是具體這個規則如何實現,linux的連接追蹤模塊又是怎樣處理ftp的nat問題的,本文詳述之。
首先從ip_conntrack的HOOK函數說起:
unsigned int ip_conntrack_in(...)
{
...
proto = ip_ct_find_proto((*pskb)->nh.iph->protocol); //從數據包中取出協議號
...//resolve_normal_ct會試圖在已建立的連接中尋找剛進入的包屬於的連接,如果找不到則新建立一個狀態為NEW的連接,同時還要初始化該連接相關的數據,比如helper
ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo);//此中調用的init_conntrack函數是一個做了很多事的函數
...
if (ret != NF_DROP && ct->helper) { //如果有helper則調用其help函數
ret = ct->helper->help(*pskb, ct, ctinfo);
...
}
...
}
init_conntrack中有如下邏輯:
...//從鏈表中查找該連接,如果找到說明這是一個“預測”的連接
expected = LIST_FIND(&ip_conntrack_expect_list, expect_cmp,
struct ip_conntrack_expect *, tuple);
...
if (expected) {
__set_bit(IPS_EXPECTED_BIT, &conntrack->status); //預測的連接到了,設置一個標誌,在resolve_normal_ct得到已有連接的情況下會判斷如果有了這個標誌,則設置IP_CT_RELATED狀態,該狀態可用於filter的判斷
expected->sibling = conntrack; //預測的連接已經到來並且初始化了。expected->sibling在預測的時候是NULL,因為那時僅僅是預測,連接還沒有真的到來,後面可以看到,ip_conntrak預測之後,ip_nat會使用預測結果,然後調用helper的help修改應用層的和連接相關的控制數據,比如ip地址和端口信息,在遍歷一個已有連接的所有預測到的連接從而決定是否調用ip_nat的helper時,如果一個預測即一個ip_conntrack_expect的sibling字段非NULL,ip_nat將跳過此預測結果,因為它已經是真實的連接了,說明已經在它還是預測的連接的時候就已經被help過了。
...
}
...
每一個ip_conntrack都可以擁有多個helper,用於幫助處理連接相關的信息,比如ftp協議穿越防火墻就需要處理nat和副連接(data連接)問題,因此就有必要用一個helper模塊來處理這一類情況,處理ftp nat的helper和處理副連接的helper其實不是一類helper,前者是ip_nat_ftp結構體,後者是ip_conntrack_ftp結構體,雖然不同,但是它們的處理邏輯和註冊邏輯都是一樣的,因此到後面說ftp nat的時候再統一說明。下面是ip_conntrack_ftp註冊的help函數的實現邏輯
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;
//以上的這個array就是server需要連接的ip地址
for (i = 0; i < ARRAY_SIZE(search); i++) {
if (search[i].dir != dir) continue;
found = find_pattern(...);//在ftp_buffer中尋找search字符,如果找到了,則說明本次數據包需要help,其中有個參數是個數組,數組的每一個元素都是一個匹配鍵,此謂search,是一個ftp_search結構體類型的數組
if (found) break;
}
...//如果找不到則返回,說明本次到來的數據不需要help
exp = ip_conntrack_expect_alloc();
... //初始化一個ip_conntrack_expect,可以用於描述一個將要建立的連接
exp->expectfn = NULL;
ip_conntrack_expect_related(exp, ct); //準備添加一個RELATED的連接,如果用戶在iptables規則中配置RELATED連接可以通過,那麽ftp的port模式數據連接就可以暢行無阻了。iptables的RELATED連接就是在這裏被“預料”到的,然後加入進已有的連接。
ret = NF_ACCEPT;
out:
UNLOCK_BH(&ip_ftp_lock);
return ret;
}
最終會在ip_conntrack_expect_insert函數中將“預料”到的連接加入與此“預料”的連接相關聯的已有連接的鏈表中,同時還將這個預料到的連接加入一個系統全局的鏈表中,並且如果已有的連接需要限制“預料”連接的建立連接時間,則需要啟動一個定時器,定時器超時連接還不到的話,就會刪除該預料的連接。這個related連接會被netfilter的state模塊使用,比如你使用--state NEW/ESTABLISHED/...的話,在state模塊中的match回調函數中,系統會取出該數據包屬於的連接,然後取出該連接的state,將之與參數的state比較,然後返回進入target抉擇。
以上是數據在ip_conntrack模塊中的流程,出了ip_conntrack就該進入ip_nat了,還是從其HOOK說起:
static unsigned int ip_nat_fn(...)
{
...
ct = ip_conntrack_get(*pskb, &ctinfo); //得到連接,如果沒有得到則返回NULL
... //如果沒有得到既有連接則返回ACCEPT(註意有ICMP重定向的特殊情況),由後續的鏈來抉擇,不管怎樣nat總在conntrack之後起作用,因此只要有連接,conntrack就會將之加入hash
switch (ctinfo) {
...
case IP_CT_NEW: //如果是一個連接的第一個包,那麽就要初始化一系列結構體,包括兩個方向的nat轉換表,ftp等等需要help的協議的相關結構體等等
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;
...//實施地址/端口轉換,省略。就是在兩個方向的轉換表中根據方向和地址/端口信息來修改數據包的協議頭
helper = info->helper; //info在ip_nat_setup_info也就是初始化連接的時候就會被建立,這裏只是取出來
if (helper) {
...//一個主連接可以有多個與之RELATED的副連接,因此下面就遍歷這些副連接
list_for_each_prev(cur_item, &ct->sibling_list) {
...//如果已經是established的連接了,則說明下面將要做的工作已經作過了,就不再做了。
if (exp_for_packet(exp, *pskb)) { //包合理則調用help函數,在help函數中處理特殊的nat轉換,比如ftp的port模式相關的nat轉換
ret = helper->help(ct, exp, info, ctinfo, hooknum, pskb);
...
}
對於ip_nat_ftp幫助模塊而言,其help函數的執行邏輯如下:
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調用了mangle[ct_ftp_info->ftptype](...)函數,顯然最後的函數完成了對數據包的修改,對數據包進行修改就是為了ftp服務器可以成功連接到客戶端。由於客戶端很多時候在具有nat功能的防火墻後,並且都是用私網地址,而在ftp的port模式下,如果客戶端將一個私有地址建議給了ftp服務器用於連接,服務器是連接不到的,這個建議的ip地址在ftp的數據包中,因此必須修改數據包,將建議的地址和端口修改為nat後的地址和端口,同時再鋪設一條nat,用於服務器連接客戶端時將請求真正轉到內網的客戶端。ip_nat_mangle_tcp_packet是ip_nat_helper.c中的一個很重要的函數,就是它完成了對應用層數據包的修改。
ip_conntrack和ip_nat處理ftp總的過程就是,ip_conntrack模塊得到了連接的信息,然後根據連接信息可以得到一個helper,需要說明,一個連接完全可以沒有helper,而且大多數的都沒有helper,是否需要helper是根據連接的類型決定的,一般觸及應用層控制數據的修改時才會使用helper,比如ftp的控制命令是在應用層數據中被傳輸的,連接的類型是可以從數據包以及協議頭中得到的,因此ip_conntrack模塊需要數據包不能分段,也就是說需要完整的ip數據包。得到helper之後開始調用其help函數,然後判斷當前數據包是否需要help,比如判斷是否是ftp的特殊命令,該命令可以建立一條新的連接,如果是這樣的話,那麽help函數則“預測”到一條即將建立的連接並將之和當前連接關聯,然後ip_conntrack基本就沒有什麽做的了,數據包繼續在netfilter中流動,進入nat,同樣的,nat也如ip_conntrack判斷是否需要help,如果是則調用helper的help函數,判斷是否需要help的依據一般就是是否在ip_conntrack模塊中“預測”到了即將建立的連接,如果預測到了,那麽就調用nat的helper的help函數,並且將預測到的連接參數傳入,在ip_nat_ftp的help函數中根據預測連接的信息對應用層控制數據進行修改。
兩類helper的註冊都是在模塊初始化的時候進行的,而helper與連接或者nat的綁定則是在連接初始化的時候進行的。ip_nat_fn是nat的HOOK,其中對於IP_CT_NEW包來講需要調用call_expect,而後者最終調用下面的函數實現ftp相關的ip_nat_helper結構體的指定,該結構體在模塊初始化時被註冊:
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中,會調用ip_conntrack_helper_register將ftp相關的信息註冊進內核,這些信息包含在ip_nat_helper結構體中,其中有很多靜態數據是用於匹配helper的,比如新建一個連接,當數據越過conntrack而進入nat時會調用ip_nat_setup_info,在該函數中,如上述調用LIST_FIND,其實就是使用當前的addr,port等信息和註冊的helper逐個進行比較,一旦有命中的則將此helper取出留作後用,ip_nat_helper中最重要的就是help函數了。
...
}
ip_nat_ftp模塊的初始化函數如下:
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也是在模塊初始化時註冊,在連接初始化時被指定特定的連接的,道理和nat是一樣的。
總之,helper模塊一般是對需要在應用層數據中傳輸控制數據的協議進行幫助的,因為OS實現的協議棧並不包含應用層,但是有的時候必須對應用層控制數據進行修改,這時就不得不需要一個額外的幫助模塊了,註意,一般helper修改的都是控制數據,而不是業務數據,所謂控制數據就是和業務無關的,僅僅影響到連接本身的數據。
再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow
linux內核netfilter之ip conntrack模塊的作用舉例--ftp為例