1. 简介
sctp_recvmsg
函数除了可以接收普通的消息外,还可以接收一些特殊的消息;这里我们要说的SCTP notification就是一种特殊的消息,SCTP notification可以提供给我们一些SCTP通信过程中的一些额外信息,比如说什么时候建立SCTP、什么时候SCTP关闭了,等等。
2. 使能SCTP notification
可以通过设置socket属性:SCTP_EVENT来使能SCTP notification的接收,参看如下代码
struct sctp_event_subscribe evnts;
bzero(&evnts, sizeof(evnts));
evnts.sctp_association_event = 1;
setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts, sizeof(evnts));
sctp_event_subscribe
是一个结构体,内部有多个flag标志,
struct sctp_event_subscribe {
__u8 sctp_data_io_event;
__u8 sctp_association_event;
__u8 sctp_address_event;
__u8 sctp_send_failure_event;
__u8 sctp_peer_error_event;
__u8 sctp_shutdown_event;
__u8 sctp_partial_delivery_event;
__u8 sctp_adaptation_layer_event;
__u8 sctp_authentication_event;
__u8 sctp_sender_dry_event;
__u8 sctp_stream_reset_event;
__u8 sctp_assoc_reset_event;
__u8 sctp_stream_change_event;
};
比如说把sctp_event_subscribe::sctp_association_event
设置成1,就可以开启SCTP notification的SCTP_ASSOC_CHANGE事件了
3. SCTP notification的消息结构
SCTP的notification是一个Union类型,定义如下,
union sctp_notification {
struct {
__u16 sn_type; /* Notification type. */
__u16 sn_flags;
__u32 sn_length;
} sn_header;
struct sctp_assoc_change sn_assoc_change;
struct sctp_paddr_change sn_paddr_change;
struct sctp_remote_error sn_remote_error;
struct sctp_send_failed sn_send_failed;
struct sctp_shutdown_event sn_shutdown_event;
struct sctp_adaptation_event sn_adaptation_event;
struct sctp_pdapi_event sn_pdapi_event;
struct sctp_authkey_event sn_authkey_event;
struct sctp_sender_dry_event sn_sender_dry_event;
struct sctp_stream_reset_event sn_strreset_event;
struct sctp_assoc_reset_event sn_assocreset_event;
struct sctp_stream_change_event sn_strchange_event;
};
这个联合体的内部额外定义了一个结构体sn_header
,这个是为了收到消息的时候可以识别当前的notification是什么类型的,这个结构体每个字段的意义如下
-
sn_type
:标识这个通知事件的类型; -
sn_flags
:每个事件特有的flags; -
sn_length
:当前这个sctp_notification结构体的长度,这个长度包含这个sn_header;
所有事件都以宏的方式定义如下,
enum sctp_sn_type {
SCTP_SN_TYPE_BASE = (1<<15),
SCTP_ASSOC_CHANGE,
#define SCTP_ASSOC_CHANGE SCTP_ASSOC_CHANGE
SCTP_PEER_ADDR_CHANGE,
#define SCTP_PEER_ADDR_CHANGE SCTP_PEER_ADDR_CHANGE
SCTP_SEND_FAILED,
#define SCTP_SEND_FAILED SCTP_SEND_FAILED
SCTP_REMOTE_ERROR,
#define SCTP_REMOTE_ERROR SCTP_REMOTE_ERROR
SCTP_SHUTDOWN_EVENT,
#define SCTP_SHUTDOWN_EVENT SCTP_SHUTDOWN_EVENT
SCTP_PARTIAL_DELIVERY_EVENT,
#define SCTP_PARTIAL_DELIVERY_EVENT SCTP_PARTIAL_DELIVERY_EVENT
SCTP_ADAPTATION_INDICATION,
#define SCTP_ADAPTATION_INDICATION SCTP_ADAPTATION_INDICATION
SCTP_AUTHENTICATION_EVENT,
#define SCTP_AUTHENTICATION_INDICATION SCTP_AUTHENTICATION_EVENT
SCTP_SENDER_DRY_EVENT,
#define SCTP_SENDER_DRY_EVENT SCTP_SENDER_DRY_EVENT
SCTP_STREAM_RESET_EVENT,
#define SCTP_STREAM_RESET_EVENT SCTP_STREAM_RESET_EVENT
SCTP_ASSOC_RESET_EVENT,
#define SCTP_ASSOC_RESET_EVENT SCTP_ASSOC_RESET_EVENT
SCTP_STREAM_CHANGE_EVENT,
#define SCTP_STREAM_CHANGE_EVENT SCTP_STREAM_CHANGE_EVENT
};
这里每个事件的结构体的头部都要保持和sn_header有相同的字段填充,这样收到一个notification的时候才有可能从union里面直接取出这个sn_header,就如下面这个sctp_assoc_change的定义一样
struct sctp_assoc_change {
__u16 sac_type;
__u16 sac_flags;
__u32 sac_length;
__u16 sac_state;
__u16 sac_error;
__u16 sac_outbound_streams;
__u16 sac_inbound_streams;
sctp_assoc_t sac_assoc_id;
__u8 sac_info[0];
};
4. SCTP notification可能的各种事件
4.1 SCTP_ASSOC_CHANGE
结构体的定义参看前面,主要一些字段的定义如下,
-
sac_type
设置为SCTP_ASSOC_CHANGE -
sac_state
可以设置成如下值
- SCTP_COMM_UP:association建立好了;
- SCTP_COMM_LOST:association失败了;
- SCTP_RESTART:检测到association重启了,这种场景可能出现在,比如说客户端重启了,然后基于原先的端口号来建立association;
- SCTP_SHUTDOWN_COMP:正常的association关闭
- SCTP_CANT_STR_ASSOC:建立association失败
-
sac_assoc_id
携带当前这个association的ID
4.2 SCTP_PEER_ADDR_CHANGE
通过设置sctp_event_subscribe::sctp_address_event
标志可以开启这个事件的notification。
当一个多宿主(multi-home)的对端地址发生变更时,触发这个事件通知
结构体定义如下:
struct sctp_paddr_change {
__u16 spc_type; // 定义为SCTP_PEER_ADDR_CHANGE
__u16 spc_flags; // 不使用
__u32 spc_length;
struct sockaddr_storage spc_aaddr; //受影响的远端IP地址
int spc_state;
int spc_error;
sctp_assoc_t spc_assoc_id;
} __attribute__((packed, aligned(4)));
spc_state状态有以下这些类型
- **SCTP_ADDR_AVAILABLE **:这个远端地址可以连接上了
- **SCTP_ADDR_UNREACHABLE **:这个远端地址变成连不上了;
- **SCTP_ADDR_REMOVED **:这个远端地址从association里面移除了;
- **SCTP_ADDR_ADDED **:这个远端地址添加到association里面了
- **SCTP_ADDR_MADE_PRIM **:这个远端地址变成这个association的主地址了
- **SCTP_ADDR_CONFIRMED **:这个远端地址变成这个association的主地址了
4.3 SCTP_REMOTE_ERROR
通过设置sctp_event_subscribe::sctp_peer_error_event
标志可以开启这个事件的notification。
当从对端收到错误的数据块的时候出发这个事件。
结构体定义如下:
struct sctp_remote_error {
__u16 sre_type; // 定义为SCTP_REMOTE_ERROR
__u16 sre_flags; //不使用
__u32 sre_length;
__be16 sre_error;
sctp_assoc_t sre_assoc_id;
__u8 sre_data[0]; //整个错误的包(trunk)
};
4.4 SCTP_SHUTDOWN_EVENT
通过设置sctp_event_subscribe::sctp_shutdown_event
标志可以开启这个事件的notification。
当对端发起shutdown的时候接收通知
结构体定义如下,
struct sctp_shutdown_event {
__u16 sse_type; //定义为SCTP_SHUTDOWN_EVENT
__u16 sse_flags;
__u32 sse_length;
sctp_assoc_t sse_assoc_id;
};
4.5 SCTP_ADAPTATION_INDICATION
通过设置sctp_event_subscribe::sctp_adaptation_layer_event
标志可以开启这个事件的notification。
当对端发送了适配层指示的时候触发。
结构体定义如下,
struct sctp_adaptation_event {
__u16 sai_type; // 定义为SCTP_ADAPTATION_INDICATION
__u16 sai_flags;
__u32 sai_length;
__u32 sai_adaptation_ind;
sctp_assoc_t sai_assoc_id;
};
4.6 SCTP_PARTIAL_DELIVERY_EVENT
通过设置sctp_event_subscribe::sctp_partial_delivery_event
标志可以开启这个事件的notification。
在一条消息的部分发送过程中出现了异常错误,比如说association down了,触发这个事件;
结构体定义如下:
struct sctp_pdapi_event {
__u16 pdapi_type; // 定义为SCTP_ADAPTATION_INDICATION
__u16 pdapi_flags;
__u32 pdapi_length;
__u32 pdapi_indication; //发送的indication,只有一个值,SCTP_PARTIAL_DELIVERY_ABORTED
sctp_assoc_t pdapi_assoc_id;
};
4.7 SCTP_AUTHENTICATION_EVENT
通过设置sctp_event_subscribe::sctp_authentication_event
标志可以开启这个事件的notification。
当SCTP鉴权失败的时候发生;结构体定义如下,
struct sctp_authkey_event {
__u16 auth_type; // 定义为SCTP_AUTHENTICATION_EVENT
__u16 auth_flags;
__u32 auth_length;
__u16 auth_keynumber;
__u16 auth_altkeynumber;
__u32 auth_indication;
sctp_assoc_t auth_assoc_id;
};
auth_indication
字段可以定义如下
- SCTP_AUTH_NEW_KEY,指示一个新激活的key;
- SCTP_AUTH_NO_AUTH ,对端不支持鉴权;
- SCTP_AUTH_FREE_KEY ,当前这个key不再使用了
4.8 SCTP_SENDER_DRY_EVENT
SCTP协议栈没有更多的用户数据可以发送了,会触发这个事件
4.9 SCTP_NOTIFICATIONS_STOPPED_EVENT
SCTP协议栈资源不够,需要停止notification的时候,触发事件
5. 示例程序
5.1 接收notification
前面已经描述过怎么使能notification了,下面这段代码演示怎么接收SCTP notification
rd_sz = sctp_recvmsg(sock_fd, readbuf, sizeof(readbuf),
&cliaddr, &len, &sri, &msg_flags);
if(msg_flags&MSG_NOTIFICATION)
{
// 接收的是一个notification
}
举个例子来说,当使用SCTP往server端发送第一条消息的时候,server端第一个收到的是一个SCTP_ASSOC_CHANGE的事件,状态是SCTP_COMM_UP。
5.2 处理收到的notification
本节以一个打印sctp notification的示例程序来演示怎么进行notification的分类处理,
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
void PrintAssocChange(union sctp_notification *notification)
{
struct sctp_assoc_change *sctpAssociationChange;
char* str;
sctpAssociationChange = ¬ification->sn_assoc_change;
switch(sctpAssociationChange->sac_state)
{
case SCTP_COMM_UP:
str = "COMMUNICATION UP";
break;
case SCTP_COMM_LOST:
str = "COMMUNICATION LOST";
break;
case SCTP_RESTART:
str = "SCTP RESTART";
break;
case SCTP_SHUTDOWN_COMP:
str = "SHUTDOWN COMPLETE";
break;
case SCTP_CANT_STR_ASSOC:
str = "CAN'T START ASSOCIATION";
break;
default:
str = "UNKOWN";
break;
}
printf("[Notification]: SCTP_ASSOC_CHANGE: '%s' received with assoc id = 0x%x\n", str, sctpAssociationChange->sac_assoc_id);
}
void PrintPeerAddrChange(union sctp_notification *notification)
{
char* str;
struct sctp_paddr_change *sctpPeerAddChange;
sctpPeerAddChange = ¬ification->sn_paddr_change;
switch(sctpPeerAddChange->spc_state) {
case SCTP_ADDR_AVAILABLE:
str = "ADDRESS AVAILABLE";
break;
case SCTP_ADDR_UNREACHABLE:
str = "ADDRESS UNREACHABLE";
break;
case SCTP_ADDR_REMOVED:
str = "ADDRESS REMOVED";
break;
case SCTP_ADDR_ADDED:
str = "ADDRESS ADDED";
break;
case SCTP_ADDR_MADE_PRIM:
str = "ADDRESS PRIMARY";
break;
default:
str = "UNKNOWN";
break;
}
printf("[Notification]: SCTP_PEER_ADDR_CHANGE: '%s' received with assoc id = 0x%x/n", str, sctpPeerAddChange->spc_assoc_id);
}
void PrintRemoteError(union sctp_notification *notification)
{
struct sctp_remote_error *sctpRemoteErr;
sctpRemoteErr = ¬ification->sn_remote_error;
printf("[Notification]: SCTP_REMOTE_ERROR received with assoc id = 0x%x error= %d!\n",
sctpRemoteErr->sre_assoc_id, sctpRemoteErr->sre_error);
}
void PrintSendFailed(union sctp_notification *notification)
{
struct sctp_send_failed *sctpSendFailed;
sctpSendFailed = ¬ification->sn_send_failed;
printf("[Notification]: SCTP_SEND_FAILED received with assoc id = 0x%x error = %d !\n",
sctpSendFailed->ssf_assoc_id, sctpSendFailed->ssf_error);
}
void PrintAdaptionIndication(union sctp_notification *notification)
{
struct sctp_adaptation_event *sctpAdaptEvent;
sctpAdaptEvent = ¬ification->sn_adaptation_event;
printf("[Notification]: SCTP_ADAPTION_INDICATION: 0x%x received!\n",
sctpAdaptEvent->sai_adaptation_ind);
}
void PrintPartialDeliveryEvent(union sctp_notification *notification)
{
struct sctp_pdapi_event *sctpPdapiEvent;
sctpPdapiEvent = ¬ification->sn_pdapi_event;
if(sctpPdapiEvent->pdapi_indication == SCTP_PARTIAL_DELIVERY_ABORTED)
printf("[Notification]: SCPT_PARTIAL_DELIVERY_ABORTED!");
else
printf("[Notification]: Unkown SCPT_PARTIAL_DELIVERY_EVENT!");
}
void PrintShutDownEvent(union sctp_notification *notification)
{
struct sctp_shutdown_event *sctpShutdownEvent;
sctpShutdownEvent = ¬ification->sn_shutdown_event;
printf("[Notification]: SCTP_SHUTDOWN_EVENT: assoc id = 0x%x\n", sctpShutdownEvent->sse_assoc_id);
}
void PrintSctpNotification(char* notify_buf)
{
union sctp_notification *notification;
notification = (union sctp_notification *) notify_buf;
switch(notification->sn_header.sn_type) {
case SCTP_ASSOC_CHANGE:
PrintAssocChange(notification);
break;
case SCTP_PEER_ADDR_CHANGE:
PrintPeerAddrChange(notification);
break;
case SCTP_REMOTE_ERROR:
PrintRemoteError(notification);
break;
case SCTP_SEND_FAILED:
PrintSendFailed(notification);
break;
case SCTP_ADAPTATION_INDICATION:
PrintAdaptionIndication(notification);
break;
case SCTP_PARTIAL_DELIVERY_EVENT:
PrintPartialDeliveryEvent(notification);
break;
case SCTP_SHUTDOWN_EVENT:
PrintShutDownEvent(notification);
break;
default:
printf("[Notification Err]: Unkown event type = 0x%x\n", notification->sn_header.sn_type);
}
}