博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
链路层到网络层的数据传递
阅读量:2220 次
发布时间:2019-05-08

本文共 4377 字,大约阅读时间需要 14 分钟。

我们知道在tcp/ip模型中,基本每一层都可以处理多重协议类型,那么当一个输入帧到达后,内核的每一层是如何来取得相应的处理函数呢?也就是说当我要把包传递给上层的时候,如何取得相应协议的处理函数。 

我们这里先来看从二层如何把把数据传递给三层。 

Java代码 
  1. struct sk_buff {  
  2. ....................................  
  3.     __be16          protocol;  
  4. .....................................  
  5.   
  6. };  
在sk_buff中的protocol字段能够表示输入帧的3层的协议,或者输入帧的mac头。在内核里面,是在函数netif_receive_skb中,通过protocol域来决定在三层的协议,以及处理函数。 
如果内核没有找到protocol所对应协议的处理函数,那么这个帧将会被丢掉。而且一个packet也有可能被分发到多个handler,举个例子,当packet sniffer运行的时候,这里的protocol域有时就会为ETH_P_ALL.此时就会放所有的包进入下一层。不过使用ETH_P_ALL一般只是为了监测网络设备或者debug。比如tcpdump。 
来看下netif_receive_skb的代码片段: 
Java代码 
  1. type = skb->protocol;  
  2.     list_for_each_entry_rcu(ptype,  
  3.             &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {  
  4.         if (ptype->type == type &&  
  5.             (ptype->dev == null_or_orig || ptype->dev == skb->dev ||  
  6.              ptype->dev == orig_dev)) {  
  7.             if (pt_prev)  
  8.                 ret = deliver_skb(skb, pt_prev, orig_dev);  
  9.             pt_prev = ptype;  
  10.         }  
  11.     }  
  12.   
  13.   
  14. static inline int deliver_skb(struct sk_buff *skb,  
  15.                   struct packet_type *pt_prev,  
  16.                   struct net_device *orig_dev)  
  17. {  
  18.     atomic_inc(&skb->users);  
  19.     return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);  
  20. }  
我们可以看到最终会通过判断protocol来决定调用哪一个ptype,并调用相应的虚函数func,接下来我们就来看packet_type这个结构体,也就是所有的协议handler结构。 
首先来看它的整体结构: 
 
Java代码 
  1. static struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;  
这里有一个全局的ptype_base,他是一个hash链表。它表示所有的协议handler结构。当通过dev_add_pack注册协议的时候就插入到相应的hashcode的相应链表。 
Java代码 
  1. static struct list_head ptype_all __read_mostly;    /* Taps */  
ptype_all也是一个全局链表,可以看到他完全是和ptype_base独立的。他就表示了ETH_P_ALL协议。 
接下来就来看packet_type的结构 
Java代码 
  1. struct packet_type {  
  2.     __be16          type;   /* This is really htons(ether_type). */  
  3.     struct net_device   *dev;   /* NULL is wildcarded here       */  
  4.     int         (*func) (struct sk_buff *,  
  5.                      struct net_device *,  
  6.                      struct packet_type *,  
  7.                      struct net_device *);  
  8.     struct sk_buff      *(*gso_segment)(struct sk_buff *skb,  
  9.                         int features);  
  10.     int         (*gso_send_check)(struct sk_buff *skb);  
  11.     void            *af_packet_priv;  
  12.     struct list_head    list;  
  13. };  
type表示了协议的类型,dev表示了那个设备的协议类型,如果是NULL,则表示是所有设备,比如tcpdum就能通过指定设备名来监测某个指定设备。func对应协议的处理函数。af_pack_priv,被PF_PACKET类型的socket所使用(具体看unix网络编程). list链表(也就是hash链表中,相同的桶所处的链表). 中间的两个gso开头的函数,可以自己去看下gso的相关资料。 
来看下ip协议的注册,以及初始化: 
Java代码 
  1. static struct packet_type ip_packet_type = {  
  2.     .type = __constant_htons(ETH_P_IP),  
  3.     .func = ip_rcv,  
  4.     .gso_send_check = inet_gso_send_check,  
  5.     .gso_segment = inet_gso_segment,  
  6. };  
  7.   
  8. static int __init inet_init(void)  
  9. {  
  10. 。。。。。。。。。。。。。。。。。。。。  
  11.   
  12.     dev_add_pack(&ip_packet_type);  
  13. .........................................  
  14.   
  15. }  
可以看到在初始化函数中,调用dev_add_pack来上面定义的ip_packet_type加入到全局的hash链表中。 
Java代码 
  1. void dev_add_pack(struct packet_type *pt)  
  2. {  
  3.     int hash;  
  4.   
  5.     spin_lock_bh(&ptype_lock);  
  6. ///判断类型是否为ETH_P_ALL  
  7.     if (pt->type == htons(ETH_P_ALL))  
  8.         list_add_rcu(&pt->list, &ptype_all);  
  9.     else {  
  10. ///计算hash值  
  11.         hash = ntohs(pt->type) & PTYPE_HASH_MASK;  
  12. ///插入到相应的hash表位置  
  13.         list_add_rcu(&pt->list, &ptype_base[hash]);  
  14.     }  
  15.     spin_unlock_bh(&ptype_lock);  
  16. }  
