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

在Linux上實現一個可用的stateless雙向靜態NAT模塊

關於Linux上如何配置NAT的資料已經不少,可謂鋪天蓋地!本文與此無關。本文提供一種iptables之外的方式。

iptables?不!why?因為iptables配置的NAT是stateful的,它的實現依賴一個叫做conntrack的模塊,什麼是conntrack?Oh,NO!這可是我的專長,但我不想在本文中說它,認識我的人都知道,我扯這個話題我能扯上12個小時...都還扯不完。也許你不知道什麼是stateful NAT,但是如果你是一個有心人,或者說是一個技術還算精湛的Linux網絡管理員或者愛好者,你肯定在配置NAT的時候遇到過這樣那樣的問題,比如“在一個連接已經建立的時候再配置NAT為何不能及時生效”,“為什麼iptables配置NAT之後數據只能從一個方向主動發往另一個方向”之類的。這就是state在作怪,你知道的,IP是無state的,但是NAT加入了第四層的邏輯之後就有了state,這就是stateful NAT,也就是iptables -t nat ..配置出來的NAT固有的性質,你改變不了。起碼我在iptables最新的版本中看到的NAT還是stateful的。有的時候..

有的時候,你可能,你必須...

你必須配置一種stateless的NAT,雙向的,靜態的。這個問題,唉..

這個問題折騰了我半年,2013年的前9個月,一個讓我歡喜讓我憂的三個季度,我的精力幾乎全部撲在了一件事上,從寒冬到40+攝氏度的高溫,從早上6點半出發去上班到半夜10點多還呆在機房...要不是大前天收拾書架時發現了一張當時還沒有報銷掉的120元的例行加班打車票,我本來不想寫這個模塊了。120塊不算什麼,但借此機會回憶往事,順便補上殘缺的那一部分,算是給自己報銷了,而且價值遠大於120元。我得承認,那三個季度裡並不是stateless NAT顯得最為重要,我之所以在一年後的今天把它拿出來,是因為其它的問題都被我當時就overcome了,不管花多久,曾經有過72小時驚魂解決conntrack confirm問題,有過由於混亂急躁和陌生女人一起吃烤肉被老婆詐出真相...但是就這個stateless NAT始終沒有解決,沒有解決,這是why?

我做的是一個產品而不是個人試驗,我所在的是一個公司的團隊而不是干私活兒,所有使用的技術必須經過技術預研,確保可行性,更重要的是,要保證所有人處在一樣的節奏,也許是並行,大的旋律卻始終是個一,這是在玩卡農啊。我不能加入一些個人色彩,比如個人的突發奇想(日後我坦白,這一點我做的不好!),如果非要加入,那麼必須有下面這麼一個過程:

把領導,團隊所有人拉進來開會,培訓,確保任何研發人員和技術負責人都對使用的技術了如指掌不留死角。

但是哪有這個時間?!人性是脆弱的,不管人生多麼堅強。任何人都可能突然哪天掛了,由於不可抗原因去別的城市或者國家了,和根本不熟識的同事由於接開水打了一架離職了...如果你做的東西也因此而離去了,對於一個公司而言,說明你根本沒來過。......有點扯遠了。

所以,即便當時我已經可以實現stateless NAT(當時我采用了路由target的方式,也許我之前的blog有寫過),我也不能拿來用,我只能找機會,找一個閒暇的午後,一個沒有緊急任務的下午,讓所有與此相關的人都了解了這個技術,然後用與不用就是一個需求問題了,最好的辦法除了代碼還是代碼,對於程序員而言,說1G個字符都是瞎掰,沒有能跑起來的代碼都是扯淡,就算代碼狠爛也無所謂,正因為如此現實主義的性格,我喜歡這個職業,不發狠話,不長篇闊論,不打架,不煽動,只要代碼能跑起來,僅此而已。

