SCTP流控制传输协议简析

描述

SCTP

SCTP(Stream Control Transmission Protocol,流控制传输协议,RFC 2960、RFC 3286、RFC 3309)是一个 IP 协议之上的、可靠的、面向控制信令的、传输层协议。SCTP 可以在 “尽力而为(无连接、不可靠)” 的 IP 网络之上为电信级信令传输提供高效、可靠的信令传输服务,例如 5GC 中的 N2 接口就使用了 SCTP 协议。 SCTP 的诞生是为了弥补 TCP/UDP 在电信网络控制信令传输场景中的不足,它们都难以完全满足在电信网络中信令承载的要求。包括:

UDP 是一种无连接的不可靠传输协议,自然无法满足信令对传输质量的要求。

TCP 虽然是一种基于连接的可靠传输协议,但同时也具有队头阻塞、实时性差、支持多归属困难、易受 DDOS(拒绝服务)攻击的缺陷。

为了解决 TCP/UDP 协议在传输实时信令时所面临的不可靠传输和时延等问题,SCTP 协议设计了以下的核心特性,包括:

适当的拥塞控制;

防止泛滥和伪装攻击;

更优的实时性能;

多属主(Multi-homing)特性支持;

多流(Multi-streaming)特性支持;

等等。

TCP通信

SCTP 与 TCP 的区别

和 TCP 类似,SCTP 是面向连接、端到端、全双工、带有流量和拥塞控制的可靠传输协议。数据可靠传输都是通过确认机制来实现的,与 TCP 的区别是:

TCP 是以字节为单位传输的,SCTP 是以数据块为单位传输的。

TCP 通常是单路径传输,SCTP 可以多路径传输。

TCP 的两端都只能用一个 IP 来建立连接,连接建立之后就只能用这一对 IP 来相互收发消息。如果这一对 IP 之间的路径出了问题,那这条 TCP 连接就不可用了;而 SCTP 的两端都可以绑定到多个 IP 上,只要有其中一对 IP 能通,这条 SCTP 连接就还可以用。这就是 SCTP 的多宿主机制。

TCP 是单流的有序传输,SCTP 可以多流独立有序/无序传输。这就是 SCTP 的多流机制。

TCP 连接的建立过程需要三步握手,SCTP 连接的建立过程需要四步握手。

SCTP 有 Heartbeat(心跳)机制来管理路径的可用性。

SCTP 与 QUIC 的区别

SCTP 在 WebRTC 上已有基于 UDP 的实现,但 SCTP/UDP 与 QUIC/UDP 相比还不够好:

没有解决数据流的队头阻塞问题。

连接建立时需要决定数据流的数量。

没有稳固的 TLS 安全性支持。

建立连接时候需要 4 次握手,而 QUIC 一次都不用(0-RTT)。

QUIC 是类 TCP 的字节流,而 SCTP 是信息流(message-based)。

QUIC 连接支持 IP 地址迁移,SCTP 不行。

SCTP 的基本概念

主机(Host)和端点(Endpoint)

主机(Host)就是具有一个或多个 IP 地址的计算机,是一个物理概念;

端点(Endpoint)就是一个 SCTP Endpoint,是一个逻辑概念,作为 SCTP 协议收/发的两端。

SCTP Endpoint 有一个 L4 传输层端口号(Port)和若干个 L3 网络层 IP 地址组成(通常是 2 个 IP 地址),但一个 SCTP Endpoint 所含有的若干个 IP 地址必须使用同一个相同的 Port。

TCP通信

多宿主(Multi-homing)

SCTP Endpoint(具有多个 IP 地址)是 SCTP 多宿主特性的基础,一个 SCTP Endpoint 可能有多个冗余的 IP 网络连接。当不同 Hosts 之间的 SCTP Endpoint 建立了一个偶联之后,如果它的某个 IP 网络连接发生故障了,SCTP 就可以通过切换到另一个 IP 地址来避免业务中断。是一种类似于主备的高可用思想。 如下图所示,我们可以配置 “双宿主” 模式。

TCP通信

通路(Path)和首选通路(Primary Path)

多宿主特性提高了网络的容错能力,这也引出了另一对概念:通路(Path)和首选通路(Primary Path)。 如果 Receiver 是多宿主的,那么对于 Sender 来说每一个 Receiver 的 IP 地址就代表着一条通往对端的 Path,这样 Sender 可以选择任一条 Path 来发送数据。

SCTP 规定任何时间都有一条路径作为 Primary Path 来发送数据,其他路径作为 Backup Path。如果 Primary Path 因接口故障或者网络拥塞等原因而失效,SCTP 可以自动切换到另外一条 Path 来发送,避免单点失效,从而提高整个关联的容错能力。

TCP通信

偶联(Association)

SCTP 使用 “偶联” 一词替代 TCP 的 “连接” 是为了避免内涵的混淆:一个连接只涉及两个 IP 地址间的通信。 SCTP 协议规定在任何时刻,2 个 SCTP Endpoint 之间能且仅能建立一个偶联,它可能因为 SCTP 的多宿主特性而涉及若干个 IP 地址。

SCTP 支持多种网络协议,当 SCTP 在 IP 网络上运行时,Local IP 地址、Local SCTP Port 号、Remote IP 地址、Remote SCTP Port 号等 4 个参数,可以唯一标识一个 SCTP 偶联。 SCTP 的偶联需要通过 4 次握手建立。相对于 TCP 的 3 次握手建立连接,SCTP 的偶联能够抵御 DoS 攻击,从而提高了安全性。数据只有在建立偶联之后与关闭偶联之前才可发送。SCTP 偶联通过 3 次握手关闭,不支持类似 TCP 的半关闭连接。也就是在任何一方关闭偶联后,对方即不再发送数据。

TCP通信

传输控制块(TCB)

TCB(Transmission Control Block,传输控制块)是 SCTP 内部的数据结构,是一个 SCTP Endpoint 为其他 Endpoint 之间已经建立的每 个偶联生成的。TCB 包括 Endpoint 的所有状态、操作信息,便于维护和管理相应的偶联。

多流(Multi-streaming)

一个 SCTP 偶联中的 Stream(流)用来表示需要按顺序递交到高层协议的用户消息序列,在同一个 Stream 中的 Msg 需要按照其顺序进行递交。严格地说,Stream 就是一个 SCTP 偶联中,从一个 Endpoint 到另一个 Endpoint 的单向逻辑通道。一个偶联是由多个单向的 Stream 组成的。

Multi-streaming 是 SCTP 协议的另一个关键特性,主要解决了 TCP 队头拥塞的问题。在一个 SCTP 偶联中,各个 Stream 之间相对独立,使用 Stream ID 进行标识。在同一 Stream 内发送的消息有序,而不同 Stream 之间的消息无序,因此不同 Stream 之间的消息传输是相对独立的,每个 Stream 可以单独发送数据而不受其他流的影响。

一个 SCTP 偶联中可用的 Stream 的数量是在建立 SCTP 偶联时由双方端点协商决定的,但一个 Stream 只能属于一个 SCTP 偶联。SCTP 报文会在不同的 Stream 内发送,这也是 “流控制传输协议“ 名称的由来。

TCP通信  

每个 Stream 上某个 Msg 的丢失不会阻塞同一关联其他 Stream 上消息的投递。这种做法正好与 TCP 相反,就 TCP 而言,在单一字节流中任何位置的字节丢失都将在阻塞该连接上其后所有数据的递送,直到该丢失被修复为止。SCTP 在某一个 Stream 内由于数据传输失败而引起的阻塞不会影响其他 Stream 的消息递交,多流特性可以帮助解决 TCP 中的队头阻塞(HOL)问题。

这一点与 HTTP/2 协议非常类型。因为 TCP 传输是按字节严格有序的,先行传送的字节如果丢失或损坏,即使后续的字节正确地被接收到也不能向上层递交,必须在接收端缓冲起来,直到先行字节由于重传而全部正确接收到后才可以提交,并且释放缓冲区。

传输顺序号(TSN)和流顺序号(SSN)

TSN(Transmission Sequence Number):SCTP 使用 TSN 机制实现数据的确认传输。一个偶联的发送端为发送的每个数据块顺序分配一个基于初始 TSN 的 32 位顺序号,以便接收端进行确认。TSN 是基于偶联进行维护的。

