![d80c44ec95393036f3323684f6b0e103.png](https://i-blog.csdnimg.cn/blog_migrate/80eb751cbbc635f7aa9048477b6e629d.png)
本文详细介绍了一次Telnet进行HTTP访问的过程,并对该过程进行协议上的分析以及wireshark抓包分析。文章只是自己记录实验过程所作,内部若有问题还请各路大神在评论区加以指正,如有问题也欢迎大家交流讨论。
涉及到的知识点有:
1.Telnet协议
2.三次握手流程
3.TCP协议字段详解
4.HTTP报文分片传输与重组
5.TCP的seq和ack变化过程
6.4次挥手的现实情况--简化版的3次挥手
今天才知道可以利用telnet进行http连接并发送请求,特此记录。
![9afae44d9a0b37d3fcd7a4897e8e11ba.png](https://i-blog.csdnimg.cn/blog_migrate/feb9077061cc2e63eaab0c4c5a387d5c.png)
首先输入telnet 主机地址 端口号,在本例中为telnet http://www.baidu.com 80
看到回显第一行就是正在尝试IP地址,该地址就是百度服务器地址。
进一步剖析内部原理如下:
telnet是一个应用层协议,基于TCP/IP协议栈,在其与服务器沟通时,首先与服务器建立TCP连接,经历3次握手后发送HTTP请求,并通过TCP将回显发送回本地shell。
TCP协议为传输层协议,发送至服务器需要经过网络层和数据链路层。而经过网络层时需要知道目的地址,此时需要先通过DNS协议获取到百度的IP。
所以在我们输入telnet http://www.baidu.com 80这条指令时,网络数据包如下图所示
![0b9edba99db15b8f4a3ad45ede193f34.png](https://i-blog.csdnimg.cn/blog_migrate/cc997a6c5693fd7ba8c5a6a7eab768a9.png)
这是3次握手的数据包,首先看第一个包。
1.本地IP为115.25.46.77,目的地址为220.181.112.244,协议为TCP
2.长度74(包括以太网长度14,IP长度20,TCP长度40,TCP中加入了5个可选项,有MSS和Timestamps等)
3.源端口为40862,目的端口为80(刚才设置的)
4.seq=0,这是一个SYN请求,发送的seq是一种验证机制,需要对方发送seq+1的ack进行确认
5.窗口大小29200,指发送方请求的发送窗口为29200字节
6.载荷长度0,由于是请求报文,内部不携带数据,所以载荷长度为0
7.最大分片大小1460字节,由于MTU限制每一个以太网帧的最大包长为1500字节,IP和TCP各占20字节,所以最大MSS为1460字节。需要强调的是,MTU并不是数据链路的物理限制,而是一种逻辑限制,为的是能够得到迅速的响应。举个例子:如果线路速率是9600 b/s,而一个字节有8 bit,加上一个起始比特和一个停止比特,那么线路的速率就是960 B/s(字节/秒)。以这个速率传输一个1024字节的分组需要1066 ms。如果该链路同时运行着一个交互式程序和一个数据传输程序,那么通常来说必须等待一半时间(533ms)才能把交互式应用程序分组传输出去。假设交互的数据可以在数据传输之前被发送,即将服务进行排队,交互型数据优先的算法,这种算法是不完善的,它不应该影响已经进入串行驱动程序的队列。因此MTU被设置为1500以平衡快速响应和最大吞吐量之间的矛盾。
8.SACK_perm是选择确认选项,为的是如果TCP收到乱序数据,发送重复ACK给对端,对端收到重复ACK后,会推测是哪个包产生的错误,在进行重传。如果乱序数据比较零散,这种机制效率会很低,所以使用SACK选项可以告知发送方收到了哪些数据,发送方即可重传缺失部分,大大提高数据重传速度。
9.TSval和TSecr为时间戳选项,后者为响应时间。
http://10.WS为窗口扩大因子,只能在连接建立阶段确定。新的窗口值=首部中定义的窗口值乘以2的(窗口扩大因子)的次方。
以上便是第一个包携带的信息,之后看第二个包。
1.这个包与刚才第一个包IP地址和端口相反,包长也是74,标志位有SYN和ACK,只有ACK标志位为1时,ACK值才有效。
2.服务器接收到本机的SYN请求,返回ACK=1,并且发送一个seq为0给本机
3.窗口大小为8192字节。并设置其他MSS,SACK_PERM和WS参数。
第二个包对第一个包做出响应,返回seq=0,ack=1,这是第二次握手过程。第三个包如下:
1.与第一个包的IP地址和端口相同,也是本机发往服务器的数据包。
2.没有SYN标志位,只有ACK标识位,并返回seq=1,ack=1,最后建立连接。
3.注意此时TCP包长仅为54字节,此包长属于最小TCP包长,没有任何可选项。因为刚才的两次握手已经将参数设置好。
此时经历三次握手,本地与服务器之间已经建立一条TCP连接。于是第二行回显为:connected to www.a.shifen.com.
接下来利用左侧ctrl+]来进入回显模式,再按一次回车进入输入模式,输入GET / HTTP/1.1,回车
![651eac711cda8c8d25b7ff154ce9b992.png](https://i-blog.csdnimg.cn/blog_migrate/296670070015d322c2b45dd5a0c6fad7.jpeg)
再按一次回车便可得到回显
![32eff7e8da8b76640e429e89c74d405b.png](https://i-blog.csdnimg.cn/blog_migrate/fb548bf66a7718612303102b4a9d0e57.jpeg)
![ceba6ba24b06dc2531a653003847b227.png](https://i-blog.csdnimg.cn/blog_migrate/faee7d2c09ac87c84ddc328e3879bb97.jpeg)
此时便收到了HTTP响应报文,来看一下wireshark包的情况
![635d7bb3e7395a9ff5c969cb505f201a.png](https://i-blog.csdnimg.cn/blog_migrate/f20e1268a35fdad5f1cbcbcea19a26ba.png)
这是紧跟着三次握手的4个数据包,我们来分析一下
1.第一个包是带PSH标志位的TCP包,seq=1,ack=1,len=16,这个报文和第三个报文组合起来是一个完整的HTTP请求,但只有第三个包是HTTP协议。
原以为HTTP请求并不一定需要全部按照HTTP协议发送,只需要有一个报文进行HTTP发送,剩下的报文按照HTTP报文中IP的分片标识重组便可得到完整HTTP请求。
IP协议利用三个部分来确保分片重组:
a.标识位:每一个不同的报文标识位都不相同,但若是一个报文的某一个片,其标识位都相同。例如标识位为2的数据包为分
片报文,不管分成多少片,它们的标识位都是2.
b.分片标志位(DF):如果分片标志位为0,则说明该报文是某一个报文的片段,若标志位为1,则报文不分片,标识位不起作用
c.片内偏移:片内偏移值应该是前一个报文的长度+1。
但后来分析该数据包的时候发现,两个数据包的IP头部DF标志位都是1,说明不分片,那么他们两个应该是属于不同的报文,而且他们的标识位不同,第二个HTTP协议的报文标识位是第一个TCP报文+1.我就很纳闷这一点,上网搜索才知道,原来该分段并不是由网络层来进行的,而是应用层进行的,底层并不知道分段细节,因此底层会将其视为两个独立的数据报文进行传输。那么TCP segment of a reassembled PDU是什么意思呢?字面意思是要重组的协议数据单元(PDU:Protocol Data Unit)的TCP段,这里的分段是指:上层协议HTTP的应答由多个分段组成,每个分段都是TCP协议的。TCP本身没有分段的概念,它的sequence number和acknowledge number 是使TCP是基于流的协议的支撑,TCP segment of a reassembled PDU的出现是因为Wireshark分析了其上层的HTTP协议而给出的摘要,如果配置Wireshark不支持HTTP协议解析,则没有该提示,所有的报文都将成为独立的报文进行传输,由应用层进行解释重组。(参考网址:https://www.cnblogs.com/yinghao1991/p/7532889.html)。由于TCP是基于流的,利用seq和ack进行重组,所以应用层可以利用这一点对报文进行分段重组。
2.HTTP报文共18字节,其中每一个字母和字符都需要一个字节,一个空格也是一个字节。看看我们的请求,GET / HTTP/1.1
数下来也就14个字节,还有4个字节在哪里呢?其实HTTP请求报文最后还有两个隐藏的字节,就是rn
rn是原来早先打字机时代的产物,一个是换行,一个是将指针挪到最左侧,现代已将这两个功能合并为回车键。所以我们进
行回车时其实发送的是两个字符rn,也就是0x0d和0x0a。而HTTP请求以两个rn作为结束,所以总共是18个字节。
3.PSH位说明:PSH位是TCP头部的一个标志位,表明该报文直接被上传至进程,而不用延时等待下一个报文的到来。TCP为了减轻网络负担,并不是对每一个请求都发送一个响应的。TCP收到第一个数据报文后,先将报文放入接收报文缓冲区,然后会等待一个短暂的延迟,若有另一个报文到达,则将两个报文同时发送给进程,然后立即回传一个ack,ack为seq+两个报文的长度.
而有了PSH位,当设置了PSH位的报文到达时,将直接发送给进程进行处理,并立即回传ack报文。这也就是我们看到第二个数据包的由来,该数据包的ACK是17,正好是1+16.
4.然后第二个HTTP请求报文到达,服务器处理回传ACK报文,并将收到的两个TCP报文进行整合,对其做出响应。
![1824ca227b4b61578821e8d139d02a33.png](https://i-blog.csdnimg.cn/blog_migrate/87c13a7bbe3be31572ae3224052ff415.jpeg)
之后的过程就是响应包的发送过程。
1.首先服务器向本地发送了2213字节的数据,此时服务器向本机发送的seq是1,ACK是19.本机收到该数据后进行回传ACK报文,ACK=seq+len=1+2213=2214.
2.虽然第一个包长为2267,但其数据载荷长度为2213,有54字节的头部长度,第二个包长为54,说明其不带有任何数据。
3.观察本地传送窗口大小的增长,据观察,本地传送报文中窗口大小增长速度是一定的,每次增长2944,但有一个例外是第3146个报文,它仅增长了2816。这与TCP中window size value相关,每次window size value值都会增长23,而前文说过的WS是128,因此每一次窗口大小增长23*128.然而在第3146个报文时,window size value 只增长了22,所以仅增长2816。但是为何该值在该报文处出现下降却无法从数据包中得到答案,我猜想应该是本地的资源不足或是某些网络间的波动导致的。
4.在所有数据传输完成后,服务器返回了一个200 OK的响应。这里也是和请求一样,只有一个HTTP协议,剩下的都是TCP报文。说明HTTP发送报文时,先将报文载荷全部发送过去,最后再发送一个结束的标志,说明HTTP报文的传输结束,然后接收端将该结束标志之前的所有TCP报文组合,得到完整的HTTP报文。
5.在接收到200 OK报文后,本机发送ACK响应告诉服务器报文已成功接收,下面的Continuation是我人为再次在Telnet端发送回车指令造成的,正好56字节。服务器接收到该请求马上发送ACK响应,表明收到该请求。该ACK响应为60字节,但结尾字节都是00,毫无意义,目前并未知道为何会产生这种情况。
6.服务器解析刚才收到的HTTP请求,但是发现无法解析该请求,因此发送400状态码,并关闭连接。
7.关闭连接时有一个很重要的点,就是原本应该是4次挥手,为何只有3个报文?
4次挥手的介绍
![1cdef24483ae3164e5e9e70c3b9274d3.png](https://i-blog.csdnimg.cn/blog_migrate/b593012a3618596b52b6da24d57dead2.jpeg)
上图是一个完整的4次挥手的过程
1.第一次挥手:客户端 发送一个[FIN+ACK],表示自己没有数据要发送了,想断开连接,并进入FIN_WAIT_1状态(不能再发送数据到服务端,但能够发送控制信息ACK到服务端)。
2.第二次挥手:服务端收到FIN后,知道不会再有数据从客户端传来,发送ACK进行确认,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),服务端进入CLOSE_WAIT状态。
3.第三次挥手:服务端发送FIN给对方,表示自己没有数据要发送了,服务端进入LAST_ACK状态,然后直接断开TCP会话的连接,释放相应的资源。
4.第四次挥手:客户端收到了服务端对FIN的ACK后,进入FIN_WAIT2状态(等待服务端完成资源释放的一系列工作:然后释放你为创建这个连接所分配的资源,并通知我你关闭了); 客户端收到了服务端的FIN信令后,进入TIMED_WAIT状态,并发送ACK确认消息。客户端在TIMED_WAIT状态下,等待2MSL一段时间,没有数据到来的,就认为对面已经收到了自己发送的ACK并正确关闭了进入CLOSE状态,自己也断开了到服务端的TCP连接,释放所有资源。当服务端收到客户端的ACK回应后,会进入CLOSE状态,并关闭本端的会话接口,释放相应资源。
上网查了很长时间,寻找合理的解释,想知道4次挥手中的2,3次挥手是否能合并。然而所有的答案都说挥手是四次,并没有给出我要的答案。但是事实如此,就是当前网络设计时,为了加速释放资源,简化流程,第2次挥手和第3次挥手是可以进行合并的。
就如本文中流量包所示,第1次挥手数据包到达时,服务器知道了客户端不会再发送请求,而本身也并没有数据正在发送,所以直接对于第一次挥手数据包应答FIN和ACK,然后客户端收到后直接返回ack即可。那么4次挥手就简化成了3次挥手。
还有一点值得注意的就是FIN占用一个seq。可以从ack的变化中得到答案。
最开始客户端发送结束请求FIN和ACK报文,seq=15417,ack=21,然后服务器收到请求,也回传FIN和ACK报文,此时的seq=21,而ack变成了15418,并没有数据传送,但是seq加了1。照这样说客户端下次返回的ack应该是22才对,事实确实如此。
![6781fb7ca8a902221efaff4fa281f0f7.png](https://i-blog.csdnimg.cn/blog_migrate/8fbb12d0ff94f244f605056e9dc574d7.png)
通过本次实验,深刻理解了HTTP报文传输的流程,再次加深对于TCP序列号以及3次握手和4次挥手过程的的理解。
路漫漫其修远兮,吾将上下而求索。
最后欢迎大家光临我的博客
陈京九的博客www.chenjingjiu.cn