linux nf_conntrack 連接跟蹤機制


PRE_ROUTING和LOCAL_OUT點可以看作是整個netfilter的入口,而POST_ROUTING和LOCAL_IN可以看作是其出口;

報文到本地PRE_ROUTING----LOCAL_IN---本地進程

需要本機轉發的數據包:PRE_ROUTING---FORWARD---POST_ROUTING---外出

從本機發出的數據包:LOCAL_OUT----POST_ROUTING---外出

 數據包文到達內核協議棧時,使用sk_buff{}(即skb),其類型為struct nf_conntrack  *;該結構記錄了連接記錄被公開應用的計數,也方便其他地方對連接跟蹤的引用;

連接跟蹤在實際應用中一般都通過強制類型轉換將nfct轉換成指向struct nf_conn { }類型;

同時:skb->nfctinfo 記錄了該數據包的連接狀態和該連接狀態的相關信息;nfctinfo表示了每個數據包的幾種連接狀態;

Neftilter框架用struct   nf_conn{ }來記錄一個數據包與其連接的狀態關系;

其中nfctinfo 取值有

/* Connection state tracking for netfilter.  This is separated from,
   but required by, the NAT layer; it can also be used by an iptables
   extension. */
enum ip_conntrack_info {
    /* Part of an established connection (either direction). 
      已建立連接的一部分(任一方向) */
    IP_CT_ESTABLISHED,

    /* Like NEW, but related to an existing connection, or ICMP error
       (in either direction). */
 /* 已建立連接的關聯連接,或者是ICMP錯誤(任一方向) */
    IP_CT_RELATED,

    /* Started a new connection to track (only
           IP_CT_DIR_ORIGINAL); may be a retransmission. */
/* 開始一個新連接; 可能是重傳 */
    IP_CT_NEW,
 /* >=這個值的都是響應方向的 */
    /* >= this indicates reply direction */
    IP_CT_IS_REPLY,
/* 已建立連接的響應 */
    IP_CT_ESTABLISHED_REPLY = IP_CT_ESTABLISHED + IP_CT_IS_REPLY,
 /* 已建立連接的關聯連接的響應 */
    IP_CT_RELATED_REPLY = IP_CT_RELATED + IP_CT_IS_REPLY,
    /* No NEW in reply direction. */
 /* IP_CT類型的數量 */
    /* Number of distinct IP_CT types. */
    IP_CT_NUMBER,

    /* only for userspace compatibility */
#ifndef __KERNEL__
    IP_CT_NEW_REPLY = IP_CT_NUMBER,
#endif
};        

在連接跟蹤內部,收到的每個skb首先被轉換成一個nf_conntrack_tuple{}結構,也就是說nf_conntrack_tuple{}結構才是連接跟蹤系統所“認識”的數據包 ;

skb和ip_conntrack_tuple{}結構之間是如何轉換的呢?

對於TCP/UDP協議,根據“源、目的IP+源、目的端口”再加序列號就可以唯一的標識一個數據包了;對於ICMP協議,根據“源、目的IP+類型+代號”再加序列號才可以唯一確定一個ICMP報文等等

nf_conntrack 數據結構

 

/*
 struct sk_buff {

      struct nf_conntrack *nfct;//指向struct nf_conn實例
      ..............
}; 
*/
struct nf_conn {//每個struct nf_conn實例代表一個連接。每個skb都有一個指針,指向和它相關聯的連接。
    /* Usage count in here is 1 for hash table/destruct timer, 1 per skb,
     * plus 1 for any connection(s) we are `master' for
     *
     * Hint, SKB address this struct and refcnt via skb->nfct and
     * helpers nf_conntrack_get() and nf_conntrack_put().
     * Helper nf_ct_put() equals nf_conntrack_put() by dec refcnt,
     * beware nf_ct_get() is different and don't inc refcnt.
     */
    struct nf_conntrack ct_general; //對連接的引用計數

    spinlock_t    lock;
    u16        cpu;

