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 <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 = &notification->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 = &notification->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 = &notification->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 = &notification->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 = &notification->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 = &notification->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 = &notification->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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容