13 votes

SCTP Multihoming

J'ai développé une application client-serveur simple en C où le client envoie des données aléatoires au serveur et le serveur écoute ce que le client envoie. Le protocole que j'utilise est SCTP et je m'intéresse à la façon d'y implémenter la fonction multihoming.

J'ai fait des recherches sur Internet à propos de SCTP et du multihoming et je n'ai pas pu trouver d'exemples sur la façon de demander à SCTP d'utiliser plusieurs adresses pour communiquer. J'ai seulement réussi à trouver les commandes à utiliser pour configurer SCTP avec le multihoming et cela devrait être assez simple.

J'ai créé un client et un serveur qui utilisent tous deux les deux interfaces WLAN de mon ordinateur comme points de connexion. Les deux adaptateurs sont connectés au même AP. Le serveur écoute les données du client à partir de ces interfaces et le client envoie des données par leur intermédiaire. Le problème est que lorsque je déconnecte l'adaptateur WLAN primaire vers lequel le client envoie des données, la transmission s'arrête alors qu'elle devrait revenir à la connexion secondaire. J'ai suivi les paquets avec Wireshark et les premiers paquets INIT et INIT_ACK indiquent que le client et le serveur utilisent les adaptateurs WLAN comme liens de communication.

Lorsque je reconnecte la connexion WLAN primaire, la transmission continue après un petit moment et envoie une énorme charge de paquets au serveur, ce qui n'est pas normal. Les paquets auraient dû être transmis par la connexion secondaire. Sur de nombreux sites, il est dit que SCTP passe automatiquement d'une connexion à l'autre, mais dans mon cas, ce n'est pas le cas. Avez-vous une idée de la raison pour laquelle la transmission ne bascule pas vers la connexion secondaire lorsque la liaison primaire est interrompue, même si le client et le serveur connaissent leurs adresses respectives, y compris l'adresse secondaire ?

A propos du serveur :

Le serveur crée un socket de type SOCK_SEQPACKET et lie toutes les interfaces trouvées avec INADDR_ANY. getladdrs rapporte que le serveur est lié à 3 adresses (dont 127.0.0.1). Après cela, le serveur écoute le socket et attend que le client envoie des données. Le serveur lit les données avec l'appel sctp_recvmsg.

À propos du client :

Le client crée aussi un socket SEQPACKET et se connecte à une adresse IP spécifiée par un argument de ligne de commande. getladdrs dans ce cas retourne aussi 3 adresses comme dans le cas des serveurs. Ensuite, le client commence à envoyer des données au serveur avec un délai d'une seconde jusqu'à ce que l'utilisateur interrompe l'envoi avec Ctrl-C.

Voici un peu de code source :

Serveur :

#define BUFFER_SIZE (1 << 16)
#define PORT 10000   

int sock, ret, flags;
int i;
int addr_count = 0;
char buffer[BUFFER_SIZE];
socklen_t from_len;

struct sockaddr_in addr;
struct sockaddr_in *laddr[10];
struct sockaddr_in *paddrs[10];
struct sctp_sndrcvinfo sinfo;
struct sctp_event_subscribe event;  
struct sctp_prim prim_addr; 
struct sctp_paddrparams heartbeat;
struct sigaction sig_handler;

void handle_signal(int signum);

int main(void)
{
    if((sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)) < 0)
        perror("socket");

    memset(&addr, 0, sizeof(struct sockaddr_in));
    memset((void*)&event, 1, sizeof(struct sctp_event_subscribe));

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(PORT);

    from_len = (socklen_t)sizeof(struct sockaddr_in);

    sig_handler.sa_handler = handle_signal;
    sig_handler.sa_flags = 0;

    if(sigaction(SIGINT, &sig_handler, NULL) == -1)
        perror("sigaction");

    if(setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(struct sctp_event_subscribe)) < 0)
        perror("setsockopt");

    if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))< 0)
        perror("setsockopt");

    if(bind(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)
        perror("bind");

    if(listen(sock, 2) < 0)
        perror("listen");

    addr_count = sctp_getladdrs(sock, 0, (struct sockaddr**)laddr);
    printf("Addresses binded: %d\n", addr_count);

    for(i = 0; i < addr_count; i++)
         printf("Address %d: %s:%d\n", i +1, inet_ntoa((*laddr)[i].sin_addr), (*laddr)[i].sin_port);

    sctp_freeladdrs((struct sockaddr*)*laddr);

    while(1)
    {
        flags = 0;

        ret = sctp_recvmsg(sock, buffer, BUFFER_SIZE, (struct sockaddr*)&addr, &from_len, NULL, &flags);

        if(flags & MSG_NOTIFICATION)
        printf("Notification received from %s:%u\n",  inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));

        printf("%d bytes received from %s:%u\n", ret, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));      
    }

    if(close(sock) < 0)
        perror("close");
}   