SSN(Stream Sequence Number):SCTP 为一个偶联的发送端中的一个流中发送的每个数据块顺序分配一个 16 位 SSN,以便保证流 内的顺序传递。在偶联建立时,所有流中的 SSN 都是从 0 开始。当 SSN 到达 65535 后,则接下来的 SSN 为 0。

TSN 和 SSN 的分配是相互独立的。此外,SCTP 还定义了无序消息。如果消息带有无序标志,则不论它在哪个流中(在具体实现中,数据块中的 Steam ID 不被解析),只要被正确接收,都提交给 ULP,从而实现和流无关的无序递交,具有更好的灵活性。

SCTP 的报文格式

一个 SCTP 报文包含了一个 Common Header(公共报头)和若干个 Chunk(数据块),每 个数据块中既可以包含控制信息,也可以包含用户数据。

TCP通信

INIT 数据块的报文中必须为 0。

含 SHUTDOWN-COMPLETE 数据块且设置了 T 比特的报文中,验证标签必须要从包含 SHUTDOWN-ACK 数据块的报文中复制。

含 ABORT 数据块的报文中,验证标签必须要从触发这个 ABORT 发送的报文中复制。

0:净荷数据(DATA)

1:启动(INIT)

2:启动证实(INIT ACK)

3:选择证实(SACK)

4:Heartbeat 请求(HEARTBEAT)

5:Heartbeat 证实(HEARTBEAT ACK)

6:中止(ABORT)

7:关闭(SHUTDOWN)

8:关闭证实(SHUTDOWN ACK)

9:操作差错(ERROR)

10:状态 Cookie(COOKIE ECHO)

11:Cookie 证实(COOKIE ACK)

12:为明确拥塞通知响应(ECNE)预留

13:为降低拥塞窗口(CWR)预留

14:关闭完成(SHUTDOWN COMPLETE)

15-62:IETF 预留

63:IETF 定义的数据块扩展

64-126:IETF 预留

127:IETF 定义的数据块扩展

128-190:IETF 预留

191:IETF 定义的数据块扩展

192-254:IETF 预留

255:IETF 定义的数据块扩展

Source Port Number(源端口号):接收方可以使用源端口号、源 IP 地址、目的端口号和目的 IP 地址标识该 SCTP 报文所属的偶联。

Destination Port Number(目的端口号):接收方可以使用目的端口号将 SCTP 报文复用到正确的端点或应用中。

Verification Tag(验证标签):偶联建立时,本端端点为这个偶联生成一个随机标识。偶联建立过程中,双方会交换这个 Tag,到了数据传递时,发送端必须在 Common Header 中带上对端的这个 Tag,以备校验。

Checksum:SCTP 通过对用户数据使用 ADLER-32 算法,计算出一个 32 位的校验码,带在数据报文中,在接收端进行同样的运算,通过检查校验码是否相等来验证用户数据是否遭到破坏。

Chunk Type:定义块值(Chunk Value)中消息所属的类型。包括:INIT、INIT ACK、 SACK、ABORT、ERROR、SHUTDOWN、COOKIE ACK 等 13 种数据块类型。该参数的取值范围为 0~254,255 留作今后的扩展。数据块类型字段的编码分配如下:

另外,Chunk type 的高两位 bit 指示了接收端不认识对应的 Chunk type 的处理原则:

00:停止处理数据报并丢弃,不再处理报中的其他 Chunk。

01:与 00 相同处理外,还要在 ERROR 或 INIT ACK 中上报,原因为不认识的参数类型。

10:忽略该 Chunk,继续处理数据报中的其他 Chunk。

11:同 10 相同处理外,还要在 ERROR 中上报,原因为不认识的 Chunk 类型。

SCTP 的数据传输方式

从 SCTP 的报文格式可以看出 SCTP 是通过传输 Chunk 来传输数据的。对比 TCP 传输的数据方式:TCP 接收端确认的是收到的字节数(TCP 基于数据流进行数据传输),SCTP 接收端确认的是接收到的数据块(SCTP 基于数据块进行数据传输)。

