skb结构和相关操作函数

skb是linux kernel中收发数据包用到的控制结构体,有些字段指向分配的内存用于存放数据包,
向协议栈传送时,通过移动指针来获取到以太头,网络头,传输头等信息。

  1. skb结构和相关操作函数
    a. skb结构体,如下图片(盗图)

     

    image.png

struct sk_buff {

__u16 transport_header; //传输头相对于skb->head的偏移

__u16 network_header;//网络头相对于skb->head的偏移

__u16 mac_header;//以太网头相对于skb->head的偏移

/* These elements must be at the end, see alloc_skb() for details. */

sk_buff_data_t tail;

sk_buff_data_t end;

unsigned char *head, *data;

}

head和end分别指向存放数据内存区域的头和尾,一旦分配就固定不变。

data和tail分别是真正数据的起始位结束。

head和data之间的区域成为headroom,data和tail之间的区域存放真正的数据,tail和end之间的区域成为tailroom。skb刚分配时,head,data和tail在同一位置,end在末尾,所以刚开始时,headroom大小为0,tailroom大小为size,后续对数据包的操作,通过移动data和tail完成,head和end固定不变。

head|data|tail ----size-----end

len 和 data_len

len代表整个数据区域的长度!
这里要提前解释几个定义,skb的组成是有sk_buff控制 + 线性数据 + 非线性数据
(skb_shared_info) 组成!后面会具体解释是什么意思!在sk_buff这个里面没有实际的数据,这
里仅仅是控制信息,数据是通过后面的data指针指向其他内存块的!那个内存块中是线性数据和
非线性数据!那么len就是length(线性数据) + length(非线性数据)!!!
data_len: 指的是length(非线性数据)!!!那么可以知道:length(线性数据) = skb->len - skb->data_len

b. 分配skb函数

struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
int flags, int node)
{
 cache = (flags & SKB_ALLOC_FCLONE)
? skbuff_fclone_cache : skbuff_head_cache;
//从 cache里取出一个skb结构体。为了提高分配skb效率,会在初始化时,分配一个skb
//放在cache中。
skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
//分配数据区域和skb_shared_info,它俩是在一块连续内存中
size = SKB_DATA_ALIGN(size);
size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);
/* kmalloc(size) might give us more room than requested.
* Put skb_shared_info exactly at the end of allocated zone,
* to allow max possible filling before reallocation.
*/
分配完内存后,将size减去skb_shared_info 的大小,此时size只表示存放数据的大小
size = SKB_WITH_OVERHEAD(ksize(data));
#define SKB_WITH_OVERHEAD(X) \
((X) - SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
prefetchw(data + size);
/*
* Only clear those fields we need to clear, not those that we will
* actually initialise below. Hence, don't put any more fields after
* the tail pointer in struct sk_buff!
*/
//将skb tail前面的成员全部清零。tail后面的不用清零,因为随后就会赋值
memset(skb, 0, offsetof(struct sk_buff, tail));
/* Account for allocated memory : skb + skb->head */
skb->truesize = SKB_TRUESIZE(size);
skb->pfmemalloc = pfmemalloc;
//引用计数设置为1
atomic_set(&skb->users, 1);
//在后续报文处理过程中,head和end分别表示数据内存的起始和结尾,是固定不变的
//通过偏移data和tail来指向不同的数据位置
//初始化时head和data指针都指向data
skb->head = data;
skb->data = data;
//在64位下,tail和end都是整数,表示相对于head的偏移
//初始时tailf为0,即指向head
//end为tail+size,即指向内存中存放数据的末尾,skb_shared_info的起始
skb_reset_tail_pointer(skb);
    skb->tail = skb->data - skb->head
skb->end = skb->tail + size;
skb->mac_header = (typeof(skb->mac_header))~0U;
skb->transport_header = (typeof(skb->transport_header))~0U;

/* make sure we initialize shinfo sequentially */
//返回 skb->head + skb->end,即为skb_shared_info的首地址
shinfo = skb_shinfo(skb);
  #define skb_shinfo(SKB) ((struct skb_shared_info *)  
    (skb_end_pointer(SKB)))skb->head + skb->end
memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
//设置shinfo引用计数为1
atomic_set(&shinfo->dataref, 1);
kmemcheck_annotate_variable(shinfo->destructor_arg);
}
  1. 操作skb的一些函数
    在skb头部添加 len 字节

unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
  skb->data -= len;
  skb->len += len;

在skb头部删除 len 字节

unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
  skb->len -= len;
  skb->data += len;

在skb尾部添加 len 字节

unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
skb->tail += len;
skb->len += len;

预留headroom

static inline void skb_reserve(struct sk_buff *skb, int len)
  skb->data += len;
  skb->tail += len;
  1. 数据包从驱动接收到上送协议栈的处理中,skb的操作,mac,网络和传输头的变化
    3.1 eth_type_trans
    不管虚拟网卡还是硬件网卡,数据包都会存放在skb中。收到数据包后,调用eth_type_trans
    设置skb->mac_header,向后偏移skb->data, 减少skb->len,设置数据包类型

