Network Address Translation
来源:http://alexanderlaw.blog.hexun.com/9791596_d.html
地址转换用来改变源/目的地址/端口,是netfilter的一部分,也是通过hook点上注册相应的结构来工作
Nat注册的hook点和conntrack相同,只是优先级不同,数据包进入netfilter之后先经过conntrack,再经过nat。而在数据包离开netfilter之前先经过nat,再经过conntrack。
1 nat模块的初始化
1.1 数据结构 ip_nat_standalone.c
在ip_conntrack结构中有为nat定义的一个nat结构,为什么把这个结构放在ip_conntrack里呢。简单的说,对于非初始化连接的数据包,即后续的数据包,一旦确定它属于某个连接,则可以直接利用连接状态里的nat信息来进行地址转换;而对于初始数据包,必须在nat表里查找相应的规则,确定了地址转换的内容后,将这些信息放到连接跟踪结构的nat参量里面,供后续的数据包使用。
#ifdef CONFIG_IP_NF_NAT_NEEDED
struct {
struct ip_nat_info info;
union ip_conntrack_nat_help help;
#if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || \
defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)
int masq_index;
#endif
#if defined(CONFIG_IP_NF_RTSP) || defined(CONFIG_IP_NF_RTSP_MODULE)
struct ip_nat_rtsp_info rtsp_info;
#endif
} nat;
#endif /* CONFIG_IP_NF_NAT_NEEDED */
#if defined(CONFIG_IP_NF_CONNTRACK_MARK)
unsigned long mark;
#endif
它包括两个参数,struct ip_nat_info和union ip_conntrack_nat_help,后一个暂时没什么用,只看前一个
struct ip_nat_info
{
/* 用来检测该连接是否已经进行过某类nat初始化了,在新的内核中该参数被去掉了,当然,有其它方法来实现它的作用。 */
int initialized;
unsigned int num_manips;
/* 这个就是用来存储关于如何进行地址转换的相关信息的数据结构,其中IP_NAT_MAX_MANIPS代表某个连接的数据包在经过netfilter一次的过程中最多能进行的地址转换的次数,这里是(2*3)=6 。意思大概是说对于某个连接,如果nat表的每条链上都有一条规则:
NF_IP_PRE_ROUTING==>NF_IP_POST_ROUTING
如果在NF_IP_PRE_ROUTING上做目的转换,要在NF_IP_POST_ROUTING上做反方向上的源转换
NF_IP_POST_ROUTING==>NF_IP_PRE_ROUTING
如果在NF_IP_POST_ROUTING上做源转换,要在NF_IP_PRE_ROUTING上做反方向上的目的转换
NF_IP_LOCAL_OUT==>NF_IP_LOCAL_IN
如果在NF_IP_LOCAL_OUT做源转换,要在NF_IP_LOCAL_IN上做反方向上的目的转换
算下来就是最多进行6次地址转换 */
struct ip_nat_info_manip manips[IP_NAT_MAX_MANIPS];
/* 两个全局hash表,用来将所有需要进行地址转换的连接组织起来 */
struct ip_nat_hash bysource, byipsproto;
/* 做特殊用途,通常是NULL */
struct ip_nat_helper *helper;
struct ip_nat_seq seq[IP_CT_DIR_MAX];
};
ip_nat_info_manip结构定义如下:
struct ip_nat_info_manip
{
/* 方向,初始或应答 */
u_int8_t direction;
/* 转换发生的hook点 */
u_int8_t hooknum;
/* 转换的类型,源还是目的 */
u_int8_t maniptype;
/* Manipulations to occur at each conntrack in this dirn. */
struct ip_conntrack_manip manip;
};
struct ip_conntrack_manip
{
u_int32_t ip;
union ip_conntrack_manip_proto u;
};
ip_nat_hash结构 ip_nat.h
struct ip_nat_hash
{
struct list_head list;
struct ip_conntrack *conntrack;
};
1.2 init()函数 ip_nat_standalone.c
static int __init init(void)
{
return init_or_cleanup(1);
}
init()函数直接调用init_or_cleanup()
static int init_or_cleanup(int init)
{
int ret = 0;
/* nat依赖于conntrack,这个函数是空的 */
need_ip_conntrack();
if (!init) goto cleanup;
/* 初始化nat规则 */
ret = ip_nat_rule_init();
if (ret < 0) {
printk("ip_nat_init: can‘t setup rules.\n");
goto cleanup_nothing;
}
/* 初始化nat */
ret = ip_nat_init();
if (ret < 0) {
printk("ip_nat_init: can‘t setup rules.\n");
goto cleanup_rule_init;
}
/* 注册hook,共在四个hook点上注册了函数,分别是:
NF_IP_PRE_ROUTING ip_nat_fn
NF_IP_POST_ROUTING ip_nat_out
NF_IP_LOCAL_OUT ip_nat_local_fn
NF_IP_LOCAL_IN ip_nat_fn
NF_IP_LOCAL_OUT和NF_IP_LOCAL_IN需要定义CONFIG_IP_NF_NAT_LOCAL
其中在ip_nat_out和ip_nat_local_fn中都会调用ip_nat_fn
*/
ret = nf_register_hook(&ip_nat_in_ops);
if (ret < 0) {
printk("ip_nat_init: can‘t register in hook.\n");
goto cleanup_nat;
}
ret = nf_register_hook(&ip_nat_out_ops);
if (ret < 0) {
printk("ip_nat_init: can‘t register out hook.\n");
goto cleanup_inops;
}
#ifdef CONFIG_IP_NF_NAT_LOCAL
ret = nf_register_hook(&ip_nat_local_out_ops);
if (ret < 0) {
printk("ip_nat_init: can‘t register local out hook.\n");
goto cleanup_outops;
}
ret = nf_register_hook(&ip_nat_local_in_ops);
if (ret < 0) {
printk("ip_nat_init: can‘t register local in hook.\n");
goto cleanup_localoutops;
}
#endif
return ret;
cleanup:
#ifdef CONFIG_IP_NF_NAT_LOCAL
nf_unregister_hook(&ip_nat_local_in_ops);
cleanup_localoutops:
nf_unregister_hook(&ip_nat_local_out_ops);
cleanup_outops:
#endif
nf_unregister_hook(&ip_nat_out_ops);
cleanup_inops:
nf_unregister_hook(&ip_nat_in_ops);
cleanup_nat:
ip_nat_cleanup();
cleanup_rule_init:
ip_nat_rule_cleanup();
cleanup_nothing:
MUST_BE_READ_WRITE_UNLOCKED(&ip_nat_lock);
return ret;
}
1.3 ip_nat_rule_init()函数 ip_nat_rule.c
int __init ip_nat_rule_init(void)
{
int ret;
/* 注册nat表 */
ret = ipt_register_table(&nat_table);
if (ret != 0)
return ret;
/* 注册了两个target,一个是snat一个是dnat */
ret = ipt_register_target(&ipt_snat_reg);
if (ret != 0)
goto unregister_table;
ret = ipt_register_target(&ipt_dnat_reg);
if (ret != 0)
goto unregister_snat;
return ret;
unregister_snat:
ipt_unregister_target(&ipt_snat_reg);
unregister_table:
ipt_unregister_table(&nat_table);
return ret;
}
看一下nat表的初始化:
static struct ipt_table nat_table = {
.name = "nat",
.table = &nat_initial_table.repl,
.valid_hooks = NAT_VALID_HOOKS,
.lock = RW_LOCK_UNLOCKED,
.me = THIS_MODULE,
};
和filter表的初始化类似,一开始规则都是空的
两个target的初始化:
static struct ipt_target ipt_snat_reg = {
.name = "SNAT",
.target = ipt_snat_target,
.checkentry = ipt_snat_checkentry,
};
static struct ipt_target ipt_dnat_reg = {
.name = "DNAT",
.target = ipt_dnat_target,
.checkentry = ipt_dnat_checkentry,
};
两个target函数分别是ipt_snat_target和ipt_dnat_target
1.4 ip_nat_init()函数 ipt_nat_core.c
int __init ip_nat_init(void)
{
size_t i;
/* nat的hash表大小和conntrack的hash表相同 */
ip_nat_htable_size = ip_conntrack_htable_size;
/* 初始化了一个叫bysource的全局链表指针 */
bysource = vmalloc(sizeof(struct list_head) * ip_nat_htable_size*2);
if (!bysource) {
return -ENOMEM;
}
/* 全局链表指针byipsproto,在bysource之后。bysource和byipsproto实际上也是两个hash表,每个节点是一个ip_nat_hash结构,包含一个list_head和一个ip_conntrack。有点特别的就是nat用两个hash表来组织地址转换的数据结构,其本质是一样的,只是所使用的hash算法不同,bysource一般用于SNAT的处理,计算bysource的hash值的函数是hash_by_src();byipsproto用于DNAT的处理,计算byipsproto的hash值的函数是hash_by_ipsproto()。*/
byipsproto = bysource + ip_nat_htable_size;
/* 注册一些内建的协议,&protos是用来维护nat模块中用到的协议结构ip_nat_protocol的全局链表 */
WRITE_LOCK(&ip_nat_lock);
list_append(&protos, &ip_nat_protocol_tcp);
list_append(&protos, &ip_nat_protocol_udp);
list_append(&protos, &ip_nat_protocol_icmp);
WRITE_UNLOCK(&ip_nat_lock);
for (i = 0; i < ip_nat_htable_size; i++) {
/* 初始化bysource和byipsproto中的所有链表,两个数组的大小都是ip_nat_htables_size,数组的每个节点是一个链表头 */
INIT_LIST_HEAD(&bysource[i]);
INIT_LIST_HEAD(&byipsproto[i]);
}
IP_NF_ASSERT(ip_conntrack_destroyed == NULL);
/* 初始化一个ip_conntrack_destroyed函数,ip_nat_cleanup_conntrack(struct ip_conntrack *conn) 的作用是在bysource和byipproto链表中删除conn对应的节点 */
ip_conntrack_destroyed = &ip_nat_cleanup_conntrack;
/* Initialize fake conntrack so that NAT will skip it */
ip_conntrack_untracked.nat.info.initialized |=
(1 << IP_NAT_MANIP_SRC) | (1 << IP_NAT_MANIP_DST);
return 0;
}
2 地址转换的过程
2.1 ip_nat_fn函数 ip_nat_standalone.c
ip_nat_fn()是nat中的主要函数,nat在netfilter中注册了四个hook,最终都会调用该函数
static unsigned int
ip_nat_fn(unsigned int hooknum,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
struct ip_nat_info *info;
/* 根据所在的hook点判断转换类型是源地址转换还是目的地址转换,为0(IP_NAT_MANIP_SRC)表示源地址转换,为1(IP_NAT_MANIP_DST)表示目的地址转换 */
enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);
/* 前面函数中已经处理过分片的情况,这里应该不会再出现分片包了. */
IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off
& htons(IP_MF|IP_OFFSET)));
/*因为地址转换会修改数据包,所以这里先初始化将其设置为“未修改”标志,后面进行数据包修改时再来重置这个标志*/
(*pskb)->nfcache |= NFC_UNKNOWN;
/* 校验和 */
if ((*pskb)->ip_summed == CHECKSUM_HW)
if (skb_checksum_help(pskb, (out == NULL)))
return NF_DROP;
/*取得数据包的连接状态*/
ct = ip_conntrack_get(*pskb, &ctinfo);
/* 如果找不到对应连接,则应该直接放行它,而不再对其进行转换处理,特别地,ICMP重定向报文将会被丢弃*/
if (!ct) {
/* Exception: ICMP redirect to new connection (not in
hash table yet). We must not let this through, in
case we‘re doing NAT to the same network. */
if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
struct icmphdr hdr;
if (skb_copy_bits(*pskb, (*pskb)->nh.iph->ihl*4,
&hdr, sizeof(hdr)) == 0
&& hdr.type == ICMP_REDIRECT)
return NF_DROP;
}
return NF_ACCEPT;
}
/* 判断连接状态,调用相应的处理函数*/
switch (ctinfo) {
case IP_CT_RELATED:
case IP_CT_RELATED+IP_CT_IS_REPLY:
if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
if (!icmp_reply_translation(pskb, ct, hooknum,
CTINFO2DIR(ctinfo)))
return NF_DROP;
else
return NF_ACCEPT;
}
/* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
/* 如果是一个初始连接的数据包 */
case IP_CT_NEW:
info = &ct->nat.info;
WRITE_LOCK(&ip_nat_lock);
/* 观察这个连接中的nat部分是否已经被初始化过了,如果有则跳过下面的部分,直接进行地址转换,如果没有,进一步判断 */
if (!(info->initialized & (1 << maniptype))
#ifndef CONFIG_IP_NF_NAT_LOCAL
&& !(ct->status & IPS_CONFIRMED)
#endif
) {
unsigned int ret;
/* 如果该连接是由expect创建的,并且有expect函数,则在这里调用 */
if (ct->master
&& master_ct(ct)->nat.info.helper
&& master_ct(ct)->nat.info.helper->expect) {
ret = call_expect(master_ct(ct), pskb,
hooknum, ct, info);
} else {
#ifdef CONFIG_IP_NF_NAT_LOCAL
/* LOCAL_IN hook doesn‘t have a chain! */
if (hooknum == NF_IP_LOCAL_IN)
ret = alloc_null_binding(ct, info,
hooknum);
else
#endif
/* 既没有被nat修改过,也不是由expect创建,这是一个初始的数据包,开始在nat表中查找规则 */
ret = ip_nat_rule_find(pskb, hooknum, in, out, ct, info);
}
if (ret != NF_ACCEPT) {
WRITE_UNLOCK(&ip_nat_lock);
return ret;
}
} else
/* 如果该连接的nat部分已经被初始化了,打印调试信息 */
DEBUGP("Already setup manip %s for ct %p\n",
maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",
ct);
WRITE_UNLOCK(&ip_nat_lock);
break;
default:
/* ESTABLISHED */
IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED
|| ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
info = &ct->nat.info;
}
IP_NF_ASSERT(info);
/* 前面已经修改了连接跟踪表,这里正式修改了数据包里的地址 */
return do_bindings(ct, ctinfo, info, hooknum, pskb);
}
2.2 ip_nat_rule_find函数 ip_nat_rule.c
int ip_nat_rule_find(struct sk_buff **pskb,
unsigned int hooknum,
const struct net_device *in,
const struct net_device *out,
struct ip_conntrack *ct,
struct ip_nat_info *info)
{
int ret;
/* 调用ipt_do_tables函数,第五个参数是&nat_table */
ret = ipt_do_table(pskb, hooknum, in, out, &nat_table, NULL);
if (ret == NF_ACCEPT) {
if (!(info->initialized & (1 << HOOK2MANIP(hooknum))))
/* NUL mapping */
ret = alloc_null_binding(ct, info, hooknum);
}
return ret;
}
nat表和filter表一样,都是通过调用ipt_do_table函数来工作的
ipt_do_table查找表中的所有entry,如果match全都匹配,则调用target函数
此时的target函数就是在nat初始化时注册的ipt_snat_target和ipt_dnat_target
2.3 ipt_s(d)nat_target函数 ip_nat_rule.c
static unsigned int ipt_snat_target(struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const void *targinfo,
void *userinfo)
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);
/* 取得数据包的连接状态 */
ct = ip_conntrack_get(*pskb, &ctinfo);
/* Connection must be valid and new. */
IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));
IP_NF_ASSERT(out);
return ip_nat_setup_info(ct, targinfo, hooknum);
}
ipt_dnat_target和ipt_snat_target差不多,都是调用ip_nat_setup_info完成地址转换,这里的targinfo参数来自ipt_entry_target结构的unsigned char data[0]参数,一个长度为0的数组,指向target的末尾
static unsigned int ipt_dnat_target(struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const void *targinfo,
void *userinfo)
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
#ifdef CONFIG_IP_NF_NAT_LOCAL
IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
|| hooknum == NF_IP_LOCAL_OUT);
#else
IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING);
#endif
ct = ip_conntrack_get(*pskb, &ctinfo);
/* Connection must be valid and new. */
IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));
return ip_nat_setup_info(ct, targinfo, hooknum);
}
2.4 ip_nat_setup_info()函数 ip_nat_rule.c
unsigned int
ip_nat_setup_info(struct ip_conntrack *conntrack, /* 数据包的连接状态 */
const struct ip_nat_multi_range *mr, /* 转换后的地址池 */
unsigned int hooknum) /* hook点 */
{
struct ip_conntrack_tuple new_tuple, inv_tuple, reply;
struct ip_conntrack_tuple orig_tp;
struct ip_nat_info *info = &conntrack->nat.info;
int in_hashes = info->initialized;
MUST_BE_WRITE_LOCKED(&ip_nat_lock);
IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
|| hooknum == NF_IP_POST_ROUTING
|| hooknum == NF_IP_LOCAL_IN
|| hooknum == NF_IP_LOCAL_OUT);
IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS);
IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum))));
/* 对当前状态的应答方向的tuple调用invert_tuplepr取反,得到一个orig_tp,如果之前没有进行过地址或端口转换,通常这里得到的orig_tp就等于初始方向的tuple */
invert_tuplepr(&orig_tp, conntrack->tuplehash[IP_CT_DIR_REPLY].tuple);
do {
/* 进行地址转换,new_tuple为转换后的地址的tuple */
if (!get_unique_tuple(&new_tuple,&orig_tp,mr,conntrack,hooknum))
{
DEBUGP("ip_nat_setup_info: Can‘t get unique for %p.\n",
conntrack);
return NF_DROP;
}
/* 对new_tuple取反,得到经过转换后的应答方向的tuple */
invert_tuplepr(&reply, &new_tuple);
/* 修改conntrack中的应答方向的reply tuple,在这之前还要检查如果该reply tuple已经在hash表里存在了,即被其它连接占用(存在初始方向tuple不同,应答方向tuple相同的连接),则还要回头继续修改 */
} while (!ip_conntrack_alter_reply(conntrack, &reply));
/* 对orig_tp取反,实际上又得到了原conntrack的reply_tuple…… */
invert_tuplepr(&inv_tuple, &orig_tp);
/* 将所作转换的相关信息保存到连接状态conntrack里,这样该连接的后续数据包就可以直接利用这些信息进行地址转换,不用重新查找nat表了 */
/* 如果是源地址改变(SNAT) */
if (!ip_ct_tuple_src_equal(&new_tuple, &orig_tp)) {
/* In this direction, a source manip. */
info->manips[info->num_manips++] =
((struct ip_nat_info_manip)
{ IP_CT_DIR_ORIGINAL, hooknum,
IP_NAT_MANIP_SRC, new_tuple.src });
IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS);
/* 在相对的hook点上必然有对应的目的地址改变(DNAT) */
info->manips[info->num_manips++] =
((struct ip_nat_info_manip)
/* opposite_hook即是求当前hook点的对应hook点 */
{ IP_CT_DIR_REPLY, opposite_hook[hooknum],
IP_NAT_MANIP_DST, orig_tp.src });
IP_NF_ASSERT(info->num_manips <= IP_NAT_MAX_MANIPS);
}
/* 如果是目的地址改变(DNAT) */
if (!ip_ct_tuple_dst_equal(&new_tuple, &orig_tp)) {
/* In this direction, a destination manip */
info->manips[info->num_manips++] =
((struct ip_nat_info_manip)
{ IP_CT_DIR_ORIGINAL, hooknum,
IP_NAT_MANIP_DST, reply.src });
IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS);
/* In the reverse direction, a source manip. */
info->manips[info->num_manips++] =
((struct ip_nat_info_manip)
{ IP_CT_DIR_REPLY, opposite_hook[hooknum],
IP_NAT_MANIP_SRC, inv_tuple.src });
IP_NF_ASSERT(info->num_manips <= IP_NAT_MAX_MANIPS);
}
/* 如果这个连接不是某个连接的预期的连接(子连接),则在全局链表helpers查找对应的ip_nat_helper结构 */
if (!conntrack->master)
info->helper = LIST_FIND(&helpers, helper_cmp, struct ip_nat_helper *, &reply);
/* 转换完了,标记一下 */
info->initialized |= (1 << HOOK2MANIP(hooknum));
/* 将所做的地址转换的数据结构加入到全局hash表bysource和byipsproto中,如果该地址转换是某地址转换基础上的再次转换,则用replace_in_hashes替换,反之则用place_in_hashes */
if (in_hashes) {
IP_NF_ASSERT(info->bysource.conntrack);
replace_in_hashes(conntrack, info);
} else {
place_in_hashes(conntrack, info);
}
return NF_ACCEPT;
}
2.5 get_unique_tuple ()函数 ip_nat_core.c
get_unique_tuple,获得一个唯一的tuple,就是说除了要做地址/段口的转换,还要保证转换得到的tuple是唯一的。
很复杂的一个函数。。。
第三个参数是用来替换的地址或端口的范围
static int
get_unique_tuple(struct ip_conntrack_tuple *tuple,
const struct ip_conntrack_tuple *orig_tuple,
const struct ip_nat_multi_range *mrr,
struct ip_conntrack *conntrack,
unsigned int hooknum)
{
struct ip_nat_protocol *proto
= find_nat_proto(orig_tuple->dst.protonum);
struct ip_nat_range *rptr;
unsigned int i;
int ret;
struct ip_nat_multi_range *mr = (void *)mrr;
/* 下面这一段比较晕,和p2p,udp打洞等技术有关。 */
if (hooknum == NF_IP_POST_ROUTING) {
/* ip_conntrack_manip结构包含一个ip地址和一个协议端口 */
struct ip_conntrack_manip *manip;
/* find_appropriate_src函数先调用hash_by_src函数计算orig_tuple的hash值,然后去bysource表里查找,如果能找到源地址和端口都匹配的连接,并且如果该连接的地址/端口本身就满足目标地址/端口范围的话,就直接返回查到的这个连接的源ip */
manip = find_appropriate_src(orig_tuple, mr);
if (manip) {
/* Apply same source manipulation. */
*tuple = ((struct ip_conntrack_tuple)
{ *manip, orig_tuple->dst });
DEBUGP("get_unique_tuple: Found current src map\n");
/* 还要保证连接跟踪表里没有这个连接 */
if (!ip_nat_used_tuple(tuple, conntrack))
return 1;
}
}
/* orig_tuple是转换之前的,tuple是转换之后的 */
*tuple = *orig_tuple;
/* 循环 ,尝试mr参数所指定的地址/端口范围,直到能满足其tuple是唯一的 */
while ((rptr = find_best_ips_proto_fast(tuple, mr, conntrack, hooknum))
!= NULL) {
DEBUGP("Found best for "); DUMP_TUPLE(tuple);
/* IP_NAT_MANIP_SRC, 进行SNAT
IP_NAT_MANIP_DST 进行DNAT
IP_NAT_RANGE_MAP_IPS 在range里指定了IP地址
IP_NAT_RANGE_PROTO_SPECIFIED 在range里指定了port
如果没有指定协议端口范围,或者满足了所指定的范围 */
if ((!(rptr->flags & IP_NAT_RANGE_PROTO_SPECIFIED)
|| proto->in_range(tuple, HOOK2MANIP(hooknum),
&rptr->min, &rptr->max))
&& !ip_nat_used_tuple(tuple, conntrack)) {
ret = 1;
goto clear_fulls;
} else {
if (proto->unique_tuple(tuple, rptr,
HOOK2MANIP(hooknum),
conntrack)) {
/* Must be unique. */
IP_NF_ASSERT(!ip_nat_used_tuple(tuple,
conntrack));
ret = 1;
goto clear_fulls;
} else if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) {
/* Try implicit source NAT; protocol
may be able to play with ports to
make it unique. */
struct ip_nat_range r
= { IP_NAT_RANGE_MAP_IPS,
tuple->src.ip, tuple->src.ip,
{ 0 }, { 0 } };
DEBUGP("Trying implicit mapping\n");
if (proto->unique_tuple(tuple, &r,
IP_NAT_MANIP_SRC,
conntrack)) {
/* Must be unique. */
IP_NF_ASSERT(!ip_nat_used_tuple
(tuple, conntrack));
ret = 1;
goto clear_fulls;
}
}
DEBUGP("Protocol can‘t get unique tuple %u.\n",
hooknum);
}
/* Eliminate that from range, and try again. */
rptr->flags |= IP_NAT_RANGE_FULL;
*tuple = *orig_tuple;
}
ret = 0;
clear_fulls:
/* Clear full flags. */
IP_NF_ASSERT(mr->rangesize >= 1);
for (i = 0; i < mr->rangesize; i++)
mr->range[i].flags &= ~IP_NAT_RANGE_FULL;
return ret;
}
Network Address Translation
地址转换用来改变源/目的地址/端口,是netfilter的一部分,也是通过hook点上注册相应的结构来工作
Nat注册的hook点和conntrack相同,只是优先级不同,数据包进入netfilter之后先经过conntrack,再经过nat。而在数据包离开netfilter之前先经过nat,再经过conntrack。
1 nat模块的初始化
1.1 数据结构 ip_nat_standalone.c
在ip_conntrack结构中有为nat定义的一个nat结构,为什么把这个结构放在ip_conntrack里呢。简单的说,对于非初始化连接的数据包,即后续的数据包,一旦确定它属于某个连接,则可以直接利用连接状态里的nat信息来进行地址转换;而对于初始数据包,必须在nat表里查找相应的规则,确定了地址转换的内容后,将这些信息放到连接跟踪结构的nat参量里面,供后续的数据包使用。
#ifdef CONFIG_IP_NF_NAT_NEEDED
struct {
struct ip_nat_info info;
union ip_conntrack_nat_help help;
#if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || \
defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)
int masq_index;
#endif
#if defined(CONFIG_IP_NF_RTSP) || defined(CONFIG_IP_NF_RTSP_MODULE)
struct ip_nat_rtsp_info rtsp_info;
#endif
} nat;
#endif /* CONFIG_IP_NF_NAT_NEEDED */
#if defined(CONFIG_IP_NF_CONNTRACK_MARK)
unsigned long mark;
#endif
它包括两个参数,struct ip_nat_info和union ip_conntrack_nat_help,后一个暂时没什么用,只看前一个
struct ip_nat_info
{
/* 用来检测该连接是否已经进行过某类nat初始化了,在新的内核中该参数被去掉了,当然,有其它方法来实现它的作用。 */
int initialized;
unsigned int num_manips;
/* 这个就是用来存储关于如何进行地址转换的相关信息的数据结构,其中IP_NAT_MAX_MANIPS代表某个连接的数据包在经过netfilter一次的过程中最多能进行的地址转换的次数,这里是(2*3)=6 。意思大概是说对于某个连接,如果nat表的每条链上都有一条规则:
NF_IP_PRE_ROUTING==>NF_IP_POST_ROUTING
如果在NF_IP_PRE_ROUTING上做目的转换,要在NF_IP_POST_ROUTING上做反方向上的源转换
NF_IP_POST_ROUTING==>NF_IP_PRE_ROUTING
如果在NF_IP_POST_ROUTING上做源转换,要在NF_IP_PRE_ROUTING上做反方向上的目的转换
NF_IP_LOCAL_OUT==>NF_IP_LOCAL_IN
如果在NF_IP_LOCAL_OUT做源转换,要在NF_IP_LOCAL_IN上做反方向上的目的转换
算下来就是最多进行6次地址转换 */
struct ip_nat_info_manip manips[IP_NAT_MAX_MANIPS];
/* 两个全局hash表,用来将所有需要进行地址转换的连接组织起来 */
struct ip_nat_hash bysource, byipsproto;
/* 做特殊用途,通常是NULL */
struct ip_nat_helper *helper;
struct ip_nat_seq seq[IP_CT_DIR_MAX];
};
ip_nat_info_manip结构定义如下:
struct ip_nat_info_manip
{
/* 方向,初始或应答 */
u_int8_t direction;
/* 转换发生的hook点 */
u_int8_t hooknum;
/* 转换的类型,源还是目的 */
u_int8_t maniptype;
/* Manipulations to occur at each conntrack in this dirn. */
struct ip_conntrack_manip manip;
};
struct ip_conntrack_manip
{
u_int32_t ip;
union ip_conntrack_manip_proto u;
};
ip_nat_hash结构 ip_nat.h
struct ip_nat_hash
{
struct list_head list;
struct ip_conntrack *conntrack;
};
1.2 init()函数 ip_nat_standalone.c
static int __init init(void)
{
return init_or_cleanup(1);
}
init()函数直接调用init_or_cleanup()
static int init_or_cleanup(int init)
{
int ret = 0;
/* nat依赖于conntrack,这个函数是空的 */
need_ip_conntrack();
if (!init) goto cleanup;
/* 初始化nat规则 */
ret = ip_nat_rule_init();
if (ret < 0) {
printk("ip_nat_init: can‘t setup rules.\n");
goto cleanup_nothing;
}
/* 初始化nat */
ret = ip_nat_init();
if (ret < 0) {
printk("ip_nat_init: can‘t setup rules.\n");
goto cleanup_rule_init;
}
/* 注册hook,共在四个hook点上注册了函数,分别是:
NF_IP_PRE_ROUTING ip_nat_fn
NF_IP_POST_ROUTING ip_nat_out
NF_IP_LOCAL_OUT ip_nat_local_fn
NF_IP_LOCAL_IN ip_nat_fn
NF_IP_LOCAL_OUT和NF_IP_LOCAL_IN需要定义CONFIG_IP_NF_NAT_LOCAL
其中在ip_nat_out和ip_nat_local_fn中都会调用ip_nat_fn
*/
ret = nf_register_hook(&ip_nat_in_ops);
if (ret < 0) {
printk("ip_nat_init: can‘t register in hook.\n");
goto cleanup_nat;
}
ret = nf_register_hook(&ip_nat_out_ops);
if (ret < 0) {
printk("ip_nat_init: can‘t register out hook.\n");
goto cleanup_inops;
}
#ifdef CONFIG_IP_NF_NAT_LOCAL
ret = nf_register_hook(&ip_nat_local_out_ops);
if (ret < 0) {
printk("ip_nat_init: can‘t register local out hook.\n");
goto cleanup_outops;
}
ret = nf_register_hook(&ip_nat_local_in_ops);
if (ret < 0) {
printk("ip_nat_init: can‘t register local in hook.\n");
goto cleanup_localoutops;
}
#endif
return ret;
cleanup:
#ifdef CONFIG_IP_NF_NAT_LOCAL
nf_unregister_hook(&ip_nat_local_in_ops);
cleanup_localoutops:
nf_unregister_hook(&ip_nat_local_out_ops);
cleanup_outops:
#endif
nf_unregister_hook(&ip_nat_out_ops);
cleanup_inops:
nf_unregister_hook(&ip_nat_in_ops);
cleanup_nat:
ip_nat_cleanup();
cleanup_rule_init:
ip_nat_rule_cleanup();
cleanup_nothing:
MUST_BE_READ_WRITE_UNLOCKED(&ip_nat_lock);
return ret;
}
1.3 ip_nat_rule_init()函数 ip_nat_rule.c
int __init ip_nat_rule_init(void)
{
int ret;
/* 注册nat表 */
ret = ipt_register_table(&nat_table);
if (ret != 0)
return ret;
/* 注册了两个target,一个是snat一个是dnat */
ret = ipt_register_target(&ipt_snat_reg);
if (ret != 0)
goto unregister_table;
ret = ipt_register_target(&ipt_dnat_reg);
if (ret != 0)
goto unregister_snat;
return ret;
unregister_snat:
ipt_unregister_target(&ipt_snat_reg);
unregister_table:
ipt_unregister_table(&nat_table);
return ret;
}
看一下nat表的初始化:
static struct ipt_table nat_table = {
.name = "nat",
.table = &nat_initial_table.repl,
.valid_hooks = NAT_VALID_HOOKS,
.lock = RW_LOCK_UNLOCKED,
.me = THIS_MODULE,
};
和filter表的初始化类似,一开始规则都是空的
两个target的初始化:
static struct ipt_target ipt_snat_reg = {
.name = "SNAT",
.target = ipt_snat_target,
.checkentry = ipt_snat_checkentry,
};
static struct ipt_target ipt_dnat_reg = {
.name = "DNAT",
.target = ipt_dnat_target,
.checkentry = ipt_dnat_checkentry,
};
两个target函数分别是ipt_snat_target和ipt_dnat_target
1.4 ip_nat_init()函数 ipt_nat_core.c
int __init ip_nat_init(void)
{
size_t i;
/* nat的hash表大小和conntrack的hash表相同 */
ip_nat_htable_size = ip_conntrack_htable_size;
/* 初始化了一个叫bysource的全局链表指针 */
bysource = vmalloc(sizeof(struct list_head) * ip_nat_htable_size*2);
if (!bysource) {
return -ENOMEM;
}
/* 全局链表指针byipsproto,在bysource之后。bysource和byipsproto实际上也是两个hash表,每个节点是一个ip_nat_hash结构,包含一个list_head和一个ip_conntrack。有点特别的就是nat用两个hash表来组织地址转换的数据结构,其本质是一样的,只是所使用的hash算法不同,bysource一般用于SNAT的处理,计算bysource的hash值的函数是hash_by_src();byipsproto用于DNAT的处理,计算byipsproto的hash值的函数是hash_by_ipsproto()。*/
byipsproto = bysource + ip_nat_htable_size;
/* 注册一些内建的协议,&protos是用来维护nat模块中用到的协议结构ip_nat_protocol的全局链表 */
WRITE_LOCK(&ip_nat_lock);
list_append(&protos, &ip_nat_protocol_tcp);
list_append(&protos, &ip_nat_protocol_udp);
list_append(&protos, &ip_nat_protocol_icmp);
WRITE_UNLOCK(&ip_nat_lock);
for (i = 0; i < ip_nat_htable_size; i++) {
/* 初始化bysource和byipsproto中的所有链表,两个数组的大小都是ip_nat_htables_size,数组的每个节点是一个链表头 */
INIT_LIST_HEAD(&bysource[i]);
INIT_LIST_HEAD(&byipsproto[i]);
}
IP_NF_ASSERT(ip_conntrack_destroyed == NULL);
/* 初始化一个ip_conntrack_destroyed函数,ip_nat_cleanup_conntrack(struct ip_conntrack *conn) 的作用是在bysource和byipproto链表中删除conn对应的节点 */
ip_conntrack_destroyed = &ip_nat_cleanup_conntrack;
/* Initialize fake conntrack so that NAT will skip it */
ip_conntrack_untracked.nat.info.initialized |=
(1 << IP_NAT_MANIP_SRC) | (1 << IP_NAT_MANIP_DST);
return 0;
}
2 地址转换的过程
2.1 ip_nat_fn函数 ip_nat_standalone.c
ip_nat_fn()是nat中的主要函数,nat在netfilter中注册了四个hook,最终都会调用该函数
static unsigned int
ip_nat_fn(unsigned int hooknum,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
struct ip_nat_info *info;
/* 根据所在的hook点判断转换类型是源地址转换还是目的地址转换,为0(IP_NAT_MANIP_SRC)表示源地址转换,为1(IP_NAT_MANIP_DST)表示目的地址转换 */
enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);
/* 前面函数中已经处理过分片的情况,这里应该不会再出现分片包了. */
IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off
& htons(IP_MF|IP_OFFSET)));
/*因为地址转换会修改数据包,所以这里先初始化将其设置为“未修改”标志,后面进行数据包修改时再来重置这个标志*/
(*pskb)->nfcache |= NFC_UNKNOWN;
/* 校验和 */
if ((*pskb)->ip_summed == CHECKSUM_HW)
if (skb_checksum_help(pskb, (out == NULL)))
return NF_DROP;
/*取得数据包的连接状态*/
ct = ip_conntrack_get(*pskb, &ctinfo);
/* 如果找不到对应连接,则应该直接放行它,而不再对其进行转换处理,特别地,ICMP重定向报文将会被丢弃*/
if (!ct) {
/* Exception: ICMP redirect to new connection (not in
hash table yet). We must not let this through, in
case we‘re doing NAT to the same network. */
if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
struct icmphdr hdr;
if (skb_copy_bits(*pskb, (*pskb)->nh.iph->ihl*4,
&hdr, sizeof(hdr)) == 0
&& hdr.type == ICMP_REDIRECT)
return NF_DROP;
}
return NF_ACCEPT;
}
/* 判断连接状态,调用相应的处理函数*/
switch (ctinfo) {
case IP_CT_RELATED:
case IP_CT_RELATED+IP_CT_IS_REPLY:
if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
if (!icmp_reply_translation(pskb, ct, hooknum,
CTINFO2DIR(ctinfo)))
return NF_DROP;
else
return NF_ACCEPT;
}
/* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
/* 如果是一个初始连接的数据包 */
case IP_CT_NEW:
info = &ct->nat.info;
WRITE_LOCK(&ip_nat_lock);
/* 观察这个连接中的nat部分是否已经被初始化过了,如果有则跳过下面的部分,直接进行地址转换,如果没有,进一步判断 */
if (!(info->initialized & (1 << maniptype))
#ifndef CONFIG_IP_NF_NAT_LOCAL
&& !(ct->status & IPS_CONFIRMED)
#endif
) {
unsigned int ret;
/* 如果该连接是由expect创建的,并且有expect函数,则在这里调用 */
if (ct->master
&& master_ct(ct)->nat.info.helper
&& master_ct(ct)->nat.info.helper->expect) {
ret = call_expect(master_ct(ct), pskb,
hooknum, ct, info);
} else {
#ifdef CONFIG_IP_NF_NAT_LOCAL
/* LOCAL_IN hook doesn‘t have a chain! */
if (hooknum == NF_IP_LOCAL_IN)
ret = alloc_null_binding(ct, info,
hooknum);
else
#endif
/* 既没有被nat修改过,也不是由expect创建,这是一个初始的数据包,开始在nat表中查找规则 */
ret = ip_nat_rule_find(pskb, hooknum, in, out, ct, info);
}
if (ret != NF_ACCEPT) {
WRITE_UNLOCK(&ip_nat_lock);
return ret;
}
} else
/* 如果该连接的nat部分已经被初始化了,打印调试信息 */
DEBUGP("Already setup manip %s for ct %p\n",
maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",
ct);
WRITE_UNLOCK(&ip_nat_lock);
break;
default:
/* ESTABLISHED */
IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED
|| ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
info = &ct->nat.info;
}
IP_NF_ASSERT(info);
/* 前面已经修改了连接跟踪表,这里正式修改了数据包里的地址 */
return do_bindings(ct, ctinfo, info, hooknum, pskb);
}
2.2 ip_nat_rule_find函数 ip_nat_rule.c
int ip_nat_rule_find(struct sk_buff **pskb,
unsigned int hooknum,
const struct net_device *in,
const struct net_device *out,
struct ip_conntrack *ct,
struct ip_nat_info *info)
{
int ret;
/* 调用ipt_do_tables函数,第五个参数是&nat_table */
ret = ipt_do_table(pskb, hooknum, in, out, &nat_table, NULL);
if (ret == NF_ACCEPT) {
if (!(info->initialized & (1 << HOOK2MANIP(hooknum))))
/* NUL mapping */
ret = alloc_null_binding(ct, info, hooknum);
}
return ret;
}
nat表和filter表一样,都是通过调用ipt_do_table函数来工作的
ipt_do_table查找表中的所有entry,如果match全都匹配,则调用target函数
此时的target函数就是在nat初始化时注册的ipt_snat_target和ipt_dnat_target
2.3 ipt_s(d)nat_target函数 ip_nat_rule.c
static unsigned int ipt_snat_target(struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const void *targinfo,
void *userinfo)
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);
/* 取得数据包的连接状态 */
ct = ip_conntrack_get(*pskb, &ctinfo);
/* Connection must be valid and new. */
IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));
IP_NF_ASSERT(out);
return ip_nat_setup_info(ct, targinfo, hooknum);
}
ipt_dnat_target和ipt_snat_target差不多,都是调用ip_nat_setup_info完成地址转换,这里的targinfo参数来自ipt_entry_target结构的unsigned char data[0]参数,一个长度为0的数组,指向target的末尾
static unsigned int ipt_dnat_target(struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const void *targinfo,
void *userinfo)
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
#ifdef CONFIG_IP_NF_NAT_LOCAL
IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
|| hooknum == NF_IP_LOCAL_OUT);
#else
IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING);
#endif
ct = ip_conntrack_get(*pskb, &ctinfo);
/* Connection must be valid and new. */
IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));
return ip_nat_setup_info(ct, targinfo, hooknum);
}
2.4 ip_nat_setup_info()函数 ip_nat_rule.c
unsigned int
ip_nat_setup_info(struct ip_conntrack *conntrack, /* 数据包的连接状态 */
const struct ip_nat_multi_range *mr, /* 转换后的地址池 */
unsigned int hooknum) /* hook点 */
{
struct ip_conntrack_tuple new_tuple, inv_tuple, reply;
struct ip_conntrack_tuple orig_tp;
struct ip_nat_info *info = &conntrack->nat.info;
int in_hashes = info->initialized;
MUST_BE_WRITE_LOCKED(&ip_nat_lock);
IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
|| hooknum == NF_IP_POST_ROUTING
|| hooknum == NF_IP_LOCAL_IN
|| hooknum == NF_IP_LOCAL_OUT);
IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS);
IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum))));
/* 对当前状态的应答方向的tuple调用invert_tuplepr取反,得到一个orig_tp,如果之前没有进行过地址或端口转换,通常这里得到的orig_tp就等于初始方向的tuple */
invert_tuplepr(&orig_tp, conntrack->tuplehash[IP_CT_DIR_REPLY].tuple);
do {
/* 进行地址转换,new_tuple为转换后的地址的tuple */
if (!get_unique_tuple(&new_tuple,&orig_tp,mr,conntrack,hooknum))
{
DEBUGP("ip_nat_setup_info: Can‘t get unique for %p.\n",
conntrack);
return NF_DROP;
}
/* 对new_tuple取反,得到经过转换后的应答方向的tuple */
invert_tuplepr(&reply, &new_tuple);
/* 修改conntrack中的应答方向的reply tuple,在这之前还要检查如果该reply tuple已经在hash表里存在了,即被其它连接占用(存在初始方向tuple不同,应答方向tuple相同的连接),则还要回头继续修改 */
} while (!ip_conntrack_alter_reply(conntrack, &reply));
/* 对orig_tp取反,实际上又得到了原conntrack的reply_tuple…… */
invert_tuplepr(&inv_tuple, &orig_tp);
/* 将所作转换的相关信息保存到连接状态conntrack里,这样该连接的后续数据包就可以直接利用这些信息进行地址转换,不用重新查找nat表了 */
/* 如果是源地址改变(SNAT) */
if (!ip_ct_tuple_src_equal(&new_tuple, &orig_tp)) {
/* In this direction, a source manip. */
info->manips[info->num_manips++] =
((struct ip_nat_info_manip)
{ IP_CT_DIR_ORIGINAL, hooknum,
IP_NAT_MANIP_SRC, new_tuple.src });
IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS);
/* 在相对的hook点上必然有对应的目的地址改变(DNAT) */
info->manips[info->num_manips++] =
((struct ip_nat_info_manip)
/* opposite_hook即是求当前hook点的对应hook点 */
{ IP_CT_DIR_REPLY, opposite_hook[hooknum],
IP_NAT_MANIP_DST, orig_tp.src });
IP_NF_ASSERT(info->num_manips <= IP_NAT_MAX_MANIPS);
}
/* 如果是目的地址改变(DNAT) */
if (!ip_ct_tuple_dst_equal(&new_tuple, &orig_tp)) {
/* In this direction, a destination manip */
info->manips[info->num_manips++] =
((struct ip_nat_info_manip)
{ IP_CT_DIR_ORIGINAL, hooknum,
IP_NAT_MANIP_DST, reply.src });
IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS);
/* In the reverse direction, a source manip. */
info->manips[info->num_manips++] =
((struct ip_nat_info_manip)
{ IP_CT_DIR_REPLY, opposite_hook[hooknum],
IP_NAT_MANIP_SRC, inv_tuple.src });
IP_NF_ASSERT(info->num_manips <= IP_NAT_MAX_MANIPS);
}
/* 如果这个连接不是某个连接的预期的连接(子连接),则在全局链表helpers查找对应的ip_nat_helper结构 */
if (!conntrack->master)
info->helper = LIST_FIND(&helpers, helper_cmp, struct ip_nat_helper *, &reply);
/* 转换完了,标记一下 */
info->initialized |= (1 << HOOK2MANIP(hooknum));
/* 将所做的地址转换的数据结构加入到全局hash表bysource和byipsproto中,如果该地址转换是某地址转换基础上的再次转换,则用replace_in_hashes替换,反之则用place_in_hashes */
if (in_hashes) {
IP_NF_ASSERT(info->bysource.conntrack);
replace_in_hashes(conntrack, info);
} else {
place_in_hashes(conntrack, info);
}
return NF_ACCEPT;
}
2.5 get_unique_tuple ()函数 ip_nat_core.c
get_unique_tuple,获得一个唯一的tuple,就是说除了要做地址/段口的转换,还要保证转换得到的tuple是唯一的。
很复杂的一个函数。。。
第三个参数是用来替换的地址或端口的范围
static int
get_unique_tuple(struct ip_conntrack_tuple *tuple,
const struct ip_conntrack_tuple *orig_tuple,
const struct ip_nat_multi_range *mrr,
struct ip_conntrack *conntrack,
unsigned int hooknum)
{
struct ip_nat_protocol *proto
= find_nat_proto(orig_tuple->dst.protonum);
struct ip_nat_range *rptr;
unsigned int i;
int ret;
struct ip_nat_multi_range *mr = (void *)mrr;
/* 下面这一段比较晕,和p2p,udp打洞等技术有关。 */
if (hooknum == NF_IP_POST_ROUTING) {
/* ip_conntrack_manip结构包含一个ip地址和一个协议端口 */
struct ip_conntrack_manip *manip;
/* find_appropriate_src函数先调用hash_by_src函数计算orig_tuple的hash值,然后去bysource表里查找,如果能找到源地址和端口都匹配的连接,并且如果该连接的地址/端口本身就满足目标地址/端口范围的话,就直接返回查到的这个连接的源ip */
manip = find_appropriate_src(orig_tuple, mr);
if (manip) {
/* Apply same source manipulation. */
*tuple = ((struct ip_conntrack_tuple)
{ *manip, orig_tuple->dst });
DEBUGP("get_unique_tuple: Found current src map\n");
/* 还要保证连接跟踪表里没有这个连接 */
if (!ip_nat_used_tuple(tuple, conntrack))
return 1;
}
}
/* orig_tuple是转换之前的,tuple是转换之后的 */
*tuple = *orig_tuple;
/* 循环 ,尝试mr参数所指定的地址/端口范围,直到能满足其tuple是唯一的 */
while ((rptr = find_best_ips_proto_fast(tuple, mr, conntrack, hooknum))
!= NULL) {
DEBUGP("Found best for "); DUMP_TUPLE(tuple);
/* IP_NAT_MANIP_SRC, 进行SNAT
IP_NAT_MANIP_DST 进行DNAT
IP_NAT_RANGE_MAP_IPS 在range里指定了IP地址
IP_NAT_RANGE_PROTO_SPECIFIED 在range里指定了port
如果没有指定协议端口范围,或者满足了所指定的范围 */
if ((!(rptr->flags & IP_NAT_RANGE_PROTO_SPECIFIED)
|| proto->in_range(tuple, HOOK2MANIP(hooknum),
&rptr->min, &rptr->max))
&& !ip_nat_used_tuple(tuple, conntrack)) {
ret = 1;
goto clear_fulls;
} else {
if (proto->unique_tuple(tuple, rptr,
HOOK2MANIP(hooknum),
conntrack)) {
/* Must be unique. */
IP_NF_ASSERT(!ip_nat_used_tuple(tuple,
conntrack));
ret = 1;
goto clear_fulls;
} else if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) {
/* Try implicit source NAT; protocol
may be able to play with ports to
make it unique. */
struct ip_nat_range r
= { IP_NAT_RANGE_MAP_IPS,
tuple->src.ip, tuple->src.ip,
{ 0 }, { 0 } };
DEBUGP("Trying implicit mapping\n");
if (proto->unique_tuple(tuple, &r,
IP_NAT_MANIP_SRC,
conntrack)) {
/* Must be unique. */
IP_NF_ASSERT(!ip_nat_used_tuple
(tuple, conntrack));
ret = 1;
goto clear_fulls;
}
}
DEBUGP("Protocol can‘t get unique tuple %u.\n",
hooknum);
}
/* Eliminate that from range, and try again. */
rptr->flags |= IP_NAT_RANGE_FULL;
*tuple = *orig_tuple;
}
ret = 0;
clear_fulls:
/* Clear full flags. */
IP_NF_ASSERT(mr->rangesize >= 1);
for (i = 0; i < mr->rangesize; i++)
mr->range[i].flags &= ~IP_NAT_RANGE_FULL;
return ret;
}