首页 > 代码库 > 在Linux上实现一个可用的stateless双向静态NAT模块

在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 = http://www.mamicode.com/(ent->type == dir) ?>

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...这是很多人都不涉足的,我感叹,我悲哀,微斯人,吾谁与归?...

在Linux上实现一个可用的stateless双向静态NAT模块