本節主要是分析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真的是太強大了。