SCTP 的数据块(Data Chunk)通常会携带应用的一个数据报文,或者说是应用要发送的一个消息(Message)。 在实际的应用中,TCP 发送方的可以将应用程序需要发送的多个消息打包到一个 TCP 数据报文中发出。比如,应用程序连续调用两次 send() 向对端发送两条消息,TCP 协议可能把这两条消息都打包放在同一个 TCP 数据报文中。接收端在收到这个 TCP 报文时,回给对端的 ACK 只是表明自己接收到了多少个字节,TCP 协议本身并不会把收到的数据重新拆散分成两条应用层消息并通知应用程序去接收。

事实上,应用程序可能只需要调用一次 receive(),就会把两条消息都收上来了,然后应用需要根据应用程序自己定义的格式去拆成两条消息。 与 TCP 不同,SCTP 是将应用程序每次调用 sendmsg() 发送的数据当作一个整体,放到一个 Data Chunk,接收端也是以 Data Chunk 为单位接收数据,并重新组包,通知应用程序接收。

通常,应用程序每次调用 recvmesg() 都会收到一条完整的消息。在 SCTP 的发送端,多条短的应用层消息可以被 SCTP 协议打包放在同一个 SCTP 报文中,此时在 SCTP 包中可以看到多个 Data Chunk。另一方面,一条太长(比如,超过了路径 MTU)的应用层消息也可能被 SCTP 协议拆分成多个片段,分别放在多个 Data Chunk 并通过不同的 SCTP 报文发送给对端。这两种情况下,SCTP 的接收端都能重新组包,并通知应用程序去接收。

数据库(Data Chunk)格式

TCP通信  

这里重点关注 Chunk Type=0 的数据块:

Reserved:预留,应当设置为全 0,在接收方忽略。

U(比特):非顺序比特。如果该比特设置为 1,则指示这是一个非顺序的数据块,不需要给该数据块分配流顺序号码,所有接收方必须忽略流顺序号码。在重新组装完成后(如果需要),非顺序的数据块不需要尝试任何重新排序的过程,可以由接收方直接递交到高层;如果一个非顺序的用户消息被分段,则消息的每个分段中的 U 比特必须被设置为 1。

B(比特):分段开始比特。如果该比特被设置,则表示这是用户消息的第一个分段。

E(比特):分段结束比特。如果该比特被设置,则指示这是用户消息的最后一个分段。一个未分段的用户消息应当把所有的 B 和 E 比特设置为 1。如果 B 和 E 比特都设置为 0,则表明这是一个分段的用户消息的一个中间分段。当用户消息被分段到多个数据块中,接收方需要使用 TSN 对消息进行重组,这意味着给分段的用户消息的每个分段都必须要使用连续的 TSN。

B、E 比特的取值含义如下:

0 表示高层未对该协议净荷规定应用标识符。

M2UA 协议净荷使用编码 2。

M3UA 协议净荷使用编码 3。

SUA 协议净荷使用编码 4。

M2PA 协议净荷使用的编码待定。

B=1、E=0:用户消息的第 1 个分片。

B=0、E=0:用户消息的中间分片。

B=0、E=1:用户消息的最后一个分片。

B=1、E=1:未分片的消息。

Length:表示数据块从类型字段开始到用户数据字段结束之间的字节数,但不包含任何填充字节,如果数据块的用户数据字段为 0,则长度字段设为 16。

TSN:TSN 的值达到 4294967295 后将回转到 0。

Stream Identifier S:表示用户数据属于的流。

Stream Sequence Number n:表示所在流中的用户数据的顺序号码。有效值为 0~65535。当一个用户消息被 SCTP 分段后,则必须在消息的每个分段中都带有相同的流顺序号码。

Payload Protocol Identifier:表示一个应用(或上层协议)特定的协议标识符。这个值由高层协议传递到 SCTP,并发送到对端。这个标识符不由 SCTP 使用,但却可以由特定的网络实体或对等的应用来识别在数据块中携带的信息类型。甚至在每个分段的数据块中也应包含该字段(以确保对网络中间的代理可用)。

User Data:用来携带用户数据净荷。该字段必须被填充为 4 字节的整数,发送方填充的字节数应不超过 3 个字节,接收方忽略所有的填充字节。

TCP通信  

