網絡子係統86_inet協議族-l4向下(一)
// 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報頭內的值。
最後更新:2017-04-03 12:55:21