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

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static unsigned int  
  2. nf_nat_in(unsigned int hooknum,  
  3.   struct sk_buff **pskb,  
  4.   const struct net_device *in,  
  5.   const struct net_device *out,  
  6.   int (*okfn)(struct sk_buff *))  
  7. {  
  8. unsigned int ret;  
  9. __be32 daddr = (*pskb)->nh.iph->daddr;  
  10.    
  11. ret = nf_nat_fn(hooknum, pskb, in, out, okfn);  
  12. if (ret != NF_DROP && ret != NF_STOLEN &&  
  13.     daddr != (*pskb)->nh.iph->daddr) {  
  14. dst_release((*pskb)->dst);  
  15. (*pskb)->dst = NULL;  
  16. }  
  17. return ret;  
  18. }  

 

 

 

 

该函数主要是通过调用nf_nat_fn,该函数是一个通用NAT转换函数,待会着重分析这个函数

1.2 nf_nat_out

这个函数是NAT模块在POST_ROUTING hook点的hook回调函数,该函数实现如下功能:

1. 调用函数ip_nat_fn实现SNAT转换

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static unsigned int  
  2. nf_nat_out(unsigned int hooknum,  
  3.    struct sk_buff **pskb,  
  4.    const struct net_device *in,  
  5.    const struct net_device *out,  
  6.    int (*okfn)(struct sk_buff *))  
  7. {  
  8. #ifdef CONFIG_XFRM  
  9. struct nf_conn *ct;  
  10. enum ip_conntrack_info ctinfo;  
  11. #endif  
  12. unsigned int ret;  
  13.    
  14. /* root is playing with raw sockets. */  
  15. if ((*pskb)->len < sizeof(struct iphdr) ||  
  16.     (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))  
  17. return NF_ACCEPT;  
  18.    
  19. ret = nf_nat_fn(hooknum, pskb, in, out, okfn);  
  20. #ifdef CONFIG_XFRM  
  21. if (ret != NF_DROP && ret != NF_STOLEN &&  
  22.     (ct = nf_ct_get(*pskb, &ctinfo)) != NULL) {  
  23. enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);  
  24.    
  25. if (ct->tuplehash[dir].tuple.src.u3.ip !=  
  26.     ct->tuplehash[!dir].tuple.dst.u3.ip  
  27.     || ct->tuplehash[dir].tuple.src.u.all !=  
  28.        ct->tuplehash[!dir].tuple.dst.u.all  
  29.     )  
  30. return ip_xfrm_me_harder(pskb) == 0 ? ret : NF_DROP;  
  31. }  
  32. #endif  
  33. return ret;  
  34. }  



 

这个函数同样是调用函数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里实现路由 查找。)。

 

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static unsigned int  
  2. nf_nat_local_fn(unsigned int hooknum,  
  3. struct sk_buff **pskb,  
  4. const struct net_device *in,  
  5. const struct net_device *out,  
  6. int (*okfn)(struct sk_buff *))  
  7. {  
  8. struct nf_conn *ct;  
  9. enum ip_conntrack_info ctinfo;  
  10. unsigned int ret;  
  11.    
  12. /* root is playing with raw sockets. */  
  13. if ((*pskb)->len < sizeof(struct iphdr) ||  
  14.     (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))  
  15. return NF_ACCEPT;  
  16.    
  17. ret = nf_nat_fn(hooknum, pskb, in, out, okfn);  
  18. if (ret != NF_DROP && ret != NF_STOLEN &&  
  19.     (ct = nf_ct_get(*pskb, &ctinfo)) != NULL) {  
  20. enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);  
  21.    
  22. if (ct->tuplehash[dir].tuple.dst.u3.ip !=  
  23.     ct->tuplehash[!dir].tuple.src.u3.ip) {  
  24. if (ip_route_me_harder(pskb, RTN_UNSPEC))  
  25. ret = NF_DROP;  
  26. }  
  27. #ifdef CONFIG_XFRM  
  28. else if (ct->tuplehash[dir].tuple.dst.u.all !=  
  29.  ct->tuplehash[!dir].tuple.src.u.all)  
  30. if (ip_xfrm_me_harder(pskb))  
  31. ret = NF_DROP;  
  32. #endif  
  33. }  
  34. return ret;  
  35. }  



 

