linux核心netfilter之ip_conntrack模組的作用舉例--ftp為例__linux

來源:互聯網
上載者:User

很多協議的控制資訊在應用程式層資料中被包含,這些資訊直接影響到了鏈路的建立,比如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修改的都是控制資料,而不是業務資料,所謂控制資料就是和業務無關的,僅僅影響到串連本身的資料。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.