void handle_signal(int signum)
{
    switch(signum)
    {
        case SIGINT:
            if(close(sock) != 0)
                perror("close");
            exit(0);
            break;  
        default: exit(0);
            break;
    }
}

Et le client :

#define PORT 10000
#define MSG_SIZE 1000
#define NUMBER_OF_MESSAGES 1000
#define PPID 1234

int sock;
struct sockaddr_in *paddrs[10];
struct sockaddr_in *laddrs[10];

void handle_signal(int signum);

int main(int argc, char **argv)
{
    int i;
    int counter = 1;
    int ret;
    int addr_count;
    char address[16];
    char buffer[MSG_SIZE];
    sctp_assoc_t id;
    struct sockaddr_in addr;
    struct sctp_status status;
    struct sctp_initmsg initmsg;
    struct sctp_event_subscribe events;
    struct sigaction sig_handler;

    memset((void*)&buffer,  'j', MSG_SIZE);
    memset((void*)&initmsg, 0, sizeof(initmsg));
    memset((void*)&addr,    0, sizeof(struct sockaddr_in));
    memset((void*)&events, 1, sizeof(struct sctp_event_subscribe));

    if(argc != 2 || (inet_addr(argv[1]) == -1))
    {
        puts("Usage: client [IP ADDRESS in form xxx.xxx.xxx.xxx] ");        
        return 0;
    }

    strncpy(address, argv[1], 15);
    address[15] = 0;

    addr.sin_family = AF_INET;
    inet_aton(address, &(addr.sin_addr));
    addr.sin_port = htons(PORT);

    initmsg.sinit_num_ostreams = 2;
    initmsg.sinit_max_instreams = 2;
    initmsg.sinit_max_attempts = 5;

    sig_handler.sa_handler = handle_signal;
    sig_handler.sa_flags = 0;

    if(sigaction(SIGINT, &sig_handler, NULL) == -1)
        perror("sigaction");

    if((sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)) < 0)
        perror("socket");

    if((setsockopt(sock, SOL_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg))) != 0)
        perror("setsockopt");

    if((setsockopt(sock, SOL_SCTP, SCTP_EVENTS, (const void *)&events, sizeof(events))) != 0)
        perror("setsockopt");

    if(sendto(sock, buffer, MSG_SIZE, 0, (struct sockaddr*)&addr, sizeof(struct sockaddr)) == -1)
        perror("sendto");

    addr_count = sctp_getpaddrs(sock, 0, (struct sockaddr**)paddrs);
    printf("\nPeer addresses: %d\n", addr_count);

    for(i = 0; i < addr_count; i++)
    printf("Address %d: %s:%d\n", i +1, inet_ntoa((*paddrs)[i].sin_addr), (*paddrs)[i].sin_port);

    sctp_freepaddrs((struct sockaddr*)*paddrs);

    addr_count = sctp_getladdrs(sock, 0, (struct sockaddr**)laddrs);
    printf("\nLocal addresses: %d\n", addr_count);

    for(i = 0; i < addr_count; i++)
    printf("Address %d: %s:%d\n", i +1, inet_ntoa((*laddrs)[i].sin_addr), (*laddrs)[i].sin_port);

    sctp_freeladdrs((struct sockaddr*)*laddrs);

    i = sizeof(status);
    if((ret = getsockopt(sock, SOL_SCTP, SCTP_STATUS, &status, (socklen_t *)&i)) != 0)
        perror("getsockopt");

    printf("\nSCTP Status:\n--------\n");
    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--------\n\n", status.sstat_outstrms);

    for(i = 0; i < NUMBER_OF_MESSAGES; i++)
    {
        counter++;
        printf("Sending data chunk #%d...", counter);
        if((ret = sendto(sock, buffer, MSG_SIZE, 0, (struct sockaddr*)&addr, sizeof(struct sockaddr))) == -1)
            perror("sendto");

        printf("Sent %d bytes to peer\n",ret);

        sleep(1);
    }

    if(close(sock) != 0)
        perror("close");
}

