Linux netfilter 学习笔记 之十二 ip层netfilter的NAT模块代码分析
本节主要是分析NAT模块相关的hook函数与target函数,主要是理清NAT模块实现的原理等。
1.NAT相关的hook函数分析
NAT模块主要是在NF_IP_PREROUTING、NF_IP_POSTROUTING、NF_IP_LOCAL_OUT、NF_IP_LOCAL_IN四个节点上进行NAT操作,在上一节中我们知道nat表中只有PREROUTING、POSTROUTING、LOCAL_OUT三条链,而没有NF_IP_LOCAL_IN链,所以不能创建在LOCAL_IN hook点的SNAT操作。
而NAT模块在注册hook函数时又在LOCAL_IN点注册了hook函数,且hook函数也调用了NAT转换的通用处理函数,难道也要对LOCAL_IN的数据包进行NAT转换吗?
其实,在LOCAL_IN注册hook函数主要不是为了进行NAT转换,因为在系统为一个源ip为A的转发数据包进行了SNAT后,可能会对源端口获取一个随机的值,这时如果源ip为A的数据包要发送给网关时,可能源端口就是刚才NAT转换的那个源端口,此时为了保证连接跟踪项的原始方向的tuple变量的唯一性,就需要在LOCAL_IN的hook点通过调用NAT转换的通用处理函数,改变源端口值,重新获取一个新的唯一的且未被使用的tuple变量。这应该就是LOCAL_IN也需要hook回调函数的原因吧。
1.1 nf_nat_in
这个函数是NAT模块在PRE_ROUTING hook点上注册的回调函数,该函数主要是实现DNAT功能,该函数的定义如下,主要实现如下两个功能:
1. 调用函数ip_nat_fn实现DNAT转换
2.当转换后数据包的目的ip地址改变后,需要调用dst_release,将skb对dst_entry的引用减一,然后将skb->dst置为NULL
- static unsigned int
- nf_nat_in(unsigned int hooknum,
- struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- unsigned int ret;
- __be32 daddr = (*pskb)->nh.iph->daddr;
- ret = nf_nat_fn(hooknum, pskb, in, out, okfn);
- if (ret != NF_DROP && ret != NF_STOLEN &&
- daddr != (*pskb)->nh.iph->daddr) {
- dst_release((*pskb)->dst);
- (*pskb)->dst = NULL;
- }
- return ret;
- }
该函数主要是通过调用nf_nat_fn,该函数是一个通用NAT转换函数,待会着重分析这个函数
1.2 nf_nat_out
这个函数是NAT模块在POST_ROUTING hook点的hook回调函数,该函数实现如下功能:
1. 调用函数ip_nat_fn实现SNAT转换
- static unsigned int
- nf_nat_out(unsigned int hooknum,
- struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- #ifdef CONFIG_XFRM
- struct nf_conn *ct;
- enum ip_conntrack_info ctinfo;
- #endif
- unsigned int ret;
- /* root is playing with raw sockets. */
- if ((*pskb)->len < sizeof(struct iphdr) ||
- (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
- return NF_ACCEPT;
- ret = nf_nat_fn(hooknum, pskb, in, out, okfn);
- #ifdef CONFIG_XFRM
- if (ret != NF_DROP && ret != NF_STOLEN &&
- (ct = nf_ct_get(*pskb, &ctinfo)) != NULL) {
- enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
- if (ct->tuplehash[dir].tuple.src.u3.ip !=
- ct->tuplehash[!dir].tuple.dst.u3.ip
- || ct->tuplehash[dir].tuple.src.u.all !=
- ct->tuplehash[!dir].tuple.dst.u.all
- )
- return ip_xfrm_me_harder(pskb) == 0 ? ret : NF_DROP;
- }
- #endif
- return ret;
- }
这个函数同样是调用函数nf_nat_fn实现SNAT转换
1.3nf_nat_local_fn
这个函数是NAT模块在OUTPUT hook点的hook回调函数,该函数实现如下功能:
功能:实现DNAT转换功能
1. 调用函数ip_nat_fn实现DNAT转换
2.调用ip_route_me_harder,重新进行路由操作(与PRE_ROUTING不同的是,对于 OUTPUT的hook回调函数,当目的地址改变后,需要在该函数里调用ip_route_me_harder重新查找路由,而在PRE_ROUTING链中,则是将skb->dst置为空, 然后在数据包往下执行时会自行重新查找路由。OUTPUT链接收的数据均是已经路由 的数据包,且后续调用函数中不会再有查找路由的操作,所以要nf_nat_out里实现路由 查找。)。
- static unsigned int
- nf_nat_local_fn(unsigned int hooknum,
- struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- struct nf_conn *ct;
- enum ip_conntrack_info ctinfo;
- unsigned int ret;
- /* root is playing with raw sockets. */
- if ((*pskb)->len < sizeof(struct iphdr) ||
- (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
- return NF_ACCEPT;
- ret = nf_nat_fn(hooknum, pskb, in, out, okfn);
- if (ret != NF_DROP && ret != NF_STOLEN &&
- (ct = nf_ct_get(*pskb, &ctinfo)) != NULL) {
- enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
- if (ct->tuplehash[dir].tuple.dst.u3.ip !=
- ct->tuplehash[!dir].tuple.src.u3.ip) {
- if (ip_route_me_harder(pskb, RTN_UNSPEC))
- ret = NF_DROP;
- }
- #ifdef CONFIG_XFRM
- else if (ct->tuplehash[dir].tuple.dst.u.all !=
- ct->tuplehash[!dir].tuple.src.u.all)
- if (ip_xfrm_me_harder(pskb))
- ret = NF_DROP;
- #endif
- }
- return ret;
- }
这个函数其实也是调用函数nf_nat_fn实现NAT转换的。
1.4 LOCAL_IN hook
NAT模块在NF_LOCAL_IN的hook回调函数就是直接调用nf_nat_fn,此处需要注意以下信息:
对于NF_LOCAL_IN链来说,因为nat表中并没有INPUT链,所以对于NF_LOCAL_IN点来说,并不会修改数据包的ip地址,也就是调用alloc_null_binding实现NAT转换,最大的可能就是修改数据包的源端口号,以实现数据连接跟踪项的reply的nf_conntrack_tuple变量是唯一的,且没有被其他连接跟踪项使用。
这也就是为什么需要在NF_LOCAL_IN HOOK点注册HOOK回调函数而又没有在nat表中注册INPUT链的原因。
1.5 通用NAT转换函数
对于通用NAT转换函数,最主要的就是函数nf_nat_fn,而nf_nat_fn的实现中涉及了许多的函数,此处我们依依分析之。
1.5.1 nf_nat_fn
该函数主要功能就是实现数据的NAT操作(包括SNAT与DNAT),具体来说,就是对一个数据流对应的连接跟踪项仅执行一次SNAT、DNAT,而当数据流对应的连接跟踪项的NAT操作执行完成以后,对于后续的数据包,则直接根据连接跟踪项的reply方向的nf_conntrac_tuple变量的值进行NAT转换,然后将数据再交给协议栈处理。
下面分析这个函数:
功能:实现NAT功能(包括SNAT/DNAT功能)
1.首先判断数据包是否符合要求(必须不是分段的),数据包对应的连接跟踪项是否符合转换要求等
2.对于期望连接来说,对于icmp报文,需要对报文进行NAT转换
3.只对new状态的且未进行NAT转换的连接跟踪项,且不是NF_LOCAL_IN hook点时,调用nf_nat_rule_find进行连接跟踪
项的NAT转换
4.进行了上述3的操作后,则会调用nf_nat_packet对数据包进行NAT转换操作。
连接跟踪项的NAT转换只会发生在连接跟踪项刚被创建且还没有进行confirm时,且每个NAT类型只会执行一次NAT转换。
- static unsigned int
- nf_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 nf_conn *ct;
- enum ip_conntrack_info ctinfo;
- struct nf_conn_nat *nat;
- struct nf_nat_info *info;
- /* maniptype == SRC for postrouting. */
- /*获取NAT类型*/
- enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum);
- /* We never see fragments: conntrack defrags on pre-routing
- and local-out, and nf_nat_out protects post-routing. */
- NF_CT_ASSERT(!((*pskb)->nh.iph->frag_off
- & htons(IP_MF|IP_OFFSET)));
- /*获取该数据包对应的连接跟踪项*/
- ct = nf_ct_get(*pskb, &ctinfo);
- /* Can't track? It's not due to stress, or conntrack would
- have dropped it. Hence it's the user's responsibilty to
- packet filter it out, or implement conntrack/NAT for that
- protocol. 8) --RR */
- /*
- 当数据包没有连接跟踪项,且为icmp_redirect时,返回DROP;
- 当数据包没有连接跟踪项,且不是icmp_redirect时,返回ACCEPT;
- */
- 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, *hp;
- hp = skb_header_pointer(*pskb,
- (*pskb)->nh.iph->ihl*4,
- sizeof(_hdr), &_hdr);
- if (hp != NULL &&
- hp->type == ICMP_REDIRECT)
- return NF_DROP;
- }
- return NF_ACCEPT;
- }
- /* Don't try to NAT if this packet is not conntracked */
- /*对于连接跟踪项为nf_conntrack_untracked,则说明不对该数据包进行连接跟踪,此时直接返回ACCEPT*/
- if (ct == &nf_conntrack_untracked)
- return NF_ACCEPT;
- /*当连接跟踪项没有关联的nf_conn_nat变量时,返回ACCEPT*/
- nat = nfct_nat(ct);
- if (!nat)
- return NF_ACCEPT;
- /*
- 对于期望连接original与reply方向的数据包,对于icmp协议的数据包,进行nat操作;
- 对于期望连接、及非期望连接的NEW状态下的连接跟踪项,只有连接跟踪项的NAT操作没有进行
- 的情况下才进行NAT转换操作。
- a)对于LOCAL_IN的hook点,调用alloc_null_binding进行NAT操作,可能会修改四层协议相关的关键字
- b)对于已经确认过却没有进行NAT操作的连接跟踪项,调用alloc_null_binding_confirmed进行NAT操作,只有可能修改
- 四层协议相关的关键字。
- c)对于其他情况,则通过nf_nat_rule_find,查找iptables的nat表中有没有匹配该数据流的NAT规则,若有则根据
- NAT类型,调用相应的target进行NAT操作(SNAT target 、DNAT target)
- */
- switch (ctinfo) {
- case IP_CT_RELATED:
- case IP_CT_RELATED+IP_CT_IS_REPLY:
- if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
- if (!nf_nat_icmp_reply_translation(ct, ctinfo,
- hooknum, pskb))
- return NF_DROP;
- else
- return NF_ACCEPT;
- }
- /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
- case IP_CT_NEW:
- info = &nat->info;
- /* Seen it before? This can happen for loopback, retrans,
- or local packets.. */
- if (!nf_nat_initialized(ct, maniptype)) {
- unsigned int ret;
- if (unlikely(nf_ct_is_confirmed(ct)))
- /* NAT module was loaded late */
- ret = alloc_null_binding_confirmed(ct, info,
- hooknum);
- else if (hooknum == NF_IP_LOCAL_IN)
- /* LOCAL_IN hook doesn't have a chain! */
- ret = alloc_null_binding(ct, info, hooknum);
- else
- ret = nf_nat_rule_find(pskb, hooknum, in, out,
- ct, info);
- if (ret != NF_ACCEPT) {
- return ret;
- }
- } else
- DEBUGP("Already setup manip %s for ct %p\n",
- maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",
- ct);
- break;
- default:
- /* ESTABLISHED */
- NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED ||
- ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
- info = &nat->info;
- }
- NF_CT_ASSERT(info);
- /*调用nf_nat_packet,根据连接跟踪项的reply tuple变量实现对数据包的NAT操作*/
- return nf_nat_packet(ct, ctinfo, hooknum, pskb);
- }
这个函数主要涉及了函数nf_nat_initialized、alloc_null_binding_confirmed、alloc_null_binding、nf_nat_rule_find、nf_nat_packet,下面我们开始分析这些函数。
1.5.1.1 nf_nat_initialized
这个函数主要是判断传递的连接跟踪项,有没有进行过manip类型的NAT转换。
若manip的值为IP_NAT_MANIP_SRC,则判断连接跟踪项的status的
IPS_SRC_NAT_DONE_BIT位是否为1,若为1,则说明该连接跟踪项已经进行了SNAT转换,不需要再次转换;
对于DNAT的判断与上述SNAT的判断类似,根据这个函数,就可以避免多次对一个连接跟踪项进行SNAT或者DNAT操作。
- static inline int nf_nat_initialized(struct nf_conn *ct,
- enum nf_nat_manip_type manip)
- {
- if (manip == IP_NAT_MANIP_SRC)
- return test_bit(IPS_SRC_NAT_DONE_BIT, &ct->status);
- else
- return test_bit(IPS_DST_NAT_DONE_BIT, &ct->status);
- }
1.5.1.2 alloc_null_binding_confirmed
这个是针对连接跟踪项已经确认,但是其NAT操作还没有进行的情况。
按照我们的逻辑来说,对连接跟踪项的NAT操作是在连接跟踪项创建之后,且连接跟踪项被确认之前的。
那怎么会出现连接跟踪项已经确认,但是NAT转换还没有进行的呢?
当连接跟踪模块已经加载并且已经工作一段时间后,才加载NAT模块,就会导致这种情况出现。
那既然NAT模块是后面加载的,那还有必要对先前已经确认的连接跟踪项进行NAT转换吗?
是这样的,对于先前已经确认的连接跟踪项,虽然已经确认了,但是由于NAT模块没有加载,使其没有添加到by_source[]数组相对应的链表上,而NAT模块在对连接跟踪项进行转换时,是通过将转换后的nf_conntrack_tuple变量,与连接跟踪项上所有的nf_conntrack_tuple变量进行对比来确保转换后的连接跟踪项的唯一性。基于这个原理,如果不把先前已经确认的连接跟踪项通过NAT转换并添加到by_source[]链表上的话,则可能出现已经转换后的连接跟踪项的tuple变量与先前已经确认的连接跟踪项冲突,所以需要将先前已经的确认的连接跟踪项也进行NAT操作,不过这次NAT转换不会修改ip地址,最大的可能就是对源或目的端口进行微调。
这个函数还是比较简单的,跟踪hook的类型,确认NAT转换的类型,然后就将range中的ip地址就设置reply方向的nf_conntrack_tuple的对应的ip地址(从这也能看出,没有修改ip地址)。 然后就是调用函数nf_nat_setup_info实现对连接跟踪项的NAT转换操作。
- unsigned int
- alloc_null_binding_confirmed(struct nf_conn *ct,
- struct nf_nat_info *info,
- unsigned int hooknum)
- {
- __be32 ip
- = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC
- ? ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip
- : ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.ip);
- u_int16_t all
- = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC
- ? ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.all
- : ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u.all);
- struct nf_nat_range range
- = { IP_NAT_RANGE_MAP_IPS, ip, ip, { all }, { all } };
- DEBUGP("Allocating NULL binding for confirmed %p (%u.%u.%u.%u)\n",
- ct, NIPQUAD(ip));
- return nf_nat_setup_info(ct, &range, hooknum);
- }
函数nf_nat_setup_info就是最终实现NAT转换的最最重要的函数,后面的alloc_null_binding、 masquerade_target,、ipt_snat_target、ipt_dnat_target最终都是调用这个函数实现连接跟踪项的NAT转换,有必要单独拿出来讲这个函数。
1.5.1.3 alloc_null_binding
在函数nf_nat_fn里,只有对于LOCAL_IN 的hook点上,才会调用该函数进行NAT转换的,因为nat表没有LOCAL_IN链,所以在LOCAL_IN链肯定不会匹配NAT转换规则的。但是内核绝不会把一个没用的代码放在那里一直不改的,对于LOCAL_IN 的hook点,虽然该数据流没有进行nat,但是存在其他三层ip相同数据流进行nat时,将该不需NAT数据流的四层端口号给占用的情况,这就好导致数据连接跟踪项的冲突。为了解决这个问题,就需要调用nf_nat_setup_info为当前不需NAT数据流找到一个唯一的tuple变量(新的唯一tuple变量的值有两种:原来的tuple变量即是唯一的;修改原来tuple变量的四层协议相关的关键字, 得到一个新的唯一的tuple变量。),并将该连接跟踪项添加到by_source[]相对应的链表上,这样在其他数据连接跟踪项进行转换时,就会先将转换后的nf_conntrac_tuple变量与连接跟踪项的确认链表中的值进行比较,在没有冲突的情况下再进行NAT转换。
这个函数的执行流程与alloc_null_binding_confirmed类似,且最终也是调用nf_nat_setup_info进行连接跟踪项的转换。
- inline unsigned int
- alloc_null_binding(struct nf_conn *ct,
- struct nf_nat_info *info,
- unsigned int hooknum)
- {
- /* Force range to this IP; let proto decide mapping for
- per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED).
- Use reply in case it's already been mangled (eg local packet).
- */
- __be32 ip
- = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC
- ? ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip
- : ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.ip);
- struct nf_nat_range range
- = { IP_NAT_RANGE_MAP_IPS, ip, ip, { 0 }, { 0 } };
- DEBUGP("Allocating NULL binding for %p (%u.%u.%u.%u)\n",
- ct, NIPQUAD(ip));
- return nf_nat_setup_info(ct, &range, hooknum);
- }
1.5.1.4 nf_nat_rule_find
当连接跟踪项不是以上1.5.1.2、1.5.1.3这两个类型,且是NEW、RELATED、RELATED+REPLY状态的连接跟踪项,则会调用函数nf_nat_rule_find,遍历nat表中对应的规则链:
若找到nat规则,则调用相应的target函数(ipt_snat_target或者ipt_dnat_target)实现连接跟踪项的转换;
若没找到nat规则,则调用 alloc_null_binding确保连接跟踪项的reply方向的nf_conntrack_tuple变量的唯一性,并添加到by_source[]相应的链表中,实现的功能与1.5.1.2、1.5.1.3中介绍的大致类似。
功能:实现对数据包关联的连接跟踪项的NAT转换操作。
1.调用ipt_do_table,查找nat表中有没有匹配该连接跟踪项的nat规则,若有则根据NAT类型调用相应的target实现对连接跟踪项的NAT操作(SNAT target 、DNAT target),且将该连接跟踪项的status值中设置已进行NAT转换标志(关于ipt_do_table函数的分析,请参考如下文章Linux netfilter 学习笔记 之五 ip层netfilter的table中规则的匹配检查)。
2.在调用完ipt_do_table后,该连接跟踪项还没有进行NAT转换,则调用alloc_null_binding进行NAT转换。alloc_null_binding并不会修改连接跟踪项的reply方向的tuple变量的三层ip地址,只有在该连接跟踪项使用的tuple变量值不唯一时,则更新连接跟踪项的reply方向的tuple变量的四层协议相关的关键字(也就是端口号之类的)即可。
- int nf_nat_rule_find(struct sk_buff **pskb,
- unsigned int hooknum,
- const struct net_device *in,
- const struct net_device *out,
- struct nf_conn *ct,
- struct nf_nat_info *info)
- {
- int ret;
- ret = ipt_do_table(pskb, hooknum, in, out, &nat_table);
- if (ret == NF_ACCEPT) {
- if (!nf_nat_initialized(ct, HOOK2MANIP(hooknum)))
- /* NUL mapping */
- ret = alloc_null_binding(ct, info, hooknum);
- }
- return ret;
- }
1.5.1.5 nf_nat_packet
在nf_nat_fn的最后,会调用函数nf_nat_packet对数据包进行nat转换。
当一个连接跟踪项已经被NAT转换后,后续的数据包则直接进入函数nf_nat_packet,对数据包中的ip地址、端口等进行NAT转换操作。
当一个连接跟踪项刚被被NAT转换后,则其第一个数据包也要接进入函数nf_nat_packet,对数据包中的ip地址、端口等进行NAT转换操作。
/* Do packet manipulations according to nf_nat_setup_info. */
/*
功能:实现数据包的NAT操作:
当为SNAT操作,且是reply方向的PREROUTING时,经过下面的异或后同样可以调用manip_pkt,而因为此时为DNAT,因此就实现了De-SNAT;
当为DNAT操作,且是reply方向的PREROUTING时,经过下面的异或后同样可以调用manip_pkt,而因为此时为SNAT因此就实现了De-DNAT;
当为SNAT操作,且是original方向的POSTROUTING时,则调用manip_pkt执行SNAT操作;
当为DNAT操作,且是original方向的PREROUTING/OUTPUT时,则调用manip_pkt执行DNAT操作。
1.根据hook点设置statusbit的值
2.对于reply方向,需要执行异或操作
3.当连接跟踪项的status变量与statusbit进行位与的结果不为0时:
调用函数manip_pkt根据NAT类型修改数据包的ip地址。
- unsigned int nf_nat_packet(struct nf_conn *ct,
- enum ip_conntrack_info ctinfo,
- unsigned int hooknum,
- struct sk_buff **pskb)
- {
- enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
- unsigned long statusbit;
- enum nf_nat_manip_type mtype = HOOK2MANIP(hooknum);
- if (mtype == IP_NAT_MANIP_SRC)
- statusbit = IPS_SRC_NAT;
- else
- statusbit = IPS_DST_NAT;
- /* Invert if this is reply dir. */
- if (dir == IP_CT_DIR_REPLY)
- statusbit ^= IPS_NAT_MASK;
- /* Non-atomic: these bits don't change. */
- if (ct->status & statusbit) {
- struct nf_conntrack_tuple target;
- /* We are aiming to look like inverse of other direction. */
- nf_ct_invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);
- if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
- return NF_DROP;
- }
- return NF_ACCEPT;
- }
以上把函数nf_nat_fn相关的函数都分析了,现在就分析最最重要的NAT转换函数nf_nat_setup_info
1.5.2 nf_nat_setup_info
这个函数会对4个hook点进来的连接跟踪项进行NAT转换,所以这个函数至此SNAT、DNAT转换,根据HOOK点的类型能够决定转换的类型。该函数最精髓的地方就是调用函数get_unique_tuple,获取一个唯一的且未被其他已经进行NAT转换的连接跟踪项使用的nf_conntrack_tuple变量。当转换成功后,置标记位。
该函数执行的步骤如下:
1.判断传入的hook点是否是NAT相关的hook点,NAT只在PRE_ROUTING、POST_ROUTING、LOCAL_OUT、LOCAL_IN这四个hook点起作用
2.若此时连接跟踪项的status变量中的 IPS_SRC_NAT_DONE_BIT或者IPS_DST_NAT_DONE_BIT位已经被置位了,则打印bug信息,并调用kernel panic
3.根据reply方向的nf_conntrack_tuple结构的变量,获取其反方向的nf_conntrack_tuple结构的变量
4. 调用get_unique_tuple,根据传递的tuple变量,获取一个新的且经过NAT转换的tuple变量,其方向依然是原始方向
5.当新的tuple变量的值与当前的原始方向的tuple变量的值不相等时,进行NAT转换(因为只有在两个值不同时才需要NAT操作):
a)对传递过来的新的tuple变量的值,调用get_unique_tuple,获取该tuple变量反方向的 tuple变量值,即新的reply方向的值
b)调用nf_conntrack_alter_reply将连接跟踪项的reply方向的tuplehash[IP_CT_DIR_REPLY].tuple替换为a)中得到的reply方向的tuple变量,当连接跟踪项不是期望连接项,且还没有创建期望连接时,对根据新的reply反向的tuple变量,在helpers链表中查找新的符合要求的helper变量,并替换调用连接跟踪项中的原来的nf_conntrack_helper变量
c)根据连接跟踪项的NAT类型,设置连接跟踪项的status中相应位(IPS_SRC_NAT/IPS_DST_NAT)
6.若连接跟踪项的当前status变量的IPS_DST_NAT_DONE 与 IPS_SRC_NAT_DONE位均没有置位,则需要将经过NAT操作后的连接跟踪项添加到bysource[]相应的链表中去(调用hash_by_src根据传入的原始方向的tuple变量计算hash值,根据该hash值获取相应的链表bysourece[hash])
7. 根据NAT类型,将连接跟踪项的status变量的IPS_DST_NAT_DONE或者IPS_SRC_NAT_DONE位置位。
在函数的最后有个置位IPS_DST_NAT_DONE_BIT、IPS_SRC_NAT_DONE_BIT的操作,这就是为了保证一个数据连接跟踪项
在某一个NAT转换类型(SNAT、DNAT)上只能初始化一次。
- unsigned int
- nf_nat_setup_info(struct nf_conn *ct,
- const struct nf_nat_range *range,
- unsigned int hooknum)
- {
- struct nf_conntrack_tuple curr_tuple, new_tuple;
- struct nf_conn_nat *nat = nfct_nat(ct);
- struct nf_nat_info *info = &nat->info;
- int have_to_hash = !(ct->status & IPS_NAT_DONE_MASK);
- enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum);
- NF_CT_ASSERT(hooknum == NF_IP_PRE_ROUTING ||
- hooknum == NF_IP_POST_ROUTING ||
- hooknum == NF_IP_LOCAL_IN ||
- hooknum == NF_IP_LOCAL_OUT);
- 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) */
- nf_ct_invert_tuplepr(&curr_tuple,
- &ct->tuplehash[IP_CT_DIR_REPLY].tuple);
- get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);
- if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
- struct nf_conntrack_tuple reply;
- /* Alter conntrack table so will recognize replies. */
- 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. */
- if (have_to_hash) {
- unsigned int srchash;
- srchash = hash_by_src(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
- write_lock_bh(&nf_nat_lock);
- list_add(&info->bysource, &bysource[srchash]);
- write_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;
- }
nf_ct_invert_tuplepr主要是根据输入的nf_conntrack_tuple变量,获取其反方向的nf_conntrack_tuple变量。
1.5.2.1 get_unique_tuple
该函数根据传递的orig_tuple与range变量,得到一个新的tuple,此tuple的ip地址或者端口号已经进行了NAT转换。
函数的执行步骤如下:
1.当为SNAT,且通过find_appropriate_src在bysource[]相应链表上得到了一个符合要求的原始tuple变量,且在连接跟踪项的确认链表中,没有与该tuple变量相关的连接跟踪项,则可以使用该tuple变量作为SNAT的依据。
2.进入此步,则说明需要对传入的连接跟踪项进行NAT,则调用find_best_ips_proto设置传入tuple的源或者目的ip地址
3.调用__nf_nat_proto_find查看在nf_nat_protos数组中有没有注册与连接跟踪项的四层协议相关的 nf_nat_protocol变量
4.当range支持IP_NAT_RANGE_PROTO_RANDOM时,则需要调用四层协议nf_nat_protocol类型变量的unique_tuple,随机选择一个新的四层协议号
5.当range不支持IP_NAT_RANGE_PROTO_SPECIFIED,则不需要修改四层协议相关的端口号或者其他信息,程序返回
6.当range支持IP_NAT_RANGE_PROTO_SPECIFIED,且发现tuple的四层协议相关的端口号或者其他信息已经在range变量对应的
四层相关值的范围之内,且该tuple的相反tuple与连接跟踪项的reply方向的tuple变量不等,则无需再进行 四层协议相关的端口号或者其他值的转换,程序返回
7.若以上5与6均不满足,则调用四层协议nf_nat_protocol类型变量的unique_tuple,对tuple的四层协议相关的端口号等信息进行NAT转换
*/
- static void
- get_unique_tuple(struct nf_conntrack_tuple *tuple,
- const struct nf_conntrack_tuple *orig_tuple,
- const struct nf_nat_range *range,
- struct nf_conn *ct,
- enum nf_nat_manip_type maniptype)
- {
- struct nf_nat_protocol *proto;
- /* 1) If this srcip/proto/src-proto-part is currently mapped,
- and that same mapping gives a unique tuple within the given
- range, use that.
- This is only required for source (ie. NAT/masq) mappings.
- So far, we don't do local source mappings, so multiple
- manips not an issue. */
- if (maniptype == IP_NAT_MANIP_SRC) {
- if (find_appropriate_src(orig_tuple, tuple, range)) {
- DEBUGP("get_unique_tuple: Found current src map\n");
- if (!(range->flags & IP_NAT_RANGE_PROTO_RANDOM))
- if (!nf_nat_used_tuple(tuple, ct))
- return;
- }
- }
- /* 2) Select the least-used IP/proto combination in the given
- range. */
- *tuple = *orig_tuple;
- find_best_ips_proto(tuple, range, ct, maniptype);
- /* 3) The per-protocol part of the manip is made to map into
- the range to make a unique tuple. */
- rcu_read_lock();
- proto = __nf_nat_proto_find(orig_tuple->dst.protonum);
- /* Change protocol info to have some randomization */
- if (range->flags & IP_NAT_RANGE_PROTO_RANDOM) {
- proto->unique_tuple(tuple, range, maniptype, ct);
- goto out;
- }
- /* Only bother mapping if it's not already in range and unique */
- if ((!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) ||
- proto->in_range(tuple, maniptype, &range->min, &range->max)) &&
- !nf_nat_used_tuple(tuple, ct))
- goto out;
- /* Last change: get protocol to try to obtain unique tuple. */
- proto->unique_tuple(tuple, range, maniptype, ct);
- out:
- rcu_read_unlock();
- }
这个函数里有两个重要函数find_appropriate_src与nf_nat_used_tuple
1.5.2.1.1 find_appropriate_src
这个函数只会被SNAT转换调用。此处是在已进行NAT的bysource[]相应的链表中,查找是否有原始tuple值的src ip、src port、l4proto的值与给定的tuple的src ip、src port、l4proto的值相等的连接跟踪项:
若有,则说明我们大概可以可以用这个已经经过SNAT的连接跟踪项的reply方向的tuple值的取反后的tuple变量作为SNAT的依据,且将该tuple的dst替换成传入tuple变量的dst值。
功能:给定一个tuple变量,判断在已进行SNAT转换,且其nf_conn_nat变量已经在bysource[]的连接跟踪项,是否存在连接跟踪项的原始方向tuple 是否与传入tuple相等:
若有,则说明找到了一个合适的经NAT后的原始tuple项,并存放在result中,程序返回
1.通过hash_by_src获取原始方向tuple的hash值为h(此处为假设)
2. 在bysource[h]链表中查找已进行NAT操作的
- static int
- find_appropriate_src(const struct nf_conntrack_tuple *tuple,
- struct nf_conntrack_tuple *result,
- const struct nf_nat_range *range)
- {
- unsigned int h = hash_by_src(tuple);
- struct nf_conn_nat *nat;
- struct nf_conn *ct;
- read_lock_bh(&nf_nat_lock);
- list_for_each_entry(nat, &bysource[h], info.bysource) {
- ct = (struct nf_conn *)((char *)nat - offsetof(struct nf_conn, data));
- if (same_src(ct, tuple)) {
- /* Copy source part from reply tuple. */
- nf_ct_invert_tuplepr(result,
- &ct->tuplehash[IP_CT_DIR_REPLY].tuple);
- result->dst = tuple->dst;
- if (in_range(result, range)) {
- read_unlock_bh(&nf_nat_lock);
- return 1;
- }
- }
- }
- read_unlock_bh(&nf_nat_lock);
- return 0;
- }
same_src的功能是判断传入tuple的源ip及源端口号是否与传入连接跟踪项的原始方向的tuple变量的源ip及源端口号相等。此处只是对原始方向的tuple变量的原始方向的ip地址、原始方向的端口号等以及四层协议号相等,即认为相同,而不管目的ip地址与目的端口号是否相等。
- static inline int
- same_src(const struct nf_conn *ct,
- const struct nf_conntrack_tuple *tuple)
- {
- const struct nf_conntrack_tuple *t;
- t = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
- return (t->dst.protonum == tuple->dst.protonum &&
- t->src.u3.ip == tuple->src.u3.ip &&
- t->src.u.all == tuple->src.u.all);
- }
in_range的作用是判断一个tuple中的源ip地址是否在range范围内。
1. 若nf_nat_range结构 的range变量,支持ip地址的NAT取值范围功能,若tuple中的源地址小于range变量的最小ip地址,或者大于range变量的最大ip地址,则返回0,说明tuple没有在range范围内。
2.进入此步骤后,则说明tuple的源ip地址已经在range范围内了,此时判断range是否增加了协议识别标签:
若没有添加协议识别标签,则返回tuple值满足range的要求,返回1;
若有添加协议识别标签,且调用四层协议的in_range函数后,也返回1,则说明tuple满足range要求,程序返回1;
若不满足以上两种case中的一个,则返回0.
*/
- static int
- in_range(const struct nf_conntrack_tuple *tuple,
- const struct nf_nat_range *range)
- {
- struct nf_nat_protocol *proto;
- int ret = 0;
- /* If we are supposed to map IPs, then we must be in the
- range specified, otherwise let this drag us onto a new src IP. */
- if (range->flags & IP_NAT_RANGE_MAP_IPS) {
- if (ntohl(tuple->src.u3.ip) < ntohl(range->min_ip) ||
- ntohl(tuple->src.u3.ip) > ntohl(range->max_ip))
- return 0;
- }
- rcu_read_lock();
- /*根据四层协议号,在nf_nat_protos[]数组中找到相应的nf_nat_protocol变量*/
- proto = __nf_nat_proto_find(tuple->dst.protonum);
- /*
- 当range没有标识四层相关的关键字检查标签或者
- 设置四层检查的标签,且调用相应四层nf_nat_protocol变量的in_range函数后,检查通过时,则返回1
- */
- if (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) ||
- proto->in_range(tuple, IP_NAT_MANIP_SRC,
- &range->min, &range->max))
- ret = 1;
- rcu_read_unlock();
- return ret;
- }
1.5.2.1.2 nf_nat_used_tuple
该函数主要是判断已传入tuple取反获取到的reply tuple变量,是否已经被其他连接跟踪项使用了:
若已经被其他连接跟踪项使用了,则返回TRUE;
若没有被其他连接跟踪项使用,则返回FALSE
- int
- nf_nat_used_tuple(const struct nf_conntrack_tuple *tuple,
- const struct nf_conn *ignored_conntrack)
- {
- /* Conntrack tracking doesn't keep track of outgoing tuples; only
- incoming ones. NAT means they don't have a fixed mapping,
- so we invert the tuple and look for the incoming reply.
- We could keep a separate hash if this proves too slow. */
- struct nf_conntrack_tuple reply;
- nf_ct_invert_tuplepr(&reply, tuple);
- return nf_conntrack_tuple_taken(&reply, ignored_conntrack);
- }
主要是通过函数 nf_conntrack_tuple_taken实现的。
nf_conntrack_tuple_taken是判断连接跟踪项的确认链表中,是否存在连接跟踪项不等于ignored_conntrack,且连接跟踪项的某个tuple值与传入值相等,若存在,则返回TRUE,则不能用传入的tuple变量作为NAT的依据;若不存在,返回FALSE,说明可以用传入的tuple变量作为NAT的依据
- int
- nf_conntrack_tuple_taken(const struct nf_conntrack_tuple *tuple,
- const struct nf_conn *ignored_conntrack)
- {
- struct nf_conntrack_tuple_hash *h;
- read_lock_bh(&nf_conntrack_lock);
- h = __nf_conntrack_find(tuple, ignored_conntrack);
- read_unlock_bh(&nf_conntrack_lock);
- return h != NULL;
- }
而函数__nf_conntrack_find才是最终的函数,该函数查找连接跟踪的确认链表中,是否存在连接跟踪项不等于ignored_conntrack,且连接跟踪项的某一个tuple变量与传入的tuple变量相等的情况,若有则说明传入的tuple变量不能作为NAT转换的依据。
- struct nf_conntrack_tuple_hash *
- __nf_conntrack_find(const struct nf_conntrack_tuple *tuple,
- const struct nf_conn *ignored_conntrack)
- {
- struct nf_conntrack_tuple_hash *h;
- unsigned int hash = hash_conntrack(tuple);
- list_for_each_entry(h, &nf_conntrack_hash[hash], list) {
- if (nf_ct_tuplehash_to_ctrack(h) != ignored_conntrack &&
- nf_ct_tuple_equal(tuple, &h->tuple)) {
- NF_CT_STAT_INC(found);
- return h;
- }
- NF_CT_STAT_INC(searched);
- }
- return NULL;
- }
至此分析完了函数get_unique_tuple,这个函数真的很重要,考虑到了多种case。
1.5.2.2 nf_conntrack_alter_reply
当获取了一个唯一的nf_conntrack_tuple变量后,就可以调用该函数修改连接跟踪项的reply方向的nf_conntrack_tuple变量了。
功能:根据传递的nf_conntrack_tuple变量,修改ct的reply方向的nf_conntrack_tuple值以及helper的值
1.修改输入连接跟踪项的reply方向的nf_conntrack_tuple变量值
2.根据新的reply方向的nf_conntrack_tuple变量,修改连接跟踪项项的helper变量
因为我们知道在创建连接跟踪项时,就是根据reply方向的nf_conntrack_tuple变量,在helpers链表中查找的helper变量;当reply方向的nf_conntrack_tuple变量修改后,则肯定需要再次查找helpers,用以找到新的符合条件的helper变量。因此必须在NAT转换完成以后,才能调用连接跟踪模块的helpe相关的回调函数。
这也是为什么SNAT的HOOK回调函数的优先级高于连接跟踪项的ipv4_conntrack_help hook回调函数的原因。
- void nf_conntrack_alter_reply(struct nf_conn *ct,
- const struct nf_conntrack_tuple *newreply)
- {
- struct nf_conn_help *help = nfct_help(ct);
- write_lock_bh(&nf_conntrack_lock);
- /* Should be unconfirmed, so not in hash table yet */
- NF_CT_ASSERT(!nf_ct_is_confirmed(ct));
- DEBUGP("Altering reply tuple of %p to ", ct);
- NF_CT_DUMP_TUPLE(newreply);
- ct->tuplehash[IP_CT_DIR_REPLY].tuple = *newreply;
- if (!ct->master && help && help->expecting == 0)
- help->helper = __nf_ct_helper_find(newreply);
- write_unlock_bh(&nf_conntrack_lock);
- }
至此将nf_nat_setup_info分析完了。下面看下target的定义,其实都差不多,都是调用函数nf_nat_setup_info实现的,还是简单分析一下。
2.target相关的函数分析
2.1 SNAT target
功能:实现SNAT功能
1.调用nf_ct_get,获取传入数据包关联的nf_conn变量
2.此处进行SNAT只是设置连接跟踪项中的reply方向的nf_conntrack_tuple变量,因此:
对于主连接,仅设置连接跟踪项的状态为NEW的SNAT操作,因为对于状态不为NEW的连接跟踪项,其reply方向的nf_conntrack_tuple结构的变量的目的地址和端口号已经修改过了,不需要再次修改了;
对于期望连接来说,当期望连接刚建立时,其状态仅为IP_CT_RELATED或者IP_CT_RELATED+IP_CT_IS_REPLY,所以
也只对这两种情况的期望连接,进行SNAT操作。
3.调用nf_nat_setup_info,根据targinfo中的地址范围与端口值修改连接跟踪项的reply方向的nf_conntrack_tuple
变量中的值。
执行这个target只是修改了数据包对应的连接跟踪项的reply方向的tuple变量,并没有修改数据包的ip地址,而修改数据包的ip地址是nat模块的hook函数中执行的(在执行了target操作后才会执行,调用函数nf_nat_packet实现)。
(疑问:为什么是期望连接时,状态为IP_CT_RELATED或者IP_CT_RELATED+IP_CT_IS_REPLY都认为是起始状态呢?
状态为IP_CT_RELATED时,认为是新创建的连接跟踪项,我是能理解的,但是IP_CT_RELATED+IP_CT_IS_REPLY也
做为新创建的连接跟踪项的依据,我没有搞懂? 而且我感觉不会出现状态为IP_CT_RELATED+IP_CT_IS_REPLY的
连接跟踪项
)
- static unsigned int ipt_snat_target(struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- unsigned int hooknum,
- const struct xt_target *target,
- const void *targinfo)
- {
- struct nf_conn *ct;
- enum ip_conntrack_info ctinfo;
- const struct nf_nat_multi_range_compat *mr = targinfo;
- NF_CT_ASSERT(hooknum == NF_IP_POST_ROUTING);
- ct = nf_ct_get(*pskb, &ctinfo);
- /* Connection must be valid and new. */
- NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||
- ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
- NF_CT_ASSERT(out);
- return nf_nat_setup_info(ct, &mr->range[0], hooknum);
- }
2.2 DNAT target
功能:实现DNAT功能
1.调用nf_ct_get,获取传入数据包关联的nf_conn变量
2.此处进行DNAT只是设置连接跟踪项中的reply方向的nf_conntrack_tuple变量,因此:
对于主连接,仅设置连接跟踪项的状态为NEW的DNAT操作,因为对于状态不为NEW的连接跟踪项,其reply方向的nf_conntrack_tuple结构的变量的目的地址和端口号已经修改过了,不需要再次修改了;
对于期望连接来说,当期望连接刚建立时,其状态仅为IP_CT_RELATED,才进行DNAT操作。
3.调用nf_nat_setup_info,根据targinfo中的地址范围与端口值修改连接跟踪项的reply方向的nf_conntrack_tuple变量中的值。
执行这个target只是修改了数据包对应的连接跟踪项的reply方向的tuple变量,并没有修改数据包的ip地址,而修改数据包的ip地址是nat模块的hook函数中执行的(在执行了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 struct xt_target *target,
- const void *targinfo)
- {
- struct nf_conn *ct;
- enum ip_conntrack_info ctinfo;
- const struct nf_nat_multi_range_compat *mr = targinfo;
- NF_CT_ASSERT(hooknum == NF_IP_PRE_ROUTING ||
- hooknum == NF_IP_LOCAL_OUT);
- ct = nf_ct_get(*pskb, &ctinfo);
- /* Connection must be valid and new. */
- NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));
- if (hooknum == NF_IP_LOCAL_OUT &&
- mr->range[0].flags & IP_NAT_RANGE_MAP_IPS)
- warn_if_extra_mangle((*pskb)->nh.iph->daddr,
- mr->range[0].min_ip);
- return nf_nat_setup_info(ct, &mr->range[0], hooknum);
- }
2.3 xt_target masquerade
static struct xt_target masquerade = {
.name = "MASQUERADE",
.family = AF_INET,
.target = masquerade_target,
.targetsize = sizeof(struct ip_nat_multi_range_compat),
.table = "nat",
.hooks = 1 << NF_IP_POST_ROUTING,
.checkentry = masquerade_check,
.me = THIS_MODULE,
};
这个target也是实现SNAT转换,但是比SNAT更智能,不需要输入SNAT转换后的源ip地址,可以根据出口设备与下一跳网关ip,找到要转换到的源ip地址,然后再调用nf_nat_setup_info函数,实现连接跟踪项的SNAT转换。
与ipt_snat_target相比增加了获取要转换到的源ip地址,主要是根据出口设备与下一跳网关地址,通过调用函数inet_select_addr(关于这个函数,可参看Linux inet_select_addr分析),获取ip地址的。
- static unsigned int
- masquerade_target(struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- unsigned int hooknum,
- const struct xt_target *target,
- const void *targinfo)
- {
- #ifdef CONFIG_NF_NAT_NEEDED
- struct nf_conn_nat *nat;
- #endif
- struct ip_conntrack *ct;
- enum ip_conntrack_info ctinfo;
- struct ip_nat_range newrange;
- const struct ip_nat_multi_range_compat *mr;
- struct rtable *rt;
- __be32 newsrc;
- IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);
- ct = ip_conntrack_get(*pskb, &ctinfo);
- #ifdef CONFIG_NF_NAT_NEEDED
- nat = nfct_nat(ct);
- #endif
- IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED
- || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
- /* Source address is 0.0.0.0 - locally generated packet that is
- * probably not supposed to be masqueraded.
- */
- #ifdef CONFIG_NF_NAT_NEEDED
- if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip == 0)
- #else
- if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip == 0)
- #endif
- return NF_ACCEPT;
- mr = targinfo;
- rt = (struct rtable *)(*pskb)->dst;
- newsrc = inet_select_addr(out, rt->rt_gateway, RT_SCOPE_UNIVERSE);
- if (!newsrc) {
- printk("MASQUERADE: %s ate my IP address\n", out->name);
- return NF_DROP;
- }
- write_lock_bh(&masq_lock);
- #ifdef CONFIG_NF_NAT_NEEDED
- nat->masq_index = out->ifindex;
- #else
- ct->nat.masq_index = out->ifindex;
- #endif
- write_unlock_bh(&masq_lock);
- /* Transfer from original range. */
- newrange = ((struct ip_nat_range)
- { mr->range[0].flags | IP_NAT_RANGE_MAP_IPS,
- newsrc, newsrc,
- mr->range[0].min, mr->range[0].max });
- /* Hand modified range to generic setup. */
- return ip_nat_setup_info(ct, &newrange, hooknum);
- }
至此,将NAT转换相关的所有主要的函数都分析完了。下面进行实例分析下。
3.实例分析
对于nat相关的函数,我们都分析完了,那我们就分别以SNAT与DNAT两种情况,来分析下数据包在网关中是如何实现地址转换的。
3.1 SNAT
这个就是典型的路由器工作机制,路由器的lan侧设备需要访问到互联网,而又只有路由器上的wan连接存在一个公网地址,此时lan侧pc发来的数据就需要进行SNAT转换。
3.1.1环境说明
lan1 pc ip:192.168.1.123
route wan ip为115.22.112.12
需要访问的外网的地址为ip 14.17.88.99
网关通过iptables做了SNAT,命令如下:
iptables -t nat -A POSTROUTING -s 192.168.1.132/32 -o wan0 -j SNAT --to-source 115.22.112.12
(iptables-t nat -A POSTROUTING -s 192.168.1.0/24 -o wan0 -j MASQUERADE真实的规则应该是这一个,因为真实的网关中,其wan侧ip可能会经常改变。此处分析SNAT时就以上面的命令为准)
3.1.2 数据SNAT转换分析
当第一个lan侧数据进入到路由器的wan接口时,在 PRE_ROUTING 创建一个nf_conn和两个nf_conntrack_tuple(origin 与reply)
其中origin tuple.src=192.168.1.123 origin tuple.dst=14.17.88.99 reply tuple.src=14.17.88.99 reply tuple.dst=192.168.1.3
当查找路由成功,要转发该数据包时,进入到POST_ROUTING链时,进入到NAT的hook函数时,查看到有SNAT的规则,经过SNAT后,会将tuple里的值修改如下:
其中origin tuple.src=192.168.1.123 origin tuple.dst=14.17.88.99 reply tuple.src=14.17.88.99 reply tuple.dst=115.22.112.12
当服务器14.17.88.99回复了一个数据包后(src=14.17.88.99 dst=115.22.112.12),进入到wan侧接口的PRE_ROUTING链时,则在调用其nat相关的hook函数后,会调用函数ip_nat_packet获取到origin tuple值,然后再根据origin tuple,计算出反方向的tuple,即为new_tuple.src = 14.17.88.99 new_tuple.dst = 192.168.1.123,然后就会根据这个新的tuple修改其目的ip地址,修改后的数据包的目的地址即为192.168.1.123 。然后再查找路由,将数据发送到正常的lan口。这就是nat的De-SNAT
3.2 DNAT
即路由器的lan侧设备中,有一个设备要作为server使用,这时候就需要使用dnat了。
3.2.1 环境说明
lan1 pc ip:192.168.1.183
route wan ip为115.22.123.12(外网看到的server的ip地址)
外网的地址为ip 14.17.88.22
iptables -t nat -A PREROUTING -i wan0 -j DNAT --to-destination 192.168.1.183
3.2.2 数据的DNAT分析
当外网client发送一个到server的请求数据。(其src ip 14.17.88.22 dst 115.22.123.12)
当数据到达路由器的wan0口,进入到PRE_ROUTING时,会先建立一个nf_conn结构,和两个nf_conntrack_tuple(origin 与reply)
其中origin tuple.src=14.17.88.22 origin tuple.dst=115.22.123.12 reply tuple.src=115.22.123.12 reply tuple.dst=14.17.88.22;然后又会进入到PRE_ROUTING的hook点的nat hook中,然后调用nat hook,查找nat表的DNAT规则,刚好找到了我们上面创建的规则,接着就会修改reply tuple。将reply tuple.src=115.22.123.12 reply tuple.dst=14.17.88.22修改为reply tuple.src=192.168.1.183 reply tuple.dst=14.17.88.22,然后再根据修改后的reply tuple,取反获取到新的tuple,即new_tuple.src=14.17.88.22,new_tuple.dst=192.168.1.183,然后就会根据这个tuple值将数据包的目的地址修改为192.168.1.183,接着查找路由,将数据包发送给lan侧server。
当lan侧发送一个回应的报文时(数据包的src为192.168.1.183 dst为14.17.88.22),然后当数据进入wan0的PRE_ROUTING链时,由于查找到的nf_conn没有SNAT标志,
则会继续查找路由,然后forward这个数据包;当数据包到达POST_ROUTING时,根据nf_conn的flag置位为DNAT,且为reply方向,就会查找origin tuple,然后根据origin
tuple的值,取反得到新的tuple:new_tuple.src=115.22.123.12, new_tuple.dst=14.17.88.22,然后根据这个新的tuple,修改数据包的src地址,修改后的数据包的地址
为src=115.22.123.12,dst=14.17.88.22,这就是nat的De-DNAT功能。
至此,将NAT转换的大致内容分析完了,在分析的过程中,自己学到了很多,通过这次分析后,再次使用iptables命令,发现比以前熟悉多了,看来理解一件事情的来龙去脉后,就能从一个高度上进行整体的分析了,有了更全面的视角。通过分析netfilter,让我明白了,能在netfilter里实现的功能,尽量不要通过修改协议栈的代码来实现,以保证协议栈的稳定性,对于一个网络程序员来说,尽量少的修改协议栈代码,应尽量使用netfilter来完成大多数的过滤、转换、限制等功能,netfilter真的是太强大了。