SCTP介绍
转自:http://www.ibm.com/developerworks/cn/linux/l-sctp/
SCTP(STREAM CONTROL TRANSMISSION PROTOCOL 流控制传输协议)是IETF新定义的一个传输层transport layer协议(2000年)。是提供基于不可靠传输业务的协议之上的可靠的数据报传输协议。SCTP的设计用于通过IP网传输SCN窄带信令消息。
简介: 流控制传输协议(Stream Control Transmission Protocol,SCTP)是一种可靠的传输协议,它在两个端点之间提供稳定、有序的数据传递服务(非常类似于 TCP),并且可以保护数据消息边界(例如 UDP)。然而,与 TCP 和 UDP 不同,SCTP 是通过多宿主(Multi-homing)和多流(Multi-streaming)功能提供这些收益的,这两种功能均可提高可用性。
SCTP实际上是一个面向连接的协议,但SCTP偶联的概念要比TCP的连接具有更广的概念,SCTP对TCP的缺陷进行了一些完善,使得信令传输具有更高的可靠性,SCTP的设计包括适当的拥塞控制、防止泛滥和伪装攻击、更优的实时性能和多归属性支持。RFC 2960详细说明了SCTP,RFC 4960(2007)是RFC 2960的替代协议,介绍性的文档是RFC 3286。HUAWEI GT800 GTSOFTX3000第四章对SCTP协议介绍:SCTP协议
SCTP最初是被设计用于在IP上传输电话(SS7),把SS7信令网络的一些可靠特性引入IP。IETF的这方面的工作称为信令传输SIGTRAN。与此期间,也提出了这个协议的其他一些用途。
SCTP 提供如下服务:
* 确认用户数据的无错误和无复制传输;
* 数据分段以符合发现路径最大传输单元的大小;
* 在多数据流中用户信息的有序发送,带有一个选项,用户信息可以按到达顺序发送;
* 选择性的将多个用户信息绑定到单个SCTP 包;
* 通过关联的一个终端或两个终端多重宿主支持来为网络故障规定容度。
与TCP的关系:
作为一个传输层协议,SCTP兼有TCP及UDP两者的特点。SCTP可以称为是TCP的改进协议,但他们之间仍然存在着较大的差别。首先SCTP和TCP之间的最大区别是SCTP的连接可以是多宿主连接的,TCP则一般是单地址连接的。在进行SCTP建立连接时,双方均可声明若干IP地址(IPv4,Ipv6或主机名)通知对方本端所有的地址。若当前连接失效,则协议可切换到另一个地址,而不需要重新建立连接。
其次SCTP是基于消息流,而TCP则是基于字节流。所谓基于消息流,是指发送数据和应答数据的最小单位是消息包chunk)。一个SCTP连接(Association)同时可以支持多个流stream),每个流包含一系列用户所需的消息数据chunk)。而TCP则只能支持一个流。在网络安全方面,SCTP增加了防止恶意攻击的措施。不同于TCP连接采用的三次握手机制,SCTP连接采用四次握手机制,有效的防止了类似于SYN Flooding的防范拒绝服务攻击。SCTP主要的贡献是对多重联外线路的支持,一个端点可以由多于一个IP地址组成,使得传输可在主机间或网卡间做到透明的网络容错备援。
IP 堆栈
SCTP是在IP网络上使用的一种可靠的通用传输层协议。Internet协议套件被划分成几层;每层都提供特定功能,如图 1 所示。
图1 IP 堆栈的层次化架构
自下而上:
链路层(link layer) 提供了通信介质的物理接口(例如以太网设备)。
网络层(network layer) 负责管理网络中的报文移动,具体来说就是确保报文都到达自己的目标(也称为路由)。
传输层(transport layer) 为应用层控制了报文在两台主机之间的流动。它还代表通信的应用程序端点,称为 端口(port)。
应用层(application layer) 对通过套接字传递数据具有深刻的意义。这些数据可能包括通过简单邮件传输协议(Simple Mail Transport Protocol,SMTP)发送的 e-mail 消息,或通过超文本传输协议(Hypertext Transport Protocol,HTTP)呈现的 Web 页面。
所有应用层协议都使用套接字层作为与传输层协议之间的接口。Sockets API 是由 UC Berkeley 在 BSD UNIX操作系统上开发的。
传输层协议
两种最流行的传输层协议是传输控制协议(TCP)和用户数据报协议(UDP):
TCP 是一种可靠的协议,它可以确保有序地发送数据,并管理网络中的拥塞问题。
UDP 是一种面向消息的协议,它不能确保有序地发送数据,也无法管理网络拥塞的问题。然而,UDP 是一种快速协议,可以保护自己传输的消息的边界。
SCTP 它提供了像TCP一样可靠、有序地发送数据的功能,但却以像UDP一样面向消息的方式来进行操作,这可以保护消息边界。SCTP 还提供了几个高级特性:
多宿主(Multi-homing)
多流(Multi-streaming)
初始化保护(Initiation protection)
消息分帧(Message framing)
可配置的无序发送(Configurable unordered delivery)
平滑关闭(Graceful shutdown)
SCTP 的关键特性
SCTP 相对于传统的传输层协议来说,两个重要的增强是终端主机的多宿主和多流功能。
多宿主
多宿主为应用程序提供了比TCP更高的可用性。多宿主主机就是一台具有多个网络接口的主机,因此可以通过多个IP地址来访问这台主机。在TCP中,连接(connection)是指两个端点之间的一个通道(在这种情况下,就是两台主机的网络接口之间的一个套接字)。SCTP 引入了 联合(association) 的概念,它也是存在于两台主机之间,但可以使用每台主机上的多个接口进行协作。图 2 阐述了TCP连接与SCTP联合之间的区别。
图2 TCP连接与SCTP联合
该图的上面部分是 TCP 连接,每个主机都只包含一个网络接口;连接是在每个客户机和服务器之间的单个接口之间建立的。在建立连接时,就被绑定到了每个接口上。在该图的下面部分中,您可以看到这样一个架构:每台主机上都包含两个网络接口。通过独立网络提供了两条路径,一条是从接口C0到S0,另外一条是从接口C1到S1。在SCTP中,这两条路径可以合并到一个联合中。
SCTP负责使用内嵌的heartbeat机制来监视联合的路径;在检测到一条路径失效时,协议就会通过另外一条路径来发送通信数据。应用程序甚至都不必知道发生了故障恢复。故障转移也可以用于维护网络应用程序的连通性。例如,让我们来考虑一台包含一个无线 802.11 接口和一个以太网接口的笔记本的例子。当笔记本放到固定的位置上时,我们倾向于使用高速的以太网接口(在SCTP中称为主地址(primary address));但是在这个连接丢失时(例如离开了固定位置),连接可迁移到无线接口上。在返回固定位置时,以太网连接会被重新检测到,通信就可以在这个接口上恢复。这是一种能提供更高的可用性和可靠性的强大机制。
多流
从某种意义上来讲,SCTP连接与TCP连接类似,不同之处只是SCTP能够在一个联合中支持多流机制。一个联合中的所有流都是独立的,但均与该联合相关(参见图 3)。
图 3. SCTP 联合与流之间的关系
每个流都给定了一个流编号,它被编码到 SCTP 报文中,通过联合在网络上传送。多流非常重要,因为阻塞的流(例如等待重传的流会导致报文的丢失)不会影响同一联合中的其他流。这个问题统称为 head-of-line blocking(对头阻塞)。TCP 很容易出现这类阻塞问题。
多流如何在传输数据时提供更好的响应性呢?例如,HTTP 协议会在相同套接字上共享控制和数据。Web 客户机从服务器上请求一个文件,服务器通过相同的连接将这个文件发回给客户机。多流的 HTTP 服务器可以提供更好的交互能力,因为在联合中各单独的流上可以处理多个请求。这种功能可以并行化响应,尽管速度不一定会更快,但可以同时加载 HTML 和图像映像,从而表现出更好的响应性。
多流处理是SCTP的一个重要特性,尤其是在协议的设计中考虑一些控制和数据的问题时更是如此。在TCP中,控制和数据通常都是通过相同的连接进行共享的,这可能会产生问题,因为控制报文可能会在数据报之后延时。如果控制和数据被划分成单独的流,控制数据就可以以一种更及时的方式进行处理,从而可以更好地利用可用资源。
初始化保护
TCP和SCTP中对新连接的初始化是通过报文握手来完成的。在TCP中,这种机制称为 三次握手(three-way handshake)。客户机向服务器首先发送一个SYN报文(Synchronize 的简写),服务器使用一个SYN-ACK报文进行响应(Synchronize-Acknowledge)。最后,客户机使用一个ACK报文确认已接收到报文(请参见图 4)。
图 4. TCP和STCP握手使用的报文交换
当恶意客户机使用虚假的源地址来伪造一个IP报文时,TCP就会出现问题了,这会大量TCP SYN报文攻击服务器。服务器在接收SYN报文之前,要为连接分配资源,但是在大量产生SYN报文的情况下,最终会耗尽自己的资源,从而无法处理新的请求。这种情况就称为 服务拒绝(Denial of Service)(DoS)攻击。
SCTP可以通过一种4次握手的机制并引入cookie 的概念来有效地防止这种攻击的产生。在 SCTP 中,客户机使用一个 INIT 报文发起一个连接。服务器使用一个 INIT-ACK 报文进行响应,其中就包括了 cookie(标识这个连接的惟一上下文)。客户机然后就使用一个COOKIE-ECHO报文进行响应,其中包含了服务器所发送的cookie。现在,服务器要为这个连接分配资源,并通过向客户机发送一个 COOKIE-ACK 报文对其进行响应。
要解决使用这种 4 次握手机制解决延时数据移动的问题,SCTP允许把数据包含到 COOKIE-ECHO和COOKIE-ACK报文中。
消息分帧
使用消息分帧机制,就可以保护消息只在一个边界内通过socket进行通信;这意味着如果客户机向服务器先发送100个字节,然后又发送50个字节。那么服务器就会在两次读取操作中分别读取到100个字节和50个字节。UDP也是这样进行操作,这对于面向消息的协议非常有益。
与此不同,TCP是按照字节流的方式进行操作。如果没有分帧机制,一端接收到的数据可能比另外一端发送的数据多或少(这会将一次写操作划分成多次操作,或者将多次写操作合并到一个读操作中)。这种行为需要在 TCP 之上进行操作的面向消息的协议可以在应用层中提供数据缓冲和消息分帧机制(这可能是一项复杂的任务)。
SCTP 在数据传输中提供了消息分帧功能。当一端对一个套接字执行写操作时,可确保对等端读出的数据大小与此相同(请参见图 5)。对于面向流的数据来说,例如音频和视频数据,可以没有分帧机制。
图 5. UDP/SCTP中的消息分帧与面向字节流协议的比较
可配置的无序发送
SCTP中的消息的传输十分可靠,但未必是按照想要的次序来传输的。TCP可以确保数据是按照次序发送的(考虑到TCP是一种流协议,这是一件好事)。UDP无法确保有序地发送数据。但是如果需要,您也可以在 SCTP 中配置流来接受无序的消息。这种特性在面向消息的协议中可能非常有用,因为其中的消息都是独立的,次序并不重要。另外,您可以在一个联合中按照逐个流配置无序发送。
平滑关闭
TCP 和 SCTP 都是基于连接的协议,而 UDP 则是一种无连接的协议。TCP 和 SCTP 都需要在对等的两端建立和拆除连接。SCTP与TCP中关闭连接的不同之处在于TCP中连接的删除是半关闭(half-close)的。图 6 给出了TCP和SCTP的关闭序列。
图6 TCP和SCTP的连接结束序列
TCP中,一端可以关闭自己这端的socket(这样会导致发送一个FIN 报文),但是仍然可以继续接收数据。FIN说明这个端点不会再发送数据,但是在这一端关闭自己这端的套接字之前,它一直可以继续传输数据。应用程序很少使用这种半关闭状态,因此SCTP的设计者就选择放弃这种状态,并将其替换成了一个显式的终结序列。当一端关闭自己的套接字时(导致产生一个SHUTDOWN原语),对等的两端全部需要关闭,将来任何一端都不允许再进行数据的移动了。
多流的展示
本节使用C编程语言编写的一个样例服务器和客户机展示SCTP的多流特性。这个例子开发了一个服务器,它实现了一种形式的日期查询协议。这个传统的服务器会在连接上来的客户机上打印当前时间,但是对于 SCTP 来说,我们会在流 0 上打印本地时间,在流 1 上打印格林威治时间(GMT)。这个例子让我们可以展示如何使用这些 API 来开发流通信。
图 7 对整个过程进行了归纳,它不但从套接字 API 的角度展示了应用程序的流程,而且还从客户机和服务器的角度介绍了它们之间的关系。
图7 在多流日期查询服务器和客户机中使用的套接字函数
这些应用程序是在 GNU/Linux 操作系统上开发的,其内核版本是 2.6.11,并且包含了 Linux Kernel SCTP 项目(lksctp)。其中非标准的 socket 函数是在 lksctp 工具包中提供的,这个工具包可以从SourceForge上获得。
daytime 服务器
清单 1 给出了这个多流 daytime 服务器的代码。清单1中的服务器首先创建服务器的套接字(使用IPPROTO_SCTP 来创建一个SCTP的一对一的套接字)。然后创建一个 sockaddr结构,指定这个连接可以从任何本地接口上创建(使用通配符地址INADDR_ANY)。我们使用 bind 调用将这个 sockaddr 结构绑定到socket上,然后将服务器套接字设置成监听状态。现在就可以接收到达的连接了。注意 SCTP 使用了很多与 TCP 和 UDP 相同的套接字 API。在 lksctp 开发工具中还提供了其他一些 API 函数(请参看 参考资料)。在服务器的循环中,一直等待新客户机的连接请求。在从 accept 函数返回时,会使用 connSock socket 标识新客户机的连接。我们使用 time 函数来获取当前时间,然后使用 snprintf 将其转换成字符串。使用 sctp_sendmsg 函数(一个非标准的 socket 调用),可以通过指定特定的流程(LOCALTIME_STREAM,将这个字符串发送给客户机。当发送本地时间字符串之后,我们将使用GMT表示的当前时间转换成一个字符串,然后将其发送到流 GMT_STREAM 上。
现在,日期查询服务器已经完成了自己的职责,因此我们就可以关闭这个socket,然后等待一个新的客户机连接。
清单 1. 使用多流机制为 SCTP 编写的日期查询服务器
/*
* sctpsrvr.c
*
* SCTP multi-stream server.
*
* M. Tim Jones <mtj@mtjones.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
//#include "common.h"
#define MAX_BUFFER 1024
#define MY_PORT_NUM 19000
#define LOCALTIME_STREAM 0
#define GMT_STREAM 1
int main)
{
int listenSock, connSock, ret;
struct sockaddr_in servaddr;
struct sctp_initmsg initmsg;
char buffer[MAX_BUFFER+1];
time_t currentTime;
/* Create SCTP TCP-Style Socket */
listenSock = socket AF_INET, SOCK_STREAM, IPPROTO_SCTP );
/* Accept connections from any interface */
bzero void *)&servaddr, sizeofservaddr) );
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl INADDR_ANY );
servaddr.sin_port = htonsMY_PORT_NUM);
ret = bind listenSock, struct sockaddr *)&servaddr, sizeofservaddr) );
/* Specify that a maximum of 5 streams will be available per socket */
memset &initmsg, 0, sizeofinitmsg) );
initmsg.sinit_num_ostreams = 5;
initmsg.sinit_max_instreams = 5;
initmsg.sinit_max_attempts = 4;
ret = setsockopt listenSock, IPPROTO_SCTP, SCTP_INITMSG,
&initmsg, sizeofinitmsg) );
/* Place the server socket into the listening state */
listen listenSock, 5 );
/* Server loop... */
while 1 ) {
/* Await a new client connection */
printf"Awaiting a new connection\n");
connSock = accept listenSock, struct sockaddr *)NULL, int *)NULL );
/* New client socket has connected */
/* Grab the current time */
currentTime = timeNULL);
/* Send local time on stream 0 local time stream) */
snprintf buffer, MAX_BUFFER, "%s\n", ctime¤tTime) );
ret = sctp_sendmsg connSock, void *)buffer, size_t)strlenbuffer),
NULL, 0, 0, 0, LOCALTIME_STREAM, 0, 0 );
/* Send GMT on stream 1 GMT stream) */
snprintf buffer, MAX_BUFFER, "%s\n", asctime gmtime ¤tTime ) ) );
ret = sctp_sendmsg connSock, void *)buffer, size_t)strlenbuffer),
NULL, 0, 0, 0, GMT_STREAM, 0, 0 );
/* Close the client connection */
close connSock );
}
return 0;
}
日期查询客户机
多流客户机如清单 2 所示。在客户机中,我们首先创建了一个SCTP 套接字,然后创建了一个sockaddr结构,其中包含了将要连接的端点。connect函数然后建立一个到服务器的连接。要获取消息的流编号,SCTP需要启用套接字选项sctp_data_io_event。通过启用这个选项,我们就可以通过sctp_recvmsg API 函数接收一条消息,我们还接收到一个包含流编号的sctp_sndrcvinfo 结构。这个编号让我们可以区分开流 0(本地时间)和流 1(GMT)的消息。
清单 2. 使用多流机制为 SCTP 编写的日期查询客户机
/*
* sctpclnt.c
*
* SCTP multi-stream client.
*
* M. Tim Jones <mtj@mtjones.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
//#include "common.h"
#define MAX_BUFFER 1024
#define MY_PORT_NUM 19000
#define LOCALTIME_STREAM 0
#define GMT_STREAM 1
int main)
{
int connSock, in, i, ret, flags;
struct sockaddr_in servaddr;
struct sctp_status status;
struct sctp_sndrcvinfo sndrcvinfo;
struct sctp_event_subscribe events;
struct sctp_initmsg initmsg;
char buffer[MAX_BUFFER+1];
/* Create an SCTP TCP-Style Socket */
connSock = socket AF_INET, SOCK_STREAM, IPPROTO_SCTP );
/* Specify that a maximum of 5 streams will be available per socket */
memset &initmsg, 0, sizeofinitmsg) );
initmsg.sinit_num_ostreams = 5;
initmsg.sinit_max_instreams = 5;
initmsg.sinit_max_attempts = 4;
ret = setsockopt connSock, IPPROTO_SCTP, SCTP_INITMSG,
&initmsg, sizeofinitmsg) );
/* Specify the peer endpoint to which we'll connect */
bzero void *)&servaddr, sizeofservaddr) );
servaddr.sin_family = AF_INET;
servaddr.sin_port = htonsMY_PORT_NUM);
servaddr.sin_addr.s_addr = inet_addr "127.0.0.1" );
/* Connect to the server */
ret = connect connSock, struct sockaddr *)&servaddr, sizeofservaddr) );
/* Enable receipt of SCTP Snd/Rcv Data via sctp_recvmsg */
memset void *)&events, 0, sizeofevents) );
events.sctp_data_io_event = 1;
ret = setsockopt connSock, SOL_SCTP, SCTP_EVENTS,
const void *)&events, sizeofevents) );
/* Read and emit the status of the Socket optional step) */
in = sizeofstatus);
ret = getsockopt connSock, SOL_SCTP, SCTP_STATUS,
void *)&status, socklen_t *)&in );
printf"assoc id = %d\n", status.sstat_assoc_id );
printf"state = %d\n", status.sstat_state );
printf"instrms = %d\n", status.sstat_instrms );
printf"outstrms = %d\n", status.sstat_outstrms );
/* Expect two messages from the peer */
for i = 0 ; i < 2 ; i++) {
in = sctp_recvmsg connSock, void *)buffer, sizeofbuffer),
struct sockaddr *)NULL, 0, &sndrcvinfo, &flags );
if in > 0) {
buffer[in] = 0;
if sndrcvinfo.sinfo_stream == LOCALTIME_STREAM) {
printf"Local) %s\n", buffer);
} else if sndrcvinfo.sinfo_stream == GMT_STREAM) {
printf"GMT ) %s\n", buffer);
}
}
}
/* Close our socket and exit */
closeconnSock);
return 0;
}
SCTP 的未来发展
SCTP是一个相当新的协议,它在2000年10月份才成为一个RFC规范。从那以后,它开始进入所有的主流操作系统,包括 GNU/Linux、BSD和Solaris。在Microsoft Windows操作系统上也有第三方的商业包可以使用。
在获得高可用性的同时,应用程序也已经开始使用SCTP作为自己的主要传输机制。诸如FTP和HTTP之类的传统应用程序已经在SCTP的特性基础上进行了构建。其他一些协议也正在开始使用SCTP,例如会话初始化协议(Session Initiation Protocol,SIP)和通用通道信号系统 7(SS7)。在商业领域中,您可以在 Cisco 的 IOS 中找到 SCTP 的影子。
随着 SCTP 被吸纳到 2.6 版本的 Linux 内核中,现在我们可以构建并部署高可用性、高可靠性的网络应用程序。作为一种基于 IP 的协议,SCTP不但可以无缝地替换TCP和 UDP,而且扩展了很多新服务,例如多宿主、多流,并且对安全性也有了很大的提高。