    /* These are my tuples; original and reply */
    /* Connection tracking(鏈接跟蹤)用來跟蹤、記錄每個鏈接的信息(目前僅支持IP協議的連接跟蹤)。
            每個鏈接由“tuple”來唯一標識,這里的“tuple”對不同的協議會有不同的含義,例如對tcp,udp
                 來說就是五元組: (源IP,源端口,目的IP, 目的端口,協議號),對ICMP協議來說是: (源IP, 目
            的IP, id, type, code), 其中id,type與code都是icmp協議的信息。鏈接跟蹤是防火牆實現狀態檢
            測的基礎,很多功能都需要借助鏈接跟蹤才能實現,例如NAT、快速轉發、等等。*/
    struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];//正向和反向的連接元組信息。
/* 這是一個位圖,是一個狀態域。在實際的使用中,它通常與一個枚舉類型ip_conntrack_status(位於include/linux/netfilter_ipv4/ip_conntrack.h,Line33)進行位運算來判斷連接的狀態。其中主要的狀態包括:

IPS_EXPECTED(_BIT),表示一個預期的連接

IPS_SEEN_REPLY(_BIT),表示一個雙向的連接

IPS_ASSURED(_BIT),表示這個連接即使發生超時也不能提早被刪除

IPS_CONFIRMED(_BIT),表示這個連接已經被確認(初始包已經發出) */
 /* 可以設置由enum ip_conntrack_status中描述的狀態 */
    /* Have we seen traffic both ways yet? (bitset) */
    unsigned long status;//該連接的連接狀態

    /* Timer function; drops refcnt when it goes off. */
    struct timer_list timeout; //連接垃圾回收定時器  連接跟蹤的超時時間 

    possible_net_t ct_net;

    /* all members below initialized via memset */
    u8 __nfct_init_offset[0];
 /*結構ip_conntrack_expect位於ip_conntrack.h,這個結構用於將一個預期的連接分配給現有的連接,也就是說本連接是這個master的一個預期連接*/
    /* If we were expected by an expectation, this will be it */
    struct nf_conn *master;//如果該連接是期望連接,指向跟其關聯的主連接

#if defined(CONFIG_NF_CONNTRACK_MARK)
    u_int32_t mark;
#endif

#ifdef CONFIG_NF_CONNTRACK_SECMARK
    u_int32_t secmark;
#endif

    /* Extensions */ /*指向擴展結構,該結構中包含一些基於連接的功能擴展處理函數 */
    struct nf_ct_ext *ext;

    /* Storage reserved for other modules, must be the last member */
    union nf_conntrack_proto proto; /*存儲特定協議的連接跟蹤信息 也就是不同協議實現連接跟蹤的額外參數 */
};
//其中最主要的就是tuplehash(跟蹤連接雙方向數據)和status(記錄連接狀態)
在status中可以設置的標志,由下面的enum ip_conntrack_status描述,它們可以共存;
/* Connection state tracking for netfilter.  This is separated from,
   but required by, the NAT layer; it can also be used by an iptables
   extension. */
enum ip_conntrack_info {
    /* Part of an established connection (either direction).   表示這個數據包對應的連接在兩個方向都有數據包通過,
    並且這是ORIGINAL初始方向數據包(無論是TCP、UDP、ICMP數據包,
     只要在該連接的兩個方向上已有數據包通過,就會將該連接設置為IP_CT_ESTABLISHED狀態。不會根據協議中的標志位進行判斷,
     例如TCP的SYN等)。但它表示不了這是第幾個數據包,也說明不了這個CT是否是子連接。*/
    IP_CT_ESTABLISHED,

    /* Like NEW, but related to an existing connection, or ICMP error
       (in either direction). 表示這個數據包對應的連接還沒有REPLY方向數據包,當前數據包是ORIGINAL方向數據包。
       並且這個連接關聯一個已有的連接,是該已有連接的子連接,?
       磗tatus標志中已經設置了IPS_EXPECTED標志,該標志在init_conntrack()函數中設置)。但無法
判斷是第幾個數據包(不一定是第一個)*/
    IP_CT_RELATED,

    /* Started a new connection to track (only
           IP_CT_DIR_ORIGINAL); may be a retransmission. 
           表示這個數據包對應的連接還沒有REPLY方向數據包,當前數據包是ORIGINAL方向數據包,該連接不是子連接。但無法判斷是
第幾個數據包(不一定是第一個*/
    IP_CT_NEW,

