• 网络子系统46_ip协议数据帧的转发


    //调用路径ip_rcv->ip_rcv_finish->dst_input->(skb->dst->input)
    //ip_forward以回调函数的形式,保存在skb->dst->input,input在通过ip_route_input路由封包时被设置
    //函数的主要任务:
    //	1.更新ttl
    //	2.如果路由被重定向,则向发送方发送icmp重定向报文
    //	2.如果有选项,通过ip_forward_options处理在转发时需要更新的选项
    //	3.通过ip_output,将报文传递到ip发送路径上
    //skb的下一跳信息,在ip_rcv->ip_rcv_finish中,已经通过ip_route_input设置,保存在skb->dst中
    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!
    ");
    		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校验和
    	}
    }


  • 相关阅读:
    网络管理和nmcli命令的使用——网络接口配置-bonding实验步骤
    raid组合优缺点介绍和创建LVM实验个人笔记
    磁盘分区就是这么简单,电脑小白都能看懂的磁盘分区教程!
    C盘优化之桌面移动法,拯救你爆满的C盘!
    电脑软件打开也有讲究,电脑软件打开方式总结!
    电脑使用建议大全,注意这些细节可以让你的电脑更好用!
    CentOS服务器apache绑定多个域名的方法
    CentOS 7使用yum安装PHP5.6
    PhpMyAdmin 配置文件现在需要一个短语密码的解决方法
    CentOs 7.*中配置安装phpMyAdmin的完整步骤记录
  • 原文地址:https://www.cnblogs.com/fuhaots2009/p/3363325.html
Copyright © 2020-2023  润新知