Cisco與Linux的NAT-Linux實現Cisco風格的NAT

來源:互聯網
上載者:User
既然看到了Cisco的NAT比較靈活,那麼Linux能否實現呢?答案是肯定的!因為Linux的Netfilter是超級靈活的,Linux的NAT不靈活是因為iptables程式的不靈活,xtables-addons的RAWNAT已經朝static nat邁出了重要的一步,是iptables限制了Linux的static nat發展!於是我拋開iptables,先基於Netfilter把核心模組實現,然後用procfs作為使用者介面,看看怎麼實現Cisco風格的static nat。順帶說一句,之所以做這個程式,是因為我們在產品中真的遇到了這個需求,玩過SIP和FTP的都知道,然而因為工期受限,又怕自己做的這個不穩定,效率也沒有最佳化,因此只能放在這裡玩玩,不登大雅之堂。
        首先,我們看一下基本原理,我們不希望一條NAT綁定任何N元組或者說流,只是一個一對一的地址映射,以源地址轉換為例,在從內到外的方向將源地址A轉換為B,在從外到內的方向將源目標地址B轉換為A!必須記住,任何時候,源地址轉換都在POSTROUTING上來做,而目標地址轉換都在PREROUTING上來做,按照上述的陳述,以說明:

有了作為指示,我們就知道該怎麼做了:
1.核心中維護一個映射表,僅僅映射兩個地址;
2.在PREROUTING和POSTROUTING兩個HOOK點上基於上述的映射表執行NAT動作;
3.實現一個使用者介面,可以從使用者態進行地址映射的配置

以上3點比較容易實現,實際上使用xtables-addons的RAWNAT其實也能實現static nat,然而要想實現兩個方向的自動匹配NAT,必然要配置兩條甚至多條,最蛋疼的就是明明就是一條映射,非要寫成match的形式,所以還是做成Cisco風格的吧。不管怎樣,下面的這個代碼的實際nat部分還是使用了RAWNAT的代碼!

        代碼如下:

#include <linux/ip.h>#include <linux/ipv6.h>#include <linux/module.h>#include <linux/skbuff.h>#include <linux/tcp.h>#include <linux/udp.h>#include <linux/list.h>#include <linux/sysfs.h>#include <linux/fs.h>#include <linux/proc_fs.h>#include <linux/version.h>#include <linux/netfilter.h>#include <net/ip.h>#include "compat_xtables.h"static inline __be32remask(__be32 addr, __be32 repl, unsigned int shift){uint32_t mask = (shift == 32) ? 0 : (~(uint32_t)0 >> shift);return htonl((ntohl(addr) & mask) | (ntohl(repl) & ~mask));}static void rawnat4_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;#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)cond |= skb->ip_summed == CHECKSUM_PARTIAL;#endifif (cond) {inet_proto_csum_replace4(&udph->check, skb,oldip, newip, true);if (udph->check == 0)udph->check = CSUM_MANGLED_0;}break;}}static unsigned int rawnat4_writable_part(const struct iphdr *iph){unsigned int wlen = sizeof(*iph);switch (iph->protocol) {case IPPROTO_TCP:wlen += sizeof(struct tcphdr);break;case IPPROTO_UDP:wlen += sizeof(struct udphdr);break;}return wlen;}//實現源地址轉換static unsigned intrawsnat(struct sk_buff **pskb, __be32 addr){struct iphdr *iph;__be32 new_addr;iph = ip_hdr(*pskb);new_addr = remask(iph->saddr, addr, 32);if (iph->saddr == new_addr) {return NF_ACCEPT;}if (!skb_make_writable(pskb, rawnat4_writable_part(iph))){return NF_DROP;}iph = ip_hdr(*pskb);csum_replace4(&iph->check, iph->saddr, new_addr);rawnat4_update_l4(*pskb, iph->saddr, new_addr);iph->saddr = new_addr;return NF_ACCEPT;}//實現目標地址轉換static unsigned intrawdnat(struct sk_buff **pskb, __be32 addr){struct iphdr *iph;__be32 new_addr;iph = ip_hdr(*pskb);new_addr = remask(iph->daddr, addr, 32);if (iph->daddr == new_addr)return NF_ACCEPT;if (!skb_make_writable(pskb, rawnat4_writable_part(iph)))return NF_DROP;iph = ip_hdr(*pskb);csum_replace4(&iph->check, iph->daddr, new_addr);rawnat4_update_l4(*pskb, iph->daddr, new_addr);iph->daddr = new_addr;return NF_ACCEPT;}//定義資料結構struct addr_map {struct list_head list;__be32 addr[2];int type;  //0:源地址轉換;1:目標地址轉換};//全域的map liststatic LIST_HEAD(map_list);static unsigned int ipv4_static_nat_pre(unsigned int hooknum,                     struct sk_buff *skb,                     const struct net_device *in,                     const struct net_device *out,                     int (*okfn)(struct sk_buff *)){__be32 new_daddr = 0x0;struct addr_map *map;const struct iphdr *iph = ip_hdr(skb);if (list_empty(&map_list)) {return NF_ACCEPT;}//尋找是否需要做目標地址轉換list_for_each_entry(map, &map_list, list) {if (map->addr[((map->type-1)&0x00000001)] == iph->daddr) {new_daddr = map->addr[map->type&0x00000001];break;}}if (new_daddr == 0) {return NF_ACCEPT;}return rawdnat(&skb, new_daddr);}static unsigned int ipv4_static_nat_post(unsigned int hooknum,                     struct sk_buff *skb,                     const struct net_device *in,                     const struct net_device *out,                     int (*okfn)(struct sk_buff *)){__be32 new_saddr = 0x0;struct addr_map *map;const struct iphdr *iph = ip_hdr(skb);if (list_empty(&map_list)) {return NF_ACCEPT;}//尋找是否做源地址轉換list_for_each_entry(map, &map_list, list) {if (map->addr[map->type&0x00000001] == iph->saddr) {new_saddr = map->addr[((map->type-1)&0x00000001)];break;}}if (new_saddr == 0) {return NF_ACCEPT;}return rawsnat(&skb, new_saddr);}static struct nf_hook_ops ipv4_static_nat[] __read_mostly = {    {        .hook        = ipv4_static_nat_pre,        .owner        = THIS_MODULE,        .pf        = NFPROTO_IPV4,        .hooknum    = NF_INET_PRE_ROUTING,        .priority    = NF_IP_PRI_NAT_SRC+1,    },    {        .hook        = ipv4_static_nat_post,        .owner        = THIS_MODULE,        .pf        = NFPROTO_IPV4,        .hooknum    = NF_INET_PRE_ROUTING,        .priority    = NF_IP_PRI_RAW+1,    },};//以下是定義使用者介面//如果需要添加一條source轉換。則://echo +172.16.4.34-128.129.4.34 >/proc/STATIC_Nat/sourcestruct proc_dir_entry *nat_entry = NULL;static ssize_t write_snat(struct file *file, const char __user *buf,   size_t count, loff_t *ppos){struct addr_map *am = NULL;char addr_temp[20] = {0};__be32 addr1 = 0, addr2 = 0;int ret = count;int i = 1;for (; i < 48; i++) {if (buf[i] == '-') {memcpy(addr_temp, buf+1, i-1);break;}}addr1 = in_aton(addr_temp);addr2 = in_aton(buf + i + 1);if (buf[0] == '+') {am = kzalloc(sizeof(struct addr_map), GFP_KERNEL);INIT_LIST_HEAD(&am->list);am->addr[0] = addr1;am->addr[1] = addr2;am->type = 0;list_add(&am->list, &map_list);} else if(buf[0] == '-') {//Remove TODO}return ret;}static ssize_t write_dnat(struct file *file, const char __user *buf,   size_t count, loff_t *ppos){//TODOreturn 0;}static ssize_t read_snat(struct file *file, char __user *buf,   size_t count, loff_t *ppos){//TODOreturn 0;}static ssize_t read_dnat(struct file *file, char __user *buf,   size_t count, loff_t *ppos){//TODOreturn 0;}static const struct file_operations proc_snat_operations = {.read= read_snat,.write= write_snat,};static const struct file_operations proc_dnat_operations = {.read= read_dnat,.write= write_dnat,};static int __init static_nat_zy_init(void){int ret = 0;ret = nf_register_hooks(ipv4_static_nat, ARRAY_SIZE(ipv4_static_nat));if (ret < 0) {printk("ipv4_static_nat: can't register hooks.\n");} /* test */else {nat_entry = proc_mkdir("STATIC_Nat", NULL);proc_create("source", S_IWUSR, nat_entry, &proc_snat_operations);proc_create("destination", S_IWUSR, nat_entry, &proc_dnat_operations);}  return ret;}static void __exit static_nat_zy_exit(void){remove_proc_entry("source", nat_entry);remove_proc_entry("destination", nat_entry);remove_proc_entry("STATIC_Nat", NULL);nf_unregister_hooks(ipv4_static_nat, ARRAY_SIZE(ipv4_static_nat));return;}module_init(static_nat_zy_init);module_exit(static_nat_zy_exit);//保留原作者MODULE_AUTHOR("Jan Engelhardt <jengelh@medozas.de>");MODULE_AUTHOR("wangran <marywangran@126.com>");MODULE_DESCRIPTION("Static NAT");MODULE_LICENSE("GPL");

如此就可以實現Cisoc風格的NAT了。代碼的最佳化空間還是有很多的,比如list可以換成hash...

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.