    /* >= this indicates reply direction這個狀態一般不單獨使用,通常以下面兩種方式使用 */
    IP_CT_IS_REPLY,
/* 表示這個數據包對應的連接在兩個方向都有數據包通過,並且這是REPLY應答方向數據包。但它表示不了這是
    第幾個數據包,也說明不了這個CT是否是子連接。*/
    IP_CT_ESTABLISHED_REPLY = IP_CT_ESTABLISHED + IP_CT_IS_REPLY,
    /*這個狀態僅在nf_conntrack_attach()函數中設置,用於本機返回REJECT,例如返回一個ICMP目的不可達報文,
      或返回一個reset報文。它表示不了這是第幾個數據包
    */
    IP_CT_RELATED_REPLY = IP_CT_RELATED + IP_CT_IS_REPLY,
    /* No NEW in reply direction. */

    /* Number of distinct IP_CT types. */
    IP_CT_NUMBER,

    /* only for userspace compatibility */
#ifndef __KERNEL__
    IP_CT_NEW_REPLY = IP_CT_NUMBER,
#endif
};

#define NF_CT_STATE_INVALID_BIT            (1 << 0)
#define NF_CT_STATE_BIT(ctinfo)            (1 << ((ctinfo) % IP_CT_IS_REPLY + 1))
#define NF_CT_STATE_UNTRACKED_BIT        (1 << (IP_CT_NUMBER + 1))

/* Bitset representing status of connection. */
enum ip_conntrack_status {
    /* It's an expected connection: bit 0 set.  This bit never changed */
    IPS_EXPECTED_BIT = 0,//表示該連接是個子連接 
    IPS_EXPECTED = (1 << IPS_EXPECTED_BIT),

    /* We've seen packets both ways: bit 1 set.  Can be set, not unset. */
    IPS_SEEN_REPLY_BIT = 1,//表示該連接上雙方向上都有數據包了 
    IPS_SEEN_REPLY = (1 << IPS_SEEN_REPLY_BIT),

    /* Conntrack should never be early-expired. 
    TCP:在三次握手建立完連接后即設定該標志。UDP:如果在該連接上的兩個方向都有數據包通過,則再有數據包在該連接上通過時?
    就設定該標志。ICMP:不設置該標志
    */
    IPS_ASSURED_BIT = 2,

    IPS_ASSURED = (1 << IPS_ASSURED_BIT),

    /* Connection is confirmed: originating packet has left box 
    表示該連接已被添加到net->ct.hash表*/
    IPS_CONFIRMED_BIT = 3,
    IPS_CONFIRMED = (1 << IPS_CONFIRMED_BIT),

    /* Connection needs src nat in orig dir.  This bit never changed. 
    在POSTROUTING處,當替換reply tuple完成時, 設置該標記*/
    IPS_SRC_NAT_BIT = 4,
    IPS_SRC_NAT = (1 << IPS_SRC_NAT_BIT),

    /* Connection needs dst nat in orig dir.  This bit never changed. 
    在PREROUTING處,當替換reply tuple完成時, 設置該標記*/
    IPS_DST_NAT_BIT = 5,
    IPS_DST_NAT = (1 << IPS_DST_NAT_BIT),

    /* Both together. */
    IPS_NAT_MASK = (IPS_DST_NAT | IPS_SRC_NAT),

    /* Connection needs TCP sequence adjusted. */
    IPS_SEQ_ADJUST_BIT = 6,
    IPS_SEQ_ADJUST = (1 << IPS_SEQ_ADJUST_BIT),

    /* NAT initialization bits.  在POSTROUTING處,已被SNAT處理,並被加入到bysource鏈中,設置該標記*/
    IPS_SRC_NAT_DONE_BIT = 7,
    IPS_SRC_NAT_DONE = (1 << IPS_SRC_NAT_DONE_BIT),

    IPS_DST_NAT_DONE_BIT = 8,//在PREROUTING處,已被DNAT處理,並被加入到bysource鏈中,設置該標記
    IPS_DST_NAT_DONE = (1 << IPS_DST_NAT_DONE_BIT),

