// 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报头内的值。