1. 程式人生 > >linux之ip conntrack容易混淆的問題點滴

linux之ip conntrack容易混淆的問題點滴

ket tel 防火墻 查詢 .net orm 分享 基礎 我們

《再次深入到ip_conntrack的conntrack full問題》最後的一個問題提示

ip_conntrack有一個event機制,可以主動通報ip_conntrack的一些事件,包括追蹤信息到期刪除等事件,通知給誰呢?當然是通知給所有感興趣的模塊了,其中之一就是用戶態進程,這樣用戶態進程得知可以采取一些措施,比如防火墻上設置一些放過規則等,這個通知機制使用了觀察者設計模式。

Linux ip_conntrack的一些細節問題

由於Linux的ip_conntrack具有大量的狀態,而每一種狀態都有一定的超時時間,這些狀態中的個別可以和網絡協議的不同狀態建立映射關系,另一些則不能。如果協議本身是有狀態的,那麽就很方便建立一種映射關系,反之如果協議沒有狀態,那麽就不能建立映射關系。有時候,對於無狀態的協議而言,ip_conntrack的狀態超時時間會帶來一些令人郁悶的問題。

總之,Linux的ip_conntrack機制如果深究起來還真的看點,如果搞防火墻開發,實屬不可不察也。下面就舉幾個例子。

例子舉例

例子1:

對於UDP而言,它本身沒有狀態,無需建立連接,無需確認,純粹就是一個數據報協議,因此ip_conntrack使用經驗值來設定各個狀態的超時時間,但是如果雙方有一段時間沒有發包,那麽當初始接收端再發起一個數據包時,就會給防火墻上的基於ctstate的過濾規則帶來影響,具體參見《再次深入到ip_conntrack的conntrack full問題》。

例子2:

對於UDP而言,如果在Linux防火墻上使用NAT,那麽在數據通信期間,即使NAT規則被刪除或者被修改,該數據流依然會使用老的NAT規則而不是不使用任何規則或者使用新的規則。

例子3:

在早期的內核上,加載ip_conntrack模塊,然後ping一個可以ping通的地址,則在/proc/net/ip_conntrack中卻看不到該連接的追蹤信息,而ping一個根本不可達的地址,反而能看到一個反方向為UNREPLY的追蹤信息。值得註意的是,起碼在2.6.32內核上,這個問題不再存在,而在2.6.9內核上還是存在的,具體哪個版本修正了它,沒有詳細看內核的ChangeLog。

例子4:

對於TCP而言,只要一個連接斷開了,/proc/net/ip_conntrack中的關於該連接的追蹤信息將馬上被刪除,而不會像UDP那樣保留。

針對以上問題的一些解釋

例子1的解釋:

