数据包前送

一、ip_forward函数

ip_rcv_finish处理结束后,如果数据包的目标地址不是本机,内核就要前送数据包,如果是数据包的目标地址是本机就上传TCP/IP协议栈,处理函数由dst_input完成,根据路由选项将处理函数设置为ip_forward或则ip_local_deliver。ip_forward是数据包前送处理函数,ip_local_deliver是本地接受处理函数。

数据包的前送处理也分为两个阶段分别由ip_forward和ip_forward_finish完成,定义在net/ipv4/ip_forward.c文件中,函数到达ip_forward时前送的信息已经准备好了:

a、数据包前送路径的路由信息,存放在skb->dst数据域中,或者由ip_rcv_finish函数调用ip_route_input获取存放在skb->dst中。

b、IP选项设置已经解析完成放在ip_option数据结构类型变量中。

前送数据包主要做的事情:

1)、处理IP选项

2)、基于IP协议头的数据域确定数据包可以前送

3)、对IP协议头中的TTL数据域减1,如果TTL的值变成0就扔掉数据包

4)、基于路由的MTU,如果数据包大于MTU就要对数据包做分片处理

5)、将数据包通过选定的网络接口发送出去

6)、错误处理

完成了这些处理就调用netfilter框架中的forward链中的钩子函数处理,通过钩子函数的处理就调用ip_forward_finish函数继续处理。

ip_forward函数源码分析:

int ip_forward(struct sk_buff *skb)
{
	struct iphdr *iph;	/* Our header */
	struct rtable *rt;	/* Route we use */
	//ip选项
	struct ip_options * opt	= &(IPCB(skb)->opt);

	if (skb_warn_if_lro(skb))
		goto drop;

	if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))
		goto drop;
	//处理route AlertIP选项
	if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))
		return NET_RX_SUCCESS;
	//查看数据类型,数据包在链路层已经设置
	//PACKET_HOST
	if (skb->pkt_type != PACKET_HOST)
		goto drop;

	skb_forward_csum(skb);


	 //TTL减1为0就扔掉
	if (ip_hdr(skb)->ttl <= 1)
		goto too_many_hops;

	if (!xfrm4_route_forward(skb))
		goto drop;

	rt = skb_rtable(skb);

	//ip选项处理,严格路由选项处理
	if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
		goto sr_failed;

	//分片处理
	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;
	}

	/* We are about to mangle packet. Copy it! */
	if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))
		goto drop;
	iph = ip_hdr(skb);

	/* Decrease ttl after skb cow done */
	ip_decrease_ttl(iph);

	/*
	 *	We now generate an ICMP HOST REDIRECT giving the route
	 *	we calculated.
	 */
	if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb_sec_path(skb))
		ip_rt_send_redirect(skb);

	//设置数据域优先级,给流量控制系统(Qos)使用决定数据包发送顺序
	skb->priority = rt_tos2priority(iph->tos);

	//调用网络过滤子系统forward链上的钩子函数
	//网络过滤系统处理接受调用ip_forward_finish
	return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev,
		       rt->u.dst.dev, ip_forward_finish);

sr_failed:
	/*
	 *	Strict routing permits no gatewaying
	 */
	 //错误就回一个icmp包
	 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(skb)->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函数

当数据包到ip_forward_finish已经通过了所有正确信检查和安全性检验,准备通过网络设备发送给网络上的另一台主机。到目前位置已经处理了两个IP选项:Router Alert和Strict Source Routing(严格路由选项),余下的IP选项就会在ip_forward_finish函数中调用ip_forward_option函数处理前送数据包的IP选项,通过查询ip_option_compile解析选项时初始化的标志:如opt->rr_needaddr、opt->ts_needaddr来决定数据包在IP协议头中添加什么信息。最后数据包由dst_output处理。

ip_forward_finish代码:

static int ip_forward_finish(struct sk_buff *skb)
{
	struct ip_options * opt	= &(IPCB(skb)->opt);

	IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_OUTFORWDATAGRAMS);

	//处理前送数据包的ip选项
	if (unlikely(opt->optlen))
		ip_forward_options(skb);
	//调用dst_output继续处理数据包
	return dst_output(skb);
}

