首页
论坛
课程
招聘
[原创]Linux内核[CVE-2017-1000112] (UDP Fragment Offload) 分析
2021-1-15 16:53 2973

[原创]Linux内核[CVE-2017-1000112] (UDP Fragment Offload) 分析

2021-1-15 16:53
2973

Linux内核[CVE-2017-1000112] (UDP Fragment Offload) 分析

目录

CVE 基础

在分析 CVE-2017-1000112 之前首先应该明白如下概念。

TCP/IP

ps:幸好上学期学了计算机网络。。

 

从上到下分别是:应用层message -> 传输层segment(UDP) -> 网络层datagram(IP) -> 链路层frame。

 

封装层级:{ 数据帧frame{ IP包{ UDP包{ message/data } } } }

UFO机制

UFO 是 UDP Fragment Offload 的简称。

 

我们知道发送ipv4数据包的过程中,一个链路层帧所能承载的最大数据量为做最大传送单元(MTU)。当要求发送的IP数据报比数据链路层的MTU大时,必把该数据报分割成多个IP数据报才能发送(即ipv4的分片,可能发生在ip层或者传输层)。

 

而 UFO机制 则是通过网卡的配合辅助进行ipv4报文分片。将分片的过程从协议栈中移到网卡硬件中,从而提升效率,减少堆栈开销。

 

在 linuxfoundation 中给出了如下描述:

 

IPv4/IPv6: UFO (UDP Fragmentation Offload) Scatter-gather approach: UFO is a feature wherein the Linux kernel network stack will offload the IP fragmentation functionality of large UDP datagram to hardware. This will reduce the overhead of stack in fragmenting the large UDP datagram to MTU sized packets

 

UFO的commit在这里:UFO commit in linux kernel ,2005

UDP corking机制

cork翻译为软木塞。

 

cork机制是一种优化机制。他就像一个塞子一样,使得数据先不发出去,等到拔去塞子后再发出去。以防止不停的去封装发送碎片化的小数据包,使得利用率降低。开启corkin后,内核会尽力把小数据包拼接成一个大的数据包(一个MTU)再发送出去,当然若一定时间后(一般为200ms),内核仍然没有组合成一个MTU时也必须发送现有的数据。

 

这么做有利有弊,好处是缓解了碎片化,提高了利用效率。但是也损失了一些实时性。

 

具体的话代码如下:

 

在函数 int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)

 

 

do_append_data: 做数据追加。

 

 

如果追加数据失败,则调用 udp_flush_pending_frame 丢弃数据。底层调用 __ip_flush_pending_frames

4.4.0内核下 ip_append_data 源码分析

基本流程

对于UDP,分片工作主要在 ip_append_data(ip_apepend_page) 中完成。实际上这是比较复杂的一个函数,我们只挑关键的说。

 

他的作用主要是将上层下来的数据进行整形,如果是大数据包进行切割,变成多个小于或等于MTU的SKB。如果是小数据包,并且开启了聚合,就会将若干个数据包整合。

 

并且给出一张网上的流程图:

 

 

源码位置:https://elixir.bootlin.com/linux/v4.4/source/net/ipv4/ip_output.c#L864

 

我们顺着流程图来分析。

ip_append_data的准备工作

首先是这一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int ip_append_data(struct sock *sk, struct flowi4 *fl4,
           int getfrag(void *from, char *to, int offset, int len,
                   int odd, struct sk_buff *skb),
           void *from, int length, int transhdrlen,
           struct ipcm_cookie *ipc, struct rtable **rtp,
           unsigned int flags)
{
    struct inet_sock *inet = inet_sk(sk);
    int err;
 
    if (flags&MSG_PROBE)    //是否开启MSG_PROBE
        return 0;
 
    if (skb_queue_empty(&sk->sk_write_queue)) {
        err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
        if (err)
            return err;
    } else {
        /*队列不为空,则使用上次的路由,IP选项,以及分片长度 */
        transhdrlen = 0;
    }
 
    return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,
                sk_page_frag(sk), getfrag,
                from, length, transhdrlen, flags);
}
  • getfrag(void *from, char *to, int offset,int len, int odd, struct sk_buff *skb) 函数主要是做了将数据赋值到 skb中。而一个skb就是一个sk_buff 结构体的指针。struct sk_buff - socket buffer 就是一个sock的缓冲区。
  • 参数 int transhdrlen 是一个代表着传输层header的长度,同时也是标志是否为第一个fragment的标志。
  • 参数 unsigned int flags 则是一个标志。在本函数中主要用到了 MSG_PROBE(进行MTU路径探测,而不真正进行数据发送)、MSG_MORE(代表后续还有数据被发送)

ip_append_data 中首先判断flags是否开启了 MSG_PROBE 选项,如果开启了,那么就直接返回0。

 

接下来判断sk_buff队列是否为空,如果是空的话通过 ip_setup_cork 初始化 cork 变量。

 

如果不空那么设置 transhdrlen = 0 说明不是第一个fragment。

 

接下来调用:__ip_append_data 也是主要的处理流程

__ip_append_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
static int __ip_append_data(struct sock *sk,
                struct flowi4 *fl4,
                struct sk_buff_head *queue,
                struct inet_cork *cork,
                struct page_frag *pfrag,
                int getfrag(void *from, char *to, int offset,
                    int len, int odd, struct sk_buff *skb),
                void *from, int length, int transhdrlen,
                )
{
    struct inet_sock *inet = inet_sk(sk);
    struct sk_buff *skb;    //分配一个新的sk_buff准备将它放入sk_write_queue队列,稍后函数为数据加入IP头信息即可往下传输
 
    struct ip_options *opt = cork->opt;
    int hh_len;
    int exthdrlen;
    int mtu;
    int copy;
    int err;
    int offset = 0;
    unsigned int maxfraglen, fragheaderlen, maxnonfragsize;
    int csummode = CHECKSUM_NONE;
    struct rtable *rt = (struct rtable *)cork->dst;
    u32 tskey = 0;
 
    skb = skb_peek_tail(queue);        //获取skb队列的尾结点。
 
    exthdrlen = !skb ? rt->dst.header_len : 0;
    mtu = cork->fragsize;
    if (cork->tx_flags & SKBTX_ANY_SW_TSTAMP &&
        sk->sk_tsflags & SOF_TIMESTAMPING_OPT_ID)
        tskey = sk->sk_tskey++;
 
    hh_len = LL_RESERVED_SPACE(rt->dst.dev);        //获取链路层header长度
 
    fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);    //IP头部长度   
    maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;        //最大IP头部长度,考虑了对齐
    maxnonfragsize = ip_sk_ignore_df(sk) ? 0xFFFF : mtu;            //是否超过64k?最大为64k
 
    if (cork->length + length > maxnonfragsize - fragheaderlen) {
        ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
                   mtu - (opt ? opt->optlen : 0));
        return -EMSGSIZE;
    }
 
    /*
     * transhdrlen > 0 means that this is the first fragment and we wish
     * it won't be fragmented in the future.
     transhdrlen!=0说明ip_append_data工作在第一个片段
     transhdrlen =0说明ip_append_data没有工作在第一个片段
     */
    if (transhdrlen &&
        length + fragheaderlen <= mtu &&
        rt->dst.dev->features & NETIF_F_V4_CSUM &&
        !(flags & MSG_MORE) &&
        !exthdrlen)
        csummode = CHECKSUM_PARTIAL;        //校验和计算?
 
    cork->length += length;                    //软木塞长度更新
    if (((length > mtu) || (skb && skb_is_gso(skb))) &&
        (sk->sk_protocol == IPPROTO_UDP) &&
        (rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len &&
        (sk->sk_type == SOCK_DGRAM)) {
        /*
        对于一个UDP包如果满足:
        1.要发送的数据长度大于mtu,需要进行分片
        2.开启了UFO支持
        */
        err = ip_ufo_append_data(sk, queue, getfrag, from, length,
                     hh_len, fragheaderlen, transhdrlen,
                     maxfraglen, flags);    //调用支持UFO机制的ip_ufo_append_data
        if (err)
            goto error;
        return 0;
    }
 
    /* So, what's going on in the loop below?
     *
     * We use calculated fragment length to generate chained skb,
     * each of segments is IP fragment ready for sending to network after
     * adding appropriate IP header.
     */
 
    //如果skb为空,即sk_buff队列此时为空,那么跳转到alloc_new_skb。
    if (!skb)
        goto alloc_new_skb;

接下来涉及到一个非常关键的 copy 变量。

 

 

copy 代表的是最后一个skb的剩余空间。

 

skb->len 表示此 SKB 管理的 Data Buffer 中数据的总长度

 

他有如下几种情况:

  • copy < 0:即mtu < skb->len 溢出了。有些数据需要从当前的IP分片中移动到新的片段中
  • copy > 0:最后一个skb还有空间。
  • copy = 0:最后一个skb被填满。

copy<0

此时我们需要分配新的skb来存储溢出的数据。

 

由于这里代码较长,只分析关键部分。

 

这里有很多个不同的长度,图示如下:

 

 

首先计算我们究竟要分配多大的空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
if (copy <= 0) {
            char *data;
            unsigned int datalen;
            unsigned int fraglen;
            unsigned int fraggap;
            unsigned int alloclen;
            struct sk_buff *skb_prev;
alloc_new_skb:
            skb_prev = skb;
            if (skb_prev)//当skb存在时,需要计算究竟要从上一个skb中拿多长的数据到下一个新的skb
                fraggap = skb_prev->len - maxfraglen;    //这里其实就是负的copy
            else
                fraggap = 0;
 
            /*
             * If remaining data exceeds the mtu,
             * we know we need more fragment(s).
             */
            datalen = length + fraggap;
            if (datalen > mtu - fragheaderlen)
                datalen = maxfraglen - fragheaderlen;
            fraglen = datalen + fragheaderlen;
 
            if ((flags & MSG_MORE) &&
                !(rt->dst.dev->features&NETIF_F_SG))
                alloclen = mtu;        //最大分配大小
            else
                alloclen = fraglen; //确切分配大小,fraglen = datalen + fragheaderlen
 
            alloclen += exthdrlen;
 
            /* The last fragment gets additional space at tail.
             * Note, with MSG_MORE we overallocate on fragments,
             * because we have no idea what fragment will be
             * the last.
             */
            if (datalen == length + fraggap)
                alloclen += rt->dst.trailer_len;

当我们初步确定了需要分配的新的skb的大小 alloclen 后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (transhdrlen) {           
    //如果是第一个分片
    //调用sock_alloc_send_skb分配新的skb
            skb = sock_alloc_send_skb(sk,
                    alloclen + hh_len + 15,
                    (flags & MSG_DONTWAIT), &err);
        } else {
    //如果不是第一个分片
            skb = NULL;
            if (atomic_read(&sk->sk_wmem_alloc) <=
                2 * sk->sk_sndbuf)
                skb = sock_wmalloc(sk,
                           alloclen + hh_len + 15, 1,
                           sk->sk_allocation);
            if (unlikely(!skb))
                err = -ENOBUFS;
        }
if (!skb)
            goto error;        //分配失败跳转到error

当分配成功后,首先初始化skb中的一些控制数据

1
2
3
4
5
6
/*
             *    Fill in the control structures
             */
            skb->ip_summed = csummode;
            skb->csum = 0;
            skb_reserve(skb, hh_len);

接下来初始化时间戳

1
2
3
4
5
/* only the initial fragment is time stamped */
        skb_shinfo(skb)->tx_flags = cork->tx_flags;
        cork->tx_flags = 0;
        skb_shinfo(skb)->tskey = tskey;
        tskey = 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
            /*
             *    Find where to start putting bytes.
             */
            data = skb_put(skb, fraglen + exthdrlen);//预留L2,L3首部空间
            skb_set_network_header(skb, exthdrlen);     //设置L3层的指针
            skb->transport_header = (skb->network_header +
                         fragheaderlen);
            data += fragheaderlen + exthdrlen;
 
            if (fraggap) {                            //填充原来skb的尾部
                skb->csum = skb_copy_and_csum_bits(
                    skb_prev, maxfraglen,
                    data + transhdrlen, fraggap, 0);
                // skb_copy_and_csum_bits函数将数据从第一个创建的sk_buff复制到新分配的sk_buff
                skb_prev->csum = csum_sub(skb_prev->csum,
                              skb->csum);
                data += fraggap;
                pskb_trim_unique(skb_prev, maxfraglen);
            }
 
            copy = datalen - transhdrlen - fraggap;
            if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
                err = -EFAULT;
                kfree_skb(skb);
                goto error;
            }
 
            offset += copy;
            length -= datalen - fraggap;
            transhdrlen = 0;
            exthdrlen = 0;
            csummode = CHECKSUM_NONE;
 
            /*
             * Put the packet on the pending queue.
             */
            __skb_queue_tail(queue, skb);    //把新skb插入skb队列队尾
            continue;
 
}

