歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

如何擴展Linux的ip_conntrack

Linux中有一個基於Netfilter的連接跟蹤機制,即ip_conntrack,每一個conntrack表示的就是一個流,該流裡面保存了大量的信息字段,這些字段本地有效,指導著數據包的轉發策略,但是我覺得這些字段信息還不夠詳細,試想,一個nfmark字段好像就可以做到一切了,但是我如果想為一個數據流綁定一個字符串怎麼辦呢?也許你會說使用iptables+ipset+nfmark可以完成一切,這也是UNIX/Linux哲學的風格,一種後現代主義的風格,但是最近我上了不歸路,非要在ip_conntrack裡面擴充一個字段,為我們產品加入一個基於用戶名字符串的訪問控制和審計功能,於是我有了以下看似可以的方案,順便鄙視一下紙上談兵的人:
1.完全學ipmark的樣子,在sk_buff和nf_conn裡面均加一個mark字段,分別代表數據包的mark和數據流的mark
作罷的原因:
需要重新編譯內核,而我不希望為了一個小小的功能重新編譯內核,背後的思想是我比較崇尚熱插拔。
2.不動sk_buff,只在nf_conn裡面加一個字段,skb僅僅作為一個中轉,在iptables的target通過skb找到nf_conn,設置nf_conn的info字段
作罷的原因:
Linux嚴格控制內核模塊的版本,模塊依賴的頭文件一點都不能動,如果我改變了net/netfilter/nf_conntrack.h,那麼新編譯的所有的依賴nf_conntrack.ko的模塊中的符號CRC碼都會變化從而無法通過內核的驗證,我不得不學Netfilter的一個項目xtables-addons中compat-xtables的樣子,把所有的會改變CRC碼的導出函數全部再重新實現一遍,然而,天啊,我起初的想法太天真了,沒完美了的循環依賴,以至於我想罵兩句:
第一:
ip_conntrack為何不讓人擴展?雖然它有一個extend機制,但是MD簡直就是自說自話,全部都是預定義好的,就下面的枚舉裡面的幾類:

enum nf_ct_ext_id
{
    NF_CT_EXT_HELPER,
    NF_CT_EXT_NAT,
    NF_CT_EXT_ACCT,
    NF_CT_EXT_ECACHE,
    NF_CT_EXT_NUM,
};

你加一個新的類型,就會改變內核頭文件,既然不讓擴展,為何還叫extend呢?你干脆直接放進nf_conn就可以了,搞成extend感覺上好像多麼的模塊化,多麼的可插拔,實際上你能擴展的東西只能是邏輯,而不能是數據結構!

第二:

Linux為何把extend寫的那麼死呢?當我突然感到這是合理的時候,我就三緘其口了,後面我會說到,數據結構需要可以自解釋,即自己解釋自己。雖然人可以看到一個結構體馬上說出它的含義,但是程序卻很難將一堆數據對應到一個結構體!自解釋,如果不知道自解釋,那就說明你根本就TM就不懂計算機!雖然你可能很精通編程...

思路

既然不能擴展nf_conn的extend,也不能在nf_conn本身加新的字段,那麼只能重新編譯內核了,在重新編譯內核的時候,加入且僅僅加入一個extend類型,作為一個中間層,在這個extend中實現一個可插拔的注冊機制,以後再想加入新的擴展就可以直接在這個extend的機制上進行了。然而,我還是不想編譯內核,這是一個思想!我希望做最小的改動。萬事都難不倒偏執的人,我采用了一個常規卻不常用的方法,那就是默默地擴展結構體的大小,這也正是在《JAVA編程思想》裡面學到的一個思想。

思想

這其實是一種OO的思想,找到一個基類,然後擴展它,在擴展繼承的過程中實現你自己的邏輯,我擴展的是內核的nf_conn_counter結構體:

struct nf_conn_counter {
    u_int64_t packets;
    u_int64_t bytes;
};

我希望它成為下面的樣子:

struct nf_conn_counter {
    u_int64_t packets;
    u_int64_t bytes;
    unsigned char *info;
};

但是我又不能改變結構體的定義,所以我采用下面等價的辦法:

struct conn_info_extends_nf_conn_counter {
      struct nf_conn_counter base;
      char *info;
}

info是最關鍵的。我需要做的僅僅就是在為nf_conn_counter分配空間的時候為其多加一個指針的空間即可,至於這個指針指向什麼,自有調用者解釋。在我的需求中,它可能就是一個字符串,存在info信息。acct_extend原始定義為(之所以選擇對acct開刀,是因為它足夠簡單,在字面上裡面,其表示統計信息,加入一個info也無可厚非):

static struct nf_ct_ext_type acct_extend __read_mostly = {
    .len    = sizeof(struct nf_conn_counter[IP_CT_DIR_MAX]),
    .align    = __alignof__(struct nf_conn_counter[IP_CT_DIR_MAX]),
    .id    = NF_CT_EXT_ACCT,
};

將其修改為:

struct info_compat {
        struct nf_conn_counter nc[IP_CT_DIR_MAX];
        unsigned char * info;
};

static struct nf_ct_ext_type acct_extend __read_mostly = {
        .len    = sizeof(struct info_compat),
        .align  = __alignof__(struct info_compat),
        .id    = NF_CT_EXT_ACCT,
};

到此為止,我沒有修改任何內核頭文件,接下來我來寫一個測試模塊來進行測試:

#include <linux/ip.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/version.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_acct.h>


MODULE_AUTHOR("xtt");
MODULE_DESCRIPTION("gll");
MODULE_LICENSE("GPL");
MODULE_ALIAS("XTT and GLL");

struct nf_info {
        struct nf_conn_counter nc[IP_CT_DIR_MAX];
        char *info;
};

static unsigned int ipv4_conntrack_info (unsigned int hooknum,
                                      struct sk_buff *skb,
                                      const struct net_device *in,
                                      const struct net_device *out,
                                      int (*okfn)(struct sk_buff *))
{
        u32 addr = ip_hdr(skb)->daddr;
    // 測試我家的路由器的地址192.168.1.1
        if (addr == 0x0101a8c0) {
                struct nf_conn *ct;
                enum ip_conntrack_info ctinfo;
                struct nf_conn_counter *acct;
                struct nf_info *info;
                unsigned char *cn = NULL;
                ct = nf_ct_get(skb, &ctinfo);
                if (!ct || ct == &nf_conntrack_untracked)
                        return NF_ACCEPT;

                acct = nf_conn_acct_find(ct);
                if (acct) {
                        info = (struct nf_info *)acct;
                        info->info = (unsigned char*) kzalloc(32, GFP_ATOMIC);
                        if (!info->info) {
                                return NF_ACCEPT;
                        }
            // 測試將1234567890作為字符串設置到conntrack
                        memcpy(info->info, "1234567890", min(32, strlen("1234567890")));
                }
        }
        return NF_ACCEPT;
}

static struct nf_hook_ops ipv4_conn_info __read_mostly = {
                .hook          = ipv4_conntrack_info,
                .owner          = THIS_MODULE,
                .pf            = NFPROTO_IPV4,
                .hooknum        = NF_INET_LOCAL_OUT,
                .priority      = NF_IP_PRI_CONNTRACK + 1,
};

static int __init test_info_init(void)
{
        int err;
        err = nf_register_hook(&ipv4_conn_info);
        if (err) {
                return err;
        }
        return err;
}

static void __exit test_info_exit(void)
{
        nf_unregister_hook(&ipv4_conn_info);
}

module_init(test_info_init);
module_exit(test_info_exit);

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-04/100908p2.htm

Copyright © Linux教程網 All Rights Reserved