TCP/IP协议栈之数据包如何穿越各层协议(绝对干货)

所有互联网服务,均依赖于TCP/IP协议栈。懂得数据是如何在协议栈传输的,将会帮助你提升互联网程序的性能和解决TCP相关问题的能力。

我们讲述在Linux场景下数据包是如何在协议层传输的。

1、发送数据

应用层发送数据的过程大致如下:

 我们把上述处理过程的区域大致分为:

1. User区域

2. Kernel 区域

3. Device区域

在user和kernel区域的任务都是由本机cpu执行,这两个区域合并称为host区域,以区分device区域(网络接口卡上有单独的cpu)。device是接收和发送数据包的网络接口卡(Network Interface Card),一般也称为LAN card。

当应用程序调用write(fd, buf, len)来发送数据时,用户态区域会进入内核态区域,建立这个关系的纽带是socket fd和系统调用write。

在内核态的socket有两个buffer:

1. send socket buffer,用于发送数据

2. receive socket buffer,用于接收数据

当write系统调用被执行,用户态的数据(buf,长度)会被拷贝到内核区域的内存,并被放入到send socket buffer的末尾(见下图,发送是按照顺序发送的),然后TCP就会被调用。

 TCP中的数据结构是TCB(TCP Control Block)。TCB包含了执行TCP会话所需要的信息,包括TCP连接状态,接收窗口,拥塞窗口,序号,重传timer 等。

TCP会创建TCP数据分段,而TCP数据分段包括TCP header和payload,如下图:

 Payload是待发送的socket buffer中的数据,而TCP header是为了TCP可靠发送数据而加的辅助信息。

这些数据分段会进入到IP层,IP层会加上IP头部信息到数据分段,如下图:

IP在执行路由之前会去检查Netfilter LOCAL_OUT钩子,看是否需要执行iptables相关配置。之后执行IP路由。IP路由主要功能是寻找下一跳(例如网关或路由器)的IP地址,而路由的目的是到达目的地IP地址所在的机器。

 IP执行路由之后,检查Netfilter POST_ROUTING钩子,如果有iptables在这方面的配置,就会去执行相关操作。委托给数据链路层之前,IP层还会执行ARP(网络地址转换),通过下一跳IP地址来查找目的MAC地址,并把Ethernet头部添加到IP数据包,如下图。

 IP层同时还给用户提供了raw socket接口,即发送数据包的接口。raw socket发送的数据包与正常流程的数据包不一样,在执行Netfilter的时候,会跳过这些钩子。

IP层做完工作以后,会把数据包(上图中的数据包,一般称frame)委托给数据链路层。

由于ARP已经把目的MAC地址写入到数据包头部,这样就减轻了驱动driver的工作。进入数据链路层后,内核会去检测是否有抓包工具在监听抓包(例如tcpdump),如果有,内核会拷贝数据包信息到抓包工具的内存地址空间。

之后,根据一定的协议规则,驱动driver会要求NIC传递这个数据包。当NIC收到这个请求后,NIC复制数据包到自己的内存里,并且发送给网络。当NIC发送完一个数据包,会产生一个中断, 主机 cpu去执行中断处理程序,完成后续工作。

2、接收数据

应用程序接收数据的过程大致如下:

 首先NIC把数据包写入自己的内存,并校验数据包是不是有效的,如果是有效的,把数据包写入主机的内存空间,然后NIC给主机操作系统发送一个中断信号,这时就进入到kernel区域。

在数据链路层,内核首先会做数据包检测,然后Driver驱动把数据包进行改装,以便后续TCP/IP能够理解这个数据包。改装完以后,根据Ethernet头部信息中的Ethertype分发给上层,假设为IPv4,去除Ethernet头部,并发送给IP层。值得注意的是,委托给IP层之前,如果有抓包工具在监听抓包,那么内核就会拷贝数据包信息到抓包工具的内存地址空间。