这个函数其实也是调用函数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转换。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static unsigned int  
  2. nf_nat_fn(unsigned int hooknum,  
  3.   struct sk_buff **pskb,  
  4.   const struct net_device *in,  
  5.   const struct net_device *out,  
  6.   int (*okfn)(struct sk_buff *))  
  7. {  
  8. struct nf_conn *ct;  
  9. enum ip_conntrack_info ctinfo;  
  10. struct nf_conn_nat *nat;  
  11. struct nf_nat_info *info;  
  12. /* maniptype == SRC for postrouting. */  
  13. /*获取NAT类型*/  
  14. enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum);  
  15.    
  16. /* We never see fragments: conntrack defrags on pre-routing 
  17.    and local-out, and nf_nat_out protects post-routing. */  
  18. NF_CT_ASSERT(!((*pskb)->nh.iph->frag_off  
  19.        & htons(IP_MF|IP_OFFSET)));  
  20. /*获取该数据包对应的连接跟踪项*/  
  21. ct = nf_ct_get(*pskb, &ctinfo);  
  22. /* Can't track?  It's not due to stress, or conntrack would 
  23.    have dropped it.  Hence it's the user's responsibilty to 
  24.    packet filter it out, or implement conntrack/NAT for that 
  25.    protocol. 8) --RR */  
  26. /* 
  27. 当数据包没有连接跟踪项,且为icmp_redirect时,返回DROP; 
  28. 当数据包没有连接跟踪项,且不是icmp_redirect时,返回ACCEPT; 
  29. */     
  30. if (!ct) {  
  31. /* Exception: ICMP redirect to new connection (not in 
  32.    hash table yet).  We must not let this through, in 
  33.    case we're doing NAT to the same network. */  
  34. if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {  
  35. struct icmphdr _hdr, *hp;  
  36.    
  37. hp = skb_header_pointer(*pskb,  
  38. (*pskb)->nh.iph->ihl*4,  
  39. sizeof(_hdr), &_hdr);  
  40. if (hp != NULL &&  
  41.     hp->type == ICMP_REDIRECT)  
  42. return NF_DROP;  
  43. }  
  44. return NF_ACCEPT;  
  45. }  
  46.    
  47. /* Don't try to NAT if this packet is not conntracked */  
  48. /*对于连接跟踪项为nf_conntrack_untracked,则说明不对该数据包进行连接跟踪,此时直接返回ACCEPT*/  
  49. if (ct == &nf_conntrack_untracked)  
  50. return NF_ACCEPT;  
  51. /*当连接跟踪项没有关联的nf_conn_nat变量时,返回ACCEPT*/  
  52. nat = nfct_nat(ct);  
  53. if (!nat)  
  54. return NF_ACCEPT;  
  55. /* 
  56. 对于期望连接original与reply方向的数据包,对于icmp协议的数据包,进行nat操作; 
  57. 对于期望连接、及非期望连接的NEW状态下的连接跟踪项,只有连接跟踪项的NAT操作没有进行 
  58. 的情况下才进行NAT转换操作。 
  59. a)对于LOCAL_IN的hook点,调用alloc_null_binding进行NAT操作,可能会修改四层协议相关的关键字 
  60. b)对于已经确认过却没有进行NAT操作的连接跟踪项,调用alloc_null_binding_confirmed进行NAT操作,只有可能修改 
  61.    四层协议相关的关键字。 
  62. c)对于其他情况,则通过nf_nat_rule_find,查找iptables的nat表中有没有匹配该数据流的NAT规则,若有则根据 
  63.   NAT类型,调用相应的target进行NAT操作(SNAT target 、DNAT target) 
  64. */  
  65. switch (ctinfo) {  
  66. case IP_CT_RELATED:  
  67. case IP_CT_RELATED+IP_CT_IS_REPLY:  
  68. if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {  
  69. if (!nf_nat_icmp_reply_translation(ct, ctinfo,  
  70.    hooknum, pskb))  
  71. return NF_DROP;  
  72. else  
  73. return NF_ACCEPT;  
  74. }  
  75. /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */  
  76. case IP_CT_NEW:  
  77. info = &nat->info;  
  78.    
  79. /* Seen it before?  This can happen for loopback, retrans, 
  80.    or local packets.. */  
  81. if (!nf_nat_initialized(ct, maniptype)) {  
  82. unsigned int ret;  
  83.    
  84. if (unlikely(nf_ct_is_confirmed(ct)))  
  85. /* NAT module was loaded late */  
  86. ret = alloc_null_binding_confirmed(ct, info,  
  87.    hooknum);  
  88. else if (hooknum == NF_IP_LOCAL_IN)  
  89. /* LOCAL_IN hook doesn't have a chain!  */  
  90. ret = alloc_null_binding(ct, info, hooknum);  
  91. else  
  92. ret = nf_nat_rule_find(pskb, hooknum, in, out,  
  93.        ct, info);  
  94.    
  95. if (ret != NF_ACCEPT) {  
  96. return ret;  
  97. }  
  98. else  
  99. DEBUGP("Already setup manip %s for ct %p\n",  
  100.        maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",  
  101.        ct);  
  102. break;  
  103.    
  104. default:  
  105. /* ESTABLISHED */  
  106. NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED ||  
  107.      ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));  
  108. info = &nat->info;  
  109. }  
  110.    
  111. NF_CT_ASSERT(info);  
  112. /*调用nf_nat_packet,根据连接跟踪项的reply tuple变量实现对数据包的NAT操作*/  
  113. return nf_nat_packet(ct, ctinfo, hooknum, pskb);  
  114. }  
  115.    



 

