Linux系统下的SCTP notification

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 notificationSCTP_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

结构体的定义参看前面,主要一些字段的定义如下,

  1. sac_type设置为SCTP_ASSOC_CHANGE
  2. sac_state可以设置成如下值
  • SCTP_COMM_UP:association建立好了;
  • SCTP_COMM_LOST:association失败了;
  • SCTP_RESTART:检测到association重启了,这种场景可能出现在,比如说客户端重启了,然后基于原先的端口号来建立association;
  • SCTP_SHUTDOWN_COMP:正常的association关闭
  • SCTP_CANT_STR_ASSOC:建立association失败
  1. 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 
#include 
#include 
#include 
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);
    }
    
}

6. [REF]

结构体定义,宏定义参考:sctp.h

你可能感兴趣的:(Linux系统下的SCTP notification)