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真的是太強大了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM