ip層和4層的接口實現分析

首先來看一下基於3層的ipv4以及ipv6實現的一些4層的協議: 





這裏要注意並沒有IGMPV6,這是因爲在ipv6中,它是作爲iCMPv6的一部分實現的. 


首先我們要知道輸入數據包的ip頭中的protocol域標識了,將要傳遞的4層協議. 


我們這裏主要介紹的是ip數據包從3層傳遞到4層的接口(也就是輸入幀接口).而輸出幀的處理,我前面的blog都已經有介紹,想了解的話,可以去看前面的blog. 

先來看主要的數據結構,然後我們會分析ip_local_deliver_finish函數(也就是3層處理的出口函數). 

在內核中,每一個4層協議都是一個net_protocol結構體,而內核會在啓動的時候將所有的4層協議都註冊到一個數組inet_protos中,然後根據數據包的ip頭來得到相應的handle函數: 

Java代碼  收藏代碼
  1. struct net_protocol {  
  2. ///協議的處理函數,也就是將要處理輸入數據報的4層協議的處理函數.  
  3.     int         (*handler)(struct sk_buff *skb);  
  4. ///協議的錯誤處理函數.  
  5.     void            (*err_handler)(struct sk_buff *skb, u32 info);  
  6. ///gso相關的兩個函數.  
  7.     int         (*gso_send_check)(struct sk_buff *skb);  
  8.     struct sk_buff         *(*gso_segment)(struct sk_buff *skb,  
  9.                            int features);  
  10.   
  11. ///主要是被ipsec所使用的兩個域  
  12.     unsigned int        no_policy:1,  
  13.                 netns_ok:1;  
  14. };  



L4的協議都是在linux/in.h這個文件中,都是以IPPROTO開頭的一些宏.由於ip頭中的4層協議域是8位,因此4層協議的最大數值也就是255.而在內核中,255是raw ip, IPPPROTO_RAW: 

Java代碼  收藏代碼
  1. enum {  
  2.   IPPROTO_IP = 0,       /* Dummy protocol for TCP       */  
  3.   IPPROTO_ICMP = 1,     /* Internet Control Message Protocol    */  
  4.   IPPROTO_IGMP = 2,     /* Internet Group Management Protocol   */  
  5.   IPPROTO_IPIP = 4,     /* IPIP tunnels (older KA9Q tunnels use 94) */  
  6.   IPPROTO_TCP = 6,      /* Transmission Control Protocol    */  
  7.   IPPROTO_EGP = 8,      /* Exterior Gateway Protocol        */  
  8.   IPPROTO_PUP = 12,     /* PUP protocol             */  
  9.   IPPROTO_UDP = 17,     /* User Datagram Protocol       */  
  10.   IPPROTO_IDP = 22,     /* XNS IDP protocol         */  
  11.   IPPROTO_DCCP = 33,        /* Datagram Congestion Control Protocol */  
  12.   IPPROTO_RSVP = 46,        /* RSVP protocol            */  
  13.   IPPROTO_GRE = 47,     /* Cisco GRE tunnels (rfc 1701,1702)    */  
  14.   
  15.   IPPROTO_IPV6   = 41,      /* IPv6-in-IPv4 tunnelling      */  
  16.   
  17.   IPPROTO_ESP = 50,            /* Encapsulation Security Payload protocol */  
  18.   IPPROTO_AH = 51,             /* Authentication Header protocol       */  
  19.   IPPROTO_BEETPH = 94,         /* IP option pseudo header for BEET */  
  20.   IPPROTO_PIM    = 103,     /* Protocol Independent Multicast   */  
  21.   
  22.   IPPROTO_COMP   = 108,                /* Compression Header protocol */  
  23.   IPPROTO_SCTP   = 132,     /* Stream Control Transport Protocol    */  
  24.   IPPROTO_UDPLITE = 136,    /* UDP-Lite (RFC 3828)          */  
  25.   
  26.   IPPROTO_RAW    = 255,     /* Raw IP packets           */  
  27.   IPPROTO_MAX  
  28. };  



這裏要上面列出的協議,並不是所有的都在內核態handle的,其中一些經常在用戶態handle的例如(IPPROTO_RSVP). 


內核是通過inet_add_protocol來添加協議到inet_protos數組中的,相應的還有一個刪除方法,我們先來看inet_protos的結構: 


 


這裏要注意的就是讀寫inet_protos時,使用的是自旋鎖,而只讀時,使用的是RCU(Read-Copy Update). 


然後來看inet_add_protocol的源碼: 



Java代碼  收藏代碼
  1. struct net_protocol *inet_protos[MAX_INET_PROTOS] ____cacheline_aligned_in_smp;  
  2.   
  3.   
  4. ///這裏只是舉兩個例子,tcp和udp的協議註冊函數.我們這次暫時就不分析tcp和udp的處理函數了(我會在3層結束後,分析4層源碼)  
  5. static struct net_protocol tcp_protocol = {  
  6.     .handler =  tcp_v4_rcv,  
  7.     .err_handler =  tcp_v4_err,  
  8.     .gso_send_check = tcp_v4_gso_send_check,  
  9.     .gso_segment =  tcp_tso_segment,  
  10.     .no_policy =    1,  
  11.     .netns_ok = 1,  
  12. };  
  13.   
  14. static struct net_protocol udp_protocol = {  
  15.     .handler =  udp_rcv,  
  16.     .err_handler =  udp_err,  
  17.     .no_policy =    1,  
  18.     .netns_ok = 1,  
  19. };  
  20.   
  21.   
  22. int inet_add_protocol(struct net_protocol *prot, unsigned char protocol)  
  23. {  
  24.     int hash, ret;  
  25.   
  26. ///計算當前協議在數組中的slot.  
  27.     hash = protocol & (MAX_INET_PROTOS - 1);  
  28.   
  29. ///使用自旋鎖.  
  30.     spin_lock_bh(&inet_proto_lock);  
  31.     if (inet_protos[hash]) {  
  32.         ret = -1;  
  33.     } else {  
  34. ///將相應的prot添加到數組  
  35.         inet_protos[hash] = prot;  
  36.         ret = 0;  
  37.     }  
  38.     spin_unlock_bh(&inet_proto_lock);  
  39.     return ret;  
  40. }  



