linux下ip協議(V4)的實現(二)

這次主要介紹下forward和local delivery。 

上次我們提到當ip_rcv_finish完成後後調用相關的發送函數ip_forward或者ip_local_deliver.這次就主要介紹這兩個函數。 

先來看forward。 

forward一般由下面幾部組成: 

1 執行ip option 
2 確定這個包能被forward 
3 減小ttl,當ttl爲0時,丟掉這個包 
4 如果需要,則將這個包切片 
5 發送包到輸出網絡設備 

這裏要注意,如果包由於一些原因,不能被forward,則必鬚髮送ICMP消息到發送主機。 

Java代碼  收藏代碼
  1. int ip_forward(struct sk_buff *skb)  
  2. {  
  3.     struct iphdr *iph;  /* Our header */  
  4.     struct rtable *rt;  /* Route we use */  
  5.     struct ip_options * opt = &(IPCB(skb)->opt);  
  6. ///gso相關設置  
  7.     if (skb_warn_if_lro(skb))  
  8.         goto drop;  
  9. ///xfrm(ipsec)的相關檢測  
  10.     if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))  
  11.         goto drop;  
  12. ///判斷是否有Router_alter option(也就是保存發送端的ip),如果有的話,調用ip_call_ra_chain處理,當空間已滿,則返回false,並繼續處理。  
  13.     if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))  
  14.         return NET_RX_SUCCESS;  
  15.   
  16. ///判斷這個包是否是由本地主機的2層進行接受的。在2層設置幀的類型,當幀的目的地址就是本機2層地址的時候,skb->pkt_type設置爲PCAKET_HOST.  
  17.     if (skb->pkt_type != PACKET_HOST)  
  18.         goto drop;  
  19.   
  20. ///由於是forward,因此我們不需要在意4層的校驗。設置ip_summed爲CHECKSUM_NONE。  
  21.     skb_forward_csum(skb);  
  22.   
  23.   
  24.     /* 
  25.      *  According to the RFC, we must first decrease the TTL field. If 
  26.      *  that reaches zero, we must reply an ICMP control message telling 
  27.      *  that the packet's lifetime expired. 
  28.      */  
  29. ///ttl小於1,此時丟掉這個包  
  30.     if (ip_hdr(skb)->ttl <= 1)  
  31.         goto too_many_hops;  
  32.   
  33. ///ipsec的檢測  
  34.     if (!xfrm4_route_forward(skb))  
  35.         goto drop;  
  36.   
  37. ///得到路由表  
  38.     rt = skb->rtable;  
  39.   
  40. ///判斷是否是Strict源路由option。如果是的話,看源路由option所制定的路由能否和rt_gateway(下一跳)匹配。  
  41.     if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)  
  42.         goto sr_failed;  
  43.   
  44. ///檢測一些相關域。如果出錯,則發送icmp,並丟棄這個包  
  45.     if (unlikely(skb->len > dst_mtu(&rt->u.dst) && !skb_is_gso(skb) &&  
  46.              (ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) {  
  47.         IP_INC_STATS(dev_net(rt->u.dst.dev), IPSTATS_MIB_FRAGFAILS);  
  48.         icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,  
  49.               htonl(dst_mtu(&rt->u.dst)));  
  50.         goto drop;  
  51.     }  
  52.   
  53. ///由於我們將要修改這個skb的一些東西(在下面的ip_forward_finish中),因此我們需要複製一個拷貝(主要是防止skb共享)  
  54.     if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))  
  55.         goto drop;  
  56.     iph = ip_hdr(skb);  
  57.   
  58. ///減少ttl  
  59.     ip_decrease_ttl(iph);  
  60.   
  61. ///如果我們所找到的下一跳地址比請求的更好的話,源host現在將會收到一個ICMP REDIRESCT消息(只有當源host沒有請求 source routing option時)  
  62.     if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb->sp)  
  63.         ip_rt_send_redirect(skb);  
  64.   
  65. ///QOS的優先級設置  
  66.     skb->priority = rt_tos2priority(iph->tos);  
  67.   
  68. ///最終返回netfilter的hook,這裏我們還是暫時忽略netfilter,只關注ip_forward_finish.  
  69.     return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,  
  70.                ip_forward_finish);  
  71.   
  72. sr_failed:  
  73.     /* 
  74.      *  Strict routing permits no gatewaying 
  75.      */  
  76.      icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);  
  77.      goto drop;  
  78.   
  79. too_many_hops:  
  80.     /* Tell the sender its packet died... */  
  81.     IP_INC_STATS_BH(dev_net(skb->dst->dev), IPSTATS_MIB_INHDRERRORS);  
  82.     icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);  
  83. drop:  
  84.     kfree_skb(skb);  
  85.     return NET_RX_DROP;  
  86. }  


