协议栈学习笔记_协议栈 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

智能推荐

Eclipse中配置WebMagic(已配置好Maven)_使用eclipse搭建webmagic工程-程序员宅基地

文章浏览阅读364次。1.WebMagicWebMagic是一个简单灵活的Java爬虫框架。基于WebMagic,你可以快速开发出一个高效、易维护的爬虫。2.在Eclipse中配置WebMagic1.首先需要下载WebMagic的压缩包官网地址为:WebMagic官网最新版本为:WebMagic-0.7.3,找到对应版本,打开下载界面,注意,下载要选择Source code(zip)版本,随便下载到哪里都可以;2.下载好的压缩包需要解压,此时解压到的位置即为后续新建的Eclipse的project位置,比如我的Ecli_使用eclipse搭建webmagic工程

linux启动mysql_linux如何启动mysql服务_linux启动mysql服务命令是什么-系统城-程序员宅基地

文章浏览阅读1.9k次。mysql数据库是一种开放源代码的关系型数据库管理系统,有很多朋友都在使用。一些在linux系统上安装了mysql数据库的朋友,却不知道该如何对mysql数据库进行配置。那么linux该如何启动mysql服务呢?接下来小编就给大家带来linux启动mysql服务的命令教程。具体步骤如下:1、首先,我们需要修改mysql的配置文件,一般文件存放在/etc下面,文件名为my.cnf。2、对于mysql..._linux中 mysql 启动服务命令

php实现在线oj,详解OJ(Online Judge)中PHP代码的提交方法及要点-程序员宅基地

文章浏览阅读537次。详解OJ(Online Judge)中PHP代码的提交方法及要点Introduction of How to submit PHP code to Online Judge SystemsIntroduction of How to commit submission in PHP to Online Judge Systems在目前常用的在线oj中,codeforces、spoj、uva、zoj..._while(fscanf(stdin, "%d %d", $a, $b) == 2)

java快捷键调字体_设置MyEclipse编码、补全快捷键、字体大小-程序员宅基地