eth_type_trans
  //reset mac头位置,mac_header 是相对于skb->skb的偏移
  skb_reset_mac_header(skb);
    skb->mac_header = skb->data - skb->head;
  //将data指针向后偏移14字节,指向下一个协议头,即三层头或者vlan头
  //skb->len表示数据包的总长度,偏移14字节后,len也要减去14
  #define ETH_HLEN 14 /* Total octets in header. */
  skb_pull_inline(skb, ETH_HLEN);
    skb->len -= len;
    skb->data += len;
  //通过 skb->mac_header 仍然可以获取mac头
  eth = eth_hdr(skb);
    (struct ethhdr *)skb_mac_header(skb);
    skb->head + skb->mac_header;
  //根据数据包的目的mac决定此数据包的pkt_type
  //如果目的mac中从左往右第二个字节为1为组播,并且如果mac为     全1,则为广播,否则为组播
  //如果目的mac中从左往右第二个字节不为1为单播,并且和接收设备的mac不同则设置为PACKET_OTHERHOST
  //如果和接收设备的mac相同,则不用设置,默认为0,即   PACKET_HOST,表示 to us的数据包
  if (unlikely(is_multicast_ether_addr(eth->h_dest))) {
    if (ether_addr_equal_64bits(eth->h_dest, dev->broadcast))
      skb->pkt_type = PACKET_BROADCAST;
    else
      skb->pkt_type = PACKET_MULTICAST;
  }
  else if (unlikely(!ether_addr_equal_64bits(eth->h_dest,dev->dev_addr)))
    skb->pkt_type = PACKET_OTHERHOST;
  //如果以太网协议大于ETH_P_802_3_MIN,则返回以太网协议即可
  if (likely(ntohs(eth->h_proto) >= ETH_P_802_3_MIN))
    return eth->h_proto;

所以eth_type_trans执行后,
skb->mac_header = skb->data - skb->head;
skb->data 指向网络头

    1. __netif_receive_skb_core
      经过软中断处理后,会调用__netif_receive_skb_core上送到协议栈,根据协议调用相应的hook函数

__netif_receive_skb_core
 //设置网络头偏移量
 skb_reset_network_header(skb);
   skb->network_header = skb->data - skb->head;
 //重置mac头长度
 skb_reset_mac_len(skb);
   skb->mac_len = skb->network_header - skb->mac_header;

执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->data 仍然指向网络头

3.3. ip_rcv 数据包进入网络层的处理

ip_rcv
当数据包进入协议栈往上层递交的过程中,比如在IP层,它需要对数据包的IP头部进行分析,比如头部合法性等,这时候就需要确保IP头部在线性缓冲区中,这样才能对它进行分析,如果在非线性缓冲区中,而非线性缓冲区是unmapped的page,因此就需要从这些unmapped page当中把数据复制到线性缓冲区中。
这个艰难的工作就是__pskb_pull_tail完成的。我将对它的代码进行单独的分析。
当然最好情况是skb->data指向的线性缓冲区中的数据至少是大于len的,这样就可以直接返回了成功了。
len一定是不能大于整个skb的数据总长的。这个就不必说明吧...
线性缓冲区中数据不足,不幸还是发生了...调用__pskb_pull_tail。
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
  /* skb_headlen定义为skb->len - skb->data_len。即skb->head指向  
  的线性缓冲区里当前
  * 有效数据的长度。*/
    if (likely(len <= skb_headlen(skb)))
      return 1;
    if (unlikely(len > skb->len))
      return 0;
    return __pskb_pull_tail(skb, len - skb_headlen(skb)) != NULL;
  iph = ip_hdr(skb);
    (struct iphdr *)skb_network_header(skb);
      skb->head + skb->network_header;
  skb->transport_header = skb->network_header + iph->ihl*4;

执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->transport_header = skb->network_header + iph->ihl*4;
skb->data 仍然指向网络头

3.4. 经过查找路由,发现是本地的数据包

ip_local_deliver_finish
  //获取网络头长度
  static inline u32 skb_network_header_len(const struct sk_buff *skb)
    return skb->transport_header - skb->network_header;
  //偏移data到传输头
  __skb_pull(skb, skb_network_header_len(skb));
    skb->len -= len;
    skb->data += len;
  int protocol = ip_hdr(skb)->protocol;
  ipprot = rcu_dereference(inet_protos[protocol]);
  ipprot->handler(skb);

执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->transport_header = skb->network_header + iph->ihl*4;
skb->data 指向传输层

3.5. 数据到达传输层,以icmp为例

icmp_rcv
  pskb_pull(skb, sizeof(*icmph))
    skb->len -= len;
    skb->data += len;
  icmph = icmp_hdr(skb);
    (struct icmphdr *)skb_transport_header(skb);
      skb->head + skb->transport_header;

执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->transport_header = skb->network_header + iph->ihl*4;
skb->data 指向应用层

 

也可参考:skb结构和相关操作函数 - 简书 (jianshu.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值