ip层和4层的接口实现分析

来源:互联网 发布:合并会计报表软件 编辑:程序博客网 时间:2024/05/21 14:48

首先来看一下基于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. }  

原创粉丝点击