閱讀395 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

  上一篇:go 教你如何使用JSONP數據格式,如何使用jQuery
  下一篇:go 網絡子係統45_ip協議tos處理