對了,stateless NAT在Linux 2.4已經有好的實現了,就是使用tc/policy routing來完成,但是2.6內核下已經很難做到了。作者基於解耦合的考慮將這個模塊留給了真正想實現它的人,也許我算一個。我之所以做如此多別人看來沒有意義的事,因為我想表達一個理念,那就是“開發運維”和“運維開發”的理念,這類人一定是將來最炙手可熱的人。當我面對無數次Cisco認證工程師的責難後,我的第一個想法不是罵他們或打他們(沒人家詞匯豐富且[注意:此處不能用‘或’,要用‘且’]打不過人家就尴尬了,即便這些都不是問題,不還有法律的嗎...),我的第一個想法就是,在Linux上能不能實現相同的功能,目的不是爆他們的菊花,而是讓他覺得我可以爆他。幸運的是,我都做到了,每當此刻,我回到家裡都會寫幾個模塊然而測試,就像公司的技術預研一樣。然後就呈現給朋友們,有興趣的都可以去測試,這種事沒錢賺,也得不到肯定,沒用到git,也不是神馬GPL開源,就是特殊的朋友圈分享,我十分討厭分類,我十分喜歡隨便。然後,然後,正如小小的話,我編碼,寫了一堆爛代碼,Linux上基於Netfilter實現的一個雙向,靜態的,無狀態的NAT,代碼不復雜,只有幾百行,但是....

但是,問題有二:

1.這個模塊初級,但是可用,這是我自我肯定的一面;

2.這個模塊有大量可疑改進的空間,我自我否定。

代碼如下:

/* 
 * 
 * 用法: 
 * 對目標地址為1.2.1.2的數據包做目標地址轉換,目標轉為192.168.1.8 
 * echo +1.2.1.2 192.168.1.8 dst >/proc/net/static_nat 
 * 上述命令會同時添加一條反向的SNAT映射 
 * 
 * 請解釋: 
 * echo +192.168.184.250 192.168.184.154 src >/proc/net/static_nat 
 * 
 */ 
 
#include <linux/module.h> 
#include <linux/skbuff.h> 
#include <net/ip.h> 
#include <net/netfilter/nf_conntrack.h> 
 
#define DIRMASK 0x11 
#define BUCKETS 1024 
 
 
#define NAT_OPT_DEL        0x01 
#define NAT_OPT_FIND        0x04 
 
#define NAT_OPT_ACCT_BIT    0x02 
 
 
enum nat_dir { 
    DIR_SNAT, 
    DIR_DNAT, 
    DIR_NUM 
}; 
 
/* 
 * 記錄統計信息 
 */ 
struct nat_account { 
    u32 nat_packets; 
    u32 nat_bytes; 
}; 
 
struct static_nat_entry { 
    __be32 addr[DIR_NUM]; 
    enum nat_dir type; 
    struct nat_account acct[DIR_NUM]; 
    struct hlist_node node[DIR_NUM]; 
}; 
 
static DEFINE_SPINLOCK(nat_lock); 
 
/* 保存SNAT映射 */ 
struct hlist_head *src_list; 
 
/* 保存DNAT映射 */ 
struct hlist_head *dst_list; 
 
/* 
 * 用一個IP地址(對於PREROUTING是daddr,對於POSTROUTING是saddr)作為key來獲取value。 
 */ 
static __be32 get_address_from_map(struct sk_buff *skb, unsigned int dir, __be32 addr_key, unsigned int opt) 

    __be32 ret = 0, cmp_key, ret_value; 
    u32 hash; 
    struct hlist_head *list; 
    struct hlist_node *iter, *tmp; 
    struct static_nat_entry *ent; 
 
    hash = jhash_1word(addr_key, 1); 
    hash = hash%BUCKETS; 
 
    spin_lock(&nat_lock); 
    if (dir == DIR_DNAT) { 
        list = &dst_list[hash]; 
    } else if (dir == DIR_SNAT) { 
        list = &src_list[hash]; 
    } else { 
        spin_unlock(&nat_lock); 
        goto out; 
    } 
 
    hlist_for_each_safe(iter, tmp, list) { 
        ent = hlist_entry(iter, struct static_nat_entry, node[dir]); 
        /* 注意反轉 */ 
        cmp_key = (ent->type == dir) ? 
                            ent->addr[0]:ent->addr[1]; 
        ret_value = (ent->type == dir) ? 
                            ent->addr[1]:ent->addr[0]; 
        if (addr_key == cmp_key) { 
            ret = ret_value; 
            if (opt == NAT_OPT_DEL) { 
                if (dir == ent->type) { 
                    hlist_del(&ent->node[0]); 
                    hlist_del(&ent->node[1]); 
                    kfree(ent); 
                } else { 
                    ret = 0; 
                } 
            } 
            if (opt & NAT_OPT_ACCT_BIT) { 
                ent->acct[dir].nat_packets ++; 
                ent->acct[dir].nat_bytes += skb == NULL?1:skb->len; 
            } 
            break; 
        } 
    } 
    spin_unlock(&nat_lock); 
