协议栈学习笔记_协议栈 l1a-程序员宅基地

技术标签: 学习  c语言  网络  linux  内核  

写在前面

这是我第一次公开发表自己的笔记,内容是我学习过程中摘录或者总结的学习资料,如果有引用原作者的话、图片,希望作者能及时提醒我删除。有错误希望有大佬指出,毕竟我还是个初学者。
文章会随着我的学习深入而不断修改。

一、协议栈架构介绍

在这里插入图片描述Linux TCP/IP协议栈按照tcp/ip分层结构可以分为四层,应用层、传输层、网络层和链路层(上图的网络访问层)。简要的说,网络数据在应用层,使用套接字,加上三元数据(IP、PORT、协议)建立起客户端或者服务器,并在此基础上组织协议(HTTP、SMTP等)收发数据,然后用户数据被Socket送到内核空间,交给内核协议栈处理,最终通过互联网发到指定设备。内核协议栈处理的工作主要是一、注册每一层需要处理的协议(例如ip层注册tcp和udp协议)、协议对应的操作集合(接收、发送、检查等操作) 二、根据注册的协议内容操作用户数据(添加、卸载头部数据等)三、将数据包抛掷到合适的协议层。
在这里插入图片描述在这里插入图片描述可以看到,整个协议栈的框架如上图所示。先看协议栈发流程,设备通过驱动将数据转换成skb数据,并通过netif_receive_skb获取链路层需要的数据,比如说源Mac地址、目的Mac地址以及协议类型(type)。在数据不需要经过vlan或者bridge时,选择接收函数处理(注册在ptype_all,ptype_base链表中),就以ip协议为例,ipv4的协议号是0x8000,在遍历链路层注册好的协议后,选择了ip_rcv()函数处理数据。Ip_rcv函数后的下一级函数的头部信息(Tcp头部信息、Udp头部信息等等),函数接收链路层数据,期间处理路由信息,判断是否需要转发或者丢弃的情况,最后根据头部信息中的type值(tcp是0x06 udp是0x17)遍历链表中符合的结构体,调用handle函数接收数据(tcp是tcp_v4_recv,udp是udp_rcv)。
以tcp为例,ip层调用tcp_v4_recv函数处理接收数据(获取原始数据和地址信息),数据最终存入与socket同时生成的接收队列,然后通知socket有数据。最后socket调用recv或者recvfrom函数间接调用xxx_recvmsg函数从内核拿数据到用户空间,给socket使用。
协议栈发流程与之相反。Socket调用send或者sendto函数,间接使用xx_sendmsg函数(Raw类型是直接发送IP层的)将发送数据复制到内核空间中的skb发送队列,并添加协议头,最终通过ip_queue_xmit(TCP使用)等函数打包成ip层数据包,交由ip层处理,经过netfilter子系统后,使用dev_queue_xmit函数发送至链路层,交由设备驱动发送。

在这里插入图片描述可以看到每经过一层协议都在前面数据的基础上加上协议的头部。解包的过程正好相反,每过一层便卸载一层头部。
在这里插入图片描述应用层的各种网络数据都是通过Linux Socket来与内核空间的协议栈通信的(socket结构体包含了文件描述符、inet层操作等结构体)。从层次上讲,它属于应用层,系统给程序员提供用户API方便应用程序向传输层发送数据。
Socket屏蔽不同网络协议之间的差异,提供统一的操作函数。对于用户来讲,Socket网络通信可以向操作文件一样方便。
在这里插入图片描述网络应用可以通过改变参数来实现TCP、UDP连接,甚至可以直接构造原始的IP数据报、链路数据包。如下图所示的简易流程,展示了Socket文件的创建、使用流程。
在这里插入图片描述

用户经过上述部分后来到了内核协议栈的主要处理部分,每一层的处理逻辑如下图所示。
在这里插入图片描述

二、协议栈初始化

在协议栈可以使用前,内核需要对网络环境进行初始化。协议栈注册初始化涉及到两个函数 sock_init和 inet_init。(net/ipv4/af_net.c)