在实际开发时,需要注意在同一 SCTP 报文中对多个 DATA CHUNK 的处理。例如:如果为每个长度很短的用户数据都带上一个很大 SCTP Header,其传递效率比如会很低。因此,SCTP 协议至此将几个用户数据(容量足够的前提下)捆绑到一个 SCTP 报文里面传输,以提高带宽的利用率。这就是所谓的用户消息捆绑。

SCTP 用户能够可选地使用捆绑功能,决定是否将多个用户数据报捆绑在一个 SCTP 分组中。但为提高效率,在拥塞/重发时,捆绑功能可能仍被执行,即使用户已经禁止捆绑了。这是开发时需要注意的地方,稍不留神就可能漏掉了用户数据。

SCTP 基本信令流程

偶联的建立和发送流程

TCP通信  

上图为 SCTP EndpointA 启动建立偶联,并向 EndpointB 发送一个用户消息,随后 EndpointB 向 EndpointA 发送 两个用户消息的信令流程(假定这些消息没有捆绑和分段)。

启动标签(nitiate Tag):对端验证标签,如设为 Tag_A。Tag_A 是从 1 到 4294967295 中的一个随机数。

输出流数量(OS):本端点期望的最大出局流的数量。

输入流数量(MIS):本端点允许入局流的最大数量。

目的地 IP 地址:设置成 INIT 数据块的起源 IP 地址。

启动标签(Initiate Tag):设置成 Tag_B。

状态 COOKIE(STATE COOKIE):根据偶联的基本信息生成一个 TCB,不过这个 TCB 是一个临时 TCB。这个 TCB 生成以后,将其中的必要信息(包含一个 COOKIE 生成的时间戳、COOKIE 的生命期)和一个本端的密钥通过 RFC2401 描述的算法计算成一个32 位 的摘要 MAC(这种计算是不可逆的)。必要信息和 MAC 组合成 STATE COOKIE 参数。

本端点传送地址。

最大入局流的数量。

最大出局流的数量。

TSN:DATA 数据块的初始 TSN。流标识符(Stream Identifier):用户数据属于的流,假设流标识符为 0。流顺序码(Stream Sequence Number):所在流中的用户数据的顺序号码。该字段从 0 到 65535。用户数据(User Data):携带用户数据净荷。

累积证实 TSN 标签(Cumulative TSN Ack):EndpointA 的初始 TSN。间隔块(Gap Ack Block):此值为 0。EndpointA 收到 SACK 数据块后,停止 T3-RTX 定时器。

TSN:EndpointB 发出 DATA 数据块的初始 TSN。

流标识符(Stream Identifier):用户数据属于的流,假设流标识符为 0。

流顺序码(Stream Sequence Number):所在流中的用户数据的顺序号码。假设流 顺序码为 0。

用户数据(User Data):携带用户数据净荷。

TSN:EndpointB 发出 DATA 数据块的初始 TSN+1。

流标识符(Stream Identifier):用户数据属于的流,假设流标识符为 0。

流顺序码(Stream Sequence Number):所在流中的用户数据的顺序号码。此时流顺序码为 1。

用户数据(User Data):携带用户数据净荷。

累积证实 TSN 标签(Cumulative TSN Ack):EndpointB 的初始 TSN。

间隔块(Gap Ack Block)此值为 0。

EndpointA 创建一个数据结构 TCB(传输控制块)来描述即将发起的这个偶联(包含偶联的基本信息),然后向 EndpointB 发送 INIT 数据块。INIT 数据块中主要包括如下参数:

EndpointB 收到 INIT 消息后,立即用 INIT ACK 数据块响应。INIT ACK 数据块中必须带有如下参数:

EndpointA 收到 INIT ACK 后,首先停止 INIT,离开 COOKIE-WAIT 状态, 然后发送 COOKIE ECHO 数据块,将收到 INIT ACK 数据块中的 STATE COOKIE 参数原封带回。最后 EndpointA 启动 COOKIE 定时器并进入 COOKIE-ECHOED 状态。

EndpointB 收到 COOKIE ECHO 数据块后,进行 COOKIE 验证。将 STATE COOKIE 中的 TCB 部分和本端密钥根据 RFC2401 的 MAC 算法进行计算,得出的 MAC 和 STATE COOKIE 中携带的 MAC 进行比较。如果不同则丢弃这个消息;如果相同,则取出 TCB 部分的时间 戳,和当前时间比较,看时间是否已经超过 了COOKIE 的生命期。如果是,同样丢弃;否则根据 TCB 中的信息建立一个和 EndpointA 的偶联。EndpointB 将状态迁入 ESTABLISHED,并发出 COOKIE ACK 数据块。EndpointB 向 SCTP 用户发送 SCOMMUNCIATION UP 通知。