而链路层得到相应的输入包的协议类型是通过eth_type_trans来实现的,可以随便看一下驱动的代码,当驱动调用netif_rx之前,都会先调用这个函数来得到protocol值。 
先来看下不同的帧类型的区别: 
 
eth_type_trans有两个作用,一个是设置packet type,一个是设置协议类型。 
其中packet type也就是skb->pkt_type字段,表示了链路层的数据类型,他可以为下面几种类型: 
Java代码 
  1. #define PACKET_HOST     0       /* To us        */  
  2. #define PACKET_BROADCAST    1       /* To all       */  
  3. #define PACKET_MULTICAST    2       /* To group     */  
  4. #define PACKET_OTHERHOST    3       /* To someone else  */  
  5. #define PACKET_OUTGOING     4       /* Outgoing of any type */  
这里要注意的是PACKET_OTHERHOST类型,它表示这个帧不属于这个接收接口,可是他并不会立即被扔掉,当传递给高层的时候。它主要用来protocol sniffer. 
Java代码 
  1. /* 
  2.  *  This is an Ethernet frame header. 
  3.  */  
  4.    
  5. ///帧头的表示。  
  6. struct ethhdr {  
  7.     unsigned char   h_dest[ETH_ALEN];   /* destination eth addr */  
  8.     unsigned char   h_source[ETH_ALEN]; /* source ether addr    */  
  9.     __be16      h_proto;        /* packet type ID field */  
  10. } __attribute__((packed));  
  11.   
  12.   
  13. __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)  
  14. {  
  15.     struct ethhdr *eth;  
  16.     unsigned char *rawp;  
  17. ///一些初始化  
  18.     skb->dev = dev;  
  19.     skb_reset_mac_header(skb);  
  20.     skb_pull(skb, ETH_HLEN);  
  21.     eth = eth_hdr(skb);  
  22.   
  23.     if (is_multicast_ether_addr(eth->h_dest)) {  
  24. ///判断是否为广播。  
  25.         if (!compare_ether_addr(eth->h_dest, dev->broadcast))  
  26. ///数据位广播  
  27.             skb->pkt_type = PACKET_BROADCAST;  
  28.         else  
  29. ///多播  
  30.             skb->pkt_type = PACKET_MULTICAST;  
  31.     }  
  32.   
  33.   
  34.     else if (1 /*dev->flags&IFF_PROMISC */ ) {  
  35. ///主要用来网络的监测。  
  36.         if (unlikely(compare_ether_addr(eth->h_dest, dev->dev_addr)))  
  37.             skb->pkt_type = PACKET_OTHERHOST;  
  38.     }  
  39.   
  40. ///通过上面的图我们知道大于1536为ethernet 类型的帧,因此直接返回h_proto。  
  41.     if (ntohs(eth->h_proto) >= 1536)  
  42.         return eth->h_proto;  
  43.   
  44.     rawp = skb->data;  
  45. ///取数据位来判断是否是802.3的帧。  
  46.     if (*(unsigned short *)rawp == 0xFFFF)  
  47.         return htons(ETH_P_802_3);  
  48.   
  49.     /* 
  50.      *      Real 802.2 LLC 
  51.      */  
  52.     return htons(ETH_P_802_2);  
  53. }  

转载地址:http://ttjfb.baihongyu.com/

你可能感兴趣的文章
搞懂分布式技术21:浅谈分布式消息技术 Kafka
查看>>
后端技术杂谈1:搜索引擎基础倒排索引
查看>>
后端技术杂谈2:搜索引擎工作原理
查看>>
后端技术杂谈3:Lucene基础原理与实践
查看>>
后端技术杂谈4:Elasticsearch与solr入门实践
查看>>
后端技术杂谈5:云计算的前世今生
查看>>
后端技术杂谈6:白话虚拟化技术
查看>>
后端技术杂谈7:OpenStack的基石KVM
查看>>
后端技术杂谈8:OpenStack架构设计
查看>>
后端技术杂谈9:先搞懂Docker核心概念吧
查看>>
后端技术杂谈10:Docker 核心技术与实现原理
查看>>
夯实Java基础系列2:Java自动拆装箱里隐藏的秘密
查看>>
夯实Java基础系列1:Java面向对象三大特性(基础篇)
查看>>
夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!
查看>>
夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理
查看>>
Java 未来行情到底如何,来看看各界人士是怎么说的
查看>>
IntelliJ 平台 2020 年路线图
查看>>
走进JavaWeb技术世界8:浅析Tomcat9请求处理流程与启动部署过程
查看>>
微软宣布加入 OpenJDK,打不过就改变 Java 未来!
查看>>
MyBatis动态SQL(认真看看, 以后写SQL就爽多了)
查看>>