这个函数主要涉及了函数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操作。

 

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static inline int nf_nat_initialized(struct nf_conn *ct,  
  2.      enum nf_nat_manip_type manip)  
  3. {  
  4. if (manip == IP_NAT_MANIP_SRC)  
  5. return test_bit(IPS_SRC_NAT_DONE_BIT, &ct->status);  
  6. else  
  7. return test_bit(IPS_DST_NAT_DONE_BIT, &ct->status);  
  8. }  



 

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转换操作。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. unsigned int  
  2. alloc_null_binding_confirmed(struct nf_conn *ct,  
  3.      struct nf_nat_info *info,  
  4.      unsigned int hooknum)  
  5. {  
  6. __be32 ip  
  7. = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC  
  8.    ? ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip  
  9.    : ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.ip);  
  10. u_int16_t all  
  11. = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC  
  12.    ? ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.all  
  13.    : ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u.all);  
  14. struct nf_nat_range range  
  15. = { IP_NAT_RANGE_MAP_IPS, ip, ip, { all }, { all } };  
  16.    
  17. DEBUGP("Allocating NULL binding for confirmed %p (%u.%u.%u.%u)\n",  
  18.        ct, NIPQUAD(ip));  
  19. return nf_nat_setup_info(ct, &range, hooknum);  
  20. }  



 

 