    /* Both together */
    IPS_NAT_DONE_MASK = (IPS_DST_NAT_DONE | IPS_SRC_NAT_DONE),

    /* Connection is dying (removed from lists), can not be unset. 
    表示該連接正在被釋放,內核通過該標志保證正在被釋放的ct不會被其它地方再次引用。有了這個標志,當某個連接要被刪
      除時,即使它還在net->ct.hash中,也不會再次被引 用*/
    IPS_DYING_BIT = 9,
    IPS_DYING = (1 << IPS_DYING_BIT),

    /* Connection has fixed timeout. 
     固定連接超時時間,這將不根據狀態修改連接超時時間。通過函數nf_ct_refresh_acct()修改超時時間時檢查該標志*/
    IPS_FIXED_TIMEOUT_BIT = 10,
    IPS_FIXED_TIMEOUT = (1 << IPS_FIXED_TIMEOUT_BIT),

    /* Conntrack is a template 
     由CT target進行設置(這個target只能用在raw表中,用於為數據包構建指定ct,並打上該標志),用於表明這個ct是由CT target創建*/
    IPS_TEMPLATE_BIT = 11,
    IPS_TEMPLATE = (1 << IPS_TEMPLATE_BIT),

    /* Conntrack is a fake untracked entry */
    IPS_UNTRACKED_BIT = 12,
    IPS_UNTRACKED = (1 << IPS_UNTRACKED_BIT),

    /* Conntrack got a helper explicitly attached via CT target. */
    IPS_HELPER_BIT = 13,
    IPS_HELPER = (1 << IPS_HELPER_BIT),
};

 

 





 

/* Connections have two entries in the hash table: one for each way */
struct nf_conntrack_tuple_hash {
    struct hlist_nulls_node hnnode;
    struct nf_conntrack_tuple tuple;
};
nf_ct_ext用於實現對連接跟蹤的擴展;
/* Extensions: optional stuff which isn't permanently in struct. */
struct nf_ct_ext {
    struct rcu_head rcu;
    u16 offset[NF_CT_EXT_NUM];
    u16 len;
    char data[0];
};

 

nf_conntrack_tuple 的分析:

/* The protocol-specific manipulable parts of the tuple: always in
 * network order
 */
union nf_conntrack_man_proto {
    /* Add other protocols here. */
    __be16 all;

    struct {
        __be16 port;
    } tcp;
    struct {
        __be16 port;
    } udp;
    struct {
        __be16 id;
    } icmp;
    struct {
        __be16 port;
    } dccp;
    struct {
        __be16 port;
    } sctp;
    struct {
        __be16 key;    /* GRE key is 32bit, PPtP only uses 16bit */
    } gre;
};


/* The manipulable part of the tuple. */
struct nf_conntrack_man {
    union nf_inet_addr u3; /* 三層識別信息 */
    union nf_conntrack_man_proto u;/* 四層識別信息 */
    /* Layer 3 protocol 三層協議號 */
    u_int16_t l3num;
};

/* This contains the information to distinguish a connection.
該結構包含源目的信息用來區分一條連接 */
struct nf_conntrack_tuple {
    struct nf_conntrack_man src;/* 源, */
    /* These are the parts of the tuple which are fixed. */
    struct { /* 目的,不可操作? */
        union nf_inet_addr u3;
        union {
            /* Add other protocols here. */
            __be16 all;

            struct {
                __be16 port;
            } tcp;
            struct {
                __be16 port;
            } udp;
            struct {
                u_int8_t type, code;
            } icmp;
            struct {
                __be16 port;
            } dccp;
            struct {
                __be16 port;
            } sctp;
            struct {
                __be16 key;
            } gre;
        } u;
        /* The protocol.  協議*/
        u_int8_t protonum;
        /* The direction (for tuplehash) */ /* 方向(tuplehash使用) */
        u_int8_t dir;
    } dst;
};

Netfilter將每一個數據包轉換成tuple,再根據tuple計算出hash值,這樣,就可以使用nf_conntrack_hash[hash_id]找到hash表中鏈表的入口,並組織鏈表

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM