網絡子係統46_ip協議數據幀的轉發
// ip協議數據轉發 // ip_forward以回調函數的形式,保存在skb->dst->input,skb->dst在ip_route_input路由封包時被設置 // 調用路徑:ip_rcv->ip_rcv_finish->dst_input->(skb->dst->input) // 函數的主要任務: // 1.遞減ttl // 2.如果路由被重定向,則向發送方發送icmp重定向報文 // 2.如果有選項,通過ip_forward_options處理在轉發時需要更新的選項 // 3.通過ip_output,將報文傳遞到ip發送路徑上 1.1 int ip_forward(struct sk_buff *skb) { struct iphdr *iph; struct rtable *rt; struct ip_options * opt = &(IPCB(skb)->opt);//ip協議控製塊,由ip_options_compile初始化 //XFRM是 Linux 2.6 內核為安全處理引入的一個可擴展功能框架 if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb)) goto drop; // if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))//對router alert選項的處理 return NET_RX_SUCCESS; if (skb->pkt_type != PACKET_HOST)//重複檢查,l2地址非本機,直接丟棄 goto drop; skb->ip_summed = CHECKSUM_NONE;//需要軟件重新計算校驗和 iph = skb->nh.iph; if (iph->ttl <= 1)//數據幀到期 goto too_many_hops; //XFRM路由 if (!xfrm4_route_forward(skb)) goto drop; iph = skb->nh.iph; rt = (struct rtable*)skb->dst; //處理嚴源路由選項 // rt->rt_dst 目的ip地址 // rt->rt_gateway 當目的主機直連時,rt_gateway匹配目的地址,當需要通過網關到達目的地址時,rt_gateway設置為下一條網關 // skb->dst在ip_options_rcv_srr中,使用選項中指定的ip地址作為目標地址,通過路由查找,設置為第一個非本機ip的單播路由項 if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)//由於是嚴源路由選項,因此下一跳地址,必須等於源路由選項中列出的地址 goto sr_failed; if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))//檢查skb是否被共享,如果被共享的話,拷貝一份,並在頭部預留l3+l2 goto drop; iph = skb->nh.iph; //遞減跳數 ip_decrease_ttl(iph); //RTCF_DOREDIRECT在ip_route_input_slow中被設置,表示ICMP_REDIRECT必須被送回源地址 if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr) ip_rt_send_redirect(skb); skb->priority = rt_tos2priority(iph->tos);//通過tos字段計算skb在規則隊列中的優先級 return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, rt->u.dst.dev, ip_forward_finish); sr_failed: icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0); goto drop; too_many_hops: icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0); drop: kfree_skb(skb); return NET_RX_DROP; } // 將轉發數據傳遞到發送路徑上 // 調用路徑:ip_forward->netfilter hooks->ip_forward_finish 1.2 static inline int ip_forward_finish(struct sk_buff *skb) { struct ip_options * opt = &(IPCB(skb)->opt); IP_INC_STATS_BH(IPSTATS_MIB_OUTFORWDATAGRAMS); if (unlikely(opt->optlen)) ip_forward_options(skb);//處理寬鬆路由選項和time stamp選項 return dst_output(skb);//調用ip_output } // 數據轉發時更新的選項 // 1.record route選項 // 2.源路由選項 // 3.time stamp選項 1.3 void ip_forward_options(struct sk_buff *skb) { struct ip_options * opt = &(IPCB(skb)->opt); unsigned char * optptr; struct rtable *rt = (struct rtable*)skb->dst; unsigned char *raw = skb->nh.raw; //record route選項 if (opt->rr_needaddr) {//說明為出口skb,因為入口skb在ip_options_compile已經被使用首選源地址填寫 optptr = (unsigned char *)raw + opt->rr; ip_rt_get_source(&optptr[optptr[2]-5], rt); opt->is_changed = 1; } if (opt->srr_is_hit) {//表示在源路由選項列表中,有可用的下一跳ip地址 int srrptr, srrspace; optptr = raw + opt->srr; for ( srrptr=optptr[2], srrspace = optptr[1]; srrptr <= srrspace; srrptr += 4 ) { if (srrptr + 3 > srrspace) break; if (memcmp(&rt->rt_dst, &optptr[srrptr-1], 4) == 0)//下一跳地址在源路由地址列表中 break; } if (srrptr + 3 <= srrspace) {//表示下一跳在源路由地址列表中 opt->is_changed = 1; ip_rt_get_source(&optptr[srrptr-1], rt); skb->nh.iph->daddr = rt->rt_dst;//更新目的地址為下一跳的地址 optptr[2] = srrptr+4;//更新選項的ptr[2]指針到下一跳 } else if (net_ratelimit()) printk(KERN_CRIT "ip_forward(): Argh! Destination lost!\n"); if (opt->ts_needaddr) { optptr = raw + opt->ts; ip_rt_get_source(&optptr[optptr[2]-9], rt); opt->is_changed = 1; } } if (opt->is_changed) { opt->is_changed = 0; ip_send_check(skb->nh.iph);//計算ip校驗和 } }
最後更新:2017-04-03 14:53:38