天天看點

netfilter源碼學習(4)——NAT處理(1)

作者:[email protected]

部落格:blog.focus-linux.net   linuxfocus.blog.chinaunix.net 

本文的copyleft歸[email protected]所有,使用GPL釋出,可以自由拷貝,轉載。但轉載請保持文檔的完整性,注明原作者及原連結,嚴禁用于任何商業用途。

======================================================================================================

在前面的netfilter源碼學習中,我學習了netfilter的基本架構,其這部分代碼看起來還是比較簡單的。後來在zhangyd6這位朋友的提示下,想起自己居然遺漏了netfilter作為防火牆的主要功能NAT。這部分代碼應該還比較有意思。今天開始看這部分代碼。

注:tuple即為五元組,源位址,目的位址,源端口,目的端口,和協定。

NAT規則的源代碼位于net/ipv4/netfilter/nf_nat_rule.c中。在函數nf_nat_rule_init注冊了兩個NAT的target。

    ret = xt_register_target(&ipt_snat_reg);

    if (ret != 0)

        goto unregister_table;

    ret = xt_register_target(&ipt_dnat_reg);

        goto unregister_snat;

以ipt_snat_reg為例:

static struct xt_target ipt_snat_reg __read_mostly = {

    .name        = "SNAT",

    .target        = ipt_snat_target, //SNAT target的執行函數

    .targetsize    = sizeof(struct nf_nat_multi_range_compat),

    .table        = "nat",

    .hooks        = (1 NF_INET_POST_ROUTING) | (1 NF_INET_LOCAL_IN),

    .checkentry    = ipt_snat_checkentry, //添加SNAT規則的檢查函數

    .family        = AF_INET,

};

先看簡單的ipt_snat_checkentry

static int ipt_snat_checkentry(const struct xt_tgchk_param *par)

{

    /* 

    這個為NAT的範圍,即IP,port等。雖然該結構體的名字為multi,但是目前隻支援一個range

    該結構體的含義看其定義很明顯,這裡就說明了

    */

    const struct nf_nat_multi_range_compat *mr = par->targinfo;

     //隻支援一個範圍。

    /* Must be a valid range */

    if (mr->rangesize != 1) {

        pr_info("SNAT: multiple ranges no longer supported\n");

        return -EINVAL;

    }

    return 0;

}

Ok,那麼下面進入關鍵的函數ipt_snat_target

static unsigned int

ipt_snat_target(struct sk_buff *skb, const struct xt_action_param *par)

    struct nf_conn *ct;

    enum ip_conntrack_info ctinfo;

     /* SNAT隻能應用于POST_ROUTING和LOCAL IN */

    NF_CT_ASSERT(par->hooknum == NF_INET_POST_ROUTING ||

         par->hooknum == NF_INET_LOCAL_IN);

     /* 

     得到conn track的資訊,conn track為netfilter的一個基礎。留在以後學習。

     目前我們隻需要知道netfilter儲存了資料包的連接配接資訊。

     */

    ct = nf_ct_get(skb, &ctinfo);

    /* Connection must be valid and new. */

    這裡對conn進行了驗證。

    要做SNAT,必須是建立的連接配接——很明顯的道理。

    但是有的7層應用,可能需要多條相關的conn,這時就需要IP_CT_RELATED。

    NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||

             ctinfo == IP_CT_RELATED IP_CT_IS_REPLY));

    NF_CT_ASSERT(par->out != NULL);

    return nf_nat_setup_info(ct, &mr->range[0], IP_NAT_MANIP_SRC);

進入nf_nat_setup_inifo

unsigned int

nf_nat_setup_info(struct nf_conn *ct,

         const struct nf_nat_range *range,

         enum nf_nat_manip_type maniptype)

    struct net *net = nf_ct_net(ct);

    struct nf_conntrack_tuple curr_tuple, new_tuple;

    struct nf_conn_nat *nat;

    int have_to_hash = !(ct->status & IPS_NAT_DONE_MASK);

    /* nat helper or nfctnetlink also setup binding */

    對于netfilter的nf_conn,其使用一個struct nf_ct_ext的結構來儲存各種extension資訊,如NAT。

    這裡嘗試從ct中獲得nat。

    注意ct中可以儲存多個extension

    nat = nfct_nat(ct);

    if (!nat) {

        /* 

        沒有NAT的extension資訊,那麼就申請一個extension結構并儲存在ct中

        */

        nat = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC);

        if (nat == NULL) {

            pr_debug("failed to add NAT extension\n");

            return NF_ACCEPT;

        }

    NF_CT_ASSERT(maniptype == IP_NAT_MANIP_SRC ||

         maniptype == IP_NAT_MANIP_DST);

    BUG_ON(nf_nat_initialized(ct, maniptype));

    /* What we've got will look like inverse of reply. Normally

     this is what is in the conntrack, except for prior

     manipulations (future optimization: if num_manips == 0,

     orig_tp =

     conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple) */

    這裡要注意一點:通過reply方向的tuple資訊,來得到目前的tuple資訊,即發送方向的tuple。

    那麼為什麼不直接使用ct的IP_CT_DIR_ORIGINAL的tuple資訊呢。

    根據上面的注釋所說,一般情況下可以使用ORIGINAL方向的tuple資訊。但是如果num_manips不為0,

    那麼original方向的tuple資訊就不能使用。因為original的資訊被修改了??

    nf_ct_invert_tuplepr(&curr_tuple,

             &ct->tuplehash[IP_CT_DIR_REPLY].tuple);

     得到SNAT後的tuple。get_unique_tuple留在後面學習。

    get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);

    if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {

        /* SNAT後的tuple與之前的tuple不等,即tuple發生了變化 */

        struct nf_conntrack_tuple reply;

        /* Alter conntrack table so will recognize replies. */

        /* tuple變化了,是以需要更新conntrack中的REPLY方向的tuple */

        nf_ct_invert_tuplepr(&reply, &new_tuple);

        nf_conntrack_alter_reply(ct, &reply);

        /* Non-atomic: we own this at the moment. */

        if (maniptype == IP_NAT_MANIP_SRC)

            ct->status |= IPS_SRC_NAT;

        else

            ct->status |= IPS_DST_NAT;

    /* Place in source hash if this is the first time. */

    如注釋所說,第一次做NAT時将,ORIGINAL方向的tuple加入到hash表中。

    雖然這裡隻是将tuple加入到hash表中,但是實際上我們可以從tuple得到conntrack結構。

    不知道以後會不會有這樣的操作?

    if (have_to_hash) {

        unsigned int srchash;

        srchash = hash_by_src(net, nf_ct_zone(ct),

                 &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);

        spin_lock_bh(&nf_nat_lock);

        /* nf_conntrack_alter_reply might re-allocate exntension aera */

        nat = nfct_nat(ct);

        nat->ct = ct;

        hlist_add_head_rcu(&nat->bysource,

                 &net->ipv4.nat_bysource[srchash]);

        spin_unlock_bh(&nf_nat_lock);

    /* It's done. */

    if (maniptype == IP_NAT_MANIP_DST)

        set_bit(IPS_DST_NAT_DONE_BIT, &ct->status);

    else

        set_bit(IPS_SRC_NAT_DONE_BIT, &ct->status);

    return NF_ACCEPT;

明天再看get_unique_tuple,另外,似乎不把conn track搞定,看這部分代碼會有困惑。我來看看conn track,決定先研究一下哪個

繼續閱讀