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

智能推荐

AES加密/解密报错,Input length must be multiple of 16 when decrypting with padded cipher-程序员宅基地

文章浏览阅读4.9k次。背景:需要存储一个类似密钥的字符串,密文存储,并要求能逆向解密出来。问题描述:使用AES对该字符串进行加密后生成byte数组,使用new String()方法转为字符串后存储到数据库。从数据库中取出密文,使用getBytes()方法转换为byte数组,然后解密,此时报如下错误:javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher_input length must be multiple of 16 when decrypting with padded cipher

时间序列分析与生物信息学研究:如何解锁生物数据的时间特征-程序员宅基地

文章浏览阅读786次,点赞21次,收藏8次。1.背景介绍时间序列分析(Time Series Analysis)是一种用于分析随时间推移变化的数据的统计方法。在过去几十年里,时间序列分析在金融、经济、气象等领域取得了显著的成果。然而,在生物信息学领域,时间序列分析的应用相对较少。这篇文章将介绍如何将时间序列分析应用于生物信息学研究,以解锁生物数据中的时间特征。生物信息学研究中的时间序列数据包括基因表达谱、蛋白质修饰、细胞分裂等。这些...

决策树与随机森林算法-程序员宅基地

文章浏览阅读1.9k次,点赞28次,收藏26次。随机森林是由多棵决策树组成的集成模型,每棵决策树都是相对独立的。随机森林利用了决策树的基本思想,但通过引入随机性和集成学习的方法,进一步提升了模型的性能和稳定性。# 创建随机森林分类器随机森林是一种集成学习方法,它由多个决策树构成的分类器组成。每个决策树都是独立训练的,且它们之间相互独立,没有关联。在随机森林中,每棵决策树的训练数据是通过有放回抽样(bootstrap抽样)得到的,这意味着每棵树的训练数据集都是从原始数据集中随机抽取的,可能包含重复的样本,同时也可能有未被抽取的样本。

2017c语言二级考试题,2017年计算机c语言二级考试试题及答案(2)-程序员宅基地

文章浏览阅读395次。17 有两个整型变量dog和cat,若要从磁盘文件把数据读到其中,正确的形式是( ). BA、fscanf(dog ,2,1,fp);B、fscanf(fp,"%d%d",&dog ,&cat);C、fscanf(dog ,cat,2,1,fp);D、fscanf(fp,"%d",&dog ,&cat);二、程序设计:/*----------------------..._有两个整型变量dog和cat,若要从磁盘文件把数据读到其中,正确的形式是

基于python编写的excel表格数据标记的exe文件_python excel整行标记-程序员宅基地

文章浏览阅读339次。在编写该exe文件的时候,首先需要知道需要那些工具以及思路和需求,这样才能更好的解决整体设计以及调整方案。_python excel整行标记

精益DevOps:优化流程,提升效能【文末送书】_devops平台提升效率-程序员宅基地

文章浏览阅读4.2k次,点赞25次,收藏15次。本书为IT服务交付团队及其领导者撰写,从精益思想和精益管理的视角,深入探讨了DevOps方法的核心要素(如任务式指挥、摩擦、风险、态势感知等),并结合实际案例,阐述了如何通过DevOps方法解决IT服务交付中的各种问题,如何在整个组织内改善信息流,从而向客户的目标成果迈进。此外,本书还提供了许多实用的工具和技巧,包括OODA循环、Cynefin框架、服务交付的成熟度模型和服务工程负责人等,以帮助读者更好地应用DevOps方法。_devops平台提升效率

随便推点

手机端下载智慧中小学app,手机端下载niconico视频-程序员宅基地

文章浏览阅读560次,点赞22次,收藏14次。如果openssl不使用系统yum安装的,而是使用自己编译的比较新的版本可以使用--with-openssl=/usr/local/openssl这种方式指定,后面目录为openssl实际安装的目录,另外编译完还要将openssl的lib目录加入ld运行时目录中即可.2)安装readline到/root/Public,网址https://pypi.python.org/pypi/readline,点击download files,选择gz包。安装成功之后,安装目录就在/usr/python。

中M2018春C入门和进阶练习集-编程题51 7-51 求n以内最大的k个素数以及它们的和(20 分)_其中素数按递减顺序输出。若n以内不够k个素数则按实际个数输出。-程序员宅基地

文章浏览阅读1.4k次。7-51 求n以内最大的k个素数以及它们的和(20 分)本题要求计算并输出不超过n的最大的k个素数以及它们的和。输入格式:输入在一行中给出n(10≤n≤10000)和k(1≤k≤10)的值。输出格式:在一行中按下列格式输出:素数1+素数2+…+素数k=总和值其中素数按递减顺序输出。若n以内不够k个素数,则按实际个数输出。输入样例1:1000 10输出..._其中素数按递减顺序输出。若n以内不够k个素数则按实际个数输出。

python字符串换行连接_Python字符串换行-程序员宅基地

文章浏览阅读681次。也许您想使用drawText?这样,您的代码将from reportlab.lib.pagesizes import letterfrom reportlab.pdfgen import canvascanvas = canvas.Canvas("Forensic Report.pdf", pagesize=letter)canvas.setLineWidth(.3)canvas.setFont(..._reportlab连接字符串

POI getLastRowNum() --getPhysicalNumberOfRows() 主要区别_sheet.getphysicalnumberofrows-程序员宅基地

文章浏览阅读1.6k次,点赞2次,收藏6次。// 获得总记录数(行数) int lastRowNum = sheet.getLastRowNum(); int rowNum=sheet.getPhysicalNumberOfRows();同样的都是获取Excel工作sheet行数的两个方法getLastRowNum()getPhysicalNumberOfRows()他们的主..._sheet.getphysicalnumberofrows

【excel】万字长文,一些实用excel技巧,金融财务行业巨实用(最后有干货,配合chatgpt让你成为excel大佬)_excel 价格处理技巧-程序员宅基地

文章浏览阅读1k次,点赞12次,收藏21次。本文主要记录一些在工作中经常能用到的excel技巧,能够帮助我们提高工作效率。在文章的最后还会通过几个实战例子来加深大家的理解。建议把本文作为备查文,不需要在阅读本文的当下就将这些技巧掌握,只需了解,哪些东西通过excel是能够做到的,再实际工作中遇到问题的时候再来查阅。【不要被vba吓到,配合chatgpt,每一个没有学过代码的人都能够搞定80%的vba编写宏的需求!】_excel 价格处理技巧

经典神经网络论文超详细解读(一)——AlexNet学习笔记(翻译+精读)_alexnet论文-程序员宅基地

文章浏览阅读5.5k次,点赞41次,收藏106次。AlexNet(ImageNet Classification with Deep Convolutional Neural Networks)论文超详细解读。翻译+总结_alexnet论文