日期:2014-05-16  浏览次数:20819 次

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 &am