函数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进行连接跟踪项的转换。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. inline unsigned int  
  2. alloc_null_binding(struct nf_conn *ct,  
  3.    struct nf_nat_info *info,  
  4.    unsigned int hooknum)  
  5. {  
  6. /* Force range to this IP; let proto decide mapping for 
  7.    per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED). 
  8.    Use reply in case it's already been mangled (eg local packet). 
  9. */  
  10. __be32 ip  
  11. = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC  
  12.    ? ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip  
  13.    : ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.ip);  
  14. struct nf_nat_range range  
  15. = { IP_NAT_RANGE_MAP_IPS, ip, ip, { 0 }, { 0 } };  
  16.    
  17. DEBUGP("Allocating NULL binding for %p (%u.%u.%u.%u)\n",  
  18.        ct, NIPQUAD(ip));  
  19. return nf_nat_setup_info(ct, &range, hooknum);  
  20. }  



 

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变量的四层协议相关的关键字(也就是端口号之类的)即可。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. int nf_nat_rule_find(struct sk_buff **pskb,  
  2.      unsigned int hooknum,  
  3.      const struct net_device *in,  
  4.      const struct net_device *out,  
  5.      struct nf_conn *ct,  
  6.      struct nf_nat_info *info)  
  7. {  
  8. int ret;  
  9.    
  10. ret = ipt_do_table(pskb, hooknum, in, out, &nat_table);  
  11.    
  12. if (ret == NF_ACCEPT) {  
  13. if (!nf_nat_initialized(ct, HOOK2MANIP(hooknum)))  
  14. /* NUL mapping */  
  15. ret = alloc_null_binding(ct, info, hooknum);  
  16. }  
  17. return ret;  
  18. }  



 

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地址。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. unsigned int nf_nat_packet(struct nf_conn *ct,  
  2.    enum ip_conntrack_info ctinfo,  
  3.    unsigned int hooknum,  
  4.    struct sk_buff **pskb)  
  5. {  
  6. enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);  
  7. unsigned long statusbit;  
  8. enum nf_nat_manip_type mtype = HOOK2MANIP(hooknum);  
  9.    
  10. if (mtype == IP_NAT_MANIP_SRC)  
  11. statusbit = IPS_SRC_NAT;  
  12. else  
  13. statusbit = IPS_DST_NAT;  
  14.    
  15. /* Invert if this is reply dir. */  
  16. if (dir == IP_CT_DIR_REPLY)  
  17. statusbit ^= IPS_NAT_MASK;  
  18.    
  19. /* Non-atomic: these bits don't change. */  
  20. if (ct->status & statusbit) {  
  21. struct nf_conntrack_tuple target;  
  22.    
  23. /* We are aiming to look like inverse of other direction. */  
  24. nf_ct_invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);  
  25.    
  26. if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))  
  27. return NF_DROP;  
  28. }  
  29. return NF_ACCEPT;  
  30. }  
  31.    



 

以上把函数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)上只能初始化一次。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. unsigned int  
  2. nf_nat_setup_info(struct nf_conn *ct,  
  3.   const struct nf_nat_range *range,  
  4.   unsigned int hooknum)  
  5. {  
  6. struct nf_conntrack_tuple curr_tuple, new_tuple;  
  7. struct nf_conn_nat *nat = nfct_nat(ct);  
  8. struct nf_nat_info *info = &nat->info;  
  9. int have_to_hash = !(ct->status & IPS_NAT_DONE_MASK);  
  10. enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum);  
  11.    
  12. NF_CT_ASSERT(hooknum == NF_IP_PRE_ROUTING ||  
  13.      hooknum == NF_IP_POST_ROUTING ||  
  14.      hooknum == NF_IP_LOCAL_IN ||  
  15.      hooknum == NF_IP_LOCAL_OUT);  
  16. BUG_ON(nf_nat_initialized(ct, maniptype));  
  17.    
  18. /* What we've got will look like inverse of reply. Normally 
  19.    this is what is in the conntrack, except for prior 
  20.    manipulations (future optimization: if num_manips == 0, 
  21.    orig_tp = 
  22.    conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple) */  
  23. nf_ct_invert_tuplepr(&curr_tuple,  
  24.      &ct->tuplehash[IP_CT_DIR_REPLY].tuple);  
  25.    
  26. get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);  
  27.    
  28. if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {  
  29. struct nf_conntrack_tuple reply;  
  30.    
  31. /* Alter conntrack table so will recognize replies. */  
  32. nf_ct_invert_tuplepr(&reply, &new_tuple);  
  33. nf_conntrack_alter_reply(ct, &reply);  
  34.    
  35. /* Non-atomic: we own this at the moment. */  
  36. if (maniptype == IP_NAT_MANIP_SRC)  
  37. ct->status |= IPS_SRC_NAT;  
  38. else  
  39. ct->status |= IPS_DST_NAT;  
  40. }  
  41.    
  42. /* Place in source hash if this is the first time. */  
  43. if (have_to_hash) {  
  44. unsigned int srchash;  
  45.    
  46. srchash = hash_by_src(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);  
  47. write_lock_bh(&nf_nat_lock);  
  48. list_add(&info->bysource, &bysource[srchash]);  
  49. write_unlock_bh(&nf_nat_lock);  
  50. }  
  51.    
  52. /* It's done. */  
  53. if (maniptype == IP_NAT_MANIP_DST)  
  54. set_bit(IPS_DST_NAT_DONE_BIT, &ct->status);  
  55. else  
  56. set_bit(IPS_SRC_NAT_DONE_BIT, &ct->status);  
  57.    
  58. return NF_ACCEPT;  
  59. }  



 

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转换