out: 
    return ret; 

 
/* 
 * 更新第四層的校驗碼信息 
 */ 
static void nat4_update_l4(struct sk_buff *skb, __be32 oldip, __be32 newip) 

    struct iphdr *iph = ip_hdr(skb); 
    void *transport_hdr = (void *)iph + ip_hdrlen(skb); 
    struct tcphdr *tcph; 
    struct udphdr *udph; 
    bool cond; 
 
    switch (iph->protocol) { 
    case IPPROTO_TCP: 
        tcph = transport_hdr; 
        inet_proto_csum_replace4(&tcph->check, skb, oldip, newip, true); 
        break; 
    case IPPROTO_UDP: 
    case IPPROTO_UDPLITE: 
        udph = transport_hdr; 
        cond = udph->check != 0; 
        cond |= skb->ip_summed == CHECKSUM_PARTIAL; 
        if (cond) { 
            inet_proto_csum_replace4(&udph->check, skb, oldip, newip, true); 
            if (udph->check == 0) { 
                udph->check = CSUM_MANGLED_0; 
            } 
        } 
        break; 
    } 

 
/* 
 * 在POSTROUTING上執行源地址轉換: 
 * 1.正向源地址轉換; 
 * 2.目標地址轉換的逆向源地址轉換 
 */ 
static unsigned int ipv4_nat_out(unsigned int hooknum, 
                struct sk_buff *skb, 
                const struct net_device *in, 
                const struct net_device *out, 
                int (*okfn)(struct sk_buff *)) 

    unsigned int ret = NF_ACCEPT; 
    __be32 to_trans = 0; 
 
    struct iphdr *hdr = ip_hdr(skb); 
     
    to_trans = get_address_from_map(skb, DIR_SNAT, hdr->saddr, NAT_OPT_FIND|NAT_OPT_ACCT_BIT); 
    if (!to_trans) { 
        goto out; 
    } 
 
    if (hdr->saddr == to_trans) { 
        goto out; 
    } 
 
    /* 執行SNAT */     
    csum_replace4(&hdr->check, hdr->saddr, to_trans); 
    nat4_update_l4(skb, hdr->saddr, to_trans); 
    hdr->saddr = to_trans; 
out: 
    return ret; 

 
/* 
 * 在PREROUTING上執行目標地址轉換: 
 * 1.正向目標地址轉換; 
 * 2.源地址轉換的逆向目標地址轉換 
 */ 
static unsigned int ipv4_nat_in(unsigned int hooknum, 
                      struct sk_buff *skb, 
                      const struct net_device *in, 
                      const struct net_device *out, 
                      int (*okfn)(struct sk_buff *)) 

    unsigned int ret = NF_ACCEPT; 
    __be32 to_trans = 0; 
 
    struct iphdr *hdr = ip_hdr(skb); 
 
    if (skb->nfct && skb->nfct != &nf_conntrack_untracked.ct_general) { 
        goto out; 
    } 
     
    to_trans = get_address_from_map(skb, DIR_DNAT, hdr->daddr, NAT_OPT_FIND|NAT_OPT_ACCT_BIT); 
    if (!to_trans) { 
        goto out; 
    } 
 
    if (hdr->daddr == to_trans) { 
        goto out; 
    } 
     
    /* 執行DNAT */ 
    csum_replace4(&hdr->check, hdr->daddr, to_trans); 
    nat4_update_l4(skb, hdr->daddr, to_trans); 
    hdr->daddr = to_trans; 
     
    /* 
    *  設置一個notrack 防止其被track以及nat. 
    *  這是絕對合適的,因為既然是static的stateless NAT 
    *  我們就不希望它被狀態左右 
    **/ 
 
    /* 
    * 其實,並不是主要避開基於conntrack的NAT就可以了,因為 
    * conntrack本身就不容你對兩個方向的tuple進行隨意修改 
    */ 
    if (!skb->nfct) { 
        skb->nfct = &nf_conntrack_untracked.ct_general; 
        skb->nfctinfo = IP_CT_NEW; 
        nf_conntrack_get(skb->nfct); 
    } 
 
out: 
    return ret; 

 
