核心版本:2.6.34
這篇是關於IP層協議接收報文時的處理,重點說明了路由表的尋找,以及IP分區重組。
ip_rcv 進入IP層報文接收函數
丟棄掉不是發往原生報文,skb->pkt_type在網卡接收報文處理乙太網路頭時會根據dst mac設定, 協議棧的書會講不是發往原生廣播報文會在二層被丟棄,實際上丟棄是發生在進入上層之初。
if (skb->pkt_type == PACKET_OTHERHOST) goto drop;
在取IP前序時要注意可能帶有選項,因此報文長度應當以iph->ihl * 4為準。這裡就需要嘗試兩次, 第一次嘗試sizeof(struct iphdr),只是為了確保skb還可以容納標準的前序(即20位元組),然後可以ip_hdr(skb)得到前序;第二 次嘗試ihl * 4,這才是報文的真正長度,然後重新調用ip_hdr(skb)來得到前序。兩次嘗試pull後要重新調用ip_hdr()的原因是 pskb_may_pull()可能會調用__pskb_pull_tail()來改現現有的skb結構。
if (!pskb_may_pull(skb, sizeof(struct iphdr))) goto inhdr_error; iph = ip_hdr(skb); …… if (!pskb_may_pull(skb, iph->ihl*4)) goto inhdr_error; iph = ip_hdr(skb);
擷取到IP前序後經過一些檢查,擷取到報文的總長度len = iph->tot_len,此時調用 pskb_trim_rcsum()去除多餘的位元組,即大於len的。
if (pskb_trim_rcsum(skb, len)) { IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS); goto drop; }
然後調用ip_rcv_finish()繼續IP層的處理,ip_rcv()可以看成是尋找路由前的IP層處理,接下來的ip_rcv_finish() 會尋找路由表,兩者間調用插入的netfilter(關於NetFilter,參考前篇 http://blog.csdn.net/qy532846454/article/details/6605592)。
return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
進入ip_rcv_finish函數
ip_rcv_finish()主要工作是完成路由表的查詢,決定報 文經過IP層處理後,是繼續向上傳遞,還是進行轉寄,還是丟棄。
剛開始沒有進行路由表查詢,所以還沒有相應的路由表項 :skb_dst(skb) == NULL。則在路由表中尋找ip_route_input(),關於核心的路由表,可以參見前篇 http://blog.csdn.net/qy532846454/article/details/6726171:
if (skb_dst(skb) == NULL) { int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev); if (unlikely(err)) { if (err == -EHOSTUNREACH) IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INADDRERRORS); else if (err == -ENETUNREACH) IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INNOROUTES); goto drop; } }
通過路由表尋找,我們知道:
- 如果是丟棄的報文,則直接drop;
- 如果是不能接收或轉寄的報文,則 input = ip_error
- 如果是發往本機報文,則input = ip_local_deliver;
- 如果是廣播報文,則input = ip_local_deliver;
- 如果是組播報文,則input = ip_local_deliver;
- 如果是轉寄的報文,則input = ip_forward ;
在ip_rcv_finish()最後,會調用尋找到的路由項_skb_dst->input()繼續向上傳遞:
return dst_input (skb);
具體看下各種情況下的報文傳遞,如果是丟棄的報文,則報文被釋放,並從IP協議層返回,完成此次報文傳遞流 程。
drop: kfree_skb(skb); return NET_RX_DROP;
如果是不能處理的報文,則執行ip_error,根據error類型發送相應的ICMP錯誤判文。
static int ip_error(struct sk_buff *skb) { struct rtable *rt = skb_rtable(skb); unsigned long now; int code; switch (rt->u.dst.error) { case EINVAL: default: goto out; case EHOSTUNREACH: code = ICMP_HOST_UNREACH; break; case ENETUNREACH: code = ICMP_NET_UNREACH; IP_INC_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INNOROUTES); break; case EACCES: code = ICMP_PKT_FILTERED; break; } now = jiffies; rt->u.dst.rate_tokens += now - rt->u.dst.rate_last; if (rt->u.dst.rate_tokens > ip_rt_error_burst) rt->u.dst.rate_tokens = ip_rt_error_burst; rt->u.dst.rate_last = now; if (rt->u.dst.rate_tokens >= ip_rt_error_cost) { rt->u.dst.rate_tokens -= ip_rt_error_cost; icmp_send(skb, ICMP_DEST_UNREACH, code, 0); } out: kfree_skb(skb); return 0; }