热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux中处理需要传输的IP报文流程

本文主要讲解了Linux中处理需要传输的IP报文流程,使用的内核的版本是2.6.32.27为了方便理解,本文采用整体流程图加伪代码的方式对Linux中处理需要传输的IP报文流程进行

本文主要讲解了Linux中处理需要传输的IP报文流程,使用的内核的版本是2.6.32.27

为了方便理解,本文采用整体流程图加伪代码的方式对Linux中处理需要传输的IP报文流程进行了讲解,希望可以对大家有所帮助。阅读本文章假设大家对C语言有了一定的了解


首先从IP的更高层传输层看看是如何管理的

Linux中处理需要传输的IP报文流程

 

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

/*四层协议的注册,都注册为net_protocol结构,并hash到inet_protos表中进行统一管理*/
#ifdef CONFIG_IP_MULTICAST
static const struct net_protocol igmp_protocol = {
	.handler =	igmp_rcv,
	.netns_ok =	1,
};
#endif

static const struct net_protocol tcp_protocol = {
	.handler =	tcp_v4_rcv,
	.err_handler =	tcp_v4_err,
	.gso_send_check = tcp_v4_gso_send_check,
	.gso_segment =	tcp_tso_segment,
	.gro_receive =	tcp4_gro_receive,
	.gro_complete =	tcp4_gro_complete,
	.no_policy =	1,
	.netns_ok =	1,
};

static const struct net_protocol udp_protocol = {
	.handler =	udp_rcv,
	.err_handler =	udp_err,
	.gso_send_check = udp4_ufo_send_check,
	.gso_segment = udp4_ufo_fragment,
	.no_policy =	1,
	.netns_ok =	1,
};

static const struct net_protocol icmp_protocol = {
	.handler =	icmp_rcv,
	.no_policy =	1,
	.netns_ok =	1,
};


static int __init inet_init(void)
{
	/*
	 *	Add all the base protocols.
	 */
	if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) <0)
		printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");
	if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) <0)
		printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");
	if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) <0)
		printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");
#ifdef CONFIG_IP_MULTICAST
	if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) <0)
		printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n");
#endif	
}


在4层处理完成之后,4层会调用IP层的接口ip_qeueu_xmit进行 报文发送

 

 

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

/*ipv4.c中注册的让上层协议使用的接口*/
static const struct inet_connection_sock_af_ops dccp_ipv4_af_ops = {
	.queue_xmit	   = ip_queue_xmit,
};

/*将dccp_ipv4_af_ops注册到协议中*/
static int dccp_v4_init_sock(struct sock *sk)
{
	inet_csk(sk)->icsk_af_ops = &dccp_ipv4_af_ops;
}


/*TCP数据报文发送函数*/
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,   gfp_t gfp_mask)
{
	const struct inet_connection_sock *icsk = inet_csk(sk);
	
	/*使用ip_queue_xmit发送数据报文*/
	err = icsk->icsk_af_ops->queue_xmit(skb, 0);

}


//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------


int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
{
	struct sock *sk = skb->sk;
	struct inet_sock *inet = inet_sk(sk);
	struct ip_options *opt = inet->opt;
	struct rtable *rt;
	struct iphdr *iph;

	/*检查套接字结构中sk->dst中是否有一个指针指向路由缓存中的某个入口项
	 *如果有,再检查这个指针是否有效,由于套接字的所有包都去往同一个目标
	 *地址,因此路由就存放在skb->_skb_dst中,内容为dst_entry结构
	 */
	
	rt = skb_rtable(skb);
	if (rt != NULL)
		goto packet_routed;

	rt = (struct rtable *)__sk_dst_check(sk, 0);
	{
		if (dst && dst->obsolete && dst->ops->check(dst, COOKIE) == NULL)
		{
			sk->sk_dst_cache = NULL;
			dst_release(dst);
			return NULL;			
		}
	}
	
	/*如果尚未设置路由,那么使用ip_route_output_flow进行路由选路*/
	if (rt == NULL) 
	{
		//......
			if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
				goto no_route;
	}

	//......
packet_routed:
	/*填充IP报头*/
	//.....
	iph->ttl      = ip_select_ttl(inet, &rt->u.dst);
	iph->protocol = sk->sk_protocol;
	iph->saddr    = rt->rt_src;
	iph->daddr    = rt->rt_dst;

	/*填充IP选项*/
	if (opt && opt->optlen) {
		iph->ihl += opt->optlen >> 2;
		ip_options_build(skb, opt, inet->daddr, rt, 0);
	}

	//......
	return ip_local_out(skb);

no_route:
	//.....
}


int ip_local_out(struct sk_buff *skb)
{
	int err;

	err = __ip_local_out(skb);
	if (likely(err == 1))
		err = dst_output(skb);

	return err;
}


int __ip_local_out(struct sk_buff *skb)
{
	struct iphdr *iph = ip_hdr(skb);

	iph->tot_len = htons(skb->len);
	ip_send_check(iph);
	
	/*进入 NF_INET_LOCAL_OUT 的序列钩子进行处理,处理之后放入dst_output中处理*/
	return nf_hook(PF_INET, NF_INET_LOCAL_OUT, skb, NULL, skb_dst(skb)->dev,
		       dst_output);
}


static inline int dst_output(struct sk_buff *skb)
{
	/*调用dst_entry中注册的output函数,IP单播也就是ip_output函数*/
	return skb_dst(skb)->output(skb);
}

/*在__mkroute_output中曾经对output和input进行过注册*/
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)
{
	struct rtable *rth;
	rth->u.dst.output=ip_output;
	
	if (flags & RTCF_LOCAL) {
		rth->u.dst.input = ip_local_deliver;
	}
	
	if (flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
		if (flags & RTCF_LOCAL &&   !(dev_out->flags & IFF_LOOPBACK))
		{
			rth->u.dst.output = ip_mc_output;
		}
		if (res->type == RTN_MULTICAST)
		{
			rth->u.dst.input = ip_mr_input;
			rth->u.dst.output = ip_mc_output;
		}
	}
}

/*IPV4单播*/
int ip_output(struct sk_buff *skb)
{
	struct net_device *dev = skb_dst(skb)->dev;
	
	skb->dev = dev;
	skb->protocol = htons(ETH_P_IP);

	/*经过 NF_INET_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));
}


static int ip_finish_output(struct sk_buff *skb)
{
	/*IP分片后,进入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 inline int ip_finish_output2(struct sk_buff *skb)
{
	/*如果没有二层头,启用ARP处理*/
	if (dst->hh)
		return neigh_hh_output(dst->hh, skb);
	/*如果有二层头进行处理,侧使用dst->neighbour->output也就是 dev_queue_xmit*/
	else if (dst->neighbour)
		return dst->neighbour->output(skb);
}


/*dev_queue_xmit在ARP中的注册过程如下*/
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;

	//......
}


最终一路从ip_queue_xmit进行发送调用到二层发送的入口点dev_queue_xmit

 

关于处理流程的整体架构图,请参见我的上一篇博客

<>

关于二层是如何继续处理报文并发送的,请参考博客

<>


希望大家批评指正



 


推荐阅读
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 实现一个通讯录系统,可添加、删除、修改、查找、显示、清空、排序通讯录信息
    本文介绍了如何实现一个通讯录系统,该系统可以实现添加、删除、修改、查找、显示、清空、排序通讯录信息的功能。通过定义结构体LINK和PEOPLE来存储通讯录信息,使用相关函数来实现各项功能。详细介绍了每个功能的实现方法。 ... [详细]
  • 本文介绍了一种图的存储和遍历方法——链式前向星法,该方法在存储带边权的图时时间效率比vector略高且节省空间。然而,链式前向星法存图的最大问题是对一个点的出边进行排序去重不容易,但在平行边无所谓的情况下选择这个方法是非常明智的。文章还提及了图中搜索树的父子关系一般不是很重要,同时给出了相应的代码示例。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了使用Python解析C语言结构体的方法,包括定义基本类型和结构体类型的字典,并提供了一个示例代码,展示了如何解析C语言结构体。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
author-avatar
kuae_617
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有