TCP ADI in Linux(5): sk_buff and protocol headers

这章学习一下TCP用于表示数据包的最重要的一个数据结构:sk_buff。
sk_buff有三部分:sk_buff基本数据元素, linear-data buffer, paged-data(struct skb_shared_info)。
这章将详细的讲解如何有sk_buff构建一个被发送出去的包(主要就是包头的构建)和如何解析一个数据包得到一个sk_buff。

struct sk_buff

sk_buff结构体的定义位于:include/linux/skbuff.h文件中。其中的主要元素及解释如下所示:

struct sk_buff {
    /* These two members must be first */
    struct sk_buff      *next;  
    struct sk_buff      *prev;

    struct sk_buff      tstamp;                 // sk_buff发送时间/接收时间

    struct sock         *sk;                    // 标示这个sk_buff属于哪个sock  
    struct net_device   *dev;                   // 表示从哪个设备接收/发出  

    char                cb[48] __aligned(8);    // control buffer, TCP map this buffer to struct tcp_skb_cb  
    ...  

    unsigned int        len;                    // sk_buff的总长度  = linear-data + paged-data  
    unsigned int        data_len;               // paged-data length
    __u16               mac_len;            
    __u16               hdr_len;                // writable header length of cloned skb
    ...  

    __u32               priority;               // packet queueing priority
    ...  
    __u8                peeked:1;               // packet has been seen already, so stats have been done for it, don't do them again
    ...  
    /* 有意思的是alloc_skb时,skb->tail = skb->data - skb->head。tail其实是一个偏移量,而不是一个真正意义上的指针
     * 同时skb->end = skb->tail + size 也是一样偏移量。
     */
    sk_buff_data_t      tail;                   // last byte of the data residing in linear area (与data指针对应)
    sk_buff_data_t      end;                    // end of linear-data area (与head指针对应)
    unsigned char       *head;                  // points to the start of the linear data area (first byte allocated)
    unsigned char       *data;                  // start of data residing in the linear area   (first byte used)

    unsigned int        truesize;               // total memory allocated for this buffer = sizeof(sk_buff) + the size of the data block allocated for this sk_buff  
};

typedef unsigned char *sk_buff_data_t;

struct skb_shared_info

This data is invariant(不变的) across clones and lives at the end of the header data, ie. at skb->end.
这个数据结构主要半酣sk_buff中的nonlinear data。所谓的nonlinear data其实就是linear-data area放不下的数据就放到nonlinear area了。
书中把nonlinear data area也叫做 paged-data area. The paged-data area is possible only if DMA allows scatter-gather operations on the physically scattered pages.


Routines operating on sk_buff

  1. stuct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask, int flags, int mode)
    该函数分配一个新的sk_buff实例,需要给定的参数是linear-data area的大小和内存分配的模式。

  2. static inline void skb_reserve(struct sk_buff *skb, int len)
    该函数将skb的data和tail指针往后移动,移动长度为len。常见的用途是为协议头部保留空间。

  3. unsigned char skb_put(struct sk_buff skb, unsigned int len)
    该函数负责将sk_buff的linear-data area的tail指针增加len。可以看到3.10的kernel里面使用了skb_tail_pointer的方式获取真正意义上的指向数据使用部分的最后一个字节的指针。进一步验证了之前分析sk_buff结构体定义时认为tail是一个offset的判断。这点与书中描述的是不一致的。可见Linux kernel中关于TCP这块的更新还是很频繁的。连sk_buff这种重要的数据结构也一直在改动。

  4. unsigned char skb_push(struct sk_buff skb, unsigend int len)
    该函数负责将sk_buff的linear-data area的data指针往前移动,距离为len。不难看出,skb_put是用来构建包的数据的,而skb_push则是当一个上层包传递到下一层后,下层在添加头部数据时调用skb_push。

  5. unsigned char skb_pull(struct sk_buff skb, unsigned int len)
    该函数与skb_push相对应,它将data指针往后移动,距离为len。也就意味着skb->len要减少len。当有数据包到达后,一层层解析包头的过程中往往会用到该函数。

书中的5.5节描述了发送数据包时header是如何添加的,5.6节描述了收到的数据包头是如何被解析的。不过基本上熟悉了上面几个函数后,基本的操作方法应该都能理解了。还有不清楚的可以看下书。
这章主要就是讲sk_buff及其相关的一些操作函数,还算比较好理解。

tcp_skb_cb结构体是TCP层对于sk_buff->cb的实例化。里面主要有seq,timestamp和sacked flag