*/ 

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static void  
  2. get_unique_tuple(struct nf_conntrack_tuple *tuple,  
  3.  const struct nf_conntrack_tuple *orig_tuple,  
  4.  const struct nf_nat_range *range,  
  5.  struct nf_conn *ct,  
  6.  enum nf_nat_manip_type maniptype)  
  7. {  
  8. struct nf_nat_protocol *proto;  
  9.    
  10. /* 1) If this srcip/proto/src-proto-part is currently mapped, 
  11.    and that same mapping gives a unique tuple within the given 
  12.    range, use that. 
  13.   
  14.    This is only required for source (ie. NAT/masq) mappings. 
  15.    So far, we don't do local source mappings, so multiple 
  16.    manips not an issue.  */  
  17. if (maniptype == IP_NAT_MANIP_SRC) {  
  18. if (find_appropriate_src(orig_tuple, tuple, range)) {  
  19. DEBUGP("get_unique_tuple: Found current src map\n");  
  20. if (!(range->flags & IP_NAT_RANGE_PROTO_RANDOM))  
  21. if (!nf_nat_used_tuple(tuple, ct))  
  22. return;  
  23. }  
  24. }  
  25.    
  26. /* 2) Select the least-used IP/proto combination in the given 
  27.    range. */  
  28. *tuple = *orig_tuple;  
  29. find_best_ips_proto(tuple, range, ct, maniptype);  
  30.    
  31. /* 3) The per-protocol part of the manip is made to map into 
  32.    the range to make a unique tuple. */  
  33.    
  34. rcu_read_lock();  
  35. proto = __nf_nat_proto_find(orig_tuple->dst.protonum);  
  36.    
  37. /* Change protocol info to have some randomization */  
  38. if (range->flags & IP_NAT_RANGE_PROTO_RANDOM) {  
  39. proto->unique_tuple(tuple, range, maniptype, ct);  
  40. goto out;  
  41. }  
  42.    
  43. /* Only bother mapping if it's not already in range and unique */  
  44. if ((!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) ||  
  45.      proto->in_range(tuple, maniptype, &range->min, &range->max)) &&  
  46.     !nf_nat_used_tuple(tuple, ct))  
  47. goto out;  
  48.    
  49. /* Last change: get protocol to try to obtain unique tuple. */  
  50. proto->unique_tuple(tuple, range, maniptype, ct);  
  51. out:  
  52. rcu_read_unlock();  
  53. }  



 

