// l4数据向下l3传递
// 步骤:
// 1.如果sock->sk_write_queue为空,初始化corking
// 1.1 corking信息用于帮助ip层对数据进行分片
1.1 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;
//sk_write_queue为空
if (skb_queue_empty(&sk->sk_write_queue)) {
//初始化ip用于聚合,分片数据的信息
err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
if (err)
return err;
} else {
transhdrlen = 0;
}
//添加数据到sk->sk_wirte_queue
return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,
sk_page_frag(sk), getfrag,
from, length, transhdrlen, flags);
}
// 添加数据到队列
// 注:skb->frags数组里的数据时主缓存区中数据的扩展,
// 而frags_list里的数据代表的是独立缓存区(也就是必须作为单独ip片段而独立传输)。
// 步骤:
// 1.如果length大于mtu,或大于前一个skb剩余的空间
// 1.1 分配新skb,通过getfrag将数据从from拷贝到新skb
// 2.在拷贝过程中,需要考虑设备是否支持分散/聚集
// 2.1 有更多数据并且出口设备不支持分散/聚集IO,以最大尺寸分配skb
// 2.2 否则,以分片尺寸分配skb
1.2 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,
unsigned int flags)
{
//transhdrlen L4首部长度,用以区分第一个片段和后续片段
// transhdrlen !=0 表示ip_append_data工作在第一个片段
// transhdrlen ==0 表示ip_append_data未工作在第一个片段
struct inet_sock *inet = inet_sk(sk);
struct sk_buff *skb;
//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;
int csummode = CHECKSUM_NONE;
//路由项
struct rtable *rt = (struct rtable *)cork->dst;
//最后一个skb
skb = skb_peek_tail(queue);
exthdrlen = !skb ? rt->dst.header_len : 0;
//链路允许的最大报文长度
mtu = cork->fragsize;
//l2首部长度
hh_len = LL_RESERVED_SPACE(rt->dst.dev);
//分片ip首部长度
fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
//分片最大长度
// ip报文的长度为8字节的倍数
maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
//所有分片总长不超过64k
if (cork->length + length > 0xFFFF - fragheaderlen) {
ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
mtu-exthdrlen);
return -EMSGSIZE;
}
//length为剩余要处理的数据量
while (length > 0) {
//copy为当前ip片段中剩余的空间量
copy = mtu - skb->len;
//要加入的片段大于剩余的空间量
if (copy < length)
{
//通过使用maxfraglen是长度缩减到8字节边界
copy = maxfraglen - skb->len;
}
//copy=0表示该分配一个新的sk_buff,因为最后一个已被完全填满
//copy<0表明有些数据必须从当前ip片段中删除,并移至新片段,前一种情况的特例
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;
//保证ip数据报对齐到8字节,fraggap=已满skb超出maxfraglen的部分,将移动到新分配的skb中取
if (skb_prev)
fraggap = skb_prev->len - maxfraglen;
else
fraggap = 0;
//剩余数据长度
datalen = length + fraggap;
//剩余数据长度大于mtu
if (datalen > mtu - fragheaderlen)
datalen = maxfraglen - fragheaderlen;
//分片长度(数据长度+首部长度)
fraglen = datalen + fragheaderlen;
//有更多数据并且出口设备不支持分散/聚集IO
if ((flags & MSG_MORE) &&
!(rt->dst.dev->features&NETIF_F_SG))
{
//以最大尺寸分配内存
alloclen = mtu;
}
else
{ //以分片长度分配内存
alloclen = fraglen;
}
...
//分配新skb
if (transhdrlen) {
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);
cork->tx_flags = 0;
}
skb->ip_summed = csummode;
skb->csum = 0;
//预留l2首部
skb_reserve(skb, hh_len);
skb_shinfo(skb)->tx_flags = cork->tx_flags;
//移动skb->tail到分片尾部,返回skb->data
data = skb_put(skb, fraglen + exthdrlen);
skb_set_network_header(skb, exthdrlen);
skb->transport_header = (skb->network_header +
fragheaderlen);
data += fragheaderlen + exthdrlen;
//从上一个skb拷贝未对其到8字节边界的剩余字节到新skb
if (fraggap) {
//计算剩余字节的校验和
skb->csum = skb_copy_and_csum_bits(
skb_prev, maxfraglen,
data + transhdrlen, fraggap, 0);
//更新上一个skb的校验和
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;
//将skb添加到队列上
__skb_queue_tail(queue, skb);
continue;
}
//copy>length意味着skb有足够的空间
if (copy > length)
copy = length;
//出口设备不支持分散/聚集IO
if (!(rt->dst.dev->features&NETIF_F_SG)) {
unsigned int off;
off = skb->len;
//将数据拷贝到主缓存
if (getfrag(from, skb_put(skb, copy),
offset, copy, off, skb) < 0) {
__skb_trim(skb, off);
err = -EFAULT;
goto error;
}
} else {
...
//将数据拷贝到skb->frags中
}
offset += copy;
length -= copy;
}
return 0;
error_efault:
err = -EFAULT;
error:
cork->length -= length;
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
return err;
}
// L4校验和
// TCP,UDP协议计算的校验和会包括其报头,有效载荷以及一个伪装报头。
//TCP,UDP伪报头
// 硬件计算L4校验和
// 1.使用硬件计算L4校验和的条件:
// 1.1 由ip_append_data所构建的ip封包不会被分段(及送给ip_append_data的总数不会超过PMTU)
// 1.2 出口设备支持硬件校验和计算
// 1.3 没有转换报头(也就是IPSec集组协议)
// 2.计算方法:
// 2.1 L4协议把skb->csum初始化成正确的偏移量,并用伪报头验证和设定L4报头的校验字段
// 软件计算L4校验和
// 如果出口设备不支持硬件设备校验和,或者虽然支持,但是因为sk_write_queue有一个以上的ip片段而
// 无法使用,则L4校验和必须在软件中计算,在这种情况,getfrag把数据拷贝到缓存区时,会对L4有效载荷
// 计算部分校验和,然后,L4协议稍后会将这些值结合起来得到放在L4报头内的值。