void handle_signal(int signum)
{

    switch(signum)
    {
        case SIGINT:
            if(close(sock) != 0)
                perror("close");
            exit(0);
            break;  
        default: exit(0);
            break;
    }

}

Vous avez une idée de ce que je fais de mal ?

14voto

Kari Vatjus-Anttila Points 271

Ok, j'ai enfin résolu le problème du multihoming. Voici ce que j'ai fait.

J'ai ajusté la valeur du battement de cœur à 5000 ms avec la structure sctp_paddrparams. La variable flags située dans la structure doit être en mode SPP_HB_ENABLE car sinon SCTP ignore la valeur du battement de cœur lorsqu'on essaie de la définir avec setsockopt().

C'est la raison pour laquelle SCTP n'envoyait pas les battements de cœur aussi souvent que je le voulais. La raison pour laquelle je n'avais pas remarqué la variable flag, était le guide de référence obsolète de SCTP que je lisais, qui affirmait qu'il n'existait pas de variable flags dans la structure ! Une référence plus récente a révélé qu'il y en avait une. Le problème de battement de cœur est donc résolu !

Une autre solution consistait à modifier la valeur rto_max pour qu'elle soit, par exemple, de 2000 ms environ. En abaissant cette valeur, on indique à SCTP de changer de chemin beaucoup plus tôt. La valeur par défaut était de 60 000 ms, ce qui était trop élevé (1 minute avant de commencer à changer le chemin). La valeur rto_max peut être ajustée avec la structure sctp_rtoinfo.

Avec ces deux modifications, le multihoming a commencé à fonctionner. Oh et une autre chose. Le client doit être en mode STREAM lorsque le serveur est en mode SEQPACKET. Le client envoie des données au serveur avec la commande normale send() et le serveur lit les données avec sctp_recvmsg() où la structure addr est mise à NULL.

J'espère que ces informations aideront d'autres personnes qui se débattent avec le multihoming de SCTP. Merci pour vos avis, ils ont été d'une grande aide pour moi ! Voici un exemple de code, c'est peut-être le premier exemple simple de multihoming sur le net si vous voulez mon avis (je n'ai pas trouvé d'autres exemples que ceux de multistreaming).

Serveur :

#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdlib.h>
#include <pthread.h>

#define BUFFER_SIZE (1 << 16)
#define PORT 10000   

int sock, ret, flags;
int i, reuse = 1;
int addr_count = 0;
char buffer[BUFFER_SIZE];
socklen_t from_len;

struct sockaddr_in addr;
struct sockaddr_in *laddr[10];
struct sockaddr_in *paddrs[10];
struct sctp_sndrcvinfo sinfo;
struct sctp_event_subscribe event;  
struct sctp_prim prim_addr; 
struct sctp_paddrparams heartbeat;
struct sigaction sig_handler;
struct sctp_rtoinfo rtoinfo;

void handle_signal(int signum);

