skb结构和相关操作函数

您所在的位置:网站首页 header函数 skb结构和相关操作函数

skb结构和相关操作函数

2023-09-28 00:06| 来源: 网络整理| 查看: 265

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

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); } 操作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; 数据包从驱动接收到上送协议栈的处理中,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 指向网络头

__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->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 指向应用层



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3