IP层通过计算checksum来校验IP头部的checksum是否有效,如果有效,接着检查PRE_ROUTING钩子(比如查看是否有iptables的相应配置需要执行),然后执行IP路由,IP路由会判断这个数据包是本地处理还是转发当前数据包到其它主机。如果是转发数据包,执行FORWARD和POST_ROUTING钩子,并转发给数据链路层;如果是本地处理,IP还会检查LOCAL_IN钩子,执行完以后,根据IP头部信息的proto值,假设为TCP,去除IP头部,并把数据包传递给上层TCP。值得注意的是,委托给TCP层之前,如果有raw socket在监听抓包,那么内核会拷贝数据包信息到raw socket的内存地址空间(默认tcpcopy利用raw socket来监听IP层的数据包)。

相关视频推荐

4个小时搞懂tcp/ip协议栈,从tcp/ip协议栈原理到实现一个网络协议栈

10道面试必问的经典网络八股文,让你在面试中逼格满满

7道面试题打通C/C++后端开发的技术脉络

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908(资料包括C/C++,Linux,golang技术,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)

 TCP层会根据TCP checksum来检测数据包是否有效(如果采用了checksum offload,NIC会去做相关计算),然后就给这个数据包查找相应的TCB(TCP control block),查找的方法是通过如下组合信息来查找:

<source IP, source port, target IP, target port>

如果没有查到,一般会发送reset数据包;如果查到了,进入TCP数据包处理环节。

如果是接收到新数据,TCP就会把它放入到socket接收缓冲区,然后根据TCP状态,必要时发送ack确认数据包。Socket接收缓冲区的大小就是TCP接收窗口大小。在某种程度上,如果接收窗口很大,TCP吞吐量就会很大。目前较新的内核都能动态调整窗口的大小,无需用户去修改系统参数。

用户应用程序根据读事件去执行读操作,用户态空间进入到内核空间。内核把socket buffer里面的内容复制到用户指定的内存区域,然后把socket buffer读取过的内容释放,TCP增加接收窗口大小,如果有必要,会传递一个更新窗口的数据包给对端TCP。例如下图,TCP发送了一个ack数据包,用于通知对端TCP,本方TCP接收窗口更新了。

读取操作完成后,返回应用程序,应用程序就可以进行对数据进行处理了。 

3、抓包工具工作原理

知道了数据如何发送和接收以后,我们分析一下tcpdump抓包原理。

在数据链路层和IP层交界的地方(属于数据链路层,如下图),是数据包被tcpdump捕获的场所。

 执行到这个交界处时,内核会去查看tcpdump是否在监听,一旦有监听,就把数据包内容放入到tcpdump设置的缓冲区。理论上只要tcpdump及时去提取数据,在线上压力不大的情况下,抓包不会丢包。

tcpdump所抓到的数据包,仅仅是代表数据包经过了链路层和网络层之间的交界处。从网卡进来的数据包未来的命运,可能是继续一路往前走到TCP,也有可能在IP层被干掉,还有可能被路由转发出去;从本机发送出去的数据包,一旦被tcpdump捕获到,说明已经到了数据链路层,没有被IP层过滤掉,因为如果数据包被IP层过滤掉,这些数据包就不会到达tcpdump捕获点,也不会出现在抓包文件里。

下面我们通过一些实验来验证上述结论。

实验之前,我们先介绍一下iptables工具。iptables是被广泛使用的防火墙工具,它主要跟内核netfilter数据包过滤框架进行交互。

3.1 实验 LOCAL_IN过滤

我们在服务器上面配置如下的iptables命令:

iptables -I INPUT -p tcp --dport 3306 -s 172.17.0.2 -j QUEUE

上述iptables命令设置了'-I INPUT'参数,意味着在netfilter LOCAL_IN钩子处执行上述iptables规则,即通往服务器端TCP之前,如果匹配到上述iptables规则,则会被放入目标QUEUE(默认情况下是直接丢弃数据包),不再继续前行。

具体命令执行见下图:

 设置上述iptables后,当172.17.0.2访问172.17.0.3 3306服务时,IP数据包(如下图绿色箭头)会在服务器端IP层被丢弃掉,而红色箭头所指方向是tcpdump抓包的地方。

 我们开启tcpdump抓包:

tcpdump -i any tcp and port 3306 and host 172.17.0.2 -n -v

在172.17.0.2上利用MySQL客户端命令访问172.17.0.3上面的3306服务,如下图:

 结果经过长时间等待,最终显示连接不上。

服务器端抓包结果如下:

 我们看到第一次握手数据包反复重传。

利用netstat命令,查看有没有相应的TCP状态,结果发现没有,如下图:

 正常情况下,没有TCP状态,说明数据包没有进入服务器端TCP,第一次握手数据包在服务器端IP层被干掉了。

利用netstat -s命令,在服务器端TCP/IP统计参数里找线索:

上图服务器端IP层接收到20079个数据包,下图接收到20086个数据包,MySQL客户端登入过程累计增加了7个数据包,正好符合抓包文件显示的7个第一次握手数据包。

 在服务器端TCP层,对比上面两张图,数据没有任何变化,说明了服务器端TCP没有收到任何数据包。

实验说明了在服务器端IP层进来的方向干掉数据包,服务器端TCP层不会有任何变化。

3.2 实验 LOCAL_OUT过滤

我们这次实验的目的是查看IP层netfilter LOCAL_OUT情况下的抓包情况。

如下图:

 我们设置如下iptables命令:

iptables -I OUTPUT -p tcp --sport 3306 -d 172.17.0.2 -j QUEUE

具体操作如下图:

 上述iptables命令设置了OUTPUT参数,意味着在netfilter LOCAL_OUT钩子处会执行上述iptables规则,即IP数据包在IP路由之前,如果匹配上述iptables规则,则会被放入目标QUEUE(默认情况下直接丢弃数据包),不会继续往下走。

在172.17.0.2上利用MySQL客户端命令访问172.17.0.3上面的3306服务,如下图:

 结果经过长时间等待,最终显示连接不上。

服务器端抓包结果如下:

我们看到第一次握手数据包反复重传,跟上一个抓包结果几乎一模一样

 利用netstat命令,查看有没有相应的TCP状态,结果发现有SYN_RECV状态,如下图:

 有TCP状态,说明数据包进入服务器端TCP,并进入SYN_RECV状态,服务器端TCP会发送第二次握手数据包,但抓包显示并没有第二次握手数据包,说明被iptables配置干掉了。

查看netstat -s结果:

 上图显示了实验之前的值,下图显示了实验之后的值。

 从TCP层面信息来看,发送了17个数据分段,说明服务器端TCP发送了第二次握手数据包,而且发送了很多次,但因为设置了iptables,这些数据包被拦截掉了,所以到不了数据链路层,也就没法被tcpdump捕获到。

从这两个实验来看,tcpdump抓的数据包是一样的,都是在努力重传第一次握手数据包,但iptables设置的位置不一样,一个在入口,在TCP层无状态,一个在出口,在TCP层有状态。

进一步的分析可以尝试下面两个方向:

1. 通过分析TCP状态来区分这两种情况

2. 利用netstat -s给出的TCP/IP统计参数变化

通过上面实验,我们看出tcpdump抓包只是从一个点来观察世界,并不能看到全貌,这个时候就需要通过推理来辅助解决问题。

4、潜在协议层的干扰

4.1 接收数据

下图展示了数据包从NIC到协议栈,再到应用程序的过程。

TCP offload由NIC完成,目的是减轻TCP的工作量,但存在潜在坑;在数据链路层,存在抓包接口,供tcpdump等抓包工具抓包,同时也存在着raw socket原始抓包方式接口;在网络层,存在raw socket抓包接口,IP Forward转发功能,还有一整套Netfilter框架(存在大量坑的地方);在TCP层则相对比较清静,干扰少;用户程序通过socket接口从TCP取出数据或者获取新建连接。

4.2 发送数据

下图展示了数据包从应用发送数据到NIC的过程。

用户程序通过socket接口来委托TCP发送数据或者建立连接;在网络层,存在raw socket发包接口,还有一整套Netfilter框架(存在大量坑的地方);在数据链路层,存在pcap发包接口,同时也存在着raw socket原始发包接口;TCP offload是NIC做的,目的为了提升减轻TCP的工作量(比如分段,checksum),我们也遇到过由于TCP offload不当导致的丢包问题。