sock_init的目的是分配socket文件系统空间和初始化收发的skb结构体空间。下面的注册流程图,展示了tcp/ip协议栈 文件系统和协议功能的初始化。
在这里插入图片描述在注册协议时,inet_init 使用(proto_register)先注册了传输层协议操作集,然后(sock_register)注册了Inet层操作集合,接着(inet_add_protocol)注册了一些协议(tcp,udp,icmp,igmp)的接收函数。注册完这些,就初始化了指针数组(inetsw),有了存放空间后,使用inet_register_protosw函数,以type为索引把inetsw_array结构中的节点添加到inetsw表中,方便以后使用。完成这些后逐个初始化协议需要的变量,最终使用dev_add_pack() 处理链路层数据)将钩子函数挂到 ptype_base链表上。至此,整个协议栈环境可用。
在这里插入图片描述在这里插入图片描述在这里插入图片描述

/*net_protocol 结构定义了传输层协议(包含icmp igmp协议)
以及传输层的报文接收例程,此结构是网络层和传输层之间的桥梁,
定义了协议族中支持的传输层协议以及传输层的报文接收实例。
此结构是网络层和 传输层之间的桥梁,当网络数据包从网络层流向传输层时,
会调用此结构中的传输层协议数据时,会调用此结构中的传输层协议数据报接收处理函数(handler)。
*/
struct net_protocol {
    
	 void (*early_demux)(struct sk_buff *skb);
 	int (*handler)(struct sk_buff *skb);
 	void(*err_handler)(struct sk_buff *skb, u32 info);
	Unsigned int no_policy:1,
	icmp_strict_tag_validation:1;
};

linux目前支持多种协议族,每个协议族用一个net_porto_family结构实例来表示,在初始化时,会调用sock_register函数初始化注册到net_families[]中去。主要是协议族的创建方法inet_create。
协议族在这里插入图片描述

三、协议栈处理流程

1、链路层数据处理

首先协议栈要从设备那里拿到数据,这里调用了netif_receive_skb函数(net/core/dev.c),检查完时间戳后调用_netif_receive_skb函数开始处理skb协议包数据。然后判断数据是否要走桥、vlan等。得到协议类型后最终如下图所示遍历所有ptype(协议的type),然后上传到上一层协议。
对于ipv6协议来说,其有独有的type值,ipv6的钩子函数是ip6_rcv()(在ipv6文件夹里),与ipv4的过程一致,不在多赘述。
在这里插入图片描述IP报文结构体示例。 Type是协议类型,func是注册的钩子函数。
在这里插入图片描述下图是链路层发数据的流程图,得到skb数据后,内核分析得到协议的type是何种类型,就遍历了ptype_base数组得到注册好的协议,调用func钩子函数处理数据。也就是ip_rcv();
在这里插入图片描述netif_receive_skb()的主要作用体现在两个遍历链表的操作中,其中之一为遍历 ptype_all 链( ETH_P_ALL 被单独的放到了 ptype_all 这个表中,用于 sniffer 中),这些为注册到内核的一些 sniffer,将上传给这些 sniffer,另一个就是遍历 ptype_base,这个就是具体的协议类型(宏定义在 include/linux/if_ether.h)。当以太网接收到一个类型为 ETH_P_IP 的类型,它由 ip_rcv处理。如果这个链中还注册有其它 IP 层的协议,它也会同时发送一个副本给它。
做了初步处理后就调用deliver_skb执行相应 packet_type 里的 func 函数,如对于ETH_P_IP 类型,由上面可以看到,它执行的就是 ip_rcv 了。
数据包的发送为接收的反过程,发送过程较之接收过程的复杂性在于它有一个流量控制层(Trafficing Control Layer),用于实现QoS.当内核有数据包等待发送时,它会间接调用__netif_schedule ()去处理这些数据包。在这里插入图片描述
在这里插入图片描述
dev_hard_start_xmit(skb, dev)只是一个包装函数,它首先看有没有注册的 sniffer,要是存在的话(netdev_nit 不等于0),便将一个副本通过 dev_queue_xmit_nit(skb, dev)发送给它,再之后,就是调用驱动程序的 hard_start_xmit 完成最后的发送工作了。hard_start_xmit()只要是跟硬件打交道,一般是通知DMA完成数据的发送工作。如果有发送队列的话,就要考Qos控制发送,qdisc_run(dev)会选择“合适”的 skb 然后传递给 dev_hard_start_xmit(skb, dev)。

