结构体struct sk_buff中共有三个联合体,分别是h,
nh和mac,它们都是一些指针,指向协议栈各层协议的首部。从含有的首部类型来看,nh是h的子集,而mac是nh的子集。《Linux设备驱动程序》
第三版第522页这样介绍这三个联合体:h中包含有传输层的报文头,nh中包含有网络层的报文头,而mac中包含的是链路层的报文头。
光靠这样的一个解释可能过于抽象,让我们来看一个UDP数据报是怎么样穿过数千公里长的网线来到我们的网卡,通过网卡的驱动程序层层向上来到协议栈的上层的。
当网卡驱动程序收到一个UDP数据报后,它创建一个结构体struct
sk_buff,确保data成员指向的空间足够存放收到的数据(对于数据报分片的情况,因为比较复杂,我们暂时忽略,我们假设一次收到的是一个完整的
UDP数据报)。把收到的数据全部拷贝到data指向的空间,然后,把skb->mac.raw指向data,此时,数据报的开始位置是一个以太网
头,所以skb->mac.raw指向链路层的以太网头。然后通过调用skb_pull剥掉以太网头,所谓剥掉以太网头,只是把data加上
sizeof(struct
ethhdr),同时len减去这个值,这样,在逻辑上,skb已经不包含以太网头了,但通过skb->mac.raw还能找到它。这就是我们通常
所说的,IP数据报被收到后,在链路层被剥去以太网头。
在继续往上层的过程中,一直到我们的my_inet域的函数myip_local_deliver_finish中,我们通过
__skb_pull剥去IP首部,同样,我们可以通过skb->nh.raw找到它。最后,skb->h.raw指向data,即udp首
部,udp首部其实到最后都没有被剥去,应用程序在调用recv接收数据时,直接从skb->data+sizeof(struc
udphdr)的位置开始拷贝。
我们可以看到,从网卡驱动开始,通过协议栈层层往上传送数据报时,通过增加skb->data的值,来逐步剥离协议首部,但通过h,nh,mac这三个联合指针,我们可以访问到这些协议首部,从而利用其提供的有效信息。
但必须指出的是,《Linux设备驱动程序》中的解释并不完全准确,mac中包含链路层报文头,这是毫无疑问的,nh中包含义网络层的报文头,也没有问
题,因为ARP协议也属于网络层协议,nh中包含IP首部或者ARP首部。当我们接收到一个icmp数据报时,在
myip_local_deliver_finish中剥去IP首部后,skb->h.raw指向的是icmp首部,但icmp显然不是传输层协
议,它是网络层的一个附属协议。igmp也是相同的情况,我想这也是为什么sk_buff的三个联合体不命名为th, nh,
mac的原因,因为th(transprot header)不能准确反映它的内容。
正确的理解应该是三个联合体是按TCP/IP数据报的协议首部的排列顺序来制定的。排在最前面的是以太网头,包含在mac中,第二是网络层协议首部,包括IP和ARP,包含在nh中,第三包括传输层协议头(TCP, UDP)、ICMP, IGMP。
另外,再选择两个重要的数据成员作个简短介绍。
pkt_type,数据报的类型。这个值在网卡驱动程序中由函数eth_type_trans通过判断目的以太网地址来确定。如果目的地址是
FF:FF:FF:FF:FF:FF,则为广播地址,pkt_type=PACKET_BROADCAST,如果最高位为1,则为组播地
址,pkt_type=PACKET_MULTICAST,如果目的mac地址跟本机mac地址不相等,则不是发给本机的数据
报,pkt_type=PACKET_OTHERHOST,否则就是缺省值PACKET_HOST。
protocol, 它的值是以太网首部的第三个成员,即帧类型,对于IP数据来讲,就是ETH_P_IP(0x8000),对ARP数据报来讲,就是ETH_P_ARP(0x8086)。
sk_buff还有一组操作函数,在理解sk_buff本身的基础上,理解这些函数并不困难,这里不再作分析。关于套接字缓冲区的分析就到这里结束。
阅读(848) | 评论(0) | 转发(1) |