API IP Multicast

Philippe Dax

Sommaire


Autres documents et pointeurs sur le Multicast

Supports requis

Le multicasting IP n'est actuellement supporté que La quasi-totalité des machines en libre service dans l'Ecole sont aptes à faire de la communication Multicast, voir plus précisemment la Map du réseau Multicast de l'ENST.


Adresse de groupe et port

Une adresse multicast est une adresse IP classe D qui est comprise dans la gamme de 224.0.0.0 à 239.255.255.255. Il n'y a pas de champ network ni de champ host. Il s'agit en fait d'une adresse immaterielle dite adresse de groupe. Les 28 bits du champ adresse offrent ainsi 250 millions d'adresses possibles.
#define GROUP	"239.137.194.222"
Il sera aussi nécessaire de définir un port de communication comme cela est fait habituellement en mode unicast traditionnel.
#define PORT	55501

Initialisation des sockets

La création de la socket se fait de la manière habituelle, comme indiqué ci-dessous :
/*
 * Fichiers include
 */ 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

    /*
     * descripteurs de socket
     *
     * sdr : pour la reception
     * sdw : pour l'emission
     */
    int sdr, sdw;

    /*
     * sockets
     *
     * sock_r : pour la reception
     * sock_w : pour l'emission
     */
    struct sockaddr_in sock_r, sock_w;

    /*
     * creation des sockets
     */
    sdr = socket(PF_INET, SOCK_DGRAM, 0);
    if (sdr < 0) {
        perror("socket");
        exit(1);
    }
    sdw = socket(PF_INET, SOCK_DGRAM, 0);
    if (sdw < 0) {
        perror("socket");
        exit(1);
    }

    /*
     * initialisation de la socket de reception
     */
    memset(&sock_r, 0, sizeof(sock_r));
    sock_r.sin_family = AF_INET;
    sock_r.sin_port = htons(PORT);
    sock_r.sin_addr.s_addr = htonl(INADDR_ANY);
    /*
     * initialisation de la socket d'emission
     */
    memset(&sock_w, 0, sizeof(sock_w));
    sock_w.sin_family = AF_INET;
    sock_w.sin_port = htons(PORT);
    sock_w.sin_addr.s_addr = inet_addr(GROUP);

    len_r = sizeof(sock_r);
    len_w = sizeof(sock_w);
La fonction inet_addr("string") permet de convertir la chaîne de caractères de 4 octets décimaux représentant l'adresse IP en une valeur binaire codée sur 32 bits au format réseau.


Réception d'un datagramme multicast

Avant qu'une machine puisse recevoir des datagrammes multicast, elle doit être membre du groupe. L'application peut demander à la machine sur laquelle elle s'exécute de joindre le groupe multicast en utilisant l'option socket suivante :
    if (setsockopt(sdr, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *) &imr, sizeof(struct ip_mreq)) < 0) {
        perror("setsockopt - IP_ADD_MEMBERSHIP");
        exit(1);
    }
imr est une structure de type ip_mreq, définie dans le fichier /usr/include/netinet/in.h, qui est décrite comme suit :
    struct ip_mreq {
        struct in_addr imr_multiaddr;   /* multicast group to join */
        struct in_addr imr_interface;   /* interface to join on    */
    }

    /*
     * allocation de la structure imr
     */
    struct ip_mreq imr;

    /*
     * initialisation de la structure imr
     */
    imr.imr_multiaddr.s_addr = inet_addr(GROUP);
    imr.imr_interface.s_addr = htonl(INADDR_ANY);
En mode réception il est aussi nécessaire d'effectuer une opération bind() entre la socket et son descripteur :
    /*
     * opération bind
     */
    if (bind(sdr, (struct sockaddr *)&sock_r, sizeof(sock_r)) < 0) (
        perror("bind");
        exit(1);
    }
La réception proprement dite des données se réalisera par un appel à recvfrom() à travers l'interface socket.
    /*
     * réception des datagrammes
     */
    while (1) {
        cnt = recvfrom(sdr, buf, sizeof(buf), 0, (struct sockaddr *)&sock_r, &len_r);
        if (cnt < 0) {
            perror("recvfrom");
            exit(1);
        }
        else if (cnt == 0) { /* fin de transmission */
            break;
        }
        printf("%s\n", buf); /* affichage du message */
    }

Emission de datagrammes multicast

Par défaut les datagrammes multicast sont envoyés avec un TTL (time-to-live) de 1 qui correspond au sous-réseau sur lequel est physiquement connecté la machine par son interface ethernet. Le TTL contrôle la porté de l'émission. Habituellement un TTL de 15 permet de diffuser les datagrammes sur le site entier, un TTL de 47 permet une diffusion nationale et un TTL de 127 et au-delà une diffusion mondiale.

Les adresses comprises entre 224.0.0.0 et 224.0.0.255 peuvent être utilisées localement sur le sous-réseau mais ne seront jamais relayées par les routeurs car elles sont réservées aux protocoles de routage.

Pour faire des tests il est conseillé d'utiliser un TTL de 1 et une adresse du type "239.jj.mm.aa" où jj represente le jour, mm le mois et aa l'année.

L'opération qui permet de fixer la valeur du TTL est la suivante :

    int r;
    unsigned char ttl = 1;

    setsockopt(sdw, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
    if (r == -1) {
        perror("setsockopt: IP_MULTICAST_TTL");
    }
L'émission proprement dite des données se fera par l'appel système sendto() à travers l'interface socket.
    /*
     * emission des datagrammes
     */
    cnt = sendto(sdw, buf, strlen(buf), 0, (struct sockaddr *)&sock_w, len_w);
    if (cnt < 0) {
        perror("sendto");
        exit(1);
    }

Quitter un groupe

Si l'application décide de quitter le groupe dans lequel elle est actuellement, elle doit le signaler par l'appel suivant :
    int r;

    r = setsockopt(sdr, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, sizeof(imr));
    if (r == -1) {
        perror("setsockopt: IP_DROP_MEMBERSHIP");
    }

Port réutilisable

Plus d'un processus sur la même machine peuvent se lier au même port UDP si l'appel système bind() est précédé par :
    int r, reuse = 1;

    r = setsockopt(sdr, SOL_SOCKET, SO_REUSEADDR, (int *)&reuse, sizeof(reuse));
    if (r == -1) {
        perror("setsockopt: SO_REUSEADDR");
    }
Cet appel système doit précéder l'appel système bind().


Auto bouclage (loopback)

Si un datagramme est envoyé vers un groupe qui est déjà affecté sur la machine locale, une copie interne, au niveau de la couche IP, est réalisée pour qu'il soit délivré localement. Une option permet de contrôler ce fonctionnement. Si la variable loop vaut 1 la copie interne est effectuée et l'application émettrice reçoit le datagramme en echo, sinon elle ne l'est pas (loop = 0) et l'echo est supprimé.
    int r;
    unsigned char loop = 1;	/* 0 = disable, 1 = enable */

    r = setsockopt(sdr, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)); 
    if (r == -1) {
        perror("setsockopt: IP_MULTICAST_LOOP");
    }


Vérification d'allocation de buffer

Cet appel système permet de s'assurer que l'on dispose bien de la ressource mémoire nécessaire au buffer d'émission.
    int r, bufsize = 8192;

    r = setsockopt(sdw, SOL_SOCKET, SO_SNDBUF, (void *)&bufsize, sizeof(bufsize)); 
    if (r == -1) {
        perror("setsockopt: SO_SNDBUF");
    }


API Java (jdk1.1.6)


Utilisation de RSVP

Sur les machines qui supportent les routeurs multicast "mrouted" et sur lesquelles a été implémenté RSVP (ReSource reserVation Protocol), il est possible d'indiquer quelle direction doivent prendrent les paquets multicast en choisissant une interface virtuelle (vif), par exemple un tunnel vers une autre machine supportant un routeur mrouted et un daemon rsvpd.
    int vif = -1;	/* -1 = enable normal multicast forwarding */

    setsockopt(sdr, IPPROTO_IP, IP_MULTICAST_VIF, &vif, sizeof(vif)); 

© (Copyright) Philippe Dax - 1995-1999