這個沒有什麽好說的,根本原因就是UDP本身沒有狀態,而ip_conntrack將establish狀態強加給了一個UDP連接,所謂的ip_conntrack的establish狀態對於所有的協議都是說有去有回的流,當然對於UDP更是這樣。對於TCP而言,ip_conntrack將不是syn狀態的流量都映射成了establisd狀態(註意不是TCP的established狀態),這也符合上述定義。在ip_conntrack處理的入口的最後:
if
(set_reply) set_bit(IPS_SEEN_REPLY_BIT, &ct->status);
這說明只要set_reply為真就會修改ct的一個狀態位,而set_reply在ip_conntrack_in的resolve_normal_ct調用中就會被設置。
//只要收到反向的包,就會設置IP_CT_ESTABLISHED,且把set_reply設置為1,然後返回到ip_conntrack_in的時候,就會導致ct->status的IPS_SEEN_REPLY_BIT的設置
if (DIRECTION(h) == IP_CT_DIR_REPLY) {
    *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
    /* Please set reply bit if this packet OK */
    *set_reply = 1;
} else {
    /* Once we‘ve had two way comms, always ESTABLISHED. */
    //只要有IPS_SEEN_REPLY_BIT位被置位,那麽就是IP_CT_ESTABLISHED
    if (test_bit(IPS_SEEN_REPLY_BIT, &h->ctrack->status)) {
        DEBUGP("ip_conntrack_in: normal packet for %p\n", h->ctrack);
        *ctinfo = IP_CT_ESTABLISHED;
...
因此可見IP_CT_ESTABLISHED狀態和具體的協議是無關的,對於TCP而言,所有SYN後面的包都會是IP_CT_ESTABLISHED狀態。然而由於TCP本身擁有可以監控連接的狀態,比如close-wait等,因此它在ip_conntrack中又有一些子狀態,這個用於在適當的時候釋放ip_conntrack數據結構,因此只要TCP的ip_conntrack的time-wait子狀態到期,其ip_conntrack數據結構就會被當即釋放,這一切正是因為TCP將其協議狀態映射成了ip_conntrack的子狀態,而這些子狀態知道什麽時候一個tcp流結束了。然而這一切對於UDP以及ICMP而言就沒有這麽幸運了,它們沒有所謂的子狀態,它們只能使用大膽的ip_conntrack狀態。

例子2的解釋:

Linux的iptables/Netfilter實現的NAT是有狀態NAT,它只對一個流的頭包查詢NAT表,並將查詢結果設置給屬於該流的ip_conntrack數據結構中,一個流的頭包此後的所有數據包都使用這個結果。加之UDP沒有狀態,ip_conntrack除非等到establish狀態到期,否則無法釋放(其實它根本不知道該UDP流什麽時候會結束,就連establish的到期時間也是一個經驗值)ip_conntrack數據結構,既然不釋放該數據結構,那麽頭包保存的NAT結果就一直有效,因此才會出現這樣的問題。對於ICMP而言,較特殊,不同的內核版本是不同的,這就是例子3中的情形。

例子3的解釋:

在2.6.9的內核上,icmp_packet如下:
static int icmp_packet(struct ip_conntrack *ct,
               const struct sk_buff *skb,
               enum ip_conntrack_info ctinfo)
{
    /* Try to delete connection immediately after all replies:
           won‘t actually vanish as we still have skb, and del_timer
           means this will only run once even if count hits zero twice
           (theoretically possible with SMP) */
    //只要有包返回,則遞減icmp流的引用計數,如果是0,則釋放ip_conntrack連接。實際上非SMP情況下在resolve_normal_ct中總會將找到的ip_conntrack的引用計數加1的,如果此時到達以下語句,即使icmp.count為0,調用了timeout.function,也不會釋放ip_conntrack,進而在filter表中仍然可以使用ctsate或者state這些match,直到相關聯的skb被free之後才會調用ip_conntrack_put進而將連接追蹤的引用計數變為0從而刪除之。
    if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) {
        //如果有同一個源到同一個目的的icmp包或者其返回包經過則遞增icmp.count字段。
        if (atomic_dec_and_test(&ct->proto.icmp.count)
            && del_timer(&ct->timeout))
            ct->timeout.function((unsigned long)ct);
    } else {
        atomic_inc(&ct->proto.icmp.count);
        ip_ct_refresh_acct(ct, ctinfo, skb, ip_ct_icmp_timeout);
    }

    return NF_ACCEPT;
}
該函數被ip_conntrack_in回調。實際上,對於每一個協議都有一個類似這樣的回調函數,名為packet,該回調函數處理和協議相關的內容,比如對於TCP而言就是處理子狀態。
看了上面的代碼分析,我們得知對於可以ping通的地址,由於返回包很快到達進而可以清除ip_conntrack數據結構,然而又因為其引用計數遞減後不為0從而不被釋放,則其不影響filter表中的--state判斷,然而一旦相關的skb離開內核則會釋放skb,進而遞減1後的ip_conntrack引用計數為0而被釋放,連接追蹤數據結構隨即被釋放,這就是低版本(包括2.6.9)內核的做法,所以,當能ping通時,連接追蹤信息很快被釋放,你很難在/proc/net/ip_conntrack中看到它,而當你ping一個不可達的地址時,由於沒有返回包,其連接追蹤信息反而能被展現,雖然其返回包的狀態為NOREPLEY。
現在想想,為何Linux內核對待ICMP沒有像UDP那樣,畢竟它們都是一類的,直接駕於IP之上,沒有連接,沒有確認,沒有狀態,為何不同呢?原因在於,UDP無論如何也是體現了一種雙向或者單向的通信,而ICMP則只是在傳達一種消息而已,一般而言,沒有ICMP長時間占據一個通信流的,一般都是一個來回或者有去無回之類的,這就是它們的本質區別,因此對於ICMP,一個來回完了,連接追蹤信息也就刪除了,這很合理,看起來沒什麽不好,然而看看其對長ping的處理,對於長ping,ip_conntrack要不斷地刪除連接追蹤,然後建立新的連接追蹤...如此反復,消耗了大量的CPU資源,看起來ip_conntrack對於ICMP的做法有利於靜態空間的優化,也即是它最小化了內存空間的占用,然而它卻最大化了CPU時間的占用,內存如此之便宜的今天,這樣有意義嗎?無論如何,高一點的版本朝相反的方向改變了這一點,高版本的內核提出了另一種優化,那就是對CPU時間的優化,最終劃歸成了和UDP一樣的處理方式,設置了一個ICMP超時時間。
起碼在2.6.32版本的內核中,ip_conntrack就不再和2.6.9內核一樣地處理ICMP了,而是和UDP一樣地處理。

例子4的解釋:

通過例子1的解釋,我想這個已經不難理解了。

總結一下

Linux的ip_conntrack機制是很多基於狀態的配置策略的基石,它的狀態基於一個經驗測定的超時時間,該時間過期後將會刪除相應的連接追蹤信息。這就是所謂的Linux的“基於狀態”。然而網絡協議的狀態不一定都能和ip_conntrack的狀態一一對應得上,因此ip_conntrack不得不自己定義狀態的含義。
本質上講,ip_conntrack要應對兩類協議,一類是有連接的協議,一類是無連接的協議。對於由連接的協議,由於協議本身就知道何時拆除一個連接,因此ip_conntrack也就知道何時刪除一個ip_conntrack數據結構,然而對於無連接的協議,協議本身並不知道何時結束一個流,因此ip_conntrack也只能根據經驗值來估算之。特別要註意的是,由於ip_conntrack是全程無監控的,因此即便對於TCP這樣有連接的協議來講,其處在一個特定狀態不變時,ip_conntrack仍然無法得知其何時離開該狀態,因此對於諸如TCP之類的有狀態協議而言,在特定的某一個協議狀態(ip_conntrack子狀態)之內,其行為和無狀態的諸如UDP的協議一模一樣,比如雖然TCP的establish設置為5天已經夠長了,但是超過5天不傳輸數據,然後反方向主動傳輸數據的情形也會存在。

再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow

linux之ip conntrack容易混淆的問題點滴