Utilisation du multicast
From Livre IPv6
La programmation avec les groupes multicast n'est pas standardisée en IPv4. La nouvelle API "socket" propose un ensemble de structures et appels systèmes pour étendre l'interface de programmation sockets aux applications utilisant le multicast. Cet exemple va illustrer ce point.
Le but des deux programmes est d'échanger des données par multicast. Le programme in2multi6 diffuse les données lues en entrée ("standard input") vers un groupe multicast donné. Le programme multi2out6 écoute les paquets transmis dans ce groupe et les copie sur la sortie standard ("stdout").
multi2out6
Pour ce faire multi2out6 va faire des appels systèmes qui vont produire des paquets d'abonnement (et de désabonnement) à un groupe multicast. L'abonnement sera réalisé grâce à l'envoi de deux messages ICMPv6 successifs de type 131, c'est-à-dire des "rapports d'abonnement". Puis les messages émis dans le groupe sont reçus par le programme. Lorsque le programme s'arrête, le code de l'interface va automatiquement provoquer l'émission d'un message de réduction d'un groupe de multicast (132).
Le programme multi2out6 est appelé de la manière suivante :
multi2out6 [-i interface] <adresse de groupe multicast>
Voici le code complet du programme. Le port utilisé (ligne See ) est quelconque mais ne doit bien sûr pas correspondre à un service déjà existant.
1| #include <sys/types.h> 2| #include <sys/socket.h> 3| #include <netinet/in.h> 4| #include <arpa/inet.h> 5| #include <stdio.h> 6| #include <signal.h> 7| #include <unistd.h> 8| 9| #ifndef SO_REUSEPORT 10| #define SO_REUSEPORT SO_REUSEADDR 11| #endif 12| 13| struct sockaddr_in6 sin6; 14| 15| #define IPPORT 54321 16| 17| void Perror(const char *c) 18| { 19| perror(c); 20| exit(1); 21| } 22| 23| void Usage () 24| { 25| fprintf(stderr, "%s\n", "Usage: multi2out6 [-i interface] addr"); 26| exit(1); 27| } 28| 29| void BrokenPipe(int Signal) 30| { 31| signal(SIGPIPE, BrokenPipe); 32| return; 33| } 34|
La partie principale du programme traite les options éventuelles. En fait, il n'y en a qu'une, le choix de l'interface à abonner au groupe multicast. Une fois ces operations effectuées, il faut créer et attacher une socket à une adresse. Ces opérations sont réalisées par l'utilisation des fonctions classiques socket et bind.
On utilise une structure de données spéciale pour stocker l'adresse multicast du groupe (définie dans netinet/in.h) :
struct ipv6_mreq { struct in6_addr ipv6mr_multiaddr; /* IPv6 mcast address of group */ unsigned int ipv6mr_interface; /* local IPv6 address of interface */ };
35| main(int argc, char **argv) 36| { 37| struct ipv6_mreq mreq; 38| int cc, ccb, ch, s; 39| char buf[10240]; 40| u_int one = 1; 41| u_int ifi = 0; 42| 43| signal(SIGPIPE, BrokenPipe); 44| while ((ch = getopt(argc, argv, "i:")) != -1) 45| switch(ch) { 46| case 'i': 47| if (sscanf(optarg, "%u\0", &ifi) != 1 && 48| (ifi = if_nametoindex(optarg)) == 0) 49| Usage(); 50| break; 51| default: 52| Usage(); 53| } 54| argc -= optind; 55| argv += optind; 56| if (argc != 1) 57| Usage(); 58| 59| if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0) 60| Perror("socket"); 61| setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); 62| 63| #ifdef SIN6_LEN 64| sin6.sin6_len = sizeof(sin6); 65| #endif 66| sin6.sin6_family = AF_INET6; 67| sin6.sin6_port = htons(IPPORT); 68| if (bind(s, (struct sockaddr *)&sin6, sizeof(sin6)) < 0) 69| Perror("bind");
La fonction inet_pton va permettre la conversion du nom de groupe passé en option sous une forme textuelle (par exemple ff12::1234:5678) en forme numérique. Le résultat est directement stocké dans la variable mreq qui sera utilisée par la commande setsockop. On passe en paramètre à cette fonction l'option IPV6_JOIN_GROUP avec la variable mreq. À partir de ce moment, il y a émission de deux messages d'abonnement. La boucle qui suit va permettre la lecture des informations envoyées sur le groupe auquel on vient de s'abonner et les afficher sur la sortie standard ainsi que leur longueur sur la sortie erreur standard.
70| if (inet_pton(AF_INET6, *argv, &mreq.ipv6mr_multiaddr) != 1) 71| Usage(); 72| mreq.ipv6mr_interface = ifi; 73| if (setsockopt(s,IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) 74| Perror("setsockopt IPV6_JOIN_GROUP"); 75| for (;;) { 76| cc = read(s, buf, 10240); 77| if (cc < 0) 78| Perror("read socket"); 79| if (cc == 0) { 80| fprintf(stderr, "..\n"); 81| exit (0); 82| } 83| ccb = write(1, buf, cc); 84| if (ccb != cc) 85| Perror("write file"); 86| fprintf(stderr, "<-%d-\n", cc); 87| } 88| }
Lorsque le programme s'arrête, un close(s) implicite a lieu, et le code de l'interface va envoyer un message de réduction de groupe si elle est la dernière à avoir envoyé un rapport d'abonnement au groupe.
in2multi6
Le programme est appelé de la manière suivante :
in2multi6 [-i interface][-h max-hop-count][-l loop] <adresse de groupe multicast>
Le code est relativement simple, principalement une analyse des arguments, le positionnement d'option et une boucle lecture--émission. En effet il n'est pas nécessaire de s'abonner pour faire de l'émission multicast.
Il y a quatre arguments, trois optionnels qui sont l'interface d'émission (nom ou index numérique), le "ttl" mis dans les paquets multicast (voir le manuel de la primitive readv), et un drapeau qui sert à dire si la machine émettrice reçoit ou non les paquet émis. Le dernier argument est l'adresse du groups sous forme numérique.
Voici le code complet du programme. Le port utilisé (ligne 10) est naturellement celui de in2multi6.
1| #include <sys/types.h> 2| #include <sys/socket.h> 3| #include <netinet/in.h> 4| #include <arpa/inet.h> 5| #include <stdio.h> 6| #include <unistd.h> 7| 8| struct sockaddr_in6 sin6; 9| 10| #define IPPORT 54321 11| 12| void Perror(const char *c) 13| { 14| perror(c); 15| exit(1); 16| } 17| 18| void Usage () 19| { 20| fprintf(stderr, "%s\n", "Usage: in2multi6 [-i interface][-h hop][-l loop] addr"); 21| exit(1); 22| } 23| 24| main(int argc, char **argv) 25| { 26| u_int hops = 1, /* as defined in rfc2553 */ 27| loop = 1, /* as defined in rfc2553 */ 28| ifi = 0; 29| int s, cc, ch; 30| char buf[1024]; 31| struct in6_addr addr6; 32| extern char *optarg; 33| extern int optind; 34| 35| addr6 = in6addr_any; 36| if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0) 37| Perror("socket"); 38| while ((ch = getopt(argc, argv, "h:t:l:i:")) != -1) 39| switch(ch) { 40| case 'h': 41| case 't': 42| hops = atoi(optarg); 43| break; 44| case 'l': 45| loop = atoi(optarg); 46| break; 47| case 'i': 48| if (sscanf(optarg, "%u\0", &ifi) != 1) { 49| ifi = if_nametoindex(optarg); 50| if (ifi == 0) 51| Usage(); 52| } 53| break; 54| default: 55| Usage(); 56| } 57| argc -= optind; 58| argv += optind; 59| if (argc != 1 || inet_pton(AF_INET6, *argv, &addr6) <= 0) 60| Usage(); 61| if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 62| &hops, sizeof(hops)) < 0) 63| Perror("setsockopt IPV6_MULTICAST_HOPS"); 64| if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, 65| &loop, sizeof(loop)) < 0) 66| Perror("setsockopt IPV6_MULTICAST_LOOP"); 67| if (ifi && (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, 68| &ifi, sizeof(u_int)) < 0)) 69| Perror("setsockopt IPV6_MULTICAST_IF"); 70| 71| #ifdef SIN6_LEN 72| sin6.sin6_len = sizeof(sin6); 73| #endif 74| sin6.sin6_family = AF_INET6; 75| sin6.sin6_addr = addr6; 76| sin6.sin6_port = htons(54321); 77| 78| for (;;) { 79| cc = read(0, buf, 1024); 80| if (cc < 0) 81| Perror("read file"); 82| if (cc == 0) { 83| fprintf(stderr, ".\n", cc); 84| exit (0); 85| } 86| if (sendto(s, buf, cc, 0, 87| (struct sockaddr *)&sin6, sizeof(sin6)) < 0) 88| Perror("sendto"); 89| fprintf(stderr, "-%d->\n", cc); 90| } 91| }