static struct nf_hook_ops ipv4_nat_ops[] __read_mostly = { 
    { 
        .hook      = ipv4_nat_in, 
        .owner      = THIS_MODULE, 
        .pf    = NFPROTO_IPV4, 
        .hooknum    = NF_INET_PRE_ROUTING, 
        .priority  = NF_IP_PRI_CONNTRACK-1, 
    }, 
    { 
        .hook      = ipv4_nat_out, 
        .owner      = THIS_MODULE, 
        .pf    = NFPROTO_IPV4, 
        .hooknum    = NF_INET_POST_ROUTING, 
        .priority  = NF_IP_PRI_CONNTRACK+1, 
    }, 
}; 
 
static char *parse_addr(const char *input, __be32 *from, __be32 *to) 

    char *p1, *p2; 
    size_t length = strlen(input); 
     
    if (!(p1 = memchr(input, ' ', length))) { 
        return NULL; 
    } 
 
    if (!(p2 = memchr(p1 + 1, ' ', length - (p1 + 1 - input)))) { 
        return NULL; 
    } 
 
    if (!(in4_pton(input, p1 - input, (u8 *)from, ' ', NULL)) 
            || !(in4_pton(p1 + 1, p2 - p1 - 1, (u8 *)to, ' ', NULL))) { 
        return NULL; 
    } 
 
    return ++p2; 

 
static ssize_t static_nat_config_write(struct file *file, const char *buffer, size_t count, loff_t *unused) 

    int ret = 0; 
    size_t length = count; 
    __be32 from, to; 
    u32 normal, reverse; 
    char *buf = NULL; 
    char *p; 
    struct static_nat_entry *ent; 
 
    if (length) { 
        char *pp = (char *)(buffer + (length - 1)); 
        for (; (*pp < (char)32) || (*pp > (char)126); pp--) { 
            if (length <= 0) { 
                ret = -EINVAL; 
                goto out; 
            } 
            length--; 
        } 
    } else { 
        goto out; 
    } 
 
    buf = kzalloc((length + 1), GFP_ATOMIC); 
    if (!buf) { 
        ret = -ENOMEM; 
        goto out; 
    } 
    memcpy(buf, buffer, length); 
    if (!(p = parse_addr(buf + 1, &from, &to))) { 
        ret = -EINVAL; 
        goto out; 
    } 
 
    if ('+' == *buf) { 
        ent = (struct static_nat_entry *)kzalloc(sizeof(struct static_nat_entry), GFP_KERNEL); 
        if (!ent) { 
            ret = -EFAULT; 
            goto out; 
        } 
 
        /* 計算原始項的hash桶位置 */ 
        normal = jhash_1word(from, 1); 
        normal = normal%BUCKETS; 
 
        /* 計算反轉位置的hash桶位置 */ 
        reverse = jhash_1word(to, 1); 
        reverse = reverse%BUCKETS; 
 
        /* 
        *  設置key/value對 
        *  注意,反轉類型的hnode其key/value也要反轉 
        */ 
        ent->addr[0] = from; 
        ent->addr[1] = to; 
 
        /* 初始化鏈表節點 */ 
        INIT_HLIST_NODE(&ent->node[DIR_SNAT]); 
        INIT_HLIST_NODE(&ent->node[DIR_DNAT]); 
 
        if (strstr(p, "src")) { /* 添加SNAT項,自動生成DNAT項 */ 
            /* 首先判斷是否已經存在了 */ 
            if (get_address_from_map(NULL, DIR_SNAT, from, NAT_OPT_FIND) || 
                    get_address_from_map(NULL, DIR_SNAT, to, NAT_OPT_FIND)) { 
                ret = -EEXIST; 
                kfree(ent); 
                goto out; 
            } 
 
            /* 這是這個entry的type,用來區分生成的兩條配置項 */ 
            ent->type = DIR_SNAT; 
 
            /* 落實到鏈表 */ 
            spin_lock(&nat_lock); 
            hlist_add_head(&ent->node[DIR_SNAT], &src_list[normal]); 
            hlist_add_head(&ent->node[DIR_DNAT], &dst_list[reverse]); 
            spin_unlock(&nat_lock); 
        } else if(strstr(p, "dst")) { /* 添加DNAT項,自動生成SNAT項 */ 
            /* 首先判斷是否已經存在了 */ 
            if (get_address_from_map(NULL, DIR_DNAT, from, NAT_OPT_FIND) || 
                    get_address_from_map(NULL, DIR_DNAT, to, NAT_OPT_FIND)){ 
                ret = -EEXIST; 
                kfree(ent); 
                goto out; 
            } 
 
            /* 這是這個entry的type,用來區分生成的兩條配置項 */ 
            ent->type = DIR_DNAT; 
 
            /* 落實到鏈表 */ 
            spin_lock(&nat_lock); 
            hlist_add_head(&ent->node[DIR_DNAT], &dst_list[normal]); 
            hlist_add_head(&ent->node[DIR_SNAT], &src_list[reverse]); 
            spin_unlock(&nat_lock); 
        } else { 
            ret = -EFAULT; 
            kfree(ent); 
            goto out; 
        } 
 
    } else if ('-' ==*buf) { 
        u32 r1; 
 
        if (strstr(p, "src")) { 
            r1 = get_address_from_map(NULL, DIR_SNAT, from, NAT_OPT_DEL); 
            if (r1 == 0) { 
                ret = -ENOENT; 
                goto out; 
            } 
        } else if(strstr(p, "dst")) { 
            r1 = get_address_from_map(NULL, DIR_DNAT, from, NAT_OPT_DEL); 
            if (r1 == 0) { 
                ret = -ENOENT; 
                goto out; 
            } 
        } else { 
        } 
 
    } else { 
        ret = -EINVAL; 
        goto out; 
    } 
     
    ret = count; 
out: 
    kfree(buf); 
    return ret; 

 
static ssize_t static_nat_config_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) 

    int len = 0; 
    static int done = 0; 
    int i; 
    char from[15], to[15]; 
    char *kbuf_to_avoid_user_space_memory_page_fault = NULL; 
 
/* 每一行的最大長度 */ 
#define MAX_LINE_CHARS  128 
 
    if (done) { 
        done = 0; 
        goto out; 
    } 
     
    /* 
    * 分配一塊內核內存,為了避免直接操作用戶內存而引發頁面調度, 
    * 頁面調度會導致睡眠切換,而我們操作的內容處在自旋鎖的保護 
    * 下,所以不能切換! 
    */ 
 
    /* 
    * 問題: 
    * 我這裡僅僅分配count大小的內存,是因為這個版本不支持多次讀, 
    * 只能一次讀完。也許我應該學學seq read的方法。 
    */ 
    kbuf_to_avoid_user_space_memory_page_fault = kzalloc(count, GFP_KERNEL); 
    if (!kbuf_to_avoid_user_space_memory_page_fault) { 
        len = -ENOMEM; 
        done = 1; 
        goto out; 
    }   
 
    spin_lock(&nat_lock); 
    len += sprintf(kbuf_to_avoid_user_space_memory_page_fault + len, "Source trans table:\n"); 
    if (len + MAX_LINE_CHARS > count) { 
        goto copy_now; 
    } 
    for (i = 0; i < BUCKETS; i++) { 
        struct hlist_node *iter, *tmp; 
        struct static_nat_entry *ent; 
        hlist_for_each_safe(iter, tmp, &src_list[i]) { 
            ent = hlist_entry(iter, struct static_nat_entry, node[DIR_SNAT]); 
            sprintf(from, "%pI4", (ent->type == DIR_SNAT)? &ent->addr[0]:&ent->addr[1]); 
            sprintf(to, "%pI4", (ent->type == DIR_SNAT)? &ent->addr[1]:&ent->addr[0]); 
            len += sprintf(kbuf_to_avoid_user_space_memory_page_fault + len, "From:%-15s To:%-15s    [%s]  [Bytes:%u  Packet:%u]\n", 
                        from, 
                        to, 
                        (ent->type == DIR_SNAT)?"STATIC":"AUTO", 
                        ent->acct[DIR_SNAT].nat_bytes, 
                        ent->acct[DIR_SNAT].nat_packets); 
 
            if (len + MAX_LINE_CHARS > count) { 
                goto copy_now; 
            } 
        } 
    } 
    len += sprintf(kbuf_to_avoid_user_space_memory_page_fault + len, "\nDestination trans table:\n"); 
    if (len + MAX_LINE_CHARS > count) { 
        goto copy_now; 
    } 
    for (i = 0; i < BUCKETS; i++) { 
        struct hlist_node *iter, *tmp; 
        struct static_nat_entry *ent; 
        hlist_for_each_safe(iter, tmp, &dst_list[i]) { 
            ent = hlist_entry(iter, struct static_nat_entry, node[DIR_DNAT]); 
            sprintf(from, "%pI4", (ent->type == DIR_DNAT)? &ent->addr[0]:&ent->addr[1]); 
            sprintf(to, "%pI4", (ent->type == DIR_DNAT)? &ent->addr[1]:&ent->addr[0]); 
            len += sprintf(kbuf_to_avoid_user_space_memory_page_fault + len, "From:%-15s To:%-15s    [%s]  [Bytes:%u  Packet:%u]\n", 
                        from, 
                        to, 
                        (ent->type == DIR_DNAT)?"STATIC":"AUTO", 
                        ent->acct[DIR_DNAT].nat_bytes, 
                        ent->acct[DIR_DNAT].nat_packets); 
 
 
            if (len + MAX_LINE_CHARS > count) { 
                goto copy_now; 
            } 
        } 
    } 
