TCP/IP卷一:65---TCP连接管理之(TCP连接的建立与终止、TCP半关闭、同时打开/同时关闭、初始化序列号、连接与转换器(NAT))
发布日期:2021-06-29 22:33:08 浏览次数:2 分类:技术文章

本文共 6174 字,大约阅读时间需要 20 分钟。

  • 一个TCP连接通常分为3个阶段:启动、数据传输(也称为“连接已建立”)、退出
  • 下面我们介绍典型的TCP连接的建立与关闭过程(不包括任何数据传输)

一、TCP连接的建立(三次握手)

TCP连接的建立分为3步:

  • 1.主动开启者(通常称为客户端)发送一个SYN报文段(即一个在TCP头部的SYN位字段置位的TCP/IP数据包),并指明自己想要连接的端口号和它的客户端初始序列号(记为ISN(c),本文下面介绍)。通常,客户端还会借此发送一个或多个选项。客户端发送的这个SYN报文段称作段1
  • 2.服务器也发送自已的SYN报文段作为响应,并包含了它的初始序列号(记作ISN(s))。该段称作段2。此外,为了确认客户端的SYN,服务器将其包含的ISN(c)数值加1后作为返回的ACK数值。因此,每发送一个SYN,序列号就会自动加1。这样如果出现丢失的情况,该SYN段将会重传
  • 3.为了确认服务器的SYN,客户端将ISN(s)的数值加1后作为返回的ACK数值。这称为段3

  • 简化之后如下图所示:(在显示相关状态的同时省略了选项与初始序列号等细节)

  • 通过发送上述3个报文段就能够完成一个TCP连接的建立。它们也常称作三次握手。 三次握手的目的不仅在于让通信双方了解一个连接正在建立,还在于利用数据包的选项来承载特殊的信息,交换初始序列号(Initial Sequence Number。ISN)
  • 发送首个SYN的一方被认为是主动地打开一个连接。如上文所述,它通常是一个客户端。连接的另一方会接收这个SYN,并发送下一个SYN,因此它是被动地打开一个连接。 通常,这一方称为服务器 (后面将会介绍一种客户端与服务器同时打开一个连接的情况,但非常少见)

二、TCP连接的关闭(四次挥手)

  • 连接的任何一方都能够发起一个关闭操作。此外,该过程还支持双方同时关闭连接的操作,但这种情况非常少见
  • 在传统的情况下,负责发起关闭连接的通常是客户端。然而,一些服务器(例如web服务器)在对请求做出响应之后也会发起一个关闭操作。通常一个关闭操作是由应用程序提出关闭连接的请求而引发的(例如使用系统调用close)
  • TCP协议规定通过发送一个FIN段(即FIN位字段置位的TCP报文段)来发起关闭操作。只有当连接双方都完成关闭操作后,才构成一个完整关闭

TCP连接的关闭分为4步:

  • 1.连接的主动关闭者发送一个FIN段指明接收者希望看到的自已当前的序列号(K)。FIN段还包含了一个ACK段用于确认对方最近一次发来的数据(标记为L)
  • 2.连接的被动关闭者将K的数值加1作为响应的ACK值,以表明它已经成功接收到主动关闭者发送的FIN。此时,上层的应用程序会被告知连接的另一端已经提出了关闭的请求。通常,这将导致应用程序发起自已的关闭操作
  • 3.接着,被动关闭者将身份转变为主动关闭者,并发送自已的FIN。该报文段的序列号为L
  • 4.为了完成连接的关闭,最后发送的报文段还包含一个ACK用于确认上一个FIN。值得注意的是,如果出现FIN丢失的情况,那么发送方将重新传输直到接收到一个ACK确认为止

  • 简化之后如下图所示:(在显示相关状态的同时省略了选项与初始序列号等细节)

三、TCP半关闭

  • 如前所述,TCP支持半关闭操作。虽然一些应用需要此项功能,但它并不常见
  • 为了实现这一特性,API必须为应用程序提供一种基本的表达方式。例如,应用程序表明“我已经完成了数据的发送工作,并发送一个FIN给对方,但是我仍然希望接收来自对方的数据直到它发送一个FIN给我” 。伯克利套接字的API提供了半关闭操作。应用程序只需要调用shutdown()函数来代替基本的cIose()函数,就能实现上述操作。然而,绝大部分应用程序仍然会调用close()函数来同时关闭一条连接的两个传输方向
  • shutdown系统调用参阅:
  • close系统调用参阅:

图示

  • 下图展示了一个正在使用的半关闭示例。图中左侧的客户端负责发起半关闭操作,然而在实际应用中,通信的任何一方都能完成这项工作
  • 首先发送的两个报文段与TCP正常关闭完全相同:初始者发送的FIN,接着是接收者回应该FIN的ACK
  • 由于接收到半关闭的一方仍能够发送数据,因此下图中的后续操作与四次挥手关闭连接的情况不同不同。虽然下图在ACK之后只描述了一个数据段的传输过程,但实际应用时可以传输任意数量的数据段(后面“TCP数据流与窗口管理”将会详细地讨论数据段的交换与确认细节)。当接收半关闭的一方完成数据发送后,它将会发送一个FIN来关闭本方的连接,同时向发起半关闭的应用程序发出一个文件尾指示。当第2个FIN被确认之后,整个连接完全关闭

四、TCP的同时打开

  • 虽然两个应用程序同时主动打开连接看似不大可能,但是在特定安排的情况下是有可能实现的。通信双方在接收到来自对方的SYN之前必须先发送一个SYN;两个SYN必须经过网络送达对方。该场景还要求通信双方都拥有一个IP地址与端口号,并且将其告知对方。 上述情况十分少见(前面文章介绍的防火墙“打孔”技术除外),一旦发生,可称其为同时打开
  • 例如:
    • 主机A的一个应用程序通过本地的7777端口向主机B的8888端口发送一个主动打开请求,与此同时主机B的一个应用程序也通过本地的8888端口向主机A的7777端口提出一个主动打开请求,此时就会发生一个同时打开的情况
    • 这种情况不同于主机A的一个客户端连接主机B的一个服务器,而同时又有主机B的一个客户端连接主机A的一个服务器的情况。在这种情况下,服务器始终是连接的被动打开者而非主动打开者,而备自的客户端也会选择不同的端口号。因此,它们可以被区分为两个不同的TCP连接

图例

  • 下图显示了在一个同时打开过程中报文段的交换情况
  • 一个同时打开过程需要交换4个报文段,比普通的三次握手增加了一个
  • 由于通信双方都扮演了客户端与服务器的角色,因此不能够将任何一方称作客户端或服务器

五、TCP的同时关闭

  • 同时关闭与同时连接并没有太大区别
  • 前面的正常关闭过程,通信一方(通常是客户端,但不一定总是)提出主动关闭请求,并发送首个FIN。在同时关闭中,通信双方都会完成上述工作

图例

  • 下图显示了在一个同时关闭中需要交换的报文段
  • 同时关闭需要交换与正常关闭相同数量的报文段。两者真正的区别在于:报文段序列是交叉的还是顺序的。下文将会介绍TCP实现中同时打开与同时关闭操作使用特殊状态这一不常见的方法

  • 同时打开与同时关闭还可以参阅:

六、初始化序列号(Seq、ISN)

  • Seq(序列号)、ISN(初始化序列号)参阅TCP报文:

为什么要用序列号

  • 当一个连接打开时,任何拥有合适的IP地址、端口号、符合逻辑的序列号(即在窗口中)以及正确校验和的报文段都将被对方接收。然而,这也引人了另一个问题。在一个连接中,TCP报文段在经过网络路由后可能会存在延迟抵达与排序混乱的情况。为了解决这一问题,需要仔细选择初始序列号
  • 在发送用于建立连接的SYN之前,通信双方会选择一个初始序列号。初始序列号会随时间而改变,因此每一个连接都拥有不同的初始序列号
  • [RFCO793]指出初始序列号可被视为一个32位的计数器。该计数器的数值每4微秒加1。此举的目的在于为一个连接的报文段 安排序列号,以防止出现与其他连接的序列号重叠的情况。尤其对于同一连接的两个不同实例而言,新的序列号也不能出现重叠的情况

序列号重叠问题

  • 由于一个TCP连接是被一对端点所唯一标识的,其中包括由2个IP地址与2个端口号构成的4元组,因此即便是同一个连接也会出现不同的实例。如果连接由于某个报文段的长时间延迟而被关闭,然后又以相同的4元组被重新打开,那么可以相信延迟的报文段又会被视为有效数据重新进入新连接的数据流中
  • 上述情况会令人十分烦恼。通过采取一些步骤来避免连接实例间的序列号重叠问题,能够将风险降至最低。即便如此,一个对数据完整性有较高要求的应用程序也可以在应用层利用CRC或校验和保证所需数据在传输过程中没有出现任何错误。在任何情况下这都是一种很好的方法,并已普遍用于大文件的传输

序列号带来的攻击

  • 如前文所述,一个TCP报文段只有同时具备连接的4元组与当前活动窗口的序列号,才会在通信过程中被对方认为是正确的。然而,这也从另一个侧面反映了TCP的脆弱性:如果选择合适的序列号、 IP地址以及端口号,那么任何人都能伪造出一个TCP报文段,从而打断TCP的正常连接[RFC5961]
  • 一种抵御上述行为的方法是使初始序列号(或者临时端口号)变得相对难以被猜出,而另一种方法则是加密

不同系统对序列号的实现

  • 现代系统通常采用半随机的方法选择初始序列号。证书报告CA-2001-09 [CERTISN]讨论了这一方法的具体实现细节
  • Linux系统采用一个相对复杂的过程来选择它的初始序列号。 它采用基于时钟的方案,并且针对每一个连接为时钟设置随机的偏移量。随机偏移量是在连接标识(即4元组)的基础上利用加密散列函数得到的。散列函数的输人每隔5分钟就会改变一次。在32位的初始序列号中,最高的8位是一个保密的序列号,而剩余的备位则由散列函数生成。上述方法所生成的序列号很难被猜出,但依然会随着时间而逐步增加
  • 据报告显示,Windows系统使用了一种基于RC4[S94]的类似方案

七、TCP连接演示案例

  • 使用telnet工具连接服务器“10.0.0.2:80”

  • telnet命令是建立在TCP连接的基础上的。当Telnet应用程序连接23(Telnet协议的众所周知端口)以外的端口,它将不能用于应用协议。它仅仅将自已的字节输人拷贝至TCP连接中,反之亦然。当一个Web服务器接收到进人的连接请求时,它首先需要等待对web页面的请求。在这种情况下,我们不能提供这样的请求,因此服务器不会产生任何数据。这些均符合我们的期望,因为我们只对连接建立与终止过程中的数据包交换感兴趣

最终结果

  • 客户端发送的SYN报文段所包含的初始序列号为685506836,广告窗口为65535,该报文段还包含了若干其他选项(选项在后面介绍)
  • 第二个报文段既包含了服务器的SYN还包含了对客户端请求的ACK确认。它的序列号(服务器的初始序列号)为1479690171,ACK号为685506837。ACK号仅比客户端的初始序列号大1,说明服务器已经成功接收到了客户端的初始序列号。该报文段同样也包含了一个广告窗口以表明服务器愿意接收64240个字节。第三个数据包将最终完成三次握手,它的ACK号为1479690172。ACK号是不断累积的,并且总是表明ACK发送者希望接收到的下一个序列号(而不是它上一个接收到的序列号)
  • 在4.4秒暂停之后,Telnet应用程序被要求关闭连接。这使得客户端发送第4个报文段FIN。FIN的序列号为685506837,并由第5个报文段确认(ACK号为685506838)。稍后,服务器会发送自已的FIN,对应的序列号为1479690172。该报文段对客户端的FIN进行了再次确认。值得注意的是,该报文段的PSH位被置位。虽然这样并不会对连接的关闭过程产生实质影响,但通常用于说明服务器将不会再发送任何数据。最后一个报文段用于对服务器的FIN进行确认,ACK号为1479690173

  • 从图中我们还会发现SYN报文段包含了一个或多个选项。这些选项需要占用TCP头部额外的空间。例如,第一个TCP头部的长度为44字节,比最小的长度长24字节。TCP也提供了若干选项,下文将详细介绍当一个连接无法建立时如何使用这些选项

八、TCP连接建立超时演示案例

  • 本节的若干实例将会展示连接不能建立的情况。一种显而易见的情况是服务器关闭。为了模拟这种情况,我们将telnet命令发送给一个处于同一子网的不存在的主机。在不修改ARP表的情况下,上述做法会使客户端收到一个“无法到达主机”的错误消息后退出。由 于没有接收到针对之前发送的ARP请求而返回的ARP响应,因此会产生“无法到达主机”的消息。如果我们能事先在ARP表中为这个不存在的主机添加一条记录,那 么系统就不需要发送ARP请求,而会马上根据TCP/IP协议尝试与这个不存在的主机建立联系。相关的命令如下:

  • 上述例子选择的MAC地址00:00:1a:1b:1c:1d不能与局域网中其他主机的MAC冲突,除此之外并无特别。超时发生在发送初始命令后的3.2分钟。由于没有主机响应,例子中所 有的报文段都是由客户端产生的。下图显示了使用Wireshark软件在摘要模式下获得的输出结果

  • 有趣的是这些输出结果显示了客户端TCP为了建立连接频繁地发送SYN报文段。在首个报文段发送后仅3秒第二个报文段就被发送出去,第三个报文段则是这之后的6秒,而第四个报文段则在第三个报文段发送12秒以后被发送出去,以此类推。这一行为被称作指数 回退。在讨论以太网CSMA/CD介质访问控制协议时我们也曾见过这样的行为。然而,这两种指数回退也略有不同。此处的每一次回退数值都是前一次数值的两倍,而在以太网中最大的回退数值是上一次的两倍,实际的回退数值则需要随机选取
  • 一些系统可以配置发送初始SYN的次数,但通常选择一个相对较小的数值50。Linux系统中,系统配置变量net.ipv4.tcp_syn_retries表示了在一次主动打开申请中尝试重新发送SYN报文段的最大次数。相应地,变量net.ipv4.tcp_synack_retries表示在响应对方的一个主 动打开请求时尝试重新发送SYN + ACK报文段的最大次数。此外,它能够在设定Linux专有的TCP_SYNCNT套接字选项的基础上用于个人连接。正如上面所介绍的,默认的数值为重试5次。两次重新传输之间的指数回退时间是TCP拥塞管理响应的一部分。当我们讨论 kam算法时再仔细研究

九、连接与转换器

  • 在前面NAT的文章中,我们已经讨论了一些协议(比如TCP和UDP)如何利用传统的NAT转换地址与端口号。我们还讨论了IP数据包如何在IPv6与IPv4两个版本间进行转换。当TCP使用NAT时,伪头部的校验和通常需要调整(使用校验和中立地址修改器的情况除外)。其他协议也使用伪头部校验和。因为计算包含了与传输层、网络层相关的信息。
  • 当一个TCP连接首次被建立时,NAT能够根据报文段的SYN位探明这一事实。同样,可以通过检查SYN + ACK报文段与ACK报文段所包含的序列号来判断一个连接是否已经完全建立。上述方法还适用于连接的终止。通过在NAT中实现一部分TCP状态机能够跟踪连接,包括当前状态、备方向的序列号以及相关的ACK号。这种状态跟踪是典型的NAT实现方法
  • 当NAT扮演编辑者的角色并且向传输协议的数据负载中写人内容时,就会涉及一些更复杂的问题。对于TCP而言,它将会包括在数据流中添加与删除数据,并由此影响序列号(与报文段)的长度。此举会影响到校验和,但也会影响数据的顺序。如果利用NAT在数据 流中插人或删除数据这些数值都要做出适当调整。如果NAT的状态与终端主机的状态不同步,连接就无法正确进行下去。因此,上述做法会带来一定的脆弱性

转载地址:https://dongshao.blog.csdn.net/article/details/104027030 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:C++(对象模型):08---Function之(Function Member的各种调用方式(静态函数、非静态函数、虚函数)、附C++的mangling机制)
下一篇:Linux(内核剖析):27---中断下半部之(下半部机制的选择、在下半部之间加锁、禁止下半部(local_bh_disable、local_bh_enable))

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年04月06日 23时53分24秒