在这里可能产生两种情况:

 

(1)分配的新skb->len刚好是MTU

 

 

(2)分配的新skb->len刚好是确切的大小(小于mtu)

 

 

在这里我们只讨论最复杂的这种情况(并且也是跟我们漏洞最相关的这种情况),其余的更加细节的可以看:

 

https://blog.csdn.net/minghe_uestc/article/details/7836920?utm_source=blogxgwz2

漏洞分析与利用

poc/exp

来自国外某带师傅

 

poc/exp分析

最主要的其实就是 两次send

1
2
3
4
5
6
7
8
#define SHINFO_OFFSET 3164
 
int size = SHINFO_OFFSET + sizeof(struct skb_shared_info);
int rv = send(s, buffer, size, MSG_MORE);
int val = 1;
rv = setsockopt(s, SOL_SOCKET, SO_NO_CHECK, &val, sizeof(val));
send(s, buffer, 1, 0);    //第二次send的size为1
close(s);
  • SOCK_DGRAM 代表了UDP
  • AF_INET 代表TCP/IP协议族,在socket编程中只能是AF_INET。
  • s_addr 代表ip地址,INADDR_LOOPBACK代表绑定地址LOOPBAC, 往往是127.0.0.1, 只能收到127.0.0.1上面的连接请求。htons将其转换成一个转换成网络数据格式的数字。