EndpointA 向 EndpointB 发送一个 DATA 数据块,启动 T3-RTS 定时器。DATA 数据块中必须带 有如下参数:

EndpointB 收到 DATA 数据块后,返回 SACK 数据块。SACK 数据块中必须带有如下参数:

EndpointB 向 EndpointA 发送第一个 DATA 数据块。DATA 数据块中必须带有如下参数:

EndpointB 向 EndpointA 发送第二个 DATA 数据块。DATA 数据块中必须带有如下参数:

EndpointA 收到 DATA 数据块后,返回 SACK 数据块。SACK 数据块中必须带有如下参数:

偶联关闭流程

当一个 Endpoint 退出服务时,需要停止它的偶联。偶联的停止使用两种流程:

偶联的中止流程(非正常关闭):可以在任何未完成期间进行,偶联的两端都舍弃数据并且不提交到对端。此种方法不考虑数据的安全。偶联的中止步骤比较简单:发起端点向对端端点发送 ABORT 数据块,发送的 SCTP 分组中必须填上对端端点的验证标签,而且不在 ABORT 数据块中捆绑任何 DATA 数据;接收端点收到 ABORT 数据块后,进行验证标签的检查。如果验证标签与本端验证标签相同,接收端点从记录上清除该偶联,并向 SCTP 用户报告偶联的停止。

偶联的正常关闭流程:任何一个端点执行正常关闭程序时,偶联的两端将停止接受从其 SCTP 用户层发来的新数据,并且在发送或接收到 SHUTDOWN 数据块时,把分组中的数据递交给 SCTP 用户。偶联的关闭可以保证所有两端的未发送、发送未证实数据得到发送和证实后再终止偶联。

TCP通信  

偶联的正常关闭步骤如下:

偶联关闭发起 EndpointA 的 SCTP 用户层向 SCTP 发送请求 SHUTDOWN 原因。SCTP 偶联从 ESTABLISHED 状态迁入 SHUTDOWN-PENDING 状态。在这个状态,SCTP 不接受 SCTP 用户在这个偶联上的任何数据发送请求。同时等待 EndpointA 所有发送未证实的数据得到 EndpointB 的证实。当所有 EndpointA 发送未证实数据得到证实,则向 EndpointB 发送 SHUTDOWN 数据块。EndpointA 启动 T2-shutdown 定时器进入 SHUTDOWN-SENT 状态。启动 T2-shutdown 定时器的目的是等待 EndpointB 发回的 SHUTDOWN-ACK 数据块,如果定时器超时,则 EndpointA 必须重新发送 SHUTDOWN 数据块。

EndpointB 收到 SHUTDOWN 消息后,进入 SHOUTDOWN-RECEIVED 状态,不再接收从 SCTP 用户发来的的新数据,并且检查数据块的累积 TSN ACK 字段,验证所有未完成的 DATA 数据块已经被 SHUTDOWN 的发送方接收。当 EndpointB 所有未发送数据和发送未证实 数据得到发送和证实后, 发送 SHUTDOWN ACK 数据块并启动本端 T2-SHUTDOWN 定时器,并且进入 SHUTDOWN-ACK-SENT 状态。如果定时器超时了,EndpointB 则重新发送 SHUTDOWN ACK 数据块。

EndpointA 收到 SHUTDOWN ACK 消息后,停止 T2-shutdown 定时器,并且向 EndpointB 发送 SHUTDOWN COMPLETE 数据块,并清除偶联的所有记录。EndpointB 收到 SHUTDOWN COMPLETE 数据块后, 验证是否处于 SHUTDOWN-ACK-SENT 状态。如果不是处于该状态,则丢弃该数据块;如果端点处于 SHUTDOWN-ACK-SENT 状态,EndpointB 则停止 T2- shutdown 定时器并清除偶联的所有记录,进入 CLOSED 状态。






审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分