核心版本:2.6.34
在前一篇”IP協議”中對報文接收時IP層的處理進行了分析,本篇分析將針對報文發送時IP層的處理。
傳輸層處理完後,會調用ip_push_pending_frames()將報文傳遞給IP層:
ip_push_pending_frames() -> ip_local_out() -> __ip_local_out()
在ip_push_pending_frames()中,會設定第一個IP分區的前序欄位,tot_len和 check不會設定。
int ip_local_out(struct sk_buff *skb){ int err; err = __ip_local_out(skb); if (likely(err == 1)) err = dst_output(skb); return err;}
__ip_local_out():設定IP前序位元組總長度tot_len,校正和check。
iph->tot_len = htons(skb->len); ip_send_check(iph);
最後調用dst_output()發送資料給IP層,dst_output()實際調用skb_dst(skb)->output(skb) ,skb_dst(skb)就是skb所對應的路由項。skb_dst(skb)指向的是路由項dst_entry,它的input在收到報文時賦值 ip_local_deliver(),而output在發送報文時賦值ip_output()。
return nf_hook(PF_INET, NF_INET_LOCAL_OUT, skb, NULL, skb_dst(skb)->dev, dst_output);
在IP層的調用過程如下:
ip_output() -> ip_finish_output() - > ip_finish_output2() -> hh->hh_output()
在ip_output()中,設定了dev與協議號,從IP層往下,就是以dev驅 動資料轉送了。
skb->dev = dev; skb->protocol = htons(ETH_P_IP);
在ip_finish_output()中,判斷如果報文過大,則先調用ip_fragment()進行 分區(後面會對這個函數進行分析),然後調用ip_finish_output2()發送。
if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb)) return ip_fragment(skb, ip_finish_output2); else return ip_finish_output2(skb);
情況一:ip_fragment()
ip_fragment()與ip_append_data()是IP層傳送報文很重要的 兩個函數,弄清它們之間的關係很重要。
ip_append_data()是上層構造向IP層傳送資料的skb使用的,它會根據MTU值對傳送 資料進行分區,後續分區鏈在第一個分區的frag_list上;如果裝置支援SG,那麼同一個分區內容(當分區內容是多次輸入得到的 )不一定在一個線性空間上,後續輸入的分區內容存在分區的frags數組中。只有第一個分區才有frag_list,而每個分區都能擁 有frags。由ip_append_data()構造好的skb大致如下圖所示:
ip_fragments()字面 意思是分區,但實際上分區工作已經由ip_append_data()完成了,它只在上層分區出現問題時重新進行分區。它的主要作用還是 完成分區的後續工作。假設一個報文被分成了三份skb1, skb2, skb3,它們將獨立的傳遞到網路上,但顯然ip_append_data()得 到的skb還不是獨立的,skb1包含了整個報文的資訊,分區報文也鏈在frag_list上;而skb2, skb3則缺少IP前序的資訊,如分區 的位移,分區的標識,校正和等。ip_fragments()做的主要工作就是將skb拆分成能獨立發送的報文。由ip_fragments()處理後 的skb如圖所示: