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

来源:互联网 发布:淘宝小号无忧网 编辑:程序博客网 时间:2024/05/21 10:40
ip层和4层的接口实现分析
Socket.netCiscoLinux数据结构
首先来看一下基于3层的ipv4以及ipv6实现的一些4层的协议:


ip层和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是rawip, IPPPROTO_RAW:

Java代码  收藏代码
  1. enum  
  2.   IPPROTO_IP 0        
  3.   IPPROTO_ICMP 1      
  4.   IPPROTO_IGMP 2      
  5.   IPPROTO_IPIP 4      
  6.   IPPROTO_TCP 6       
  7.   IPPROTO_EGP 8       
  8.   IPPROTO_PUP 12      
  9.   IPPROTO_UDP 17      
  10.   IPPROTO_IDP 22      
  11.   IPPROTO_DCCP 33         
  12.   IPPROTO_RSVP 46         
  13.   IPPROTO_GRE 47      
  14.   
  15.   IPPROTO_IPV6   41       
  16.   
  17.   IPPROTO_ESP 50             
  18.   IPPROTO_AH 51              
  19.   IPPROTO_BEETPH 94          
  20.   IPPROTO_PIM    103      
  21.   
  22.   IPPROTO_COMP   108                 
  23.   IPPROTO_SCTP   132      
  24.   IPPROTO_UDPLITE 136     
  25.   
  26.   IPPROTO_RAW    255      
  27.   IPPROTO_MAX  
  28. };  



这里要上面列出的协议,并不是所有的都在内核态handle的,其中一些经常在用户态handle的例如(IPPROTO_RSVP).


内核是通过inet_add_protocol来添加协议到inet_protos数组中的,相应的还有一个删除方法,我们先来看inet_protos的结构:


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


这里要注意的就是读写inet_protos时,使用的是自旋锁,而只读时,使用的是RCU(Read-CopyUpdate).


然后来看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.   
  6.     if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) 0 
  7.         printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");  
  8.     if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) 0 
  9.         printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");  
  10.     if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) 0 
  11.         printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");  
  12. #ifdef CONFIG_IP_MULTICAST  
  13.     if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) 0 
  14.         printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n");  
  15. #endif  
  16.   
  17. ..................................  
  18.  


知道协议如何注册之后,我们来分析ip_local_deliver_finish函数,来看3层是如何将数据包发送到4层的.

1 我们知道linux支持raw数据包的发送,因此在这里会对rawsocket进行了特殊处理,它会clone一份数据包然后传递给相应的raw处理函数,然后再继续后面的处理.

2 ipsec.这时还需要加上相应的ipsec头,然后再传给4层处理.看下面的图:


ip层和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来进行rawsocket的相关处理(如果没有raw socket,则会直接返回):


当应用程序使用raw ipsocket,他只需要攒递给内核协议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.       
  23. ///交给raw socket的处理函数,raw_v4_input中会clone一个skb,然后交给最后的raw_rev函数去处理最终的数据包.  
  24.     if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))  
  25.         raw_sk NULL;  
  26.   
  27.     return raw_sk != NULL;  
  28.   

0 0
原创粉丝点击