一. 前言
内核提供了大量实用的操作sk_buff的函数,在开发网络设备驱动程序和修改网络协议栈代码时需要用到。这些函数从功能上可以分为三类:创建,释放和复制socket buffer;操作sk_buff结构中的参数和指针;管理socket buffer的队列。本文主要介绍其中一些典型的函数。本文使用的内核版本是3.18.45。
二. 创建和释放socket buffer
1. alloc_skb
alloc_skb是__alloc_skb函数的封装,是为socket buffer分配内存的主要函数。其中具体的实现不再分析了,主要介绍调用alloc_skb函数后,sk_buff各个参数和指针的情况。其中,skb->head,skb->data和skb->tail都指向分配内存的开始地址,skb->end指向分配内存的结束地址,分配的数据大小是4字节对齐的。如下图所示。
注意:调用alloc_skb分配的skb,由于实际数据长度还不确定,其中skb->len = 0。
2. kree_skb
kree_skb函数是__kfree_skb函数的封装,由于同一个socket buffer可能被多个地方使用,所以kfree_skb只有在skb->users = 1时,才会完全释放socket buffer,其他情况只会将skb->users减1。
三. 操作sk_buff参数和指针的函数
1. skb_reserve
skb_reserve的函数原型:
static inline void skb_reserve(struct sk_buff *skb, int len)
{skb->data += len;skb->tail += len;
}
skb_reserve函数的使用是在alloc_skb函数之后,作用是为网络协议栈的二层,三层以及数据负载留出空间。如下图所示:
上图中,留出的reserve len就是为协议栈的其他数据压入预留空间,从中也可以看出,skb->data到skb->tail之间保存的是真实负载数据的内容。
2. skb_put
skb_put的函数原型:
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{unsigned char *tmp = skb_tail_pointer(skb);SKB_LINEAR_ASSERT(skb);skb->tail += len;skb->len += len;if (unlikely(skb->tail > skb->end))skb_over_panic(skb, len, __builtin_return_address(0));return tmp;
}
从函数实现可知,skb_put函数将skb->tail加了len,表示将skb的实际负载数据的长度加len,要实现这一点,alloc_skb分配的len减去skb_reserve的len应该要大于等于skb_put的len,这样才能保证skb_put函数运行没问题。示意图如下:
3. skb_push
skb_push的函数原型:
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{skb->data -= len;skb->len += len;if (unlikely(skb->data<skb->head))skb_under_panic(skb, len, __builtin_return_address(0));return skb->data;
}
从函数实现可以看出,skb_push将data指针往低地址移动len长度,同时skb->len长度加len,然后程序将向该地址压入长度为len长度的数据。示意图如下:
4. skb_pull
skb_pull的函数原型:
static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
{skb->len -= len;BUG_ON(skb->len < skb->data_len);return skb->data += len;
}static inline unsigned char *skb_pull_inline(struct sk_buff *skb, unsigned int len)
{return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
}unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
{return skb_pull_inline(skb, len);
}
从函数实现可以看出,skb_pull是__skb_pull函数的封装,skb_pull_inline判断len要小于skb->len。skb_pull函数就是skb_push的反向操作函数,作用是将skb->data指针向高地址移动len,同时,skb->len减len,相当于从skb中删除了长度为len的数据。
5. skb_trim
skb_trim函数的原型:
static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset)
{skb->tail = skb->data + offset;
}static inline void __skb_trim(struct sk_buff *skb, unsigned int len)
{if (unlikely(skb_is_nonlinear(skb))) {WARN_ON(1);return;}skb->len = len;skb_set_tail_pointer(skb, len);
}void skb_trim(struct sk_buff *skb, unsigned int len)
{if (skb->len > len)__skb_trim(skb, len);
}
从函数实现可以看出,skb_trim是__skb_trim的封装,作用是将skb的负载数据长度修剪为len,并修剪长度是通过修改skb->tail的指针来实现的。示意图如下:
四. 管理socket buffer的队列
1. skb_queue_head_init
skb_queue_head_init函数的原型:
static inline void __skb_queue_head_init(struct sk_buff_head *list)
{list->prev = list->next = (struct sk_buff *)list;list->qlen = 0;
}static inline void skb_queue_head_init(struct sk_buff_head *list)
{spin_lock_init(&list->lock);__skb_queue_head_init(list);
}
从函数实现可以看出,skb_queue_head_init是__skb_queue_head_init函数的封装,作用是初始化链表的指针以及链表的队列长度。
2. skb_dequeue
skb_dequeue的函数原型:
static inline struct sk_buff *__skb_dequeue(struct sk_buff_head *list)
{struct sk_buff *skb = skb_peek(list);if (skb)__skb_unlink(skb, list);return skb;
}struct sk_buff *skb_dequeue(struct sk_buff_head *list)
{unsigned long flags;struct sk_buff *result;spin_lock_irqsave(&list->lock, flags);result = __skb_dequeue(list);spin_unlock_irqrestore(&list->lock, flags);return result;
}
函数的作用是从链表的头部取出一个skb,并且将skb从链表中删除。
3. skb_dequeue_tail
skb_dequeue_tail的函数原型:
static inline struct sk_buff *__skb_dequeue_tail(struct sk_buff_head *list)
{struct sk_buff *skb = skb_peek_tail(list);if (skb)__skb_unlink(skb, list);return skb;
}struct sk_buff *skb_dequeue_tail(struct sk_buff_head *list)
{unsigned long flags;struct sk_buff *result;spin_lock_irqsave(&list->lock, flags);result = __skb_dequeue_tail(list);spin_unlock_irqrestore(&list->lock, flags);return result;
}
函数的作用是从链表的尾部取出一个skb,并且将skb从链表中删除。
4. skb_append
skb_append的函数原型:
static inline void __skb_insert(struct sk_buff *newsk,struct sk_buff *prev, struct sk_buff *next,struct sk_buff_head *list)
{newsk->next = next;newsk->prev = prev;next->prev = prev->next = newsk;list->qlen++;
}static inline void __skb_queue_after(struct sk_buff_head *list,struct sk_buff *prev,struct sk_buff *newsk)
{__skb_insert(newsk, prev, prev->next, list);
}void skb_append(struct sk_buff *old, struct sk_buff *newsk, struct sk_buff_head *list)
{unsigned long flags;spin_lock_irqsave(&list->lock, flags);__skb_queue_after(list, old, newsk);spin_unlock_irqrestore(&list->lock, flags);
}
函数的作用是在链表中的old节点后面插入newsk的skb,链表的队列长度也加1。
五. 实例
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/udp.h>
#include <linux/ip.h>#define UDP_SRC_PORT 3015
#define UDP_DST_PORT 3016char data[32] = "chaos is a ladder!";
char src_ip[] = "192.168.0.56";
char dst_ip[] = "192.168.0.105";
char src_mac[] = {0x00, 0x0C, 0x43, 0x28, 0x80, 0xF5};
char dst_mac[] = {0x3C, 0x7C, 0x3F, 0xD7, 0x94, 0xAF};struct sk_buff *skb;
struct udphdr *uhdr;
struct iphdr *ihdr;
struct ethhdr *ehdr;
struct net_device * dev = NULL;void assemble_udp_header(struct udphdr *udp_hdr)
{udp_hdr->source = htons(UDP_SRC_PORT);udp_hdr->dest = htons(UDP_DST_PORT);udp_hdr->len = htons(sizeof(data) + sizeof(struct udphdr));udp_hdr->check = ~csum_tcpudp_magic(ihdr->saddr, ihdr->daddr, skb->len, IPPROTO_UDP, 0);
}void assemble_ip_header(struct iphdr *ip_hdr)
{ip_hdr->version = IPVERSION;ip_hdr->ihl = sizeof(struct iphdr)/4;ip_hdr->tos = 0;ip_hdr->tot_len = htons(sizeof(data) + sizeof(struct udphdr) + sizeof(struct iphdr));ip_hdr->id = htons(42351);ip_hdr->frag_off = 0;ip_hdr->ttl = 64;ip_hdr->protocol = IPPROTO_UDP;ip_hdr->check = 0;ip_hdr->saddr = in_aton(src_ip);ip_hdr->daddr = in_aton(dst_ip);
}void assemble_eth_header(struct ethhdr *eth_hdr)
{memcpy(eth_hdr->h_dest, dst_mac, 6);memcpy(eth_hdr->h_source, src_mac, 6) ;eth_hdr->h_proto = htons(ETH_P_IP);
}int __init skb_package_init(void)
{printk("skb init\n");unsigned char *p;skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC);dev = dev_get_by_name(&init_net, "br0");skb->dev = dev;skb_reserve(skb, 2 + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(data));p = skb_push(skb, sizeof(data));memcpy(p, (void*)data, sizeof(data));p = skb_push(skb, sizeof(struct udphdr));uhdr = (struct udphdr *)p;skb_reset_transport_header(skb);p = skb_push(skb, sizeof(struct iphdr));ihdr = (struct iphdr *)p;skb_reset_network_header(skb);p = skb_push(skb, sizeof(struct ethhdr));ehdr = (struct ethhdr *)p;skb_reset_mac_header(skb); assemble_ip_header(ihdr);assemble_udp_header(uhdr);assemble_eth_header(ehdr);dev_queue_xmit(skb); return 0;
}void __exit skb_package_exit(void)
{kfree_skb(skb);printk("skb exit\n");
}module_init(skb_package_init);
module_exit(skb_package_exit);
MODULE_LICENSE("GPL");
代码的功能是组一个UDP包并且通过网口br0发送出去。其中涉及到了skb的分配,预留数据包的空间,将协议头和实际负载数据放入到skb->data中,最后调用dev_queue_xmit将包发送出去的过程。
六. 总结
本文总结了socket buffer的一些常用的操作函数,并介绍了它们的作用,其中包括skb分配,以及skb的指针的操作和skb的队列的操作,这些函数是编写网络设备驱动程序的基础,应该熟练掌握。