4.3 案例

下面是一个从NIC接收数据包,并一路到应用,再发送响应出去的案例:

我们的应用程序是Nginx(Web服务器软件),其中Nginx配置监听端口为8080,且开启access log。

 上图设置了nginx keepalive_timeout = 0,即保持客户端空闲连接(方便实验)。

启动nginx,通过netstat查看,nginx已经在监听8080端口的连接请求。

 刚开始nginx没有任何访问,access log都为空,iptables也没有设置。

 在172.17.0.2机器,利用telnet访问172.17.0.3上面的8080端口服务,如下图:

 这样telnet跟nginx建立连接,下图可以看出服务器端相应连接已经进入ESTABLISHED状态。

 建立连接后,我们设置iptables命令,如下图,对返回172.17.0.2的nginx响应进行拦截并丢弃。

 我们在客户端(172.17.0.2)上面继续执行telnet命令,键入'GET hello.html',然后回车执行。

 从nginx日志来看,这个请求已经被处理了,虽然是非法请求,但请求已经确认到达nginx了。

 大概过了2分钟,查看客户端抓包情况,累计捕获了16个数据包,客户端还显示连接处于ESTABLISHED状态。

 我们查看服务器端情况,利用netstat已经查不到服务器端的相应连接了,说明连接在服务器端的TCP层已经不存在了。

 我们分析抓包情况(服务器抓包和客户端抓包效果一样):

 自从发送了请求数据包,客户端由于没有看到任何服务器端的数据包回来,一直在重传请求数据包。客户端以为服务器还没有收到请求,但其实请求已经被nginx处理完毕。

在服务器端查看netstat -st的统计情况。

 上图是执行telnet请求之前的状况,下图是执行telnet请求之后的状况。

 从上图我们可以看出connection aborted due to timeout增加了一个,说明在服务器端TCP看来,请求的响应数据包(同时带有关闭fin标志)由于发送不出去,连接被aborted,这个时候在服务器端看不到连接相应状态的存在。

在上层nginx看来,遇到了非法请求,回复了响应并关闭了连接。在TCP层看来,由于带有关闭fin的数据包到不了tcpdump抓包接口,服务器端的TCP状态会处于FIN_WAIT_1状态('遇到大量FIN_WAIT1,怎么破?'会有详细介绍),会维持一段时间并不断努力重传。由于重传一直得不到响应,TCP就把FIN_WAIT_1状态变为CLOSED状态,在服务器端查不到该连接了。

这里案例中,我们事先知道我们设置了iptables,但如果不知道呢,我们如何判断出问题出在哪一个环节呢?

仅仅靠tcpdump抓包,明显不够,因为通过抓包分析,我们只能得出服务器端没有接收到请求,我们还需要利用服务器端的信息,才能继续进一步判断。通过nginx日志,判断出请求已经被应用层处理了,说明请求数据包已经到达应用层,nginx已经处理请求,并作了响应处理,接着委托服务器端TCP去发送这些响应数据包,但显然服务器端TCP发送的响应都没有到达抓包接口,说明在IP层干掉了,于是可以根据这些信息去找数据包出去方向(outgoing)的netfilter相关配置,看看有没有这样针对这些响应进行过滤。

从上面案例,可以看出仅仅利用tcpdump是不够的,还需要综合利用各种信息,并加以推理,最终得出问题出在哪一个环节,才能解决问题。如果不会利用这些知识,客户端就就会得出服务器端没有收到请求的错误判断。

5、跨机器判断

 在跨机器访问过程中,存在着如下潜在干涉(坑):

1. 本机器自身IP层安全过滤

2. 链路层发送QUEUE丢包

3. 链路层TCP offload潜在问题(这里把NIC归入数据链路层)

4. 中途设备各种问题(设备包括路由器/交换机/防火墙/网关/负载均衡器等)

5. 对端机器链路层接收QUEUE丢包

6. 对端链路层TCP offload(NIC)潜在问题

