網絡子係統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