三、dst_output函数

所有要发送的数据包,如论是前送的数据包还是本机产生的数据包要发送给其他网络主机都要通过dst_output发送给目的网络。这时IP协议头已经处理结束,协议头中嵌入了要发送的信息和本机系统需要加入的信息。dst_output调用函数指针skb->output,skb->output根据目标地址类型来初始化,目标地址为木一个主机地址时,skb->output初始化为ip_output,目标地址是组发送地址时就初始化为ip_mac_output。ip_output函数会初始化数据包的输出网络设备和传输协议,最后进入netfilter的POST_ROUTING链处理钩子函数,POST_ROUTING链上的钩子函数处理结束后调用ip_output_finish。

ip_output函数代码分析:

int ip_output(struct sk_buff *skb)
{
	//发包的网络设备
	struct net_device *dev = skb_dst(skb)->dev;

	IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len);
	//初始化发包网络设备
	skb->dev = dev;
	//初始化协议
	skb->protocol = htons(ETH_P_IP);
	//进入POST_ROUTING链上钩子处理函数
	//钩子处理函数结束调用ip_finish_output
	return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev,
			    ip_finish_output,
			    !(IPCB(skb)->flags & IPSKB_REROUTED));
}

四、ip_finish_output函数

ip_finish_output主要任务是根据网络配置决定数据包是否要重新路由、是否要对数据包做分片处理,最后调用ip_finish_output2和相邻子系统接口。

ip_finish_output函数代码分析:

static int ip_finish_output(struct sk_buff *skb)
{
#if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)
	/* Policy lookup after SNAT yielded a new policy */
	//如果是ipsec加密,重新选路由
	if (skb_dst(skb)->xfrm != NULL) {
		IPCB(skb)->flags |= IPSKB_REROUTED;
		return dst_output(skb);
	}
#endif
	//数据包长度大于mtu而且没有设置GSO
	//就要重新分片,然后调用ip_finish_output2和相邻子系统接口
	if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))
		return ip_fragment(skb, ip_finish_output2);
	else
		return ip_finish_output2(skb);
}

五、ip_finish_output2函数

ip_finish_output2函数是与相邻子系统接口的函数,该函数的主要任务是为数据链路层协议插入其协议头,或者为数据链路层协议头预留分配空间,然后将数据包交给相邻子系统发送函数dst->neithbour->output处理,实际调用的是dev_queue_ximit。

ip_finish_output2函数代码分析:

static inline int ip_finish_output2(struct sk_buff *skb)
{
	struct dst_entry *dst = skb_dst(skb);
	struct rtable *rt = (struct rtable *)dst;
	struct net_device *dev = dst->dev;
	unsigned int hh_len = LL_RESERVED_SPACE(dev);

	//根据数据包类型更新组传送、广播传送的统计信息
	if (rt->rt_type == RTN_MULTICAST) {
		IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUTMCAST, skb->len);
	} else if (rt->rt_type == RTN_BROADCAST)
		IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUTBCAST, skb->len);

	/* Be paranoid, rather than too clever. */
	//数据包headroom小于链路层协议头长度,切设备设备
	//初始化了操作协议头函数,就要为二层协议头分配空间
	if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
		struct sk_buff *skb2;
		//为二层协议头分配空间
		skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
		if (skb2 == NULL) {
			kfree_skb(skb);
			return -ENOMEM;
		}
		if (skb->sk)
			//为新的skb设置所属套接字
			skb_set_owner_w(skb2, skb->sk);
		kfree_skb(skb);
		skb = skb2;
	}
	//将数据包交给相邻子系统处理函数
	if (dst->hh)
		return neigh_hh_output(dst->hh, skb);
	else if (dst->neighbour)
		return dst->neighbour->output(skb);

	if (net_ratelimit())
		printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");
	kfree_skb(skb);
	return -EINVAL;
}

猜你喜欢

转载自blog.csdn.net/City_of_skey/article/details/84146553