zoukankan      html  css  js  c++  java
  • Linux内核IP层的报文处理流程(一)

    本文主要讲解了Linux内核IP层的整体架构和对从网卡接受的报文处理流程,使用的内核的版本是2.6.32.27

    为了方便理解,本文采用整体流程图加伪代码的方式对Linxu内核中IP整体实现架构和对网卡报文的处理流程进行了讲解,希望可以对大家有所帮助。阅读本文章假设大家对C语言有了一定的了解


    IP层的整体实现架构




    IP层接受底层数据报文的处理流程

     

    /*
     * 在NET_RX_SOFTIRQ软中后,由ETH_P_IP触发的ipv4协议入口函数
     */
    int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
    {
    	/*
    	 * 过滤掉送往其他主机的数据包(这时网卡正在处于混杂模式)
    	 */
    	if (skb->pkt_type == PACKET_OTHERHOST)
    		goto drop;
    
    	iph = ip_hdr(skb);
    
    	/*头的长度是否至少是IP头长度(5); 是否是IPV4报文*/
    	if (iph->ihl < 5 || iph->version != 4)
    		goto inhdr_error;
    
    	/*IP头长度是否正确,不是伪造的长度*/
    	if (!pskb_may_pull(skb, iph->ihl*4))
    		goto inhdr_error;
    
    	iph = ip_hdr(skb);
    	/*检查校验和*/
    	if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
    		goto inhdr_error;
    
    	len = ntohs(iph->tot_len);
    	if (skb->len < len) {
    		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
    		goto drop;
    	} else if (len < (iph->ihl*4))
    		goto inhdr_error;
    
    	/*实际尺寸不匹配套接字缓冲(skb->len)中维护的信息,则调用skb_trim调整数据包的长度*/
    	if (pskb_trim_rcsum(skb, len)) {
    		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
    		goto drop;
    	}
    
    	/*调用IP_PRE_ROUTING(NF_INET_PRE_ROUTING)上注册的钩子,
    	  *在调用钩子处理完之后,调用钩子处理完成之后,调用ip_rcv_finish
    	  * 后面讲防火墙的时候,我们会仔细梳理*/
    	return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
    }
    
    
    /* NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish)*/
    #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) 
    	NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)
    {
    	int __ret;								       
    	if ((__ret=nf_hook_thresh(pf, hook, (skb), indev, outdev, okfn, thresh, 1)) == 1)
    		__ret = (okfn)(skb);						       
    	__ret;
    }		
    
    static inline int nf_hook_thresh(u_int8_t pf, unsigned int hook,
    				 struct sk_buff *skb,
    				 struct net_device *indev,
    				 struct net_device *outdev,
    				 int (*okfn)(struct sk_buff *), int thresh,
    				 int cond)
    {
    	/*逐个调用注册的防火墙钩子*/
    	return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh);
    }
    
    
    /*
     * 接收完数据包后的后续处理函数
     */
    static int ip_rcv_finish(struct sk_buff *skb)
    {
    	const struct iphdr *iph = ip_hdr(skb);
    	struct rtable *rt;
    
    	 /*
    	  * 激活ip_route_input,确定报文的路由,如果ip_route_input无法从FIB中找到路由
    	  * 则丢弃数据报文,ip_route_input将在IP路由中的专题中进行讲解
    	  */
    	if (skb_dst(skb) == NULL) {
    		int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);
    		if (unlikely(err)) {
    			goto drop;
    		}
    	}
    
    	/*检查IP报头里面是否含有选项,如果含有建立ip_options*/
    	if (iph->ihl > 5 && ip_rcv_options(skb))
    		goto drop;
    
    	/*根据dst_entry的结果,使用skb_dst(skb)->input(skb)进行IP的路由选择
    	 *传递给本地计算机的单播或多播,进入 ip_local_deliver();
    	 *单播转发的报文进入ip_forward()
    	 *多播转发进入ip_mr_input()
    	 */
    	return dst_input(skb);
    	{
    		skb_dst(skb)->input(skb)
    	}
    
    drop:
    	kfree_skb(skb);
    	return NET_RX_DROP;
    }
    
    /*目的地分发策略的注册*/
    static int __mkroute_input(struct sk_buff *skb,
    			   struct fib_result *res,
    			   struct in_device *in_dev,
    			   __be32 daddr, __be32 saddr, u32 tos,
    			   struct rtable **result)
    {
    	//.......
    	rth->u.dst.input = ip_forward;
    	rth->u.dst.output = ip_output;
    	//......	
    }
    
    
    static int __mkroute_output(struct rtable **result,
    			    struct fib_result *res,
    			    const struct flowi *fl,
    			    const struct flowi *oldflp,
    			    struct net_device *dev_out,
    			    unsigned flags)
    {
    	//......
    	if (flags & RTCF_LOCAL) {
    		rth->u.dst.input = ip_local_deliver;
    		rth->rt_spec_dst = fl->fl4_dst;
    	}
    	if (flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
    		rth->rt_spec_dst = fl->fl4_src;
    		if (flags & RTCF_LOCAL &&
    		    !(dev_out->flags & IFF_LOOPBACK)) {
    			rth->u.dst.output = ip_mc_output;
    			RT_CACHE_STAT_INC(out_slow_mc);
    		}
    #ifdef CONFIG_IP_MROUTE
    		if (res->type == RTN_MULTICAST) {
    			if (IN_DEV_MFORWARD(in_dev) &&
    			    !ipv4_is_local_multicast(oldflp->fl4_dst)) {
    				rth->u.dst.input = ip_mr_input;
    				rth->u.dst.output = ip_mc_output;
    			}
    		}
    #endif
    
    	}	
    	//......
    }
    


    如果IP报文需要转发,那么分析流程如下

     

    //-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
    /*单播转发处理,负责处理转发相关的所有动作*/
    int ip_forward(struct sk_buff *skb)
    {
    	/*删除不是PACKET_HOST的数据包*/
    	if (skb->pkt_type != PACKET_HOST)
    		goto drop;
    
    	/*TTL递减为1之间,丢弃该报,并返回ICMP_TIME_EXCEEDED*/
    	if (ip_hdr(skb)->ttl <= 1)
    		goto too_many_hops;
    
    	/*如果skb->len大于MTU值,且Dont-Fragment被职位,则丢弃此报文,
    	 *并返回ICMP_FRAG_NEEDED*/
    	if (unlikely(skb->len > dst_mtu(&rt->u.dst) &&  (ip_hdr(skb)->frag_off & htons(IP_DF)))) {
    		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,  htonl(dst_mtu(&rt->u.dst)));
    		goto drop;
    	}
    
    	/*检查是否有足够的空间用于输出网络设备中的MAC报头dst.header_len()
    	 *调用skb_cow来创建一个新的足够长的skb,并且拷贝原来的所有数据
    	 */
    	if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))
    		goto drop;
    	iph = ip_hdr(skb);
    
    	/*TTL减少1*/
    	ip_decrease_ttl(iph);
    
    	/*使用IP_FORWARD中注册的钩子函数,当防火墙中的钩子都与运行完成后,
    	  *进入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(skb)->dev), IPSTATS_MIB_INHDRERRORS);
    	icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
    drop:
    	kfree_skb(skb);
    	return NET_RX_DROP;
    }
    
    /*
     * 该函数没有什么用途,除非启用了FASTROUTE,
     * 将处理后的函数报文送入output阶段
     */
    static int ip_forward_finish(struct sk_buff *skb)
    {
    	struct ip_options * opt	= &(IPCB(skb)->opt);
    
    	/*使用ip_forward_options处理IP选项*/
    	if (unlikely(opt->optlen))
    		ip_forward_options(skb);
    	
    	/*送入到输出阶段*/
    	return dst_output(skb);
    	{
    		skb_dst(skb)->output(skb);
    	}
    }
    
    
    /*目的地分发策略的注册*/
    static int __mkroute_input(struct sk_buff *skb,
    			   struct fib_result *res,
    			   struct in_device *in_dev,
    			   __be32 daddr, __be32 saddr, u32 tos,
    			   struct rtable **result)
    {
    	//.......
    	rth->u.dst.input = ip_forward;
    	rth->u.dst.output = ip_output;
    	//......	
    }
    
    
    int ip_output(struct sk_buff *skb)
    {
    	struct net_device *dev = skb_dst(skb)->dev;
    
    	/*将skb->dev指向输出设备的dev*/
    	skb->dev = dev;
    	/*设置2层包类型为ETH_P_IP*/
    	skb->protocol = htons(ETH_P_IP);
    
    	/*使用防火墙中的NF_IP_POST_ROUTING中注册的钩子函数进行处理,
    	  *处理完成之后进入ip_finish_output处理*/
    	return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,
    			    ip_finish_output,
    			    !(IPCB(skb)->flags & IPSKB_REROUTED));
    }
    
    /*判定是否进行IP分片*/
    static int ip_finish_output(struct sk_buff *skb)
    {
    	/*如果报文尺寸大于MTU,则进行IP分片后送入ip_finish_output2
    	 *否则直接送入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);
    }
    
    static const struct neigh_ops arp_generic_ops = {
    	.family =		AF_INET,
    	.output =		neigh_resolve_output,
    	.hh_output =		dev_queue_xmit,
    };
    
    static const struct neigh_ops arp_hh_ops = {
    	.family =		AF_INET,
    	.output =		neigh_resolve_output,
    	.hh_output =		dev_queue_xmit,
    };
    
    
    static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst,   __be16 protocol)
    {
    	struct hh_cache	*hh;
    
    	//......
    	
    	if (n->nud_state & NUD_CONNECTED)
    		hh->hh_output = n->ops->hh_output; /*也就是dev_queue_xmit*/
    	else
    		hh->hh_output = n->ops->output;
    
    	//......
    }
    
    static void neigh_suspect(struct neighbour *neigh)
    {
    	//.....
    	neigh->output = neigh->ops->output; /*也就是neigh_resolve_output*/
    
    }
    
    
    static inline int ip_finish_output2(struct sk_buff *skb)
    {	
    	/*如果2层头数据空间不够,则重新分配足够长度的SKB,并将数据复制到新的SKB后释放原来SKB*/
    	if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
    		struct sk_buff *skb2;
    		
    		skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
    
    		if (skb->sk)
    			skb_set_owner_w(skb2, skb->sk);
    		kfree_skb(skb);
    		skb = skb2;
    	}
    
    	/*如果路由出口项中已经含有2层包头缓存的引用(dst->hh),进入neigh_hh_output*/
    	if (dst->hh)
    		return neigh_hh_output(dst->hh, skb);
    	/*如果没有dst->hh,有dst->neighbour,则启动地址解析协议,也就是neigh_resolve_output*/
    	else if (dst->neighbour)
    		return dst->neighbour->output(skb);
    
    }
    
    static inline int neigh_hh_output(struct hh_cache *hh, struct sk_buff *skb)
    {
    	/*直接复制2层包头到套接字的包数据空间中*/
    	memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);
    	skb_push(skb, hh_len);
    	
    	/*调用hh->hh_output(skb),也就是dev_queue_xmit进行硬件发送*/
    	return hh->hh_output(skb);
    }
    


    如果IP是上送本地CPU的报文,处理流程如下

     

    //-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
    /*包的本地投递*/
    int ip_local_deliver(struct sk_buff *skb)
    {
    	/*收集并组装IP分片,如果还没有收集完成,那么就等待IP分片组装完成*/
    	if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
    		if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
    			return 0;
    	}
    
    	/*进入NF_IP_LOCAL_IN的过滤器处理,处理完成后进入ip_local_deliver_finish*/
    	return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);
    }
    
    
    /*IP层处理完成后的协议分发函数*/
    static int ip_local_deliver_finish(struct sk_buff *skb)
    {
    	resubmit:
    		/*如果是RAW-IP报文,送往RAW-IP对应的处理???*/
    		raw = raw_local_deliver(skb, protocol);
    
    		/*MAX_INET_PROTOS-1 为IP报头中协议的模,
    		 *这里计算对应协议在ipprot中被散列的位置*/
    		hash = protocol & (MAX_INET_PROTOS - 1);
    		/*IP层上的ipprot负责管理所有的传输协议*/
    		ipprot = rcu_dereference(inet_protos[hash]);
    		
    		/*如果找到相应的协议,那么调用对应的处理例程*/
    		if (ipprot != NULL) {			
    			ret = ipprot->handler(skb);
    			if (ret < 0) {
    				protocol = -ret;
    				goto resubmit;
    			}			
    		}
    		/*找不到相应的处理例程*/ 
    		else {
    			/*又是RAW-IP报文,会在RAW-IP处理例程???
    		 	 * 就丢弃,并想对端发送ICMP_DEST_UNREACH,ICMP_PROT_UNREACH*/
    			if (!raw)
    			 {
    				icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
    			} 
    
    			kfree_skb(skb);
    		}
    
    	return 0;
    }
    
    static const struct net_protocol tcp_protocol = {
    	.handler =	tcp_v4_rcv, /*TCP*/
    };
    
    static const struct net_protocol udp_protocol = {
    	.handler =	udp_rcv, /*UDP*/
    };
    
    static const struct net_protocol icmp_protocol = {
    	.handler =	icmp_rcv, /*ICMP*/
    };
    
    static const struct net_protocol igmp_protocol = {
    	.handler =	igmp_rcv, /*IGMP*/
    };


    通过上面的分析讲解,我们就可以很清楚的了解到IP是如何接受一个来自于网卡的数据包,并如何进行三层报文


    关于二层数据报文的处理流程和是如何送到三层进行处理,请参考我前面的博客

    《Linux内核二层数据包接收流程》


    希望大家批评指正


  • 相关阅读:
    C Primer Plus_第三章_数据和C_复习题与编程练习
    如何确定你的编程环境下不同数据类型数据的储值范围
    vs2010调试程序出现“Cannot find or open the PDB file”
    C Primer Plus_第二章_C语言概述_复习题与编程练习
    C Primer Plus_第一章_概览_复习题与编程练习
    [au3]复制选择性粘贴文本到excel
    subversion
    Centos7.0安装配置PHP7.0
    C#的UDP服务器
    CentOS7搭建NAS,包括NFS、ISCSI
  • 原文地址:https://www.cnblogs.com/riskyer/p/3293830.html
Copyright © 2011-2022 走看看