int main(void)
{
    if((sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)) < 0)
        perror("socket");

    memset(&addr,       0, sizeof(struct sockaddr_in));
    memset(&event,      1, sizeof(struct sctp_event_subscribe));
    memset(&heartbeat,  0, sizeof(struct sctp_paddrparams));
    memset(&rtoinfo,    0, sizeof(struct sctp_rtoinfo));

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(PORT);

    from_len = (socklen_t)sizeof(struct sockaddr_in);

    sig_handler.sa_handler = handle_signal;
    sig_handler.sa_flags = 0;

    heartbeat.spp_flags = SPP_HB_ENABLE;
    heartbeat.spp_hbinterval = 5000;
    heartbeat.spp_pathmaxrxt = 1;

    rtoinfo.srto_max = 2000;

    /*Set Heartbeats*/
    if(setsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS , &heartbeat, sizeof(heartbeat)) != 0)
        perror("setsockopt");

    /*Set rto_max*/
    if(setsockopt(sock, SOL_SCTP, SCTP_RTOINFO , &rtoinfo, sizeof(rtoinfo)) != 0)
        perror("setsockopt");

    /*Set Signal Handler*/
    if(sigaction(SIGINT, &sig_handler, NULL) == -1)
        perror("sigaction");

    /*Set Events */
    if(setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(struct sctp_event_subscribe)) < 0)
        perror("setsockopt");

    /*Set the Reuse of Address*/
    if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))< 0)
        perror("setsockopt");

    /*Bind the Addresses*/
    if(bind(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)
        perror("bind");

    if(listen(sock, 2) < 0)
        perror("listen");

    /*Get Heartbeat Value*/
    i = (sizeof heartbeat);
    getsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS, &heartbeat, (socklen_t*)&i);
    printf("Heartbeat interval %d\n", heartbeat.spp_hbinterval);

    /*Print Locally Binded Addresses*/
    addr_count = sctp_getladdrs(sock, 0, (struct sockaddr**)laddr);
    printf("Addresses binded: %d\n", addr_count);
    for(i = 0; i < addr_count; i++)
         printf("Address %d: %s:%d\n", i +1, inet_ntoa((*laddr)[i].sin_addr), (*laddr)[i].sin_port);
    sctp_freeladdrs((struct sockaddr*)*laddr);

    while(1)
    {
        flags = 0;

        ret = sctp_recvmsg(sock, buffer, BUFFER_SIZE, NULL, 0, NULL, &flags);

        if(flags & MSG_NOTIFICATION)
        printf("Notification received from %s:%u\n",  inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));

        printf("%d bytes received from %s:%u\n", ret, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));      
    }

    if(close(sock) < 0)
        perror("close");
}   

void handle_signal(int signum)
{
    switch(signum)
    {
        case SIGINT:
            if(close(sock) != 0)
                perror("close");
            exit(0);
            break;  
        default: exit(0);
            break;
    }
}

Client :

#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>

#define PORT 11000
#define MSG_SIZE 1000
#define NUMBER_OF_MESSAGES 1000

int sock;
struct sockaddr_in *paddrs[5];
struct sockaddr_in *laddrs[5];

void handle_signal(int signum);

