关于Tcp/ip协议我帮你重新看了一遍

2021-09-09  本文已影响0人  叶满林

其实搞懂tcp/ip协议,总体来说就是回答六个问题:

1.Tcp/ip协议是在哪一层生效的?
2.Tcp协议与IP协议的报文是什么样子?
3.Tcp如何实现三次握手、四次挥手的?为啥要这样做?每一步都在干嘛?可以优化吗?
4.Tcp是如何实现滑动窗口的?
5.Tcp是如何实现拥塞控制的?
6.Tcp的优点与实现原理是如何一一对应的?

第一章:网络分层模型 [1]

OSI模型:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
tcp/ip模型:应用层、传输层、网络连接层、主机到我网络层

网络层级划分示意图.png

首先我们要明白:Tcp协议使用的就是传输层、Ip协议是在网络层

结合图可以看出,正常情况下,路由器只会解析IP协议,并不关心Tcp协议。为啥不关心?下面可以从报文入手看看原因。

第二章:IP报文结构 [2]

先看 IPv4的报文结构
(图中固定头部20个字节,每行4字节,32个bit位)

IPv4的报文结构.png

各个字段明细:

IPv6的报文结构

IPv4的报文结构.png

第三章:TCP的报文结构 [3]

TCP连接的特点:面向连接,提供可靠的服务,有流量控制,拥塞控制,无重复、无丢失、无差错,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),只能是点对点,首部 20 字节,全双工

TCP协议报文结构.png
要点:
(1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。    
(2)确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
(3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:
    (A)URG:紧急指针(urgent pointer)有效。
    (B)ACK:确认序号有效。
    (C)PSH:接收方应该尽快将这个报文交给应用层。
    (D)RST:重置连接。
    (E)SYN:发起一个新连接。
    (F)FIN:释放一个连接。

那么TCP协议与IP协议的关系,则如下图所示:

IP协议与TCP协议关系.png

第四章:TCP三次握手(每一次握手都是单向发起,单侧处理) [4]

第一次握手:客户端➡️服务端 [服务端知道客户端发消息没问题]
server猜测客户端要开始tcp请求,但也有可能是乱发的

报文:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。\

第二次握手:服务端➡️客户端 [客户端知道服务端收消息和发消息都没有问题]
客户端知道服务器是可以支持tcp的,并且告诉自己就是要连接tcp

报文:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。

第三次握手:客户端➡️服务端 [服务端知道客户端收消息没问题]
server知道客户端是支持tcp的,且是要建立连接的

报文:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

第五章:滑动窗口(下面以一个半双工模型为例子) [5]

第一小节:如果没有滑动窗口,如何保证传输顺序?

简单的顺序传输模型.png

最原始的方法就是,“一一确认”:发送方发送一个包1,这时候接收方确认包1。发送包2,确认包2。就这样一直下去,知道把数据完全发送完毕,这样就结束了。那么就解决了丢包,出错,乱序等一些情况。

同时也存在一些问题。问题:吞吐量非常的低。我们发完包1,一定要等确认包1.我们才能发送第二个包。整个过程,浪费了四个时间段。

第二小节:逐步优化减少耗时

简单的优化方案.png
这个就是我们把两个包一起发送,然后一起确认。可以看出我们改进的方案比之前的好很多,所花的时间只是两个时间段。
那如果数据不止两个包,我们如何去分配每一次发送包的数量呢?接下来,把这种思路优化一下,看看更优的滑动窗口模型是如何实现的。

第三小节:滑动窗口模型

滑动窗口模型.png
在图中:

正常情况:

可以看到4号包对方已经被接收到,所以被涂成了灰色。“窗口”就往右移一格,这里只要保证“窗口”是7格的。 我们就把11号包读进了我们的缓存。进入了“待发送”的状态。8、9号包已经变成了黄色,表示已经发送出去了。接下来的操作就是一样的了,确认包后,窗口往后移继续将未发送的包读进缓存,把“待发送“状态的包变为”已发送“。
整个过程窗口一直向后移动,确实“滑动窗口”的名字很形象。

丢包情况:
假设我们5号包发送出去了,但是迟迟未得到对方确认,我们的窗口就会停滞不前,变成下面这种情况:
(注意:对于接收方来说,这个Ack是要按顺序的。必须把5的ack发送过去,才能发6-11的Ack。这样就保证了滑动窗口的一个顺序。)


下面就会用到超时重传机制:
我们发现在规定的时间内收不到5号包的确认,会重新去补发一下。等收到5号ack确认,我们就可以继续将窗口向后滑动

一些小细节:
 影响超时重传机制协议效率的一个关键参数是重传超时时间(RTO,Retransmission TimeOut)。RTO的值被设置过大过小都会对协议造成不利影响。
  (1)RTO设长了,重发就慢,没有效率,性能差。
  (2)RTO设短了,重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。
  连接往返时间(RTT,Round Trip Time),指发送端从发送TCP包开始到接收它的立即响应所消耗的时间。

第六章:TCP的拥塞控制 [6]

tcp拥塞控制基于滑动窗口,它主要用到4个核心算法:慢开始(slow start)、拥塞避免(Congestion Avoidance)、快速重传(fast retransmit)、快速回复(fast recovery)

慢开始与拥塞避免:

拥塞窗口(cwnd,congestion window),其大小取决于网络的拥塞程度,并且动态地在变化。
慢开始算法的思路就是,不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。
为了防止cwnd增长过大引起网络拥塞,还需设置一个慢开始门限ssthresh状态变量。ssthresh的用法如下:

拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送发的拥塞窗口cwnd加1,而不是加倍。
无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞,就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞窗口设置为1,执行慢开始算法。如下图:


乘法减小:是指不论在慢开始阶段还是拥塞避免阶段,只要出现超时,就把慢开始门限减半,即设置为当前的拥塞窗口的一半(于此同时,执行慢开始算法)。当网络出现频繁拥塞时,ssthresh值就下降的很快,以大大将小注入到网络中的分组数。

加法增大:是指执行拥塞避免算法后是拥塞窗口缓慢增大,以防止网络过早出现拥塞。

快重传和快恢复

快速重传:

    要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方),而不要等到自己发送数据时捎带确认。

    快重传算法规定,发送方只要一连收到3个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计数器时间到期。

快速恢复:

    当发送方连续收到三个重复确认,就执行“乘法减小”算法,把慢开始门限ssthresh减半。这是为了预防网络发生拥塞。请注意:接下去不执行慢开始算法。

    由于发送方现在认为网络很可能没有发生拥塞,因此与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd现在不设置为1),而是把cwnd值设置为慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。

第七章:TCP四次挥手(每一次挥手都是,单向发起,双向处理)[7]

实际上是两次通知,
第一次:服务端告诉客户端说完了,客户端应答 【不应答,服务端还以为客户端还在听】
第二次:客户端告诉服务端说完了,服务端应答 【不应答,客户端以为服务器还没收到,还在听】

其中第二次和第三次是可以合并的(如果传输是同时完成的话)

第八章:TCP的特点 [8]

1、TCP是面向连接的运输层协议。 (从分层模型和报文结构可知)
2、每一条TCP连接只能有两个端点,每一条 TCP 连接只能是点对点的。(因为必须建立三次握手)
3、TCP提供可靠交付的服务。通过 TCP 连接传送的数据,无差错、不丢失、不重复,并且按序到达。(序号校验、顺序传输、重传机制)
4、TCP提供全双工通信。TCP 允许通信双方的应用进程在任何时候都能发送数据。TCP 连接的两端都设有发送缓存和接受缓存,用来临时存放双向通信的数据。

扩展:rst攻击

A和服务器B之间建立了TCP连接,此时C伪造了一个TCP包发给B,使B异常的断开了与A之间的TCP连接,就是RST攻击了。实际上从上面RST标志位的功能已经可以看出这种攻击如何达到效果了。

那么伪造什么样的TCP包可以达成目的呢?我们至顶向下的看。

假定C伪装成A发过去的包,这个包如果是RST包的话,毫无疑问,B将会丢弃与A的缓冲区上所有数据,强制关掉连接。

如果发过去的包是SYN包,那么,B会表示A已经发疯了(与OS的实现有关),正常连接时又来建新连接,B主动向A发个RST包,并在自己这端强制关掉连接。

这两种方式都能够达到复位攻击的效果。似乎挺恐怖,然而关键是,如何能伪造成A发给B的包呢?这里有两个关键因素,源端口和序列号。

一个TCP连接都是四元组,由源IP、源端口、目标IP、目标端口唯一确定一个连接。所以,如果C要伪造A发给B的包,要在上面提到的IP头和TCP头,把源IP、源端口、目标IP、目标端口都填对。这里B作为服务器,IP和端口是公开的,A是我们要下手的目标,IP当然知道,但A的源端口就不清楚了,因为这可能是A随机生成的。当然,如果能够对常见的OS如windows和linux找出生成source port规律的话,还是可以搞定的。

序列号问题是与滑动窗口对应的,伪造的TCP包里需要填序列号,如果序列号的值不在A之前向B发送时B的滑动窗口内,B是会主动丢弃的。所以我们要找到能落到当时的AB间滑动窗口的序列号。这个可以暴力解决,因为一个sequence长度是32位,取值范围0-4294967296,如果窗口大小像上图中我抓到的windows下的65535的话,只需要相除,就知道最多只需要发65537(4294967296/65535=65537)个包就能有一个序列号落到滑动窗口内。RST包是很小的,IP头+TCP头也才40字节,算算我们的带宽就知道这实在只需要几秒钟就能搞定。

那么,序列号不是问题,源端口会麻烦点,如果各个操作系统不能完全随机的生成源端口,或者黑客们能通过其他方式获取到source port,RST攻击易如反掌,后果很严重。

参考链接:
https://blog.csdn.net/weixin_42100064/article/details/102739531
https://www.zhihu.com/question/51074319
https://baijiahao.baidu.com/s?id=1654225744653405133&wfr=spider&for=pc
https://juejin.cn/post/6844903809995505671
https://www.cnblogs.com/postw/p/9678454.html【】


  1. 1

  2. 2

  3. 3

  4. 4

  5. 5

  6. 6

  7. 7

  8. 8

上一篇下一篇

猜你喜欢

热点阅读