copy_now: 
    spin_unlock(&nat_lock); 
    done = 1; 
    /* 這裡已經解除自旋鎖 */ 
    if (copy_to_user(buf, kbuf_to_avoid_user_space_memory_page_fault, len))  { 
        len = EFAULT; 
        goto out; 
    } 
     
out: 
    if (kbuf_to_avoid_user_space_memory_page_fault) { 
        kfree(kbuf_to_avoid_user_space_memory_page_fault); 
    } 
    return len; 

 
static const struct file_operations static_nat_file_ops = { 
    .owner      = THIS_MODULE, 
    .read      = static_nat_config_read, 
    .write      = static_nat_config_write, 
}; 
 
static int __init nf_static_nat_init(void) 

    int ret = 0; 
    int i; 
 
    src_list = kzalloc(sizeof(struct hlist_head) * BUCKETS, GFP_KERNEL); 
    if (!src_list) { 
        ret = -ENOMEM; 
        goto out; 
    } 
    dst_list = kzalloc(sizeof(struct hlist_head) * BUCKETS, GFP_KERNEL); 
    if (!dst_list) { 
        ret = -ENOMEM; 
        goto out; 
    } 
 
    ret = nf_register_hooks(ipv4_nat_ops, ARRAY_SIZE(ipv4_nat_ops)); 
    if (ret < 0) { 
        printk("nf_nat_ipv4: can't register hooks.\n"); 
        goto out; 
    } 
 
    if (!proc_create("static_nat", 0644, init_net.proc_net, &static_nat_file_ops)) { 
        ret = -ENOMEM; 
        goto out; 
    } 
 
    for (i = 0; i < BUCKETS; i++) { 
        INIT_HLIST_HEAD(&src_list[i]); 
        INIT_HLIST_HEAD(&dst_list[i]); 
    } 
    return ret; 
out: 
    if (src_list) { 
        kfree(src_list); 
    } 
    if (dst_list) { 
        kfree(dst_list); 
    } 
 
    return ret; 

 
static void __exit nf_static_nat_fini(void) 

    int i; 
 
    remove_proc_entry("static_nat", init_net.proc_net); 
    nf_unregister_hooks(ipv4_nat_ops, ARRAY_SIZE(ipv4_nat_ops)); 
 
    spin_lock(&nat_lock); 
    for (i = 0; i < BUCKETS; i++) { 
        struct hlist_node *iter, *tmp; 
        struct static_nat_entry *ent; 
        hlist_for_each_safe(iter, tmp, &src_list[i]) { 
            ent = hlist_entry(iter, struct static_nat_entry, node[0]); 
            hlist_del(&ent->node[DIR_SNAT]); 
            hlist_del(&ent->node[DIR_DNAT]); 
            kfree(ent); 
        } 
    } 
    spin_unlock(&nat_lock); 
    if (src_list) { 
        kfree(src_list); 
    } 
    if (dst_list) { 
        kfree(dst_list); 
    } 

 
module_init(nf_static_nat_init); 
module_exit(nf_static_nat_fini); 
 
MODULE_DESCRIPTION("STATIC two-way NAT"); 
MODULE_AUTHOR("[email protected]"); 
MODULE_LICENSE("GPL"); 

Makefile

obj-m += nf_rawnat.o 
 
all: 
    make -C /lib/modules/`uname -r`/build SUBDIRS=`pwd` modules 
 
clean: 
    rm -rf *.ko *.o .tmp_versions .*.mod.o .*.o.cmd *.mod.c .*.ko.cmd Module.symvers modules.order 

我不看好應用相關的私活兒並不代表我不喜歡,並不代表我不會。工作畢竟已經很累了,干嘛還要繼續累啊。工作之余,要搞點別的,別的是什麼呢?stateless NAT...這是很多人都不涉足的,我感歎,我悲哀,微斯人,吾誰與歸?...

Copyright © Linux教程網 All Rights Reserved