int main(int argc, char **argv)
{
    int i;
    int counter = 0;
    int asconf = 1;
    int ret;
    int addr_count;
    char address[16];
    char buffer[MSG_SIZE];
    sctp_assoc_t id;
    struct sockaddr_in addr;
    struct sctp_status status;
    struct sctp_initmsg initmsg;
    struct sctp_event_subscribe events;
    struct sigaction sig_handler;
    struct sctp_paddrparams heartbeat;
    struct sctp_rtoinfo rtoinfo;

    memset(&buffer,     'j', MSG_SIZE);
    memset(&initmsg,    0,   sizeof(struct sctp_initmsg));
    memset(&addr,       0,   sizeof(struct sockaddr_in));
    memset(&events,     1,   sizeof(struct sctp_event_subscribe));
    memset(&status,     0,   sizeof(struct sctp_status));
    memset(&heartbeat,  0,   sizeof(struct sctp_paddrparams));
    memset(&rtoinfo,    0, sizeof(struct sctp_rtoinfo))

    if(argc != 2 || (inet_addr(argv[1]) == -1))
    {
        puts("Usage: client [IP ADDRESS in form xxx.xxx.xxx.xxx] ");        
        return 0;
    }

    strncpy(address, argv[1], 15);
    address[15] = 0;

    addr.sin_family = AF_INET;
    inet_aton(address, &(addr.sin_addr));
    addr.sin_port = htons(PORT);

    initmsg.sinit_num_ostreams = 2;
    initmsg.sinit_max_instreams = 2;
    initmsg.sinit_max_attempts = 1;

    heartbeat.spp_flags = SPP_HB_ENABLE;
    heartbeat.spp_hbinterval = 5000;
    heartbeat.spp_pathmaxrxt = 1;

    rtoinfo.srto_max = 2000;

    sig_handler.sa_handler = handle_signal;
    sig_handler.sa_flags = 0;

    /*Handle SIGINT in handle_signal Function*/
    if(sigaction(SIGINT, &sig_handler, NULL) == -1)
        perror("sigaction");

    /*Create the Socket*/
    if((ret = (sock = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP))) < 0)
        perror("socket");

    /*Configure Heartbeats*/
    if((ret = setsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS , &heartbeat, sizeof(heartbeat))) != 0)
        perror("setsockopt");

    /*Set rto_max*/
    if((ret = setsockopt(sock, SOL_SCTP, SCTP_RTOINFO , &rtoinfo, sizeof(rtoinfo))) != 0)
        perror("setsockopt");

    /*Set SCTP Init Message*/
    if((ret = setsockopt(sock, SOL_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg))) != 0)
        perror("setsockopt");

    /*Enable SCTP Events*/
    if((ret = setsockopt(sock, SOL_SCTP, SCTP_EVENTS, (void *)&events, sizeof(events))) != 0)
        perror("setsockopt");

    /*Get And Print Heartbeat Interval*/
    i = (sizeof heartbeat);
    getsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS, &heartbeat, (socklen_t*)&i);

    printf("Heartbeat interval %d\n", heartbeat.spp_hbinterval);

    /*Connect to Host*/
    if(((ret = connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)))) < 0)
    {
        perror("connect");
        close(sock);
        exit(0);
    }

    /*Get Peer Addresses*/
    addr_count = sctp_getpaddrs(sock, 0, (struct sockaddr**)paddrs);
    printf("\nPeer addresses: %d\n", addr_count);

    /*Print Out Addresses*/
    for(i = 0; i < addr_count; i++)
        printf("Address %d: %s:%d\n", i +1, inet_ntoa((*paddrs)[i].sin_addr), (*paddrs)[i].sin_port);

    sctp_freepaddrs((struct sockaddr*)*paddrs);

    /*Start to Send Data*/
    for(i = 0; i < NUMBER_OF_MESSAGES; i++)
    {
        counter++;
        printf("Sending data chunk #%d...", counter);

        if((ret = send(sock, buffer, MSG_SIZE, 0)) == -1)
            perror("write");

        printf("Sent %d bytes to peer\n",ret);

        sleep(1);
    }

    if(close(sock) != 0)
        perror("close");
}

void handle_signal(int signum)
{
    switch(signum)
    {
        case SIGINT:
            if(close(sock) != 0)
                perror("close");
            exit(0);
            break;  
        default: exit(0);
            break;
    }
}

0voto

O.C. Points 4328

Je n'ai jamais essayé SCTP mais selon este il prend en charge deux modèles.

le style one-to-one et le style one-to-many.

D'après ce que j'ai compris, le multihoming fonctionne pour le style one-to-one.

Afin d'ouvrir une prise de style un-à-un, vous avez besoin d'un appel comme :

connSock = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP );

Notez que SOCK_STREAM est différent de votre SOCK_SEQPACKET

Quand vous le faites

sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)

il semble ouvrir une prise de style "one-to-many" dont je n'ai pas pu comprendre si elle supporte le multihoming ou non.

Essayez donc votre code avec SOCK_STREAM paramètre.

Aussi aquí est un exemple qui utilise SOCK_STREAM .

0voto

User1 Points 3826

Votre client ouvre une association en utilisant l'appel sendto(). C'est correct. Mais après cela, vous ne devriez plus utiliser sendto(). Car sendto() va probablement forcer SCTP à utiliser l'interface de l'IP donnée. Donc, utilisez write() ou send() au lieu de sendto() :

counter++;
printf("Sending data chunk #%d...", counter);

if((ret = write(sock, buffer, MSG_SIZE) == -1)
    perror("write");

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X