这个函数里有两个重要函数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操作的

 

 

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static int  
  2. find_appropriate_src(const struct nf_conntrack_tuple *tuple,  
  3.      struct nf_conntrack_tuple *result,  
  4.      const struct nf_nat_range *range)  
  5. {  
  6. unsigned int h = hash_by_src(tuple);  
  7. struct nf_conn_nat *nat;  
  8. struct nf_conn *ct;  
  9.    
  10. read_lock_bh(&nf_nat_lock);  
  11. list_for_each_entry(nat, &bysource[h], info.bysource) {  
  12. ct = (struct nf_conn *)((char *)nat - offsetof(struct nf_conn, data));  
  13. if (same_src(ct, tuple)) {  
  14. /* Copy source part from reply tuple. */  
  15. nf_ct_invert_tuplepr(result,  
  16.        &ct->tuplehash[IP_CT_DIR_REPLY].tuple);  
  17. result->dst = tuple->dst;  
  18.    
  19. if (in_range(result, range)) {  
  20. read_unlock_bh(&nf_nat_lock);  
  21. return 1;  
  22. }  
  23. }  
  24. }  
  25. read_unlock_bh(&nf_nat_lock);  
  26. return 0;  
  27. }  
  28.    



 

 

same_src的功能是判断传入tuple的源ip及源端口号是否与传入连接跟踪项的原始方向的tuple变量的源ip及源端口号相等。此处只是对原始方向的tuple变量的原始方向的ip地址、原始方向的端口号等以及四层协议号相等,即认为相同,而不管目的ip地址与目的端口号是否相等。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static inline int  
  2. same_src(const struct nf_conn *ct,  
  3.  const struct nf_conntrack_tuple *tuple)  
  4. {  
  5. const struct nf_conntrack_tuple *t;  
  6.    
  7. t = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;  
  8. return (t->dst.protonum == tuple->dst.protonum &&  
  9. t->src.u3.ip == tuple->src.u3.ip &&  
  10. t->src.u.all == tuple->src.u.all);  
  11. }  
  12.    
  13.    



 

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.

*/

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static int  
  2. in_range(const struct nf_conntrack_tuple *tuple,  
  3.  const struct nf_nat_range *range)  
  4. {  
  5. struct nf_nat_protocol *proto;  
  6. int ret = 0;  
  7.    
  8. /* If we are supposed to map IPs, then we must be in the 
  9.    range specified, otherwise let this drag us onto a new src IP. */  
  10. if (range->flags & IP_NAT_RANGE_MAP_IPS) {  
  11. if (ntohl(tuple->src.u3.ip) < ntohl(range->min_ip) ||  
  12.     ntohl(tuple->src.u3.ip) > ntohl(range->max_ip))  
  13. return 0;  
  14. }  
  15.    
  16. rcu_read_lock();  
  17. /*根据四层协议号,在nf_nat_protos[]数组中找到相应的nf_nat_protocol变量*/  
  18. proto = __nf_nat_proto_find(tuple->dst.protonum);  
  19. /* 
  20. 当range没有标识四层相关的关键字检查标签或者 
  21. 设置四层检查的标签,且调用相应四层nf_nat_protocol变量的in_range函数后,检查通过时,则返回1 
  22. */  
  23. if (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) ||  
  24.     proto->in_range(tuple, IP_NAT_MANIP_SRC,  
  25.     &range->min, &range->max))  
  26. ret = 1;  
  27. rcu_read_unlock();  
  28.    
  29. return ret;  
  30. }  
  31.    



 

 

1.5.2.1.2 nf_nat_used_tuple

 

 

该函数主要是判断已传入tuple取反获取到的reply tuple变量,是否已经被其他连接跟踪项使用了:

 若已经被其他连接跟踪项使用了,则返回TRUE;

 若没有被其他连接跟踪项使用,则返回FALSE

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. int  
  2. nf_nat_used_tuple(const struct nf_conntrack_tuple *tuple,  
  3.   const struct nf_conn *ignored_conntrack)  
  4. {  
  5. /* Conntrack tracking doesn't keep track of outgoing tuples; only 
  6.    incoming ones.  NAT means they don't have a fixed mapping, 
  7.    so we invert the tuple and look for the incoming reply. 
  8.   
  9.    We could keep a separate hash if this proves too slow. */  
  10. struct nf_conntrack_tuple reply;  
  11.    
  12. nf_ct_invert_tuplepr(&reply, tuple);  
  13. return nf_conntrack_tuple_taken(&reply, ignored_conntrack);  
  14. }  



 

主要是通过函数 nf_conntrack_tuple_taken实现的。

