上次我們提到當ip_rcv_finish完成後後調用相關的發送函數ip_forward或者ip_local_deliver.這次就主要介紹這兩個函數。
先來看forward。
forward一般由下面幾部組成:
1 執行ip option
2 確定這個包能被forward
3 減小ttl,當ttl爲0時,丟掉這個包
4 如果需要,則將這個包切片
5 發送包到輸出網絡設備
這裏要注意,如果包由於一些原因,不能被forward,則必鬚髮送ICMP消息到發送主機。
- int ip_forward(struct sk_buff *skb)
- {
- struct iphdr *iph; /* Our header */
- struct rtable *rt; /* Route we use */
- struct ip_options * opt = &(IPCB(skb)->opt);
- ///gso相關設置
- if (skb_warn_if_lro(skb))
- goto drop;
- ///xfrm(ipsec)的相關檢測
- if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))
- goto drop;
- ///判斷是否有Router_alter option(也就是保存發送端的ip),如果有的話,調用ip_call_ra_chain處理,當空間已滿,則返回false,並繼續處理。
- if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))
- return NET_RX_SUCCESS;
- ///判斷這個包是否是由本地主機的2層進行接受的。在2層設置幀的類型,當幀的目的地址就是本機2層地址的時候,skb->pkt_type設置爲PCAKET_HOST.
- if (skb->pkt_type != PACKET_HOST)
- goto drop;
- ///由於是forward,因此我們不需要在意4層的校驗。設置ip_summed爲CHECKSUM_NONE。
- skb_forward_csum(skb);
- /*
- * According to the RFC, we must first decrease the TTL field. If
- * that reaches zero, we must reply an ICMP control message telling
- * that the packet's lifetime expired.
- */
- ///ttl小於1,此時丟掉這個包
- if (ip_hdr(skb)->ttl <= 1)
- goto too_many_hops;
- ///ipsec的檢測
- if (!xfrm4_route_forward(skb))
- goto drop;
- ///得到路由表
- rt = skb->rtable;
- ///判斷是否是Strict源路由option。如果是的話,看源路由option所制定的路由能否和rt_gateway(下一跳)匹配。
- if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
- goto sr_failed;
- ///檢測一些相關域。如果出錯,則發送icmp,並丟棄這個包
- if (unlikely(skb->len > dst_mtu(&rt->u.dst) && !skb_is_gso(skb) &&
- (ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) {
- IP_INC_STATS(dev_net(rt->u.dst.dev), IPSTATS_MIB_FRAGFAILS);
- icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
- htonl(dst_mtu(&rt->u.dst)));
- goto drop;
- }
- ///由於我們將要修改這個skb的一些東西(在下面的ip_forward_finish中),因此我們需要複製一個拷貝(主要是防止skb共享)
- if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))
- goto drop;
- iph = ip_hdr(skb);
- ///減少ttl
- ip_decrease_ttl(iph);
- ///如果我們所找到的下一跳地址比請求的更好的話,源host現在將會收到一個ICMP REDIRESCT消息(只有當源host沒有請求 source routing option時)
- if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb->sp)
- ip_rt_send_redirect(skb);
- ///QOS的優先級設置
- skb->priority = rt_tos2priority(iph->tos);
- ///最終返回netfilter的hook,這裏我們還是暫時忽略netfilter,只關注ip_forward_finish.
- return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,
- ip_forward_finish);
- sr_failed:
- /*
- * Strict routing permits no gatewaying
- */
- icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
- goto drop;
- too_many_hops:
- /* Tell the sender its packet died... */
- IP_INC_STATS_BH(dev_net(skb->dst->dev), IPSTATS_MIB_INHDRERRORS);
- icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
- drop:
- kfree_skb(skb);
- return NET_RX_DROP;
- }
下來看ip_forward_finish方法,它主要是用來處理一些剩下的options,並且ip_forward_options還會重新計算ip checksum,因爲它會update一些ip頭的域:
- static int ip_forward_finish(struct sk_buff *skb)
- {
- struct ip_options * opt = &(IPCB(skb)->opt);
- IP_INC_STATS_BH(dev_net(skb->dst->dev), IPSTATS_MIB_OUTFORWDATAGRAMS);
- if (unlikely(opt->optlen))
- ip_forward_options(skb);
- ///最終返回dst_output,這個虛函數最終調用skb->dst_output,如果是單播則是ip_output,如果是多播則是ip_mc_output.而且切片(如果有需要)也會在這個函數進行).這裏還有一個neighboring subsystem的概念,我們後面會講到。
- return dst_output(skb);
- }
來看ip_local_deliver函數:
- int ip_local_deliver(struct sk_buff *skb)
- {
- /*
- * Reassemble IP fragments.
- */
- ///如果有切片,則開始組包。
- if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
- if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
- return 0;
- }
- ///返回netfilter hook,最終會調用ip_local_deliver_finish.它最終會將數據包發送往4層。下一次我們會詳細介紹這個函數。
- return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
- ip_local_deliver_finish);
- }
這裏我們可以看到一個對比,那就是forward時,不需要組包,這是因爲local delivery必須把完整的包發送往4層,而forward,不需要經過4層,就直接把幀發送出去。
最後我們來看下ip_local_deliver_finish函數片段:
- static int ip_local_deliver_finish(struct sk_buff *skb)
- {
- struct net *net = dev_net(skb->dev);
- __skb_pull(skb, ip_hdrlen(skb));
- /* Point into the IP datagram, just past the header. */
- skb_reset_transport_header(skb);
- rcu_read_lock();
- {
- int protocol = ip_hdr(skb)->protocol;
- int hash, raw;
- struct net_protocol *ipprot;
- resubmit:
- ///對raw socket進行處理。
- raw = raw_local_deliver(skb, protocol);
- hash = protocol & (MAX_INET_PROTOS - 1);
- ipprot = rcu_dereference(inet_protos[hash]);
- if (ipprot != NULL) {
- int ret;
- ...................................................
- ///將數據包交給已註冊的高層協議的處理函數。
- ret = ipprot->handler(skb);
- if (ret < 0) {
- protocol = -ret;
- goto resubmit;
- }
- IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
- }
- ...................................................
- }
- out:
- rcu_read_unlock();
- return 0;
- }