2.网络层数据处理

数据包到了网络层,需要根据type找到对应的处理函数,以Ip协议为例,skb数据传到了ip_rcv函数处。(net/ipv4/ip_input.c)。网络层的任务就是选择合适的网间路由和交换结点,确保数据及时传送。网络层将数据链路层提供的帧组成数据包,包中封装有网络层包头,其中含有逻辑地址信息- -源站点和目的站点地址的网络地址。其主要任务包括 (1)路由处理,即选择下一跳 (2)添加 IP header(3)计算 IP header checksum,用于检测 IP 报文头部在传播过程中是否出错 (4)可能的话,进行 IP 分片(5)处理完毕,获取下一跳的 MAC 地址,设置链路层报文头,然后转入链路层处理。对于上层需要发送的数据,仍然需要经过netfilter系统处理路由信息,添加ip头。还有处理其他钩子函数,直接将ip信息发给钩子函数的创建者。下面是ip层数据流路径。
在这里插入图片描述链路层将数据包上传到 IP 层时,由 IP 层相关协议的处理例程处理。对于IP 协议,这个注册的处理例程是 ip_rcv(),它处理完成后交给 NETFILTER(PRE-ROUTING)过滤,再上递给 ip_rcv_finish(), 这个函数根据 skb 包中的路由信息,决定这个数据包是转发还是上交给本机,由此产生两条路径,一为 ip_local_deliver(),它首先检查这个包是否是一个分 片 包 , 如 果 是 , 它 要 调 动 ip_defrag() 将 分 片 重 装 , 然 后 再 次 将 包 将 给 NETFILTER(LOCAL_IN)过滤后,再由 ip_local_deliver_finish()将数据上传到传输层层,这样就完成了 IP 层的处理;它负责将数据上传,另一路径为 ip_forward(),它负责将数据转发,经由 NETFILTER(FORWARD)过滤后将给 ip_forward_finish(),然后调用 dst_output()将数据包发送出去。
当上一层有数据需要发送时,它将调用ip_append_data、ip_push_pending_frams(udp,icmp, Raw IP), 或 ip_append_page(UDP),ip_queue_xmit (TCP,SCTP), 或者 raw_send_hdrinc(Raw IP, IGMP),它们将这些包交由NETFILTER(LOLACL_OUT)处理后,然后交给 dst_output,这会根据是多播或单播选择合适的发送函数。如果是单播,它会调用 ip_output(),然后是 ip_finish_output(),这个函数主要是检查待发送的数据包大小是否超过 MTU,如果是,则要首先调用 ip_fragment()将其分片,然后再传给 ip_finish_output2(),由它交给链路层处理了

3、传输层数据处理

数据包在找到注册的协议后,使用handler函数(上文提到)交给上层协议处理(Tcp、UDP、etc)。下图是收包处理。传输层为用户提供数据传输服务,在socket创建后,首先出发的便是握手程序,等待双方连接建立后(存在结构体中),即可以互相通信。作为数据载体的skb buf会加入存储队列,以供socket查阅
在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020Net/ipv4/udp.c
发包处理流程:
到此位置数据包的收工作就到了一定阶段,skb数据被存放在收队列中,并触发中断通知socket有数据需要接收。流程如下图所示。1103193452879.png#pic_center)
在这里插入图片描述

4、应用层数据处理

在这里插入图片描述在这里插入图片描述在这里插入图片描述可以看到应用层的数据处理符合服务器/客户端模型,即更上层的协议围绕socket来建立自己的服务,上图展示了socket收发数据的示意图。

四、Socket的创建与使用
Socket 创建和使用
(函数位置 在linux-3.4rt/net目录下的socket.c中)
在这里插入图片描述
在这里插入图片描述在这里插入图片描述可以看到创建一个socket文件的用户函数包含四个方面:文件句柄、协议族、协议类型和协议号。可以看到在内核中首先调用了sock_create()和sock_map_fd()。功能分别是根据协议创建sock结构体和分配文件描述符。然后使用create函数创建协议族。
网络应用调用Socket API socket (int family, int type, int protocol) 创建一个 socket,该调用最终会调用 Linux system call socket() ,并最终调用 Linux Kernel 的 sock_create() 方法。该方法返回被创建好了的那个 socket 的 file descriptor。对于每一个 userspace 网络应用创建的 socket,在内核中都有一个对应的 struct socket和 struct sock。其中,struct sock 有三个队列(queue),分别是 rx , tx 和 err,在 sock 结构被初始化的时候,这些缓冲队列也被初始化完成;在收据收发过程中,每个队列中保存要发送或者接收的skb实例(贯穿整个协议栈)。
在这里插入图片描述可以看到最终生成如上图的结构类型,每一次新建socket都会生成上图结构,只需要维护好这个结构就可以实现正常的socket运行。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_42174923/article/details/109477000

智能推荐

软件测试流程包括哪些内容?测试方法有哪些?_测试过程管理中包含哪些过程-程序员宅基地

文章浏览阅读2.9k次,点赞8次,收藏14次。测试主要做什么?这完全都体现在测试流程中,同时测试流程是面试问题中出现频率最高的,这不仅是因为测试流程很重要,而是在面试过程中这短短的半小时到一个小时的时间,通过测试流程就可以判断出应聘者是否合适,故在测试流程中包含了测试工作的核心内容,例如需求分析,测试用例的设计,测试执行,缺陷等重要的过程。..._测试过程管理中包含哪些过程

政府数字化政务的人工智能与机器学习应用:如何提高政府工作效率-程序员宅基地

文章浏览阅读870次,点赞16次,收藏19次。1.背景介绍政府数字化政务是指政府利用数字技术、互联网、大数据、人工智能等新技术手段,对政府政务进行数字化改革,提高政府工作效率,提升政府服务质量的过程。随着人工智能(AI)和机器学习(ML)技术的快速发展,政府数字化政务中的人工智能与机器学习应用也逐渐成为政府改革的重要内容。政府数字化政务的人工智能与机器学习应用涉及多个领域,包括政策决策、政府服务、公共安全、社会治理等。在这些领域,人工...

ssm+mysql+微信小程序考研刷题平台_mysql刷题软件-程序员宅基地

文章浏览阅读219次,点赞2次,收藏4次。系统主要的用户为用户、管理员,他们的具体权限如下:用户:用户登录后可以对管理员上传的学习视频进行学习。用户可以选择题型进行练习。用户选择小程序提供的考研科目进行相关训练。用户可以进行水平测试,并且查看相关成绩用户可以进行错题集的整理管理员:管理员登录后可管理个人基本信息管理员登录后可管理个人基本信息管理员可以上传、发布考研的相关例题及其分析,并对题型进行管理管理员可以进行查看、搜索考研题目及错题情况。_mysql刷题软件

根据java代码描绘uml类图_Myeclipse8.5下JAVA代码导成UML类图-程序员宅基地

文章浏览阅读1.4k次。myelipse里有UML1和UML2两种方式,UML2功能更强大,但是两者生成过程差别不大1.建立Test工程,如下图,uml包存放uml类图package com.zz.domain;public class User {private int id;private String name;public int getId() {return id;}public void setId(int..._根据以下java代码画出类图

Flume自定义拦截器-程序员宅基地

文章浏览阅读174次。需求:一个topic包含很多个表信息,需要自动根据json字符串中的字段来写入到hive不同的表对应的路径中。发送到Kafka中的数据原本最外层原本没有pkDay和project,只有data和name。因为担心data里面会空值,所以根同事商量,让他们在最外层添加了project和pkDay字段。pkDay字段用于表的自动分区,proejct和name合起来用于自动拼接hive表的名称为 ..._flume拦截器自定义开发 kafka

java同时输入不同类型数据,Java Spring中同时访问多种不同数据库-程序员宅基地

文章浏览阅读380次。原标题:Java Spring中同时访问多种不同数据库 多样的工作要求,可以使用不同的工作方法,只要能获得结果,就不会徒劳。开发企业应用时我们常常遇到要同时访问多种不同数据库的问题,有时是必须把数据归档到某种数据仓库中,有时是要把数据变更推送到第三方数据库中。使用Spring框架时,使用单一数据库是非常容易的,但如果要同时访问多个数据库的话事件就变得复杂多了。本文以在Spring框架下开发一个Sp..._根据输入的不同连接不同的数据库

随便推点

EFT试验复位案例分析_eft电路图-程序员宅基地

文章浏览阅读3.6k次,点赞9次,收藏25次。本案例描述了晶振屏蔽以及开关电源变压器屏蔽对系统稳定工作的影响, 硬件设计时应考虑。_eft电路图

MR21更改价格_mr21 对于物料 zba89121 存在一个当前或未来标准价格-程序员宅基地

文章浏览阅读1.1k次。对于物料价格的更改,可以采取不同的手段:首先,我们来介绍MR21的方式。 需要说明的是,如果要对某一产品进行价格修改,必须满足的前提条件是: ■ 1、必须对价格生效的物料期间与对应会计期间进行开启; ■ 2、该产品在该物料期间未发生物料移动。执行MR21,例如更改物料1180051689的价格为20000元,系统提示“对于物料1180051689 存在一个当前或未来标准价格”,这是因为已经对该..._mr21 对于物料 zba89121 存在一个当前或未来标准价格

联想启天m420刷bios_联想启天M420台式机怎么装win7系统(完美解决usb)-程序员宅基地

文章浏览阅读7.4k次,点赞3次,收藏13次。[文章导读]联想启天M420是一款商用台式电脑,预装的是win10系统,用户还是喜欢win7系统,该台式机采用的intel 8代i5 8500CPU,在安装安装win7时有很多问题,在安装win7时要在BIOS中“关闭安全启动”和“开启兼容模式”,并且安装过程中usb不能使用,要采用联想win7新机型安装,且默认采用的uefi+gpt模式,要改成legacy+mbr引导,那么联想启天M420台式电..._启天m420刷bios

冗余数据一致性,到底如何保证?-程序员宅基地

文章浏览阅读2.7k次,点赞2次,收藏9次。一,为什么要冗余数据互联网数据量很大的业务场景,往往数据库需要进行水平切分来降低单库数据量。水平切分会有一个patition key,通过patition key的查询能..._保证冗余性

java 打包插件-程序员宅基地

文章浏览阅读88次。是时候闭环Java应用了 原创 2016-08-16 张开涛 你曾经因为部署/上线而痛苦吗?你曾经因为要去运维那改配置而烦恼吗?在我接触过的一些部署/上线方式中,曾碰到过以下一些问题:1、程序代码和依赖都是人工上传到服务器,不是通过工具进行部署和发布;2、目录结构没有规范,jar启动时通过-classpath任意指定;3、fat jar,把程序代码、配置文件和依赖jar都打包到一个jar中,改配置..._那么需要把上面的defaultjavatyperesolver类打包到插件中

VS2015,Microsoft Visual Studio 2005,SourceInsight4.0使用经验,Visual AssistX番茄助手的安装与基本使用9_番茄助手颜色-程序员宅基地

文章浏览阅读909次。1.得下载一个番茄插件,按alt+g才可以有函数跳转功能。2.不安装番茄插件,按F12也可以有跳转功能。3.进公司的VS工程是D:\sync\build\win路径,.sln才是打开工程的方式,一个是VS2005打开的,一个是VS2013打开的。4.公司库里的线程接口,在CmThreadManager.h 里,这个里面是我们的线程库,可以直接拿来用。CreateUserTaskThre..._番茄助手颜色

推荐文章

热门文章

相关标签