流控制传输协议 (Stream Control Transmission Protocol, SCTP) 是一种可靠的传输协议,提供的服务与 TCP 提供的服务类似。此外,SCTP 还提供网络级别的容错功能。SCTP 支持关联两端具有多宿主。SCTP 套接字 API 支持模仿 TCP 的一对一的套接字风格。SCTP 套接字 API 还支持旨在用于发送信号的一对多套接字风格。一对多套接字风格可以减少在进程中使用的文件描述符数。必须链接 libsctp 库才能使用 SCTP 函数调用。
将在两个端点之间设置 SCTP 关联。这些端点使用的四次握手机制借助 cookie 来防止遭到某些类型的拒绝服务 (denial-of-service, DoS) 攻击。端点可由多个 IP 地址表示。
当 socket() 调用为 IPPROTO_SCTP 创建套接字时,它会调用特定于 SCTP 的套接字创建例程。针对 SCTP 套接字执行的套接字调用会自动调用相应的 SCTP 套接字例程。在一对一套接字中,每个套接字都对应一个 SCTP 关联。可以通过调用以下函数来创建一对一套接字:
socket(AF_INET[6], SOCK_STREAM, IPPROTO_STCP);在一对多风格套接字中,每个套接字都处理多个 SCTP 关联。每个关联都具有一个名为 sctp_assoc_t 的关联标识符。可以通过调用以下函数来创建一对多套接字:
socket(AF_INET[6], SOCK_SEQPACKET, IPPROTO_STCP);sctp_bindx() 函数管理 SCTP 套接字上的地址。如果 sock 参数为 IPv4 套接字,则传送给 sctp_bindx() 函数的地址必须为 IPv4 地址。如果 sock 参数为 IPv6 套接字,则传送给 sctp_bindx() 函数的地址可以为 IPv4 或 IPv6 地址。当传送给 sctp_bindx() 函数的地址为 INADDR_ANY 或 IN6ADDR_ANY 时,此套接字将绑定到所有可用地址。可以使用 bind(3SOCKET) 绑定 SCTP 端点。
*addrs 参数的值是指向包含一个或多个套接字地址的数组的指针。每个地址都包含在其相应的结构中。如果地址为 IPv4 地址,则它们可以包含在 sockaddr_in 结构或 sockaddr_in6 结构中。如果地址为 IPv6 地址,则它们可以包含在 sockaddr_in6 结构中。可以通过地址类型系列区分地址长度。调用方使用 addrcnt 参数指定数组中的地址数。
如果成功,则 sctp_bindx() 函数将返回 0。如果失败,则 sctp_bindx() 函数将返回 -1,并将 errno 的值设置为相应的错误代码。
如果没有为每个套接字地址提供同一端口,则 sctp_bindx() 函数将失败,并将 errno 的值设置为 EINVAL。
通过对以下零个或多个当前定义的标志执行按位 OR 运算,即可形成 flags 参数:
SCTP_BINDX_ADD_ADDR
SCTP_BINDX_REM_ADDR
SCTP_BINDX_ADD_ADDR 指示 SCTP 将给定地址添加到关联中。SCTP_BINDX_REM_ADDR 指示 SCTP 从关联中删除给定地址。这两个标志相互排斥。如果同时提供这两个标志,则 sctp_bindx() 将失败,并将 errno 的值设置为 EINVAL。
调用方无法删除关联中的所有地址。sctp_bindx() 函数拒绝此类尝试的表现为:函数失败并将 errno 的值设置为 EINVAL。应用程序可以在调用 bind() 函数之后使用 sctp_bindx(SCTP_BINDX_ADD_ADDR),将其他地址与端点关联。应用程序可以使用 sctp_bindx(SCTP_BINDX_REM_ADDR) 删除与侦听套接字关联的地址。使用 sctp_bindx(SCTP_BINDX_REM_ADDR) 删除地址之后,接受新关联将不会重新关联已删除的地址。如果端点支持动态地址,则可以使用 SCTP_BINDX_REM_ADDR 或 SCTP_BINDX_ADD_ADDR 向对等方发送消息来更改对等方的地址列表。在已连接的关联中添加和删除地址为可选功能。不支持此功能的实现将返回 EOPNOTSUPP。
如果地址族不是 AF_INET 或 AF_INET6,则 sctp_bindx() 函数将失败并返回 EAFNOSUPPORT。如果 sock 参数中传递给 sctp_bindx() 的文件描述符无效,则 sctp_bindx() 函数将失败并返回 EBADF。
sctp_opt_info() 函数将返回与 sock 参数中所述的套接字关联的 SCTP 级别选项。如果此套接字为一对多风格 SCTP 套接字,则 id 参数的值是某个特定关联。对于一对一风格 SCTP 套接字,将忽略 id 参数。opt 参数的值指定要获取的 SCTP 套接字选项。arg 参数的值是为调用程序而分配的特定于选项的结构缓冲区。*len 参数的值为选项长度。
opt 参数可以采用以下值:
返回用于初始化和绑定重新传输超时 (retransmission timeout, RTO) 可调参数的协议参数。这些协议参数使用以下结构:
struct sctp_rtoinfo { sctp_assoc_t srto_assoc_id; uint32_t srto_initial; uint32_t srto_max; uint32_t srto_min; };
调用程序提供此值,它指定所关注的关联。
此值为初始 RTO 值。
此值为最大 RTO 值。
此值为最小 RTO 值。
返回特定于关联的参数。这些参数使用以下结构:
struct sctp_assocparams { sctp_assoc_t sasoc_assoc_id; uint16_t sasoc_asocmaxrxt; uint16_t sasoc_number_peer_destinations; uint32_t sasoc_peer_rwnd; uint32_t sasoc_local_rwnd; uint32_t sasoc_cookie_life; };
调用程序提供此值,它指定所关注的关联。
此值指定关联的最大重新传输计数。
此值指定对等方具有的地址数。
此值指定对等方接收窗口的当前值。
此值指定对等方传输到的上一个已报告的接收窗口。
此值指定关联 cookie 的生命周期。可在发出 cookie 时使用此值。
所有使用时间值的参数均以毫秒为单位。
SCTP_DEFAULT_SEND_PARAM返回 sendto(3SOCKET) 函数调用在此关联中使用的缺省参数集。这些参数使用以下结构:
struct sctp_sndrcvinfo { uint16_t sinfo_stream; uint16_t sinfo_ssn; uint16_t sinfo_flags; uint32_t sinfo_ppid; uint32_t sinfo_context; uint32_t sinfo_timetolive; uint32_t sinfo_tsn; uint32_t sinfo_cumtsn; sctp_assoc_t sinfo_assoc_id; };
此值指定 sendmsg() 调用的缺省流。
此值始终为 0。
此值包含 sendmsg() 调用的缺省标志。此标志可以采用以下值:
MSG_UNORDERED
MSG_ADDR_OVER
MSG_ABORT
MSG_EOF
MSG_PR_SCTP
此值为 sendmsg() 调用的缺省有效负荷协议标识符。
sinfo_context此值为 sendmsg() 调用的缺省上下文。
sinfo_timetolive此值指定时间段(以毫秒为单位)。在此时间段过后,如果消息传输尚未开始,则消息将过期。值为 0 指示消息尚未过期。如果设置了 MSG_PR_SCTP 标志,当消息传输未在 sinfo_timetolive 所指定的时间段内成功完成时,消息将过期。
sinfo_tsn此值始终为 0。
sinfo_cumtsn此值始终为 0。
sinfo_assoc_id此值由调用程序填充。它指定所关注的关联。
SCTP_PEER_ADDR_PARAMS返回所指定对等地址的参数。这些参数使用以下结构:
struct sctp_paddrparams { sctp_assoc_t spp_assoc_id; struct sockaddr_storage spp_address; uint32_t spp_hbinterval; uint16_t spp_pathmaxrxt; };
调用程序提供此值,它指定所关注的关联。
此值指定所关注的对等地址。
此值指定心跳间隔(以毫秒为单位)。
此值指定在认为地址不可访问之前针对此地址尝试的最大重新传输数。
返回有关关联的当前状态信息。这些参数使用以下结构:
struct sctp_status { sctp_assoc_t sstat_assoc_id; int32_t sstat_state; uint32_t sstat_rwnd; uint16_t sstat_unackdata; uint16_t sstat_penddata; uint16_t sstat_instrms; uint16_t sstat_outstrms; uint32_t sstat_fragmentation_point; struct sctp_paddrinfo sstat_primary; };
调用程序提供此值,它指定所关注的关联。
此值为关联的当前状态。关联可以采用以下状态:
SCTP 端点没有任何与其关联的关联。一旦 socket() 函数调用打开一个端点或端点关闭,则端点便会处于此状态。
SCTP 端点在调用 bind() 之后绑定到一个或多个本地地址。
此端点在等待来自任何远程 SCTP 端点的关联请求。
此 SCTP 端点已发送 INIT 块并在等待 INIT-ACK 块。
此 SCTP 端点已将从其对等方的 INIT-ACK 块接收的 cookie 回显到对等方。
此 SCTP 端点可以与其对等方交换数据。
此 SCTP 端点已从其上层接收了 SHUTDOWN 元语。此端点不再从其上层接受数据。
处于 SCTP_SHUTDOWN_PENDING 状态的 SCTP 端点已向其对等方发送了 SHUTDOWN 块。仅在确认所有从此端点到其对等方的未完成数据之后,才发送 SHUTDOWN 块。当此端点的对等方发送 SHUTDOWN ACK 块时,此端点会发送 SHUTDOWN COMPLETE 块并认为关联已关闭。
SCTP 端点已从其对等方接收了 SHUTDOWN 块。此端点不再从其用户接受新数据。
处于 SCTP_SHUTDOWN_RECEIVED 状态的 SCTP 端点已向其对等方发送了 SHUTDOWN ACK 块。此端点仅在其对等方确认来自此端点的所有未完成数据之后发送 SHUTDOWN ACK 块。当此端点的对等方发送 SHUTDOWN COMPLETE 块时,将关闭关联。
此值为关联对等方的当前接收窗口。
sstat_unackdata此值为未确认的 DATA 块数。
sstat_penddata此值为等待接收的 DATA 块数。
sstat_instrms此值为传入的流数。
sstat_outstrms此值为外发的流数。
sstat_fragmentation_point如果消息、SCTP 数据包头和 IP 数据包头的组合大小超出 sstat_fragmentation_point 的值,则消息会分段。此值等于包目标地址的路径最大传输单元 (Path Maximum Transmission Unit, P-MTU)。
sstat_primary此值包含有关主要对等地址的信息。此信息使用以下结构:
struct sctp_paddrinfo { sctp_assoc_t spinfo_assoc_id; struct sockaddr_storage spinfo_address; int32_t spinfo_state; uint32_t spinfo_cwnd; uint32_t spinfo_srtt; uint32_t spinfo_rto; uint32_t spinfo_mtu; };
调用程序提供此值,它指定所关注的关联。
此值为主要对等地址。
此值可以采用 SCTP_ACTIVE 或 SCTP_INACTIVE 两个值中的任意一个。
此值为对等地址的拥塞窗口。
此值为对等地址的当前平滑往返时间计算结果,以毫秒为单位。
此值为对等地址的当前重新传输超时值,以毫秒为单位。
此值为对等地址的 P-MTU。
如果成功,则 sctp_opt_info() 函数将返回 0。如果失败,则 sctp_opt_info() 函数将返回 -1,并将 errno 的值设置为相应的错误代码。如果 sock 参数中传递给 sctp_opt_info() 的文件描述符无效,则 sctp_opt_info() 函数将失败并返回 EBADF。如果 sock 参数中传递给 sctp_opt_info() 函数的文件描述符没有描述套接字,则 sctp_opt_info() 函数将失败并返回 ENOTSOCK。如果关联 ID 对于一对多风格 SCTP 套接字而言无效,则 sctp_opt_info() 函数将失败,并将 errno 的值设置为 EINVAL。如果输入缓冲区长度对于指定的选项而言过短,则 sctp_opt_info() 函数将失败,并将 errno 的值设置为 EINVAL。如果对等地址的地址族不是 AF_INET 或 AF_INET6,则 sctp_opt_info() 函数将失败,并将 errno 的值设置为 EAFNOSUPPORT。
使用 sctp_recvmsg() 函数,可以从 s 参数所指定的 SCTP 端点接收消息。调用程序可以指定以下属性:
此参数为消息缓冲区的地址。
此参数为消息缓冲区的长度。
此参数为指向包含发送主机地址的地址的指针。
此参数为与 from 参数中的地址关联的缓冲区的大小。
此参数仅在调用程序启用 sctp_data_io_events 时处于活动状态。要启用 sctp_data_io_events,请使用套接字选项 SCTP_EVENTS 调用 setsockopt() 函数。如果启用了 sctp_data_io_events,则应用程序将接收每个传入消息的 sctp_sndrcvinfo 结构的内容。此参数为指向 sctp_sndrcvinfo 结构的指针。此结构将在接收消息时进行填充。
此参数包含所有存在的消息标志。
sctp_recvmsg() 函数将返回其接收的字节数。sctp_recvmsg() 函数将在出现错误时返回 -1。
如果在 s 参数中传递的文件描述符无效,则 sctp_recvmsg() 函数将失败,并将 errno 的值设置为 EBADF。如果在 s 参数中传递的文件描述符没有描述套接字,则 sctp_recvmsg() 函数将失败,并将 errno 的值设置为 ENOTSOCK。如果 msg_flags 参数包括值 MSG_OOB,则 sctp_recvmsg() 函数将失败,并将 errno 的值设置为 EOPNOTSUPP。如果没有建立关联,则 sctp_recvmsg() 函数将失败,并将 errno 的值设置为 ENOTCONN。
sctp_sendmsg() 函数在发送来自 SCTP 端点的消息时启用高级 SCTP 功能。
此值指定发送消息的 SCTP 端点。
此值包含 sctp_sendmsg() 函数所发送的消息。
此值为消息的长度,以字节为单位。
此值为消息的目标地址。
此值为目标地址的长度。
此值为应用程序指定的有效负荷协议标识符。
此值为此消息的目标流。
此值为消息未能成功发送到对等方的情况下消息过期之前可以等待的时间段,以毫秒为单位。
如果在发送消息时出现错误,则返回此值。
此值在将逻辑运算 OR 以按位形式应用于以下零个或多个标志位时形成:
设置此标志之后,sctp_sendmsg() 函数将无序传送消息。
设置此标志之后,sctp_sendmsg() 函数将使用 to 参数中的地址,而不使用关联的主要目标地址。此标志仅用于一对多风格 SCTP 套接字。
设置此标志之后,指定的关联将异常中止,同时向其对等方发送 ABORT 信号。此标志仅用于一对多风格 SCTP 套接字。
设置此标志之后,指定的关联将进入正常关机状态。此标志仅用于一对多风格 SCTP 套接字。
设置此标志之后,如果消息传输未在 timetolive 参数所指定的时间段内成功完成,则消息将过期。
sctp_sendmsg() 函数将返回其发送的字节数。sctp_sendmsg() 函数将在出现错误时返回 -1。
如果在 s 参数中传递的文件描述符无效,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EBADF。如果在 s 参数中传递的文件描述符没有描述套接字,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 ENOTSOCK。如果 flags 参数包括值 MSG_OOB,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EOPNOTSUPP。如果一对一风格套接字的 flags 参数包括 MSG_ABORT 或 MSG_EOF 值,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EOPNOTSUPP。如果没有建立关联,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 ENOTCONN。如果套接字关闭,禁止进一步写入,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EPIPE。如果套接字为非阻止套接字并且传输队列已满,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EAGAIN。
如果控制消息长度不正确,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EINVAL。如果指定的目标地址不属于关联,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EINVAL。如果 stream_no 的值不在关联所支持的外发流数之内,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EINVAL。如果所指定的目标地址的地址族不是 AF_INET 或 AF_INET6,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EINVAL。
sctp_send() 函数可供一对一及一对多风格套接字使用。sctp_send() 函数在发送来自 SCTP 端点的消息时启用高级 SCTP 功能。
此值指定 socket() 函数所创建的套接字。
此值包含 sctp_send() 函数所发送的消息。
此值为消息的长度,以字节为单位。
此值包含用于发送消息的参数。对于一对多风格套接字,此值可以包含消息所发送到的关联 ID。
此值与 sendmsg() 函数中的标志参数相同。
sctp_send() 函数将返回其发送的字节数。sctp_send() 函数将在出现错误时返回 -1。
如果在 s 参数中传递的文件描述符无效,则 sctp_send() 函数将失败,并将 errno 的值设置为 EBADF。如果在 s 参数中传递的文件描述符没有描述套接字,则 sctp_send() 函数将失败,并将 errno 的值设置为 ENOTSOCK。如果 sinfo 参数的 sinfo_flags 字段包括值 MSG_OOB,则 sctp_send() 函数将失败,并将 errno 的值设置为 EOPNOTSUPP。如果一对一风格套接字中 sinfo 参数的 sinfo_flags 字段包括 MSG_ABORT 或 MSG_EOF 值,则 sctp_send() 函数将失败,并将 errno 值的设置为 EOPNOTSUPP。如果没有建立关联,则 sctp_send() 函数将失败,并将 errno 的值设置为 ENOTCONN。如果套接字关闭,禁止进一步写入,则 sctp_send() 函数将失败,并将 errno 的值设置为 EPIPE。如果套接字为非阻止套接字并且传输队列已满,则 sctp_send() 函数将失败,并将 errno 的值设置为 EAGAIN。
如果控制消息长度不正确,则 sctp_send() 函数将失败,并将 errno 的值设置为 EINVAL。如果指定的目标地址不属于关联,则 sctp_send() 函数将失败,并将 errno 的值设置为 EINVAL。如果 stream_no 的值不在关联所支持的外发流数之内,则 sctp_send() 函数将失败,并将 errno 的值设置为 EINVAL。如果所指定的目标地址的地址族不是 AF_INET 或 AF_INET6,则 sctp_send() 函数将失败,并将 errno 的值设置为 EINVAL。
应用程序可以将一对多风格套接字上已建立的关联分叉为独立的套接字和文件描述符。对于具有多个偶发消息发送者或接收者的应用程序,如果这些发送者或接收者需要存在于原始一对多风格套接字之下,则独立的套接字和文件描述符非常有用。应用程序会将传输大量数据通信流量的关联分叉为独立的套接字描述符。应用程序使用 sctp_peeloff() 调用将关联分叉为独立的套接字。新套接字为一对一风格套接字。sctp_peeloff() 函数的语法如下:
int sctp_peeloff(int sock, sctp_assoc_t id);从 socket() 系统调用返回的原始一对多风格套接字描述符
要分叉为独立的文件描述符的关联的标识符
如果在 sock 参数中传递的套接字描述符不是一对多风格 SCTP 套接字,则 sctp_peeloff() 函数将失败并返回 EOPTNOTSUPP。如果 id 的值为 0 或者 id 的值大于在 sock 参数中传递的套接字描述符的最大关联数,则 sctp_peeloff() 函数将失败并返回 EINVAL。如果 sctp_peeloff() 函数无法创建新的用户文件描述符或文件结构,则此函数将失败并返回 EMFILE。
sctp_getpaddrs() 函数将返回关联中的所有对等地址。
int sctp_getpaddrs(int sock, sctp_assoc_t id, void **addrs);当 sctp_getpaddrs() 函数成功返回时,**addrs 参数的值将指向每个地址相应类型的动态分配的压缩 sockaddr 结构数组。调用线程使用 sctp_freepaddrs() 函数释放内存。**addrs 参数的值不能为 NULL。如果 sock 中给定的套接字描述符用于 IPv4 套接字,则 sctp_getpaddrs() 函数将返回 IPv4 地址。如果 sock 中给定的套接字描述符用于 IPv6 套接字,则 sctp_getpaddrs() 函数将同时返回 IPv4 和 IPv6 地址。对于一对多风格套接字,id 参数指定要查询的关联。对于一对一风格套接字,sctp_getpaddrs() 函数将忽略 id 参数。当 sctp_getpaddrs() 函数成功返回时,它将返回关联中的对等地址数。如果此套接字上没有关联,则 sctp_getpaddrs() 函数将返回 0,并且不定义 **addrs 参数的值。如果出现错误,则 sctp_getpaddrs() 函数将返回 -1,并且不定义 **addrs 参数的值。
如果 sock 参数中传递给 sctp_getpaddrs() 函数的文件描述符无效,则 sctp_getpaddrs() 函数将失败并返回 EBADF。如果 sock 参数中传递给 sctp_getpaddrs() 函数的文件描述符没有描述套接字,则 sctp_getpaddrs() 函数将失败并返回 ENOTSOCK。如果 sock 参数中传递给 sctp_getpaddrs() 函数的文件描述符描述了未连接的套接字,则 sctp_getpaddrs() 函数将失败并返回 ENOTCONN。
sctp_freepaddrs() 函数将释放所有由之前的 sctp_getpaddrs() 调用所分配的资源。sctp_freepaddrs() 函数的语法如下:
void sctp_freepaddrs(void *addrs);*addrs 参数为包含 sctp_getpaddrs() 函数所返回的对等地址的数组。
sctp_getladdrs() 函数将返回套接字上的所有本地绑定的地址。sctp_getladdrs() 函数的语法如下:
int sctp_getladdrs(int sock, sctp_assoc_t id, void **addrs);当 sctp_getladdrs() 函数成功返回时,addrs 的值将指向动态分配的压缩 sockaddr 结构数组。sockaddr 结构为每个本地地址的相应类型。调用应用程序使用 sctp_freeladdrs() 函数释放内存。addrs 参数的值不能为 NULL。
如果 sd 参数引用的套接字为 IPv4 套接字,则 sctp_getladdrs() 函数将返回 IPv4 地址。如果 sd 参数引用的套接字为 IPv6 套接字,则 sctp_getladdrs() 函数将同时返回相应的 IPv4 或 IPv6 地址。
针对一对多风格套接字调用 sctp_getladdrs() 函数时,id 参数的值指定要查询的关联。sctp_getladdrs() 函数在一对一套接字上运行时将忽略 id 参数。
当 id 参数的值为 0 时,无论为何种特定关联,sctp_getladdrs() 函数都将返回本地绑定的地址。当 sctp_getladdrs() 函数成功返回时,它将报告绑定到套接字的本地地址数。如果未绑定套接字,则 sctp_getladdrs() 函数将返回 0,并且不定义 *addrs 的值。如果出现错误,则 sctp_getladdrs() 函数将返回 -1,并且不定义 *addrs 的值。
sctp_freeladdrs() 函数将释放所有由之前的 sctp_getladdrs() 调用所分配的资源。sctp_freeladdrs() 函数的语法如下:
void sctp_freeladdrs(void *addrs);*addrs 参数为包含 sctp_getladdrs() 函数所返回的对等地址的数组。
本节详细介绍 SCTP 套接字的两种用法。
/* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* To enable socket features used for SCTP socket. */ #define _XPG4_2 #define __EXTENSIONS__ #include#include #include #include #include #include #include #include #include #include #define BUFLEN 1024 static void usage(char *a0) { fprintf(stderr, "Usage: %s \n", a0); } /* * Read from the network. */ static void readit(void *vfdp) { int fd; ssize_t n; char buf[BUFLEN]; struct msghdr msg[1]; struct iovec iov[1]; struct cmsghdr *cmsg; struct sctp_sndrcvinfo *sri; char cbuf[sizeof (*cmsg) + sizeof (*sri)]; union sctp_notification *snp; pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); fd = *(int *)vfdp; /* Initialize the message header for receiving */ memset(msg, 0, sizeof (*msg)); msg->msg_control = cbuf; msg->msg_controllen = sizeof (*cmsg) + sizeof (*sri); msg->msg_flags = 0; cmsg = (struct cmsghdr *)cbuf; sri = (struct sctp_sndrcvinfo *)(cmsg + 1); iov->iov_base = buf; iov->iov_len = BUFLEN; msg->msg_iov = iov; msg->msg_iovlen = 1; while ((n = recvmsg(fd, msg, 0)) > 0) { /* Intercept notifications here */ if (msg->msg_flags & MSG_NOTIFICATION) { snp = (union sctp_notification *)buf; printf("[ Receive notification type %u ]\n", snp->sn_type); continue; } msg->msg_control = cbuf; msg->msg_controllen = sizeof (*cmsg) + sizeof (*sri); printf("[ Receive echo (%u bytes): stream = %hu, ssn = %hu, " "flags = %hx, ppid = %u ]\n", n, sri->sinfo_stream, sri->sinfo_ssn, sri->sinfo_flags, sri->sinfo_ppid); } if (n < 0) { perror("recv"); exit(1); } close(fd); exit(0); } #define MAX_STREAM 64 /* * */ static void echo(struct sockaddr_in *addr) { int fd; uchar_t buf[BUFLEN]; ssize_t n; int perr; pthread_t tid; struct cmsghdr *cmsg; struct sctp_sndrcvinfo *sri; char cbuf[sizeof (*cmsg) + sizeof (*sri)]; struct msghdr msg[1]; struct iovec iov[1]; int ret; struct sctp_initmsg initmsg; struct sctp_event_subscribe events; /* Create a one-one SCTP socket */ if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)) == -1) { perror("socket"); exit(1); } /* * We are interested in association change events and we want * to get sctp_sndrcvinfo in each receive. */ events.sctp_association_event = 1; events.sctp_data_io_event = 1; ret = setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof (events)); if (ret < 0) { perror("setsockopt SCTP_EVENTS"); exit(1); } /* * Set the SCTP stream parameters to tell the other side when * setting up the association. */ memset(&initmsg, 0, sizeof(struct sctp_initmsg)); initmsg.sinit_num_ostreams = MAX_STREAM; initmsg.sinit_max_instreams = MAX_STREAM; initmsg.sinit_max_attempts = MAX_STREAM; ret = setsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(struct sctp_initmsg)); if (ret < 0) { perror("setsockopt SCTP_INITMSG"); exit(1); } if (connect(fd, (struct sockaddr *)addr, sizeof (*addr)) == -1) { perror("connect"); exit(1); } /* Initialize the message header structure for sending. */ memset(msg, 0, sizeof (*msg)); iov->iov_base = buf; msg->msg_iov = iov; msg->msg_iovlen = 1; msg->msg_control = cbuf; msg->msg_controllen = sizeof (*cmsg) + sizeof (*sri); msg->msg_flags |= MSG_XPG4_2; memset(cbuf, 0, sizeof (*cmsg) + sizeof (*sri)); cmsg = (struct cmsghdr *)cbuf; sri = (struct sctp_sndrcvinfo *)(cmsg + 1); cmsg->cmsg_len = sizeof (*cmsg) + sizeof (*sri); cmsg->cmsg_level = IPPROTO_SCTP; cmsg->cmsg_type = SCTP_SNDRCV; sri->sinfo_ppid = 1; /* Start sending to stream 0. */ sri->sinfo_stream = 0; /* Create a thread to receive network traffic. */ perr = pthread_create(&tid, NULL, (void *(*)(void *))readit, &fd); if (perr != 0) { fprintf(stderr, "pthread_create: %d\n", perr); exit(1); } /* Read from stdin and then send to the echo server. */ while ((n = read(fileno(stdin), buf, BUFLEN)) > 0) { iov->iov_len = n; if (sendmsg(fd, msg, 0) < 0) { perror("sendmsg"); exit(1); } /* Send the next message to a different stream. */ sri->sinfo_stream = (sri->sinfo_stream + 1) % MAX_STREAM; } pthread_cancel(tid); close(fd); } int main(int argc, char **argv) { struct sockaddr_in addr[1]; struct hostent *hp; int error; if (argc < 2) { usage(*argv); exit(1); } /* Find the host to connect to. */ hp = getipnodebyname(argv[1], AF_INET, AI_DEFAULT, &error); if (hp == NULL) { fprintf(stderr, "host not found\n"); exit(1); } addr->sin_family = AF_INET; addr->sin_addr.s_addr = *(ipaddr_t *)hp->h_addr_list[0]; addr->sin_port = htons(5000); echo(addr); return (0); }
/* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* To enable socket features used for SCTP socket. */ #define _XPG4_2 #define __EXTENSIONS__ #include#include #include #include #include #include #include #include #include #define BUFLEN 1024 /* * Given an event notification, print out what it is. */ static void handle_event(void *buf) { struct sctp_assoc_change *sac; struct sctp_send_failed *ssf; struct sctp_paddr_change *spc; struct sctp_remote_error *sre; union sctp_notification *snp; char addrbuf[INET6_ADDRSTRLEN]; const char *ap; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; snp = buf; switch (snp->sn_header.sn_type) { case SCTP_ASSOC_CHANGE: sac = &snp->sn_assoc_change; printf("^^^ assoc_change: state=%hu, error=%hu, instr=%hu " "outstr=%hu\n", sac->sac_state, sac->sac_error, sac->sac_inbound_streams, sac->sac_outbound_streams); break; case SCTP_SEND_FAILED: ssf = &snp->sn_send_failed; printf("^^^ sendfailed: len=%hu err=%d\n", ssf->ssf_length, ssf->ssf_error); break; case SCTP_PEER_ADDR_CHANGE: spc = &snp->sn_paddr_change; if (spc->spc_aaddr.ss_family == AF_INET) { sin = (struct sockaddr_in *)&spc->spc_aaddr; ap = inet_ntop(AF_INET, &sin->sin_addr, addrbuf, INET6_ADDRSTRLEN); } else { sin6 = (struct sockaddr_in6 *)&spc->spc_aaddr; ap = inet_ntop(AF_INET6, &sin6->sin6_addr, addrbuf, INET6_ADDRSTRLEN); } printf("^^^ intf_change: %s state=%d, error=%d\n", ap, spc->spc_state, spc->spc_error); break; case SCTP_REMOTE_ERROR: sre = &snp->sn_remote_error; printf("^^^ remote_error: err=%hu len=%hu\n", ntohs(sre->sre_error), ntohs(sre->sre_length)); break; case SCTP_SHUTDOWN_EVENT: printf("^^^ shutdown event\n"); break; default: printf("unknown type: %hu\n", snp->sn_header.sn_type); break; } } /* * Receive a message from the network. */ static void * getmsg(int fd, struct msghdr *msg, void *buf, size_t *buflen, ssize_t *nrp, size_t cmsglen) { ssize_t nr = 0; struct iovec iov[1]; *nrp = 0; iov->iov_base = buf; msg->msg_iov = iov; msg->msg_iovlen = 1; /* Loop until a whole message is received. */ for (;;) { msg->msg_flags = MSG_XPG4_2; msg->msg_iov->iov_len = *buflen; msg->msg_controllen = cmsglen; nr += recvmsg(fd, msg, 0); if (nr <= 0) { /* EOF or error */ *nrp = nr; return (NULL); } /* Whole message is received, return it. */ if (msg->msg_flags & MSG_EOR) { *nrp = nr; return (buf); } /* Maybe we need a bigger buffer, do realloc(). */ if (*buflen == nr) { buf = realloc(buf, *buflen * 2); if (buf == 0) { fprintf(stderr, "out of memory\n"); exit(1); } *buflen *= 2; } /* Set the next read offset */ iov->iov_base = (char *)buf + nr; iov->iov_len = *buflen - nr; } } /* * The echo server. */ static void echo(int fd) { ssize_t nr; struct sctp_sndrcvinfo *sri; struct msghdr msg[1]; struct cmsghdr *cmsg; char cbuf[sizeof (*cmsg) + sizeof (*sri)]; char *buf; size_t buflen; struct iovec iov[1]; size_t cmsglen = sizeof (*cmsg) + sizeof (*sri); /* Allocate the initial data buffer */ buflen = BUFLEN; if ((buf = malloc(BUFLEN)) == NULL) { fprintf(stderr, "out of memory\n"); exit(1); } /* Set up the msghdr structure for receiving */ memset(msg, 0, sizeof (*msg)); msg->msg_control = cbuf; msg->msg_controllen = cmsglen; msg->msg_flags = 0; cmsg = (struct cmsghdr *)cbuf; sri = (struct sctp_sndrcvinfo *)(cmsg + 1); /* Wait for something to echo */ while ((buf = getmsg(fd, msg, buf, &buflen, &nr, cmsglen)) != NULL) { /* Intercept notifications here */ if (msg->msg_flags & MSG_NOTIFICATION) { handle_event(buf); continue; } iov->iov_base = buf; msg->msg_iov = iov; msg->msg_iovlen = 1; iov->iov_len = nr; msg->msg_control = cbuf; msg->msg_controllen = sizeof (*cmsg) + sizeof (*sri); printf("got %u bytes on stream %hu:\n", nr, sri->sinfo_stream); write(0, buf, nr); /* Echo it back */ msg->msg_flags = MSG_XPG4_2; if (sendmsg(fd, msg, 0) < 0) { perror("sendmsg"); exit(1); } } if (nr < 0) { perror("recvmsg"); } close(fd); } int main(void) { int lfd; int cfd; int onoff = 1; struct sockaddr_in sin[1]; struct sctp_event_subscribe events; struct sctp_initmsg initmsg; if ((lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)) == -1) { perror("socket"); exit(1); } sin->sin_family = AF_INET; sin->sin_port = htons(5000); sin->sin_addr.s_addr = INADDR_ANY; if (bind(lfd, (struct sockaddr *)sin, sizeof (*sin)) == -1) { perror("bind"); exit(1); } if (listen(lfd, 1) == -1) { perror("listen"); exit(1); } (void) memset(&initmsg, 0, sizeof(struct sctp_initmsg)); initmsg.sinit_num_ostreams = 64; initmsg.sinit_max_instreams = 64; initmsg.sinit_max_attempts = 64; if (setsockopt(lfd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(struct sctp_initmsg)) < 0) { perror("SCTP_INITMSG"); exit (1); } /* Events to be notified for */ (void) memset(&events, 0, sizeof (events)); events.sctp_data_io_event = 1; events.sctp_association_event = 1; events.sctp_send_failure_event = 1; events.sctp_address_event = 1; events.sctp_peer_error_event = 1; events.sctp_shutdown_event = 1; /* Wait for new associations */ for (;;) { if ((cfd = accept(lfd, NULL, 0)) == -1) { perror("accept"); exit(1); } /* Enable ancillary data */ if (setsockopt(cfd, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof (events)) < 0) { perror("setsockopt SCTP_EVENTS"); exit(1); } /* Echo back any and all data */ echo(cfd); } }