nf_conntrack_tuple_taken是判断连接跟踪项的确认链表中,是否存在连接跟踪项不等于ignored_conntrack,且连接跟踪项的某个tuple值与传入值相等,若存在,则返回TRUE,则不能用传入的tuple变量作为NAT的依据;若不存在,返回FALSE,说明可以用传入的tuple变量作为NAT的依据

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. int  
  2. nf_conntrack_tuple_taken(const struct nf_conntrack_tuple *tuple,  
  3.  const struct nf_conn *ignored_conntrack)  
  4. {  
  5. struct nf_conntrack_tuple_hash *h;  
  6.    
  7. read_lock_bh(&nf_conntrack_lock);  
  8. h = __nf_conntrack_find(tuple, ignored_conntrack);  
  9. read_unlock_bh(&nf_conntrack_lock);  
  10.    
  11. return h != NULL;  
  12. }  
  13.    



 

而函数__nf_conntrack_find才是最终的函数,该函数查找连接跟踪的确认链表中,是否存在连接跟踪项不等于ignored_conntrack,且连接跟踪项的某一个tuple变量与传入的tuple变量相等的情况,若有则说明传入的tuple变量不能作为NAT转换的依据。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. struct nf_conntrack_tuple_hash *  
  2. __nf_conntrack_find(const struct nf_conntrack_tuple *tuple,  
  3.     const struct nf_conn *ignored_conntrack)  
  4. {  
  5. struct nf_conntrack_tuple_hash *h;  
  6. unsigned int hash = hash_conntrack(tuple);  
  7.    
  8. list_for_each_entry(h, &nf_conntrack_hash[hash], list) {  
  9. if (nf_ct_tuplehash_to_ctrack(h) != ignored_conntrack &&  
  10.     nf_ct_tuple_equal(tuple, &h->tuple)) {  
  11. NF_CT_STAT_INC(found);  
  12. return h;  
  13. }  
  14. NF_CT_STAT_INC(searched);  
  15. }  
  16.    
  17. return NULL;  
  18. }  



 

 

至此分析完了函数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回调函数的原因。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. void nf_conntrack_alter_reply(struct nf_conn *ct,  
  2.       const struct nf_conntrack_tuple *newreply)  
  3. {  
  4. struct nf_conn_help *help = nfct_help(ct);  
  5.    
  6. write_lock_bh(&nf_conntrack_lock);  
  7. /* Should be unconfirmed, so not in hash table yet */  
  8. NF_CT_ASSERT(!nf_ct_is_confirmed(ct));  
  9.    
  10. DEBUGP("Altering reply tuple of %p to ", ct);  
  11. NF_CT_DUMP_TUPLE(newreply);  
  12.    
  13. ct->tuplehash[IP_CT_DIR_REPLY].tuple = *newreply;  
  14. if (!ct->master && help && help->expecting == 0)  
  15. help->helper = __nf_ct_helper_find(newreply);  
  16. write_unlock_bh(&nf_conntrack_lock);  
  17. }  



 