下來看ip_forward_finish方法,它主要是用來處理一些剩下的options,並且ip_forward_options還會重新計算ip checksum,因爲它會update一些ip頭的域: 

Java代碼  收藏代碼
  1. static int ip_forward_finish(struct sk_buff *skb)  
  2. {  
  3.     struct ip_options * opt = &(IPCB(skb)->opt);  
  4.   
  5.     IP_INC_STATS_BH(dev_net(skb->dst->dev), IPSTATS_MIB_OUTFORWDATAGRAMS);  
  6.   
  7.     if (unlikely(opt->optlen))  
  8.         ip_forward_options(skb);  
  9. ///最終返回dst_output,這個虛函數最終調用skb->dst_output,如果是單播則是ip_output,如果是多播則是ip_mc_output.而且切片(如果有需要)也會在這個函數進行).這裏還有一個neighboring subsystem的概念,我們後面會講到。  
  10.     return dst_output(skb);  
  11. }  


來看ip_local_deliver函數: 

Java代碼  收藏代碼
  1. int ip_local_deliver(struct sk_buff *skb)  
  2. {  
  3.     /* 
  4.      *  Reassemble IP fragments. 
  5.      */  
  6. ///如果有切片,則開始組包。  
  7.     if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {  
  8.         if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))  
  9.             return 0;  
  10.     }  
  11. ///返回netfilter hook,最終會調用ip_local_deliver_finish.它最終會將數據包發送往4層。下一次我們會詳細介紹這個函數。  
  12.     return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL,  
  13.                ip_local_deliver_finish);  
  14. }  


這裏我們可以看到一個對比,那就是forward時,不需要組包,這是因爲local delivery必須把完整的包發送往4層,而forward,不需要經過4層,就直接把幀發送出去。 

最後我們來看下ip_local_deliver_finish函數片段: 


Java代碼  收藏代碼
  1. static int ip_local_deliver_finish(struct sk_buff *skb)  
  2. {  
  3.     struct net *net = dev_net(skb->dev);  
  4.   
  5.     __skb_pull(skb, ip_hdrlen(skb));  
  6.   
  7.     /* Point into the IP datagram, just past the header. */  
  8.     skb_reset_transport_header(skb);  
  9.   
  10.     rcu_read_lock();  
  11.     {  
  12.         int protocol = ip_hdr(skb)->protocol;  
  13.         int hash, raw;  
  14.         struct net_protocol *ipprot;  
  15.   
  16.     resubmit:  
  17. ///對raw socket進行處理。  
  18.         raw = raw_local_deliver(skb, protocol);  
  19.   
  20.         hash = protocol & (MAX_INET_PROTOS - 1);  
  21.         ipprot = rcu_dereference(inet_protos[hash]);  
  22.         if (ipprot != NULL) {  
  23.             int ret;  
  24.   
  25. ...................................................  
  26. ///將數據包交給已註冊的高層協議的處理函數。  
  27.             ret = ipprot->handler(skb);  
  28.             if (ret < 0) {  
  29.                 protocol = -ret;  
  30.                 goto resubmit;  
  31.             }  
  32.             IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);  
  33.         }   
  34. ...................................................  
  35.     }  
  36.  out:  
  37.     rcu_read_unlock();  
  38.   
  39.     return 0;  
  40. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章