文章浏览阅读534次。一、设置MyEclipse编码(1)修改工作空间的编码方式:Window-->Preferences-->General-->Workspace-->Text file encoding(2)修改一类文件的编码方式:Window-->Preferences-->General-->content Types-->修改default Encoding(..._java修改快捷缩写内容

解析蓝牙原理_蓝牙原理图详解-程序员宅基地

文章浏览阅读1.4w次,点赞19次,收藏76次。1.前言市面上关于Android的技术书籍很多,几乎每本书也都会涉及到蓝牙开发,但均是上层应用级别的,而且篇幅也普遍短小。对于手机行业的开发者,要进行蓝牙模块的维护,就必须从Android系统底层,至少框架层开始,了解蓝牙的结构和代码实现原理。这方面的文档、网上的各个论坛的相关资料却少之又少。分析原因,大概因为虽然蓝牙协议是完整的,但是并没有具体的实现。蓝牙芯片公司只负责提供最底层的API_蓝牙原理图详解

从未在一起更让人遗憾_“从未在一起和最终没有在一起哪个更遗憾”-程序员宅基地

文章浏览阅读7.7k次。图/源于网络文/曲尚菇凉1.今天早上出门去逛街,在那家冰雪融城店里等待冰淇淋的时候,听到旁边两个女生在讨论很久之前的一期《奇葩说》。那期节目主持人给的辩论题是“从未在一起和最终没有在一起哪个更遗憾”,旁边其中一个女生说,她记得当时印象最深的是有个女孩子说了这样一句话。她说:“如果我喜欢一个人呢,我就从第一眼到最后一眼,把这个人爱够,把我的感觉用光,我只希望那些年让我成长的人是他,之后的那些年他喝过..._从未在一起更遗憾

随便推点

Spring Cloud Alibaba 介绍_sprngcloud alba-程序员宅基地

文章浏览阅读175次。Spring Cloud Alibaba 介绍Sping体系Spring 以 Bean(对象) 为中心,提供 IOC、AOP 等功能。Spring Boot 以 Application(应用) 为中心,提供自动配置、监控等功能。Spring Cloud 以 Service(服务) 为中心,提供服务的注册与发现、服务的调用与负载均衡等功能。Sping Cloud介绍官方介绍​ Tools for building common patterns in distributed systems_sprngcloud alba

测试 数据类型的一些测试点和经验_基础字段的测试点-程序员宅基地

文章浏览阅读3.2k次,点赞4次,收藏21次。我这里是根据之前在测试数据类项目过程中的一些总结经验和掉过个坑,记录一下,可以给其他人做个参考,没什么高深的东西,但是如果不注意这些细节点,后期也许会陷入无尽的扯皮当中。1 需求实现的准确度根据产品需求文档描述发现不明确不详细的或者存在歧义的地方一定要确认,例如数据表中的一些字段,与开发和产品确认一遍,如有第三方相关的,要和第三方确认,数据类项目需要的是细心,哪怕数据库中的一个字段如果没有提前对清楚,后期再重新补充,会投入更大的精力。2 数据的合理性根据业务场景/常识推理,提..._基础字段的测试点

一文看懂:行业分析怎么做?_码工小熊-程序员宅基地

文章浏览阅读491次。大家好,我是爱学习的小xiong熊妹。在工作和面试中,很多小伙伴会遇到“对XX行业进行分析”的要求。一听“行业分析”四个字,好多人会觉得特别高大上,不知道该怎么做。今天给大家一个懒人攻略,小伙伴们可以快速上手哦。一、什么是行业?在做数据分析的时候,“行业”两个字,一般指的是:围绕一个商品,从生产到销售相关的全部企业。以化妆品为例,站在消费者角度,就是简简单单的从商店里买了一支唇膏回去。可站在行业角度,从生产到销售,有相当多的企业在参与工作(如下图)在行业中,每个企业常常扮._码工小熊

LLaMA 简介:一个基础的、650 亿参数的大型语言模型_llma-程序员宅基地

文章浏览阅读1.6w次,点赞2次,收藏2次。还需要做更多的研究来解决大型语言模型中的偏见、有毒评论和幻觉的风险。我们在数万亿个令牌上训练我们的模型,并表明可以仅使用公开可用的数据集来训练最先进的模型,而无需诉诸专有和不可访问的数据集。在大型语言模型空间中训练像 LLaMA 这样的小型基础模型是可取的,因为它需要更少的计算能力和资源来测试新方法、验证他人的工作和探索新的用例。作为 Meta 对开放科学承诺的一部分,今天我们公开发布 LLaMA(大型语言模型元 AI),这是一种最先进的基础大型语言模型,旨在帮助研究人员推进他们在 AI 子领域的工作。_llma

强化学习在制造业领域的应用:智能制造的未来-程序员宅基地

文章浏览阅读223次,点赞3次,收藏5次。1.背景介绍制造业是国家经济发展的重要引擎,其产能和质量对于国家经济的稳定和发展具有重要意义。随着工业技术的不断发展,制造业的生产方式也不断发生变化。传统的制造业通常依赖于人工操作和手工艺,这种方式的缺点是低效率、低产量和不稳定的质量。随着信息化、智能化和网络化等新技术的出现,制造业开始向智能制造迈出了第一步。智能制造的核心是通过大数据、人工智能、计算机视觉等技术,实现制造过程的智能化、自动化...

ansible--安装与使用_pip安装ansible-程序员宅基地

文章浏览阅读938次。系列文章目录文章目录系列文章目录 前言 一、ansible是什么? 二、使用步骤 1.引入库 2.读入数据 总结前言菜鸟一只,刚开始使用,仅作以后参考使用。边学习,边记录,介绍一下最基础的使用,可能会有理解不到位的地方,可以共同交流,废话不多说,走起。一、ansible 简介?ansible是自动化运维工具的一种,基于Python开发,可以实现批量系统配置,批量程序部署,批量运行命令,ansible是基于模块工作的,它本身没有批量部署的能力,真正.._pip安装ansible

推荐文章

热门文章

相关标签