当我们建立好socket并初始化之后,第一次send,带上标记为MSG_MORE告诉系统我们接下来还有数据要发送。此时走UFO路径

 

如果要发送的是UDP数据包,且系统支持UFO,并且需要分片(length > mtu),那么 send() 最终会进入: ip_ufo_append_data

 

 

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static inline int ip_ufo_append_data(struct sock *sk,
            struct sk_buff_head *queue,
            int getfrag(void *from, char *to, int offset, int len,
                   int odd, struct sk_buff *skb),
            void *from, int length, int hh_len, int fragheaderlen,
            int transhdrlen, int maxfraglen, unsigned int flags)
{
    struct sk_buff *skb;
    int err;
 
    /* There is support for UDP fragmentation offload by network
     * device, so create one single skb packet containing complete
     * udp datagram
     */
    skb = skb_peek_tail(queue);    //取skb队列的队尾
 
    if (!skb) {
        skb = sock_alloc_send_skb(sk,
            hh_len + fragheaderlen + transhdrlen + 20,
            (flags & MSG_DONTWAIT), &err);
 
        if (!skb)
            return err;
 
        /* reserve space for Hardware header */
        skb_reserve(skb, hh_len);
 
        /* create space for UDP/IP header */
        skb_put(skb, fragheaderlen + transhdrlen);
 
        /* initialize network header pointer */
        skb_reset_network_header(skb);
 
        /* initialize protocol header pointer */
        skb->transport_header = skb->network_header + fragheaderlen;
 
        skb->csum = 0;
 
        __skb_queue_tail(queue, skb);
    } else if (skb_is_gso(skb)) {
        goto append;
    }
 
    skb->ip_summed = CHECKSUM_PARTIAL;
    /* specify the length of each IP datagram fragment */
    skb_shinfo(skb)->gso_size = maxfraglen - fragheaderlen;
    skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
 
append:
    return skb_append_datato_frags(sk, skb, getfrag, from,
                       (length - transhdrlen));
}

调用 sock_alloc_send_skb 分配一个新的skb,然后把数据放到新的skb的非线性区域中。(skb_share_info)

 

 

其结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct skb_shared_info {
    unsigned char    nr_frags;
    __u8        tx_flags;
    unsigned short    gso_size;
    /* Warning: this field is not always filled in (UFO)! */
    unsigned short    gso_segs;
    unsigned short  gso_type;
    struct sk_buff    *frag_list;
    struct skb_shared_hwtstamps hwtstamps;
    u32        tskey;
    __be32          ip6_frag_id;
 
    /*
     * Warning : all fields before dataref are cleared in __alloc_skb()
     */
    atomic_t    dataref;
 
    /* Intermediate layers must ensure that destructor_arg
     * remains valid until skb destructor */
    void *        destructor_arg;
 
    /* must be last field, see pskb_expand_head() */
    skb_frag_t    frags[MAX_SKB_FRAGS];
};

最后新的skb入队。

 

注意在本条UFO路径中我们skb中数据的大小是大于mtu的!

 

更细节的:

 

 

通过 skb_shinfo(SKB) 宏也可以看出来skb_shared_info与skb之间的关系

1
#define skb_shinfo(SKB)    ((struct skb_shared_info *)(skb_end_pointer(SKB)))

