Jialong's Blog

Do things I love, and seek happiness.

0%

计算机网络学习日志(二)——运输层

3.1 概述和运输层服务

运输层协议在不同主机上的应用进程之间提供了逻辑通信,这并非物理上的实际通信。在发送端,运输层将从应用程序接收到的报文转换成运输层分组,该分组称为运输层报文段,实现方法是将应用报文划分为一个个小的块,为每个块加上运输层首部生成运输层报文段,然后在端系统中传递给网络层,网络层将其封装成网络层分组,向目的地发送。

因特网由TCP和UDP两种运输层协议。

3.1.1 运输层和网络层的关系

网络层提供了主机之间的逻辑通信,而运输层为运行在不同主机上的进程之间提供了逻辑通信。

运输层协议至工作在端系统中,同时网络中的路由器既不处理也不识别运输层加在应用层报文的任何信息。运输层协议能提供的服务常常受制于底层网络层协议,例如,如果网络层协议无法为主机之间发送的运输层报文段提供时延和带宽保证,运输层协议可就无法为进程之间发送的应用程序报文提供时延和带宽保证。

3.1.2 运输层概述

首先定义两个名词的意义:

  • 报文段:运输层分组,即TCP和UDP的分组
  • 数据报:网络层分组

网络层的IP被称为不可靠服务。

3.2 多路复用与多路分解

在目的主机中,运输层从紧邻其下的网络层接受报文段,并将这些报文段中的数据交付给在主机上运行的对应应用程序进程。

多路分解是指将运输层报文中的数据正确交付给正确的套接字的工作。

多路复用是指在源主机中从不同套接字中收集数据块,并为每个数据块封装上首部信息从而生成报文段,然后将报文段传递到网络层。

运输层多路复用的要求:

  • 套接字有唯一标识

  • 每个报文段有特殊字段来指示该报文段所要交付到的套接字

    • 特殊字段:源端口号字段、目的端口号字段

    0~1023范围的端口称为周知端口号,是受限的,它们分配给周知应用层协议来使用

3.3 无连接运输:UDP

UDP从应用程序得到数据,附加上用于多路复用/分解服务和目的地端口字段号,以及两个其他字段,然后将形成的报文交给网络层,网络层将运输层报文封装到一个一个IP数据报中,然后尽力交付给目的主机。

UDP被称为无连接运输是因为在发送报文段之前,发送方和接收方的运输层实体之间没有握手。

一些应用相比于TCP更适合于使用UDP的原因:

  • 关于发送什么数据以及何时发送的应用层控制更为精细
  • 无需建立连接,开始传输数据前不需要握手
  • 无连接状态。TCP中要实现可靠数据传输服务并提供拥塞控制,状态信息是必须的。
  • 分组首部占用空间小。每个TCP报文段有20个字节的首部开销,UDP仅有8个字节。

3.3.1 UDP报文段结构

3.3.2 UDP检验和

检验和用于确定当UDP报文段从源到达目的地移动时,其中的比特是否发生变化。

提供差错检验的原因是不能保证源和目的之间的所有链路都提供了差错检测。

虽然UDP提供了差错检验,但是它对于差错恢复无能为力,它只是丢弃受损的报文段。

3.4 可靠数据传输原理

可靠数据传输协议是用来实现一种服务的抽象:数据可以通过一条可靠的信道进行传输。可靠传输协议的下层也许是不可靠的,如TCP是在不可靠的端到端网络层(IP)之上实现的可靠数据传输协议。我们目前可将较低层直接视为不可靠的点对点信道。

3.4.1 构造可靠数据传输协议

1. rdt1.0:经完全可靠信道的可靠数据传输

此时的底层信道完全可靠,接收端不需要提供任何反馈信息给发送方。

rdt1
  • 有限状态机(Finite-State Machine, FSM):表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
    • FSM描述图中箭头指示了协议从一个状态便签到另一个状态
    • 横线上方:引起变迁的事件
    • 横线下方:事件发生时所采取的动作

发送方和接收方有各自的FSM

rdt1.0的发送端通过rdt_send(data)事件接受来自叫高层的数据,产生一个包含该数据的分组(由make_pkt(data)动作产生),并将分组发送到信道中。

接收端通过rdt_rcv(packet)从底层信道接受一个分组,从分组中取出数据(由extract(packet, data)动作完成),并将数据上传给叫高层(通过deliver_data(data)完成)。