至此将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的

 连接跟踪项

 )

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static unsigned int ipt_snat_target(struct sk_buff **pskb,  
  2.     const struct net_device *in,  
  3.     const struct net_device *out,  
  4.     unsigned int hooknum,  
  5.     const struct xt_target *target,  
  6.     const void *targinfo)  
  7. {  
  8. struct nf_conn *ct;  
  9. enum ip_conntrack_info ctinfo;  
  10. const struct nf_nat_multi_range_compat *mr = targinfo;  
  11.    
  12. NF_CT_ASSERT(hooknum == NF_IP_POST_ROUTING);  
  13.    
  14. ct = nf_ct_get(*pskb, &ctinfo);  
  15.    
  16. /* Connection must be valid and new. */  
  17. NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||  
  18.     ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));  
  19. NF_CT_ASSERT(out);  
  20.    
  21. return nf_nat_setup_info(ct, &mr->range[0], hooknum);  
  22. }  
  23.    



 

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操作后才会执行)。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static unsigned int ipt_dnat_target(struct sk_buff **pskb,  
  2.     const struct net_device *in,  
  3.     const struct net_device *out,  
  4.     unsigned int hooknum,  
  5.     const struct xt_target *target,  
  6.     const void *targinfo)  
  7. {  
  8. struct nf_conn *ct;  
  9. enum ip_conntrack_info ctinfo;  
  10. const struct nf_nat_multi_range_compat *mr = targinfo;  
  11.    
  12. NF_CT_ASSERT(hooknum == NF_IP_PRE_ROUTING ||  
  13.      hooknum == NF_IP_LOCAL_OUT);  
  14.    
  15. ct = nf_ct_get(*pskb, &ctinfo);  
  16.    
  17. /* Connection must be valid and new. */  
  18. NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));  
  19.    
  20. if (hooknum == NF_IP_LOCAL_OUT &&  
  21.     mr->range[0].flags & IP_NAT_RANGE_MAP_IPS)  
  22. warn_if_extra_mangle((*pskb)->nh.iph->daddr,  
  23.      mr->range[0].min_ip);  
  24.    
  25. return nf_nat_setup_info(ct, &mr->range[0], hooknum);  
  26. }  
  27.    



 

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地址的。

 

 

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static unsigned int  
  2. masquerade_target(struct sk_buff **pskb,  
  3.   const struct net_device *in,  
  4.   const struct net_device *out,  
  5.   unsigned int hooknum,  
  6.   const struct xt_target *target,  
  7.   const void *targinfo)  
  8. {  
  9. #ifdef CONFIG_NF_NAT_NEEDED  
  10. struct nf_conn_nat *nat;  
  11. #endif  
  12. struct ip_conntrack *ct;  
  13. enum ip_conntrack_info ctinfo;  
  14. struct ip_nat_range newrange;  
  15. const struct ip_nat_multi_range_compat *mr;  
  16. struct rtable *rt;  
  17. __be32 newsrc;  
  18.    
  19. IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);  
  20.    
  21. ct = ip_conntrack_get(*pskb, &ctinfo);  
  22. #ifdef CONFIG_NF_NAT_NEEDED  
  23. nat = nfct_nat(ct);  
  24. #endif  
  25. IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED  
  26.     || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));  
  27.    
  28. /* Source address is 0.0.0.0 - locally generated packet that is 
  29.  * probably not supposed to be masqueraded. 
  30.  */  
  31. #ifdef CONFIG_NF_NAT_NEEDED  
  32. if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip == 0)  
  33. #else  
  34. if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip == 0)  
  35. #endif  
  36. return NF_ACCEPT;  
  37.    
  38. mr = targinfo;  
  39. rt = (struct rtable *)(*pskb)->dst;  
  40. newsrc = inet_select_addr(out, rt->rt_gateway, RT_SCOPE_UNIVERSE);  
  41. if (!newsrc) {  
  42. printk("MASQUERADE: %s ate my IP address\n", out->name);  
  43. return NF_DROP;  
  44. }  
  45.    
  46. write_lock_bh(&masq_lock);  
  47. #ifdef CONFIG_NF_NAT_NEEDED  
  48. nat->masq_index = out->ifindex;  
  49. #else  
  50. ct->nat.masq_index = out->ifindex;  
  51. #endif  
  52. write_unlock_bh(&masq_lock);  
  53.    
  54. /* Transfer from original range. */  
  55. newrange = ((struct ip_nat_range)  
  56. { mr->range[0].flags | IP_NAT_RANGE_MAP_IPS,  
  57.   newsrc, newsrc,  
  58.   mr->range[0].min, mr->range[0].max });  
  59.    
  60. /* Hand modified range to generic setup. */  
  61. return ip_nat_setup_info(ct, &newrange, hooknum);  
  62. }  



 

 

 

至此,将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真的是太强大了。

posted @ 2016-08-04 19:00  峰一般的人  阅读(4618)  评论(0编辑  收藏  举报