在第二次send之前,我们调用 setsockopt 来设置了 SO_NO_CHECK 标志,即不校验checksum。(内核是通过SO_NO_CHECK的标志来判断用UFO机制还是non-UFO机制,这一点在刚刚的源码中并不明显。请直接看下面漏洞补丁那里的patch

 

造成接下来 第二次send的时候越过UFO路径而执行non-UFO的代码。

 

在non-UFO中,此时计算的 copy = mtu - skb_len 小于0,此时的skb是直接从skb队尾取出来的,也就是第一次send时new alloc出来的len > mtu的skb。

 

由于copy < 0,那么在 non-UFO 路径上触发了重新分配skb的操作。

 

而在重新分配结束后调用了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
copy = mtu - skb->len;
 
if(copy<0){
char *data;
unsigned int datalen;
unsigned int fraglen;
unsigned int fraggap;
unsigned int alloclen;
 
struct sk_buff *skb_prev = skb;
 
fraggap = skb_prev->len - maxfraglen;//这里其实就是负的copy
 
datalen = length + fraggap;
 
 
skb_copy_and_csum_bits(skb_prev, maxfraglen,data + transhdrlen, fraggap, 0);
 
}

其中的 skb_copy_and_csum_bits 将旧的 skb_prev 中的数据(UFO路径中的skb)复制到新分配的sk_buff中(即skb_shared_info->frags[]中的page_frag),从而造成溢出。

 

而对于 skb_shared_info 存在一个成员 void * destructor_arg 他是skb释放时的在 kfree_skb 中底层对于其产生的一个析构函数的调用。

 

 

这里很有意思的一个处理,直接将一个void 赋给一个 ubuf_info 类型。

1
2
3
4
5
struct ubuf_info {
    void (*callback)(struct ubuf_info *, bool zerocopy_success);
    void *ctx;
    unsigned long desc;
};

其作用是:

  • 当完成了skb DMA时,通过他调用回调函数做析构,释放缓冲区。并且此时skb引用计数为0。
  • 而ctx负责跟踪设备上下文。desc负责跟踪用户空间的缓冲区索引。
  • zerocopy_success代表是否发生 零拷贝

至此,通过覆盖 skb_shared_info.destructor_arg 就可以实现程序流劫持了。具体的打法有很多:

 

1.如果不开smep/kaslr,那么直接ret2usr即可。

 

2.如果开了smep,可以做内核rop先劫持cr4来关闭smep。然后jmp到payload

 

3.如果开了kaslr。我看了一下网上最通用的exp,用了一种非常有意思的方式来bypass KASLR。通过使用 klogctl 读取内核日志,然后在内核日志中查找 'Freeing unused' 这个字符串。然后找到与其同一行的ffffff开头的数字,最后 & 0xffffffffff000000ul 拿到一个地址,由于偏移不变,那么接下来就有了其他gadgets/函数在内核中的准确地址了。

 

exp地址:https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c

漏洞补丁

patch:https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=85f1bd9a7b5a79d5baa8bf44af19658f7bf77bfa

 

When iteratively building a UDP datagram with MSG_MORE and that
datagram exceeds MTU, consistently choose UFO or fragmentation.

 

Once skb_is_gso, always apply ufo. Conversely, once a datagram is
split across multiple skbs, do not consider ufo.

 

Sendpage already maintains the first invariant, only add the second.
IPv6 does not have a sendpage implementation to modify.

 

A gso skb must have a partial checksum, do not follow sk_no_check_tx
in udp_send_skb.

 

Found by syzkaller.

 

大概就是说,之前的话主要是由于 SO_NO_CHECK 可以控制UFO路径切换造成问题。但是现在的话一旦我们有了gso(Generic Segmentation Offload一种UFO分片优化,发生在数据送到网卡之前),那么就会调用ufo,而不是产生路径切换的隐患。

 

同时作者也说了如果数据报被分片到多个skb中,那么不要使用ufo了。

参考

UFO (UDP Fragmentation Offload)

 

Packet fragmentation and segmentation offload in UDP and VXLAN

 

Linux网络协议栈--ip_append_data函数分析

 

关于网络编程中MTU、TCP、UDP优化配置的一些总结

 

sk_buff整理笔记(五、队列管理函数)

 

skb_buff 详解(二)

 

CVE-2017-1000112-UFO 学习总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
if (!skb)     //前面已经传入一次数据了,所以,这个skb肯定不是空的
        goto alloc_new_skb;
    while (length > 0) {           
        /* Check if the remaining data fits into current packet. */
        copy = mtu - skb->len;                  //当前skb 还可以copy多少数据    skb->len = 3512
        if (copy < length)                   
            copy = maxfraglen - skb->len;       //copy = 对齐的最大的拷贝字节 - skb中已经存在的数据                              
        if (copy <= 0) {                        //copy<=0表示该分配一个新的sk_buff,因为最后一个已被完全填满
            char *data;                         //copy<=0表明有些数据必须从当前ip片段中删除,并移至新片段
            unsigned int datalen;             
            unsigned int fraglen;
            unsigned int fraggap;
            unsigned int alloclen;
            struct sk_buff *skb_prev;
alloc_new_skb:
            skb_prev = skb;        
            if (skb_prev)
                fraggap = skb_prev->len - maxfraglen;
            else
                fraggap = 0;
            datalen = length + fraggap; //datalen=1+2012=2013
            if (datalen > mtu - fragheaderlen)  //如果剩余的数据一个分片不够容纳,则根据MTU重新计算本次可发送的数据长度
            datalen = maxfraglen - fragheaderlen; //datalen=1500-20=1480
            fraglen = datalen + fragheaderlen; //根据本次复制的数据长度以及IP首部长度,计算三层首部及数据的总长度
            if ((flags & MSG_MORE) &&
                !(rt->dst.dev->features&NETIF_F_SG))
                alloclen = mtu;  //如果后续还有数据输出且网络设备不支持聚合分散I/O,则将MTU作为分配SKB的长度
            else
                alloclen = fraglen;//否则按数据的长度(包括IP首部)分配SKB的空间即可
            alloclen += exthdrlen; //alloclen=1500+0=1500
 
            if (datalen == length + fraggap)
                alloclen += rt->dst.trailer_len;
 
            if (transhdrlen) { //根据是否存在传输层首部,确定用何种方法分配SKB
                skb = sock_alloc_send_skb(sk,
                        alloclen + hh_len + 15,
                        (flags & MSG_DONTWAIT), &err);
            } else {
                skb = NULL;
                if (atomic_read(&sk->sk_wmem_alloc) <=
                    2 * sk->sk_sndbuf)
                    skb = sock_wmalloc(sk,                
                               alloclen + hh_len + 15, 1,
                               sk->sk_allocation);
                if (unlikely(!skb))
                    err = -ENOBUFS;
            }
            if (!skb)
                goto error;
 
            /*
             *  Fill in the control structures
             */
            skb->ip_summed = csummode; //填充用于校验的控制信息
            skb->csum = 0;
            skb_reserve(skb, hh_len);//为数据报预留用于存放二层首部、三层首部和数据的空间,并设置SKB中指向三层和四层的指针
 
            /* only the initial fragment is time stamped */
            skb_shinfo(skb)->tx_flags = cork->tx_flags;
            cork->tx_flags = 0;
            skb_shinfo(skb)->tskey = tskey;
            tskey = 0;
 
            /*
             *  Find where to start putting bytes.
             */
            data = skb_put(skb, fraglen + exthdrlen);
            skb_set_network_header(skb, exthdrlen);
            skb->transport_header = (skb->network_header +
                         fragheaderlen);
            data += fragheaderlen + exthdrlen;//data=20+0=20
 
            if (fraggap) { //如果上一个SKB的数据超过8字节对齐MTU,则将超出数据和传输层首部复制到当前SKB,重新计算校验和 fraggap=2012
                skb->csum = skb_copy_and_csum_bits( //并以8字节对齐MTU为长度截取上一个SKB的数据
                    skb_prev, maxfraglen,
                    data + transhdrlen, fraggap, 0);
                skb_prev->csum = csum_sub(skb_prev->csum,
                              skb->csum);
                data += fraggap;
                pskb_trim_unique(skb_prev, maxfraglen);
            }
            copy = datalen - transhdrlen - fraggap;//传输层首部和上个SKB多出的数据已复制,接着复制剩下的数据 //copy = 1480-0-2012=-532
            if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
                err = -EFAULT;                   
                kfree_skb(skb);
                goto error;
            }
            offset += copy; //完成本次复制数据,计算下次需复制数据的地址及剩余数据的长度。传输层首部已经复制
            length -= datalen - fraggap;//因此需要将传输层首部的transhdrlen置为0,同时IPsec首部长度exthdrlen也置为0 //length=2013-1480=533
            transhdrlen = 0;
            exthdrlen = 0;
            csummode = CHECKSUM_NONE;
 
            /*
             * Put the packet on the pending queue.
             */
            __skb_queue_tail(queue, skb);//将复制完数据的SKB添加到输出队列的尾部,接着复制剩下的数据
            continue;
        }

看雪侠者千人榜,看看你上榜了吗?

收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回