然後這些協議的註冊都是在內核boot的時候在inet_init中初始化的,下面就是inet_init的代碼片段.: 

Java代碼  收藏代碼
  1. static int __init inet_init(void)  
  2. {  
  3.     ...........................................  
  4.     /* 
  5.      *  Add all the base protocols. 
  6.      */  
  7.   
  8.     if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)  
  9.         printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");  
  10.     if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)  
  11.         printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");  
  12.     if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)  
  13.         printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");  
  14. #ifdef CONFIG_IP_MULTICAST  
  15.     if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)  
  16.         printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n");  
  17. #endif  
  18.   
  19. ..................................  
  20. }  


知道協議如何註冊之後,我們來分析ip_local_deliver_finish函數,來看3層是如何將數據包發送到4層的. 

1 我們知道linux支持raw數據包的發送,因此在這裏會對raw socket進行了特殊處理,它會clone一份數據包然後傳遞給相應的raw處理函數,然後再繼續後面的處理. 

2 ipsec.這時還需要加上相應的ipsec頭,然後再傳給4層處理.看下面的圖: 


 



Java代碼  收藏代碼
  1. static int ip_local_deliver_finish(struct sk_buff *skb)  
  2. {  
  3.   
  4. ///取出相應的net信息.  
  5.     struct net *net = dev_net(skb->dev);  
  6. ///下面兩個主要是調整data指針,使data指針指向4層的數據開始處.  
  7.     __skb_pull(skb, ip_hdrlen(skb));  
  8.     skb_reset_transport_header(skb);  
  9.   
  10. ///加rcu鎖.  
  11.     rcu_read_lock();  
  12.     {  
  13. ///取出ip頭中的協議.  
  14.         int protocol = ip_hdr(skb)->protocol;  
  15.         int hash, raw;  
  16.         struct net_protocol *ipprot;  
  17.   
  18.     resubmit:  
  19. ///得到raw socket, 如果不是raw socket,則返回0.  
  20.         raw = raw_local_deliver(skb, protocol);  
  21.   
  22. ///計算4層協議的slot.  
  23.         hash = protocol & (MAX_INET_PROTOS - 1);  
  24. ///rcu讀取相應的協議處理結構.  
  25.         ipprot = rcu_dereference(inet_protos[hash]);  
  26. ///主要是ipprot是否有被當前主機註冊.  
  27.         if (ipprot != NULL && (net == &init_net || ipprot->netns_ok)) {  
  28.             int ret;  
  29.   
  30. ///判斷ipsec,並進行相關處理.  
  31.             if (!ipprot->no_policy) {  
  32.                 if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {  
  33.                     kfree_skb(skb);  
  34.                     goto out;  
  35.                 }  
  36.                 nf_reset(skb);  
  37.             }  
  38. ///調用handler,進入相應的4層協議的處理.  
  39.             ret = ipprot->handler(skb);  
  40.             if (ret < 0) {  
  41.                 protocol = -ret;  
  42.                 goto resubmit;  
  43.             }  
  44.             IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);  
  45.         }  
  46. ................................................  
  47.  out:  
  48.     rcu_read_unlock();  
  49.   
  50.     return 0;  
  51. }  


最後來看一下raw socket的處理,通過上面我們知道,會調用raw_local_deliver來進行raw socket的相關處理(如果沒有raw socket,則會直接返回): 


當應用程序使用raw ip socket,他只需要攢遞給內核協議id(4層的協議),以及目的地址.因此這裏存取sock的hash表使用的key就是4層協議id. 
Java代碼  收藏代碼
  1. ///相應的hash表,保存raw socket.  
  2. struct raw_hashinfo {  
  3.     rwlock_t lock;  
  4.     struct hlist_head ht[RAW_HTABLE_SIZE];  
  5. };  
  6.   
  7. static struct raw_hashinfo raw_v4_hashinfo = {  
  8.     .lock = __RW_LOCK_UNLOCKED(raw_v4_hashinfo.lock),  
  9. };  
  10.   
  11.   
  12.   
  13. int raw_local_deliver(struct sk_buff *skb, int protocol)  
  14. {  
  15.     int hash;  
  16.     struct sock *raw_sk;  
  17. ///通過協議計算hash值(使用4層協議id).  
  18.     hash = protocol & (RAW_HTABLE_SIZE - 1);  
  19. ///得到相應的raw_sk.  
  20.     raw_sk = sk_head(&raw_v4_hashinfo.ht[hash]);  
  21.   
  22.     /* If there maybe a raw socket we must check - if not we 
  23.      * don't care less 
  24.      */  
  25. ///交給raw socket的處理函數,raw_v4_input中會clone一個skb,然後交給最後的raw_rev函數去處理最終的數據包.  
  26.     if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))  
  27.         raw_sk = NULL;  
  28.   
  29.     return raw_sk != NULL;  
  30.   
  31. }  

發佈了7 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章