我们讲述在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层的数据包)。
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干系问题。