-
-
[原创]Linux内核[CVE-2017-1000112] (UDP Fragment Offload) 分析
-
发表于: 2021-1-15 16:53 10842
-
在分析 CVE-2017-1000112 之前首先应该明白如下概念。
ps:幸好上学期学了计算机网络。。
从上到下分别是:应用层message -> 传输层segment(UDP) -> 网络层datagram(IP) -> 链路层frame。
封装层级:{ 数据帧frame{ IP包{ UDP包{ message/data } } } }
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
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
对于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
中首先判断flags是否开启了 MSG_PROBE 选项,如果开启了,那么就直接返回0。
接下来判断sk_buff队列是否为空,如果是空的话通过 ip_setup_cork
初始化 cork 变量。
如果不空那么设置 transhdrlen = 0
说明不是第一个fragment。
接下来调用:__ip_append_data
也是主要的处理流程。
接下来涉及到一个非常关键的 copy
变量。
copy
代表的是最后一个skb的剩余空间。
skb->len 表示此 SKB 管理的 Data Buffer 中数据的总长度
他有如下几种情况:
此时我们需要分配新的skb来存储溢出的数据。
由于这里代码较长,只分析关键部分。
这里有很多个不同的长度,图示如下:
首先计算我们究竟要分配多大的空间:
当我们初步确定了需要分配的新的skb的大小 alloclen
后:
当分配成功后,首先初始化skb中的一些控制数据
接下来初始化时间戳
在这里可能产生两种情况:
(1)分配的新skb->len刚好是MTU
(2)分配的新skb->len刚好是确切的大小(小于mtu)
在这里我们只讨论最复杂的这种情况(并且也是跟我们漏洞最相关的这种情况),其余的更加细节的可以看:
https://blog.csdn.net/minghe_uestc/article/details/7836920?utm_source=blogxgwz2
来自国外某带师傅
最主要的其实就是 两次send
当我们建立好socket并初始化之后,第一次send,带上标记为MSG_MORE告诉系统我们接下来还有数据要发送。此时走UFO路径
如果要发送的是UDP数据包,且系统支持UFO,并且需要分片(length > mtu),那么 send() 最终会进入: ip_ufo_append_data
源码如下:
调用 sock_alloc_send_skb
分配一个新的skb,然后把数据放到新的skb的非线性区域中。(skb_share_info)
其结构体定义如下:
最后新的skb入队。
注意在本条UFO路径中我们skb中数据的大小是大于mtu的!
更细节的:
通过 skb_shinfo(SKB) 宏也可以看出来skb_shared_info与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的操作。
而在重新分配结束后调用了:
其中的 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 类型。
其作用是:
至此,通过覆盖 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函数分析
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);
}
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);
}
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;
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;
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;
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;
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
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
/
*
*
Fill
in
the control structures
*
/
skb
-
>ip_summed
=
csummode;
skb
-
>csum
=
0
;
skb_reserve(skb, hh_len);
/
*
*
Fill
in
the control structures
*
/
skb
-
>ip_summed
=
csummode;
skb
-
>csum
=
0
;
skb_reserve(skb, hh_len);
/
*
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
;
/
*
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);
/
/
预留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
;
}
/
*
*
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
);