7. 对端IP层安全过滤

8. 对端TCP异常状态干扰

这些问题将在TCPCopy和其它章节会有所介绍,这里不再详细描述。

6、常用工具工作层次分析

 上图展示了部分流行性工具的工作层次,比如tcpcopy默认工作在4层,调用IP层提供的raw socket接口来抓包和发包;netstat或者ss工具可以去**TCP/IP各种统计值;LVS工作在4层,利用Netfilter来强行改变路由;tcpdump工作在数据链路层;HTTP应用工作在应用层。

懂得了这些工作原理,可以更加深刻的理解问题,并解决各种TCP相关问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/1164.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯嵌入式第五课--输入捕获

前言输入捕获的考题十分明确&#xff0c;就是测量输入脉冲波形的占空比和频率&#xff0c;对我们的板子而言&#xff0c;就是检测板载的两个信号发生器产生的信号&#xff1a;具体来说就是使用PA15和PB4来做输入捕获。输入捕获原理简介输入捕获能够对输入信号的上升沿和下降沿进…

WorkTool企微机器人接入智能问答

一、前言 最新版的企微机器人已经集成 Chat &#xff0c;无需开发可快速搭建智能对话机器人。 从官方介绍看目前集成版本使用模型为 3.5-turbo。 二、入门 创建 WorkTool 机器人 你可以通过这篇快速入门教程&#xff0c;来快速配置一个自己的企微机器人。 实现的流程如图&…

Windows与Linux端口占用、查看的方法总结

Windows与Linux端口占用、查看的方法总结 文章目录Windows与Linux端口占用、查看的方法总结一、Windows1.1Windows查看所有的端口1.2查询指定的端口占用1.3查询PID对应的进程1.4查杀死/结束/终止进程二、Linux2.1lsof命令2.2netstat命令一、Windows 1.1Windows查看所有的端口 …

基于GPT-4的免费代码生成工具

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

SpringCloud五大核心组件

Consul 等&#xff0c;提供了搭建分布式系统及微服务常用的工具&#xff0c;如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式会话和集群状态等&#xff0c;满足了构建微服务所需的所有解决方案。 服务发现——Netflix Eureka …

7个最受欢迎的Python库,大大提高开发效率

当第三方库可以帮我们完成需求时&#xff0c;就不要重复造轮子了 整理了GitHub上7个最受好评的Python库&#xff0c;将在你的开发之旅中提供帮助 PySnooper 很多时候时间都花在了Debug上&#xff0c;大多数人呢会在出错位置的附近使用print&#xff0c;打印某些变量的值 这个…

算法竞赛必考算法——动态规划(01背包和完全背包)

动态规划(一) 目录动态规划(一)1.01背包问题1.1题目介绍1.2思路一介绍(二维数组)1.3思路二介绍(一维数组) 空间优化1.4思路三介绍(输入数据优化)2.完全背包问题2.1题目描述&#xff1a;2.2思路一(朴素算法)2.3思路二(将k优化处理掉)2.4思路三(优化j的初始条件)总结1.01背包问题…

Spring Cloud Alibaba全家桶(四)——微服务调用组件Feign

前言 本文小新为大家带来 微服务调用组件Feign 的相关知识&#xff0c;具体内容包含什么是Feign&#xff0c;Spring Cloud Alibaba快速整合OpenFeign&#xff0c;Spring Cloud Feign的自定义配置及使用&#xff08;包括&#xff1a;日志配置、契约配置、自定义拦截器实现认证逻…

Autosar-ComM浅谈

文章目录 一、ComM概述二、和其他模块的依赖关系三、ComM通道状态机ComM模式与通讯能力关系表四、ComM中的PNC一、ComM概述 ComM全称是Communication Manager,顾名思义就是通信的管理,是BSW(基本软件)服务层的一个组件。 ComM的作用: 为用户简化Communication Stack的使用…

中断控制器

在Linux内核中&#xff0c;各个设备驱动可以简单地调用request_irq&#xff08;&#xff09;、enable_irq&#xff08;&#xff09;、disable_irq&#xff08;&#xff09;、 local_irq_disable&#xff08;&#xff09;、local_irq_enable&#xff08;&#xff09;等通用API来…

STM32----MPU6050

前言&#xff1a;最近几个月没有写文章了&#xff0c;因为这学期的事情真的有点多&#xff0c;但是想了想&#xff0c;文章还是要更新&#xff0c;总结自己学习的知识&#xff0c;真的很重要&#xff01;&#xff01;&#xff01; 废话不多说&#xff0c;正文开始&#xff1a;…

【vue.js】在网页中实现一个金属抛光质感的按钮

文章目录前言效果电脑效果手机效果说明完整代码index.html前言 诶&#xff1f;这有一个按钮(&#xff5e;&#xffe3;▽&#xffe3;)&#xff5e;&#xff0c;这是一个在html中实现的具有金属质感并且能镜面反射的按钮~ 效果 电脑效果 手机效果 说明 主要思路是使用 navig…

【算法基础】二分图(染色法 匈牙利算法)

一、二分图 1. 染色法 一个图是二分图,当且仅当,图中不含奇数环。在判别一个图是否为二分图⑩,其实相当于染色问题,每条边的两个点必须是不同的颜色,一共有两种颜色,如果染色过程中出现矛盾,则说明不是二分图。 for i = 1 to n:if i 未染色DFS(i, 1); //将i号点染色未…

Leetcode138. 复制带随机指针的链表

复制带随机指针的链表 第一步 拷贝节点链接在原节点的后面 第二步拷贝原节点的random &#xff0c; 拷贝节点的 random 在原节点 random 的 next 第三步 将拷贝的节点尾插到一个新链表 ,并且将原链表恢复 从前往后遍历链表 ,将原链表的每个节点进行复制&#xff0c;并l链接到原…

【STL二】STL序列式容器(array、vector、deque、list、forward_list)

【STL二】STL序列式容器&#xff08;array、vector、deque、list、forward_list&#xff09;1.array<T,N>&#xff08;数组容器&#xff09;2.vector<T>&#xff08;向量容器&#xff09;3.deque<T>&#xff08;双端队列容器&#xff09;&#xff1a;4.list&…

第一个 Qt 程序

第一个 Qt 程序 “hello world ”的起源要追溯到 1972 年&#xff0c;贝尔实验室著名研究员 Brian Kernighan 在撰写 “B 语言教程与指导(Tutorial Introduction to the Language B)”时初次使用&#xff08;程序&#xff09;&#xff0c;这是目前已 知最早的在计算机著作中将…

用sql计算两个经纬度坐标距离(米数互转)

目录 一、sql示例&#xff08;由近到远&#xff09; 二 、参数讲解 三、查询效果 - 距离&#xff08;公里 / 千米&#xff09; 四、查询效果 - 距离&#xff08;米&#xff09; 五、距离四舍五入保留后2位小数&#xff08;java&#xff09; 一、sql示例&#xff08;由近到远…

2023年最新最全 VSCode 插件推荐

Visual Studio Code 是由微软开发的一款免费的、针对于编写现代Web和云应用的跨平台源代码编辑器。它包含了一个丰富的插件市场&#xff0c;提供了很多实用的插件。下面就来分享 2023 年前端必备的 VS Code 插件&#xff01; 前端框架 ES7 React/Redux/React-Native snippets …

【OpenCV】车牌自动识别算法的设计与实现

写目录一. &#x1f981; 设计任务说明1.1 主要设计内容1.1.1 设计并实现车牌自动识别算法&#xff0c;基本功能要求1.1.2 参考资料1.1.3 参考界面布局1.2 开发该系统软件环境及使用的技术说明1.3 开发计划二. &#x1f981; 系统设计2.1 功能分析2.1.1 车辆图像获取2.1.2 车牌…

渗透测试靶机vulnhub——DC3实战笔记

vm在导入虚拟机的时候把IDE里面的改成IDE 0:0信息收集fscan扫描存活主机目标机器是192.168.1.106nmap扫描端口nmap -A 192.168.1.106 -p- …