Programmation multicast… ou comment écrire vos propres applications multicast.
Diverses extensions de l'API de programmation sont nécessaires pour utiliser le multicast. Ces extensions sont utilisables par le biais de deux appels systèmes : setsockopt() (utilisé pour envoyer des informations au noyau) et getsockopt() (utilisé pour recevoir des informations du voisinage multicast). Cela ne signifie pas que ces 2 nouveaux appels systèmes ont été ajoutés pour le fonctionnement du multicast. La paire setsockopt() / getsockopt() est là depuis des années. Depuis la BSD 4.2 au moins. L'ajout consiste en un nouveau jeu d'options (options multicast) qui est passé à ces appels systèmes et que le noyau doit comprendre.
Les deux lignes suivantes donnent le prototypage des fonctions setsockopt() / getsockopt().
int getsockopt(int s, int level, int optname, void* optval, int* optlen); int setsockopt(int s, int level, int optname, const void* optval, int optlen); |
Le premier paramètre, s, est la prise réseau (socket) à laquelle l'appel système s'applique. Pour le multicast, la prise doit être de la famille AF_INET et peut être de type SOCK_DGRAM ou SOCK_RAW. Le plus courant est l'utilisation d'une prise SOCK_DGRAM, mais si votre but est d'écrire un démon de routage ou d'en modifier un, vous aurez plutôt besoin d'une prise de type SOCK_RAW.
Le deuxième paramètre, level, identifie la couche qui capturera l'option, le message, ou la requète, ou tout ce que vous désirez appeler. De fait, SOL_SOCKET est pour la couche de la prise réseau, IPPROTO_IP est pour la couche IP, etc. Pour la programmation multicast, la valeur est toujours IPPROTO_IP.
optname identifie l'option que nous passons ou demandons. Sa valeur, soit fournie par le programme, soit retournée par le noyau, est optval. Les valeurs pour optname qui peuvent être invoquées pour la programmation multicast sont les suivantes :
setsockopt() | getsockopt() | |
IP_MULTICAST_LOOP | oui | oui |
IP_MULTICAST_TTL | oui | oui |
IP_MULTICAST_IF | oui | oui |
IP_ADD_MEMBERSHIP | oui | non |
IP_DROP_MEMBERSHIP | oui | non |
optlen transporte la taille de la structure de données à laquelle optval fait référence. Notez que pour getsockopt(), c'est un résultat et non une donnée : le noyau écrit la valeur de optname dans la zone tampon pointée par optval et nous informe de la longueur de la valeur par optlen.
Aussi bien setsockopt() que getsockopt() retournent 0 en cas de succès et -1 en cas d'erreur.
Vous devez décider, en tant que programmeur, si les données que vous voulez émettre doivent être retournées ou non sur votre hôte. Si vous pensez avoir plus d'un processus ou utilisateur en écoute, alors un retour doit être activé. À contrario, si vous émettez des images que votre caméra vidéo produit, vous ne nécessiterez probablement pas de retour, à moins que vous désiriez les voir sur votre écran. Dans ce dernier cas, votre application recevra déjà certainement les images depuis le périphérique connecté à votre ordinateur pour les envoyer à la prise réseau. De ce fait, l'application possède déjà les données, et cela devient improbable que vous vouliez les recevoir de nouveau au travers de la prise réseau.
Il est à noter que le retour est activé par défaut.
Comme optval est un pointeur, vous ne pouvez pas écrire :
setsockopt(socket, IPPROTO_IP, IP_MULTICAST_LOOP, 0, 1); |
pour désactiver le loopback vous devez écrire :
u_char loop; setsockopt(socket, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)); |
et donnez la valeur 1 à loop pour activer le retour et 0 pour le désactiver.
Pour savoir si une prise réseau a activé ou non son retour, utilisez quelque chose comme :
u_char loop; int size; getsockopt(socket, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, &size) |
Si rien n'est précisé, les datagrammes multicast sont envoyés avec comme valeur par défaut 1, dans le but d'éviter qu'ils soient transférés sur tout le réseau local. Pour changer la valeur du TTL (de 0 à 255), mettez cette valeur dans une variable (ici nommée « ttl ») et écrivez dans votre programme :
u_char ttl; setsockopt(socket, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); |
Le comportement avec getsockopt() est similaire à celui vu avec IP_MULTICAST_LOOP.
Traditionnellement, l'administrateur système indique l'interface multicast par défaut sur laquelle les datagrammes sont envoyés. Le programmeur peut surcharger cela et choisir ainsi à l'aide de cette option une interface de sortie concrête pour une prise réseau donnée.
struct in_addr interface_addr; setsockopt (socket, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr)); |
Ainsi, tout le trafic multicast généré par cette prise réseau sera envoyé sur l'interface choisie. Pour revenir au comportement par défaut et laisser le noyau choisir l'interface de sortie (choix basé sur la configuration de l'administrateur système), il suffit d'appeler setsockopt() avec la même option et INADDR_ANY comme valeur pour l'interface.
Pour connaitre ou sélectionner une interface de sortie, les ioctls suivants peuvent être utililisés : SIOCGIFADDR (pour obtenir les adresses des interfaces), SIOCGIFCONF (pour obtenir la liste de toutes les interfaces) et SIOCGIFFLAGS (pour obtenir les fonctionnalités d'une interface et, de fait, permet de déterminer si l'interface supporte le multicast ou non. Fonction indiquée par l'option IFF_MULTICAST).
Si un hôte a plus d'une interface et que l'option IP_MULTICAST_IF n'est pas mentionnée, alors les transmissions multicast sont émises sur l'interface par défaut, bien que les interfaces restantes puissent être utilisées pour des retransmissions multicast si l'hôte joue le rôle de routeur multicast.
Rappelez vous que vous devez informer le noyau des groupes multicast qui vous intéressent. Si aucun processus n'est intéressé par un groupe donné, les paquets provenant de ce groupe et qui arrivent sur notre hôte sont rejetés. Pour informer le noyau, des groupes qui vous intéressent, et de fait, pour devenir membre de ce groupe, vous devez d'abord remplir une structure ip_mreq qui sera passée plus tard au noyau dans le champ optval avec l'appel système setsockopt().
La structure ip_mreq (provenant de /usr/include/linux/in.h) a les membres suivants :
struct ip_mreq { struct in_addr imr_multiaddr; /* adresse IP du groupe multicast */ struct in_addr imr_interface; /* adresse IP de l'interface locale */ }; |
Note : la définition « physique » de la structure est contenue dans le fichier spécifié ci-dessus. Vous ne devez en aucun cas inclure <linux/in.h> si vous souhaitez que votre code soit portable. En lieu et place, incluez <netinet/in.h>, qui inclut à son tour <linux/in.h>.
Le premier membre, imr_multiaddr, contient l'adresse du groupe que vous désirez joindre. Souvenez-vous que l'appartenance à un groupe se fait aussi par le biais d'une interface, pas uniquement sur l'adresse du groupe. C'est la raison pour laquelle vous devez fournir une valeur au second membre : imr_interface. De cette façon, si vous êtes sur un hôte ayant plusieurs interfaces multicast, vous pouvez joindre un même groupe par le biais de différentes interfaces. Si vous ne désirez pas indiquer d'interface, vous pouvez toujours remplir ce dernier membre avec un joker (INADDR_ANY), alors le noyau choisira de lui même l'interface.
Une fois la structure remplie (définie en tant que struct ip_mreq mreq;) vous avez juste à appeler setsockopt() de cette manière :
setsockopt (socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); |
Notez que vous pouvez joindre plusieurs groupes à travers la même prise réseau. Cette limite supérieure est fixée par IP_MAX_MEMBERSHIPS et, a pour la version 2.0.33 du noyau Linux, la valeur de 20.
La procédure nécessaire pour quitter un groupe est semblable à celle nécessaire pour le joindre.
struct ip_mreq mreq; setsockopt (socket, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); |
Où mreq est une structure contenant les mêmes données que celles utilisées lorsque ce groupe fût joint. Si le membre imr_interface contient la valeur INADDR_ANY, alors le premier groupe pouvant correspondre est supprimé.
Si vous avez rejoint un grand nombre de groupes sur une même prise réseau, il n'est pas nécessaire de supprimer toutes les souscriptions pour terminer. Lorsque vous fermez une prise réseau, toutes les souscriptions associées à cette prise sont supprimées par le noyau. Il en est de même si le processus qui a ouvert la prise est tué.
Cependant, gardez à l'esprit que si un processus abandonne la souscription à un groupe, cela n'implique pas forcément l'arrêt de la transmission des datagrammes du groupe en question à l'hôte. En effet, si une autre prise a joint ce groupe avec la même interface avant le IP_DROP_MEMBERSHIP, alors l'hôte continuera à être membre de ce groupe.
ADD_MEMBERSHIP (aussi bien que DROP_MEMBERSHIP) est une opération non-bloquante. Il renvoie un résultat, immédiatement, indiquant s'il a réussi ou échoué.