2. rdt2.0:经具有比特差错信道的可靠数据传输

底层信道更为实际的模型是可能受损的模型,这样的比特差错通常出现在网络的物理部件中

自动重传请求协议(Automatic Repeat reQueat, ARQ)

使用肯定确认否定确认

  • 差错检测:第五章会详细说明具体机制
  • 接收方反馈:接收方向发送方回送ACK(肯定确认)或是NAK(否定确认)分组,理论上只需要1比特。
  • 重传:接收方接收到有差错分组,发送方将重传该分组
rdt2

rdt2.0的发送端由两个状态,左边的状态中,发送端协议正在等待来自上层传递的数据,当事件rdt_send(data)出现时,发送方将产生一个带有检验和的分组(sndpkt),然后发送该分组。右边的状态中,发送方协议等待来自接收方的ACK或NAK分组。如果收到ACK分组,则知道已经正确发送,协议返回到右侧的等待上层数据的状态;如果收到NAK分组,则协议重新上传一个分组并继续等待会送的ACK或NAK。

当发送方处于右侧的等待状态时,不能从上层获得更多的数据,只有收到ACK分组并离开该状态时才能开始获得上层的数据,所以,rdt2.0这样的协议又被称为停等协议。


rdt2.0有一个致命的缺陷,没有考虑到ACK或NAK分组受损的可能性。

一种解决方法是,当发送方收到含糊不清的ACK或NAK分组时,只需重传当前数据分组即可。这种方法在发送方到接收方的信道中引入了冗余分组,但问题在于接收方不知道它上次所发送的ACK或NAK分组是否被正确收到,因此它不知道当前接收到的分组是新的还是一个重传。

解决该问题的方法是在数据分组中添加一新字段,让发送方对其数据分组进行编号,即将发送数据分组的序号放在该字段,此时接收方只需要检查序号即可确定收到的分组是否一次重传。这里只需要使用1比特的序号就够了,如果当前是正在重传一个分组,则接收到的分组序号与最近的收到的分组序号相同;如果是一个新的分组,则序号与前一个相比发生了变化。

rtd2.1

rdt2_1_send
rdt2_1_rcv

rdt2.1是解决了上述rdt2.0问题后的新的协议,发送方和接收方的状态数都是之前的两倍,因为协议状态必须反映出此时发送方正在发送的分组或是接收方希望收到的分组的序号是0还是1。

3. rdt3.0:经具有比特差错的丢包信道的可靠数据传输

现在假定除了比特受损外,底层信道还会丢包,比特受损已经可以通过前面rdt2中的重传解决,而解决丢包需要增加一种新的协议机制。

假定发送方传输一个数据分组,该分组或者接受方对于该分组的ACK发生了丢失,这两种情况下发送方都收不到响应,发送方需要等一段时间以便确定分组已经丢失,然后重传该分组。注意,如果一个分组经历了一个很大的时延,发送方也可能重传该分组,这就导致引入了冗余数据分组,而前面的rdt2.1协议已经能通过序号的功能来处理这种情况。

这个等待时间值需要通过一个倒计数定时器来确定,在给定时间量过期后,可中断发送方,所以发送方需要做到:

  • 每次发送一个分组时便启动一个定时器。
  • 响应定时器中断(采取适当动作)。
  • 终止定时器。
rdt3

分组序号在0和1之间交替,所以rdt3.0又被称为比特交替协议。

3.4.2 流水线可靠数据传输

rdt3.0是一个功能正确的协议,但它的性能并不高,这里的核心问题在于它的停等协议。

在停等协议下,信道的利用率非常低,具体过程见书。

解决这个问题的方法是:不以停等方式运行

(RTT: Round-Trip Time)

流水线

允许发送方方多个分组而无需等待确认,这种技术被称为流水线,流水线对可靠数据传输协议回带来以下影响:

  • 必须增加序号的范围
  • 协议的发送方和接收方不得不缓存多个分组
  • 解决流水线差错恢复有两种基本方法:回退N步(Go-Back-N, GBN)、选择重传(Selective Repeat, SR)

3.4.3 回退N步(Go-Back-N, GBN)

GBN协议中,允许发送方发送多个分组不需等待确认的原则受限于在流水线中未确认的分组数不能超过某个最大允许数N。

GBN
  • 基序号(base):最早未确认分组的序号
  • 下一个序号(nextseqnum):最小未使用序号(下一个待发分组序号)

如上图所示,可以将序号范围分割成4段:

  • [0, base - 1]段内的序号对应已发送并确认的分组
  • [base, nextseqnum - 1]:已发送但未被确认的分组
  • [nextseqnum, base + N - 1]:对应立即要被发送的分组(如果有数据来自上层的话)
  • 大于等于base + N的序号不能使用,直到当前流水线中未被确认的分组已得到确认

所以,N常被称为窗口长度,GBN也常被称为滑动窗口协议(sliding-window protocol)

扩展FSM:基于ACK、无NAK的GBN协议的发送方和接收方描述。

(FSM: Finite-State Machine, 有限状态机)

ExtendedFSM

GBN发送方必须响应三种类型事件:

  • 上层的调用。当上层调用rdt_send()时,发送方首先检查发送窗口是否已满。如果未满,则产生一个分组并更新变量;如果已满,则发送方将数据返回给上层,隐式地指示窗口已满,过一会儿再试。实际中发送方更可能缓存这些数据或使用同步机制(仅当窗口不满时才允许上层调用rdt_send())。
  • 收到一个ACK。对序号为n的分组的确认采取累计确认,表明接收方已正确收到序号为n的之前(包括n)的所有分组。
  • 超时事件。发生超时时,发送方重传所有已发送但还未被确认过的分组。

GBN协议中,接收方丢弃所有的失序分组,即不需要缓存任何失序分组,因为如果分组丢失,发送方会重传丢失的分组和之后的分组。

3.4.4 选择重传(SR)

GBN解决了停等协议中的信道利用率的问题但GBN本身也存在着一些性能问题,当窗口长度和带宽时延都很大时,单个分组的差错就能引起GBN重传大量分组,但许多分组没有必要重传。

选择重传(SR)协议通过让发送方仅重传那些它怀疑在接收方出错的分组而避免了不必要的重传。这种方式要求接收方逐个地确认正确接收地分组

SR操作

?对于SR协议而言,窗口长度必须小于或等于序号空间大小的一半

3.5 面向连接的运输:TCP

3.5.1 TCP连接

TCP被称为时面向连接的是因为一个进程向另一个进程发送数据之前,这两个进程必须先相互握手,即相互发送一些预备报文来建立确保数据传输的参数。

这样的连接不是一条端到端的连接电路,而是逻辑连接,这种链接仅仅存在于两个通信端系统的TCP程序中。TCP协议只在端系统中运行,不在中间的网络元素中运行,中间路由看不到TCP连接,只能看到数据报。

TCP连接提供的是全双工服务,进程之间的TCP连接可以双向传输数据;TCP是点对点连接,只能在一个发送方和一个接收方之间传输。


TCP建立连接的过程

客户首先发送一个特殊TCP报文段,服务器用另一个特殊报文段响应,最后客户再用第三个特殊报文段作为响应,这种建立连接的过程通常被称为三次握手。第三个报文段可以承载有效荷载(可以包含应用层数据),前两个不行。

建立连接后,两个应用程序进程之间就可以相互方数据了。

TCP缓存

客户进程通过套接字传递数据流,数据一旦通过套接字,就由客户中运行的TCP控制,TCP将这些数据引入发送缓存,并不时地从发送缓存中取出一块数据并将数据传递到网络层。TCP从缓存中取出并放入报文段中的数据数量受到最大报文段长度(Maximum Segment Size, MSS)的限制,MSS的典型值为1460字节。

3.5.2 TCP报文段结构

TCPsegmentStructure

TCP报文由首部字段和一个数据字段组成。首部一般是20字节,比UDP多12字节。

TCP报文段首部

  • 源端口号目的端口号:被用于多路复用和多路分解。
  • 32比特的序号字段和32比特的确认号字段:用来实现可靠传输服务(是以字节来计算,不是按段来计算)
  • 4比特的首部长度字段:指示TCP首部长度
  • 6比特的标志字段
    • ACK比特用于确认字段中的值是有效的
    • RST、SYN、FIN比特用于连接的连理和拆除
    • CWR、ECE比特在明确拥塞报告中使用
    • URG比特用来指示报文段中被发送端的上层实体置为紧急的数据
  • 16比特的接收窗口字段:用于指示接收方愿意接收的字节数量
  • 16比特的检验和字段
  • 16比特的紧急数据指针字段
  • 选项字段:用于发送方和接收方协商最大报文段长度

序号和确认号

这两个字段是TCP可靠传输服务的关键

  • 一个报文的序号是该报文段首字节的字节流编号

例如,数据流由一个包含500,000字节的文件组成,其MSS为1000字节,则TCP将为该数据流创建500个报文段,第一个报文段分配的序号是0,第二个报文段的序号是1000,以此类推。

  • 主机A填充进报文段的确认号是主机A期望从主机B收到的下一个字节的序号

例如,主机A已经收到来自B的编号为0~255的所有字节,它打算发一个报文段给B期望收到256以及之后的所有字节,于是它就在报文段的确认号字段填上256

  • 累计确认:假设主机A已经先后收到了主机B发送的两个报文段(分别为字节0~535和字节900~1000的报文段),由于一些原因并未收到中间的536~899的报文段,为了完整地建立主机B地数据流,仍在等待字节536及之后的字节,因此A给B发送地下一个报文地确认号字段中包含536,因此TCP只确认该流中第一个丢失字节为止的字节。

3.5.3 往返时间的估计与超时

估计往返时间

  • SampleRTT:从报文段被发出(交给IP)到收到该报文段的确认之间的时间量,多数TCP只在某个时刻做一次SampleRTT的测量,不是为每个发送的报文都做测量。

    问题:为什么TCP仅为传输一次的报文段测量SampleRTT

  • 随着网络状态课端系统负载的变化,报文段的SampleRTT在不断地变化,为了找到一个典型地RTT,要采取一种方法对SampleRTT取平均,TCP会根据以下赋值公式来更新EstimatedRTT: \[ EstimatedRTT = (1 - a) * EstimatedRTT + a * SampleRTT \\ 在[RFC6298]标准中推荐的a值为0.125 \] 上述的这种计算平均的方法被称为指数加权移动平均,这个加权平均最最近的样本赋予的权值要大于对旧样本赋予的权值。

  • 还有一种方式:测量RTT的变化。[RFC6298]定义了RTT偏差DevRTT\[ DevRTT = (1 - \beta) * DevRTT + \beta * |SampleRTT - EstimatedRTT| \\ \beta 的推荐值为0.25 \]

设置和管理重传超时间隔

确认超时间隔的值: \[ TimeoutInterval = EstimatedRTT + 4 * DevRTT \\ 推荐的TimeoutInterval初始值为1秒 \]

3.5.4 可靠数据传输

3.5.5 流量控制(接收窗口字段)

一条TCP连接的每一侧的主机都为该连接设置了接收缓存,当TCP连接收到正确的、按序的字节后,就会将数据放入缓存,应用程序就会从该缓存中根据实际情况读取数据,接收方可能不会立即读取刚刚放入缓存中的数据,他可能忙于别的服务。因此,可能由于发送方发送得太多太快,接收方读取得太慢导致缓存溢出

所以,TCP为其应用程序提供了流量控制服务,来防止接收方的缓存溢出,该服务是一个速度匹配服务,即将发送方的发送速率与接收方的读取速率相匹配。

注意,TCP发送方也可能由于IP网络的拥塞而被遏制,这种形式的对于发送方的控制被称为拥塞控制,它与流量控制是两个完全不相同的概念,它们是针对完全不同的原因而采取的措施。

TCP通过让发送方维护一个接收窗口的变量来进行流量控制,接收窗口用来给发送方指示接收方剩余的缓存空间数量,TCP是双全工的,因此两端的发送方都各自维护一个接收窗口。

假设主机A通过一条TCP连接向主机B发送一个文件,B为该连接分配了一个接收缓存,用RecBuffer来表示其大小,定义以下的变量:

  • LastByteRead:主机B的应用程序从缓存中读取的数据流的最后一个字节的编号
  • LastByteRcved:从网络中到达并且已经放入B的缓存中的数据流的最后一个字节的编号

所以为了不使缓存溢出,下式必须成立: \[ LastByteRcved - LastByteRead \le RcvBuffer \] 接收窗口用rwnd来表示,根据缓存可用空间大小来设置: \[ rwnd = RcvBuffer - [LastByteRcvd - LastByteRead] \] 主机A要轮流跟踪两个变量:LastByteSentLastByteAcked,分别指发送到连接中的数据流的最后一个字节的编号和已经确认接收的数据流的最后一个字节的编号, 因此,两者的差就代表了主机A发送到连接中但还未被确认的数据量,只要将这个数据量控制在值rwnd以内,就不会使B的接收缓存溢出。

注意一种特殊情况,假设B的缓存已满即rwnd=0,B将这个消息发送给A后,假设B没有任何的数据要发送给A,此时B的应用程序将通过不断读取数据来清空缓存,TCP并不向主机A发送带有rwnd新值的报文段,这样,A不知道B的接收缓存有新的空间,因为A被阻塞不能发送数据。解决这个问题的方法是:TCP规定当B的接收缓存为0时,A继续发送只有一个字数据的报文,这些报文段将会被B确认,最终缓存开始清空,返回一个具有新rwnd值的报文段。

3.5.6 TCP连接管理

该节中将详细说明建立和拆除一条TCP连接的全过程。

1. 3次握手建立TCP连接:

  • 第一步:客户端的TCP首先向服务端TCP发送一个特殊TCP报文段,该报文段不含任何应用层的数据,仅将首部中的一个标志位(SYN比特)置为1,因此给报文段被称为SYN报文段。此外客户会随机选择一个初始序号(client_isn),将此编号放入起始SYN报文段的序号字段中。最后该报文段被封装在一个IP数据报中,发送给服务器。
  • 第二步:包含TCP SYN报文段的IP数据包到达服务器后,服务器会从中提取TCP SYN报文段,为该TCP连接分配缓存和变量,并向客户TCP发送允许连接的报文段(该报文段被称为SYNACK报文段)。该报文段不包含应用层数据,但首部中包含了3个重要信息:
    • SYN比特被置为1
    • 该TCP报文段首部的确认号字段被置为client_isn + 1
    • 服务器选择自己的初始序号(server_isn)并将其置放到报文首部的序号字段中
  • 第三步:收到SYNACK报文段后,客户也要为该连接分配缓存和变量,然后客户主机向服务器发送另一个报文段,该报文段对连接进行确认(通过将TCP报文段首部的确认字段置为server_isn + 1来完成),因为连接已经建立,所以SYN比特被置为0,这个报文可以携带应用层数据。

2. 4次挥手终止TCP连接的过程

TCP终止连接

如图,客户进程发出一个关闭连接的命令,这会引起客户TCP向服务器TCP发送一个特殊TCP报文段,该报文让其首部中的FIN比特标志位置为1.

服务器接收该报文段后,向发送方返回一个确认报文段。

然后,服务器发送自己的终止报文段,其中FIN比特被置为1.

最终客户对这个服务器的终止报文段进行确认,此时两台主机上的资源(缓存和变量)都被释放。

SYN洪泛攻击

客户(攻击者)发送大量TCP SYN报文段,而不完成第三次握手的步骤,这种情况下服务器不断为这些半开连接分配资源导致服务器的连接资源被消耗殆尽。

解决方法是使用SYN cookie

  • 当服务器收到一个SYN报文段时,并不知道它是合法用户还是SYN洪泛攻击,所以服务器不会为其生成半开连接。相反,服务器生成一个初始TCP序列号,该序列号是SYN报文段的源和目的IP地址与端口号以及仅有该服务器知道的一个秘密数组成的一个复杂函数,该序列号被称为cookie。服务器并不记忆该cookie或对应于SYN的其他状态信息。
  • 若客户合法,它将返回一个ACK报文段,服务器借助cookie验证该ACK于前面发送的某些SYN是否对应。对于合法ACK,其确认字段的值等于SYNACK报文段的序号值(此时为cookie值)加1,以此为依据,如果二者相等,服务器认为该ACK对应于较早的SYN报文段,它是合法的,服务器将生成一个具有套接字的全开连接。
  • 另一方面,如果客户没有返回ACK报文段,初始SYN并未对服务器产生影响,因为服务器没有为它分配资源。

几个特殊通信连接的情况

namp端口扫描工具对一台主机的目的端口6789发送一个特殊的TCP SYN报文段,有3中可能的输出:

  • 源主机从目标主机接收到一个TCPACK报文段,意味着目标主机上一个应用程序使用TCP端口67889运行,nmap返回打开。
  • 源主机从目标主机接收到一个TCP RST报文段,意味着目标主机没有运行一个使用TCP端口6789的应用程序,但知道发送的报文段没有被中间的任何防火墙阻挡。
  • 源主机什么也没收到,意味着很可能被防火墙阻挡。

3.6 拥塞控制原理

3.6.2 拥塞控制方法

端到端拥塞控制

该控制方法中,网络层没有为运输层拥塞控制提供显式帮助。端系统必须通过对网络行为的观察来推断网络拥塞1