Difference between revisions of "L'exemple « mini-ping » revisité"

From Livre IPv6

m
 
(2 intermediate revisions by one other user not shown)
Line 1: Line 1:
 +
{{suivi| L'implémentation | L'implémentation | Supervision | Supervision }}
 
Le programme <tt>one_ping6.c</tt> va être repris afin de lui ajouter deux fonctionnalités dont l'implémentation s'appuiera sur l'usage de données auxiliaires. On souhaite d'une part afficher le nombre de sauts (''hop limit'') du paquet ECHO_REPLY (éventuellement) reçu et d'autre part de permettre, à l'instar de la commande <tt>ping6</tt>, de passer une liste de relais par lesquels le paquet ECHO_REQUEST devra transiter avant d'être envoyé à l'hôte destinataire (routage par la source).
 
Le programme <tt>one_ping6.c</tt> va être repris afin de lui ajouter deux fonctionnalités dont l'implémentation s'appuiera sur l'usage de données auxiliaires. On souhaite d'une part afficher le nombre de sauts (''hop limit'') du paquet ECHO_REPLY (éventuellement) reçu et d'autre part de permettre, à l'instar de la commande <tt>ping6</tt>, de passer une liste de relais par lesquels le paquet ECHO_REQUEST devra transiter avant d'être envoyé à l'hôte destinataire (routage par la source).
  
Line 21: Line 22:
 
   
 
   
  
#ifdef sun /* For Solaris */
+
  1| #ifdef sun /* For Solaris */
#define _XOPEN_SOURCE 500 /* correct recvmsg/sendmsg/msg/CMSG_xx syntax */
+
  2| #define _XOPEN_SOURCE 500 /* correct recvmsg/sendmsg/msg/CMSG_xx syntax */
#define __EXTENSIONS__
+
  3| #define __EXTENSIONS__
#endif
+
  4| #endif
#include <stdio.h>
+
  5| #include <stdio.h>
#include <stdlib.h>
+
  6| #include <stdlib.h>
#include <unistd.h>
+
  7| #include <unistd.h>
#include <sys/uio.h>
+
  8| #include <sys/uio.h>
#include <sys/types.h>
+
  9| #include <sys/types.h>
  #include <sys/socket.h>
+
  10| #include <sys/socket.h>
  #include <netinet/in.h>
+
  11| #include <netinet/in.h>
  #include <netinet/ip6.h>
+
  12| #include <netinet/ip6.h>
  #ifndef CMSG_SPACE /* Solaris <= 9 */
+
  13| #ifndef CMSG_SPACE /* Solaris <= 9 */
  #define CMSG_SPACE(l) ((size_t)_CMSG_HDR_ALIGN(sizeof (struct cmsghdr) + (l)))
+
  14| #define CMSG_SPACE(l) ((size_t)_CMSG_HDR_ALIGN(sizeof (struct cmsghdr) + (l)))
  #define CMSG_LEN(l) ((size_t)_CMSG_DATA_ALIGN(sizeof (struct cmsghdr)) + (l))
+
  15| #define CMSG_LEN(l) ((size_t)_CMSG_DATA_ALIGN(sizeof (struct cmsghdr)) + (l))
  #endif
+
  16| #endif
 
+
17| 
  int recv_data(int sock, void *buf, int len, unsigned int flags,
+
  18| int recv_data(int sock, void *buf, int len, unsigned int flags,
              struct sockaddr *from, socklen_t *fromlen, int *hoplimit)
+
19|              struct sockaddr *from, socklen_t *fromlen, int *hoplimit)
  {
+
  20| {
  int ret, found = 0, cmsgspace = CMSG_SPACE(sizeof(int));
+
  21| int ret, found = 0, cmsgspace = CMSG_SPACE(sizeof(int));
  struct iovec iov = {buf, len};
+
  22| struct iovec iov = {buf, len};
  struct cmsghdr *cmsg = (struct cmsghdr *) malloc(cmsgspace), *ptr;
+
  23| struct cmsghdr *cmsg = (struct cmsghdr *) malloc(cmsgspace), *ptr;
  struct msghdr msg = {
+
  24| struct msghdr msg = {
    (caddr_t) from, *fromlen, &iov, 1,
+
25|    (caddr_t) from, *fromlen, &iov, 1,
    (caddr_t) cmsg, cmsgspace
+
26|    (caddr_t) cmsg, cmsgspace
  };
+
  27| };
 
+
28| 
  if (cmsg == NULL) {
+
  29| if (cmsg == NULL) {
    perror("recv_data: malloc");
+
30|    perror("recv_data: malloc");
    return -1;
+
31|    return -1;
  }
+
  32| }
  ret = recvmsg(sock, &msg, flags);
+
  33| ret = recvmsg(sock, &msg, flags);
  if (ret < 0) {
+
  34| if (ret < 0) {
    perror("recv_data: recvmsg");
+
35|    perror("recv_data: recvmsg");
    goto done;
+
36|    goto done;
  }
+
  37| }
  if (msg.msg_flags & MSG_TRUNC) {
+
  38| if (msg.msg_flags & MSG_TRUNC) {
    fprintf(stderr, "recv_data: recvmsg: data discarded before delivery\n");
+
39|    fprintf(stderr, "recv_data: recvmsg: data discarded before delivery\n");
    goto bad;
+
40|    goto bad;
  }
+
  41| }
  if (msg.msg_flags & MSG_CTRUNC) {
+
  42| if (msg.msg_flags & MSG_CTRUNC) {
    fprintf(stderr,
+
43|    fprintf(stderr,
            "recv_data: recvmsg: control data lost before delivery\n");
+
44|            "recv_data: recvmsg: control data lost before delivery\n");
    goto bad;
+
45|    goto bad;
  }
+
  46| }
  if (msg.msg_controllen)
+
  47| if (msg.msg_controllen)
    for (ptr = CMSG_FIRSTHDR(&msg); ptr; ptr = CMSG_NXTHDR(&msg, ptr)) {
+
48|    for (ptr = CMSG_FIRSTHDR(&msg); ptr; ptr = CMSG_NXTHDR(&msg, ptr)) {
      if (ptr->cmsg_level==IPPROTO_IPV6 && ptr->cmsg_type==IPV6_HOPLIMIT) {
+
49|      if (ptr->cmsg_level==IPPROTO_IPV6 && ptr->cmsg_type==IPV6_HOPLIMIT) {
          if (ptr->cmsg_len != CMSG_LEN(sizeof(int))) {
+
50|          if (ptr->cmsg_len != CMSG_LEN(sizeof(int))) {
            fprintf(stderr,
+
51|            fprintf(stderr,
                    "recvmsg: ancillary data with invalid length\n");
+
52|                    "recvmsg: ancillary data with invalid length\n");
            goto bad;
+
53|            goto bad;
          }
+
54|          }
          *hoplimit = *((int *) CMSG_DATA(ptr));
+
55|          *hoplimit = *((int *) CMSG_DATA(ptr));
          goto done;
+
56|          goto done;
      }
+
57|      }
    }
+
58|    }
    fprintf(stderr,
+
59|    fprintf(stderr,
            "recv_data: recvmsg: hoplimit not found in ancillary data\n");
+
60|            "recv_data: recvmsg: hoplimit not found in ancillary data\n");
  bad:
+
61|  bad:
    ret = -1;
+
62|    ret = -1;
  done:
+
63|  done:
    free(cmsg);
+
64|    free(cmsg);
    return ret;
+
65|    return ret;
  }
+
  66| }
  
 
Le code de la fonction <tt>recv_data</tt> ne sera pas commenté car la réception du nombre de sauts via une donnée auxiliaire a été étudiée dans un précédent exemple, la seule différence étant que la gestion des erreurs est ici plus détaillée.
 
Le code de la fonction <tt>recv_data</tt> ne sera pas commenté car la réception du nombre de sauts via une donnée auxiliaire a été étudiée dans un précédent exemple, la seule différence étant que la gestion des erreurs est ici plus détaillée.
Line 113: Line 114:
 
   
 
   
  
#ifdef sun /* For Solaris */
+
  1| #ifdef sun /* For Solaris */
#define _XOPEN_SOURCE 500 /* correct recvmsg/sendmsg/msg/CMSG_xx syntax */
+
  2| #define _XOPEN_SOURCE 500 /* correct recvmsg/sendmsg/msg/CMSG_xx syntax */
#define __EXTENSIONS__
+
  3| #define __EXTENSIONS__
#endif
+
  4| #endif
#include <stdio.h>
+
  5| #include <stdio.h>
#include <stdlib.h>
+
  6| #include <stdlib.h>
#include <unistd.h>
+
  7| #include <unistd.h>
#include <sys/uio.h>
+
  8| #include <sys/uio.h>
#include <sys/types.h>
+
  9| #include <sys/types.h>
  #include <sys/socket.h>
+
  10| #include <sys/socket.h>
  #include <netinet/in.h>
+
  11| #include <netinet/in.h>
  #include <netinet/ip6.h>
+
  12| #include <netinet/ip6.h>
  #ifndef CMSG_SPACE /* Solaris <= 9 */
+
  13| #ifndef CMSG_SPACE /* Solaris <= 9 */
  #define CMSG_SPACE(l) ((size_t)_CMSG_HDR_ALIGN(sizeof (struct cmsghdr) + (l)))
+
  14| #define CMSG_SPACE(l) ((size_t)_CMSG_HDR_ALIGN(sizeof (struct cmsghdr) + (l)))
  #define CMSG_LEN(l) ((size_t)_CMSG_DATA_ALIGN(sizeof (struct cmsghdr)) + (l))
+
  15| #define CMSG_LEN(l) ((size_t)_CMSG_DATA_ALIGN(sizeof (struct cmsghdr)) + (l))
  #endif
+
  16| #endif
  #ifndef IPV6_RECVHOPLIMIT
+
  17| #ifndef IPV6_RECVHOPLIMIT
  #define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
+
  18| #define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
  #endif
+
  19| #endif
 
+
20| 
  extern void * inet6_rth_init(); /* sometimes not in ip6.h */
+
  21| extern void * inet6_rth_init(); /* sometimes not in ip6.h */
   
+
  22|
  int send_data(int sock, void *buf, int len, unsigned int flags,
+
  23| int send_data(int sock, void *buf, int len, unsigned int flags,
              struct sockaddr *to, socklen_t tolen,
+
24|              struct sockaddr *to, socklen_t tolen,
              struct in6_addr *seg, int nseg)
+
25|              struct in6_addr *seg, int nseg)
  {
+
  26| {
  int ret = -1, rthsp, cmsgspace;
+
  27| int ret = -1, rthsp, cmsgspace;
  void *data;
+
  28| void *data;
  struct in6_addr *in6;
+
  29| struct in6_addr *in6;
  struct iovec iov = {buf, len};
+
  30| struct iovec iov = {buf, len};
  struct cmsghdr *cmsg = NULL;
+
  31| struct cmsghdr *cmsg = NULL;
  struct msghdr msg = {
+
  32| struct msghdr msg = {
                    (caddr_t) to, tolen, &iov, 1,
+
33|                    (caddr_t) to, tolen, &iov, 1,
                    NULL, 0, 0
+
34|                    NULL, 0, 0
                    };
+
35|                    };
 
+
36| 
    if (seg != NULL) {
+
37|    if (seg != NULL) {
      rthsp = inet6_rth_space(IPV6_RTHDR_TYPE_0, nseg);
+
38|      rthsp = inet6_rth_space(IPV6_RTHDR_TYPE_0, nseg);
      cmsgspace = CMSG_SPACE(rthsp);
+
39|      cmsgspace = CMSG_SPACE(rthsp);
      msg.msg_control = cmsg = (struct cmsghdr *) malloc(cmsgspace);
+
40|      msg.msg_control = cmsg = (struct cmsghdr *) malloc(cmsgspace);
      if (cmsg == NULL) {
+
41|      if (cmsg == NULL) {
          perror("recv_data: malloc");
+
42|          perror("recv_data: malloc");
          goto bad;
+
43|          goto bad;
      }
+
44|      }
      cmsg->cmsg_level = IPPROTO_IPV6;
+
45|      cmsg->cmsg_level = IPPROTO_IPV6;
      msg.msg_controllen = cmsg->cmsg_len = CMSG_LEN(rthsp);
+
46|      msg.msg_controllen = cmsg->cmsg_len = CMSG_LEN(rthsp);
      cmsg->cmsg_type = IPV6_RTHDR;
+
47|      cmsg->cmsg_type = IPV6_RTHDR;
      data = CMSG_DATA(cmsg);
+
48|      data = CMSG_DATA(cmsg);
      data = (void *)inet6_rth_init(data, rthsp, IPV6_RTHDR_TYPE_0, nseg);
+
49|      data = (void *)inet6_rth_init(data, rthsp, IPV6_RTHDR_TYPE_0, nseg);
      if (!data) {
+
50|      if (!data) {
          fprintf(stderr, "send_data: inet6_rth_init failed\n");
+
51|          fprintf(stderr, "send_data: inet6_rth_init failed\n");
          goto bad;
+
52|          goto bad;
      }
+
53|      }
      for (in6 = seg; in6 - seg < nseg; in6++)
+
54|      for (in6 = seg; in6 - seg < nseg; in6++)
          if (inet6_rth_add(data, in6) == -1) {
+
55|          if (inet6_rth_add(data, in6) == -1) {
            fprintf(stderr, "send_data: inet6_rth_add failed\n");
+
56|            fprintf(stderr, "send_data: inet6_rth_add failed\n");
            goto bad;
+
57|            goto bad;
          }
+
58|          }
    }
+
59|    }
    ret = sendmsg(sock, &msg, flags);
+
60|    ret = sendmsg(sock, &msg, flags);
    if (ret < 0) {
+
61|    if (ret < 0) {
      perror("send_data: sendmsg");
+
62|      perror("send_data: sendmsg");
      goto bad;
+
63|      goto bad;
    }
+
64|    }
  bad:
+
  65| bad:
    if (cmsg)
+
66|    if (cmsg)
    free(cmsg);
+
67|    free(cmsg);
    return ret;
+
68|    return ret;
  }
+
  69| }
  
 
Les six premiers paramètres de la fonction <tt>send_data</tt> sont identiques à ceux de la primitive système <tt>sendto</tt>, les deux derniers étant quant à eux identiques aux deux derniers arguments de la routine <tt>send_echo_request6</tt>.
 
Les six premiers paramètres de la fonction <tt>send_data</tt> sont identiques à ceux de la primitive système <tt>sendto</tt>, les deux derniers étant quant à eux identiques aux deux derniers arguments de la routine <tt>send_echo_request6</tt>.
  
Si la liste de relais est vide, on appelle <tt>sendmsg</tt> sans données auxiliaires (<tt>msg.msg_control</tt> est nul). Sinon on alloue (ligne [[See]] ) un tampon pour contenir les données auxiliaires.
+
Si la liste de relais est vide, on appelle <tt>sendmsg</tt> sans données auxiliaires (<tt>msg.msg_control</tt> est nul). Sinon on alloue (ligne 40) un tampon pour contenir les données auxiliaires.
  
 
La routine <tt>inet6_rth_space</tt> est l'une des six nouvelles routines proposées par l'API avancée afin de faciliter la tâche du programmeur lors de la manipulation des en-têtes de routage. Elle prend en arguments le type de l'extension de routage (en l'occurrence la constante <tt>IPV6_RTHDR_TYPE_0</tt> dont la valeur numérique est 0 est qui est définie dans <tt><netinet/in.h></tt>) et le nombre de relais contenus dans cette extension (pour ce type d'extension, ce nombre doit être compris entre 0 et 127 inclus). Elle retourne la taille en octets nécessaire pour contenir cette en-tête de routage. Ici cette routine va permettre d'initialiser, via la variable <tt>rthsp</tt> et à l'aide de la macro <tt>CMSG_SPACE</tt>, la variable <tt>cmsgspace</tt> à la taille en octets de la donnée auxiliaire associée à cette extension de routage.
 
La routine <tt>inet6_rth_space</tt> est l'une des six nouvelles routines proposées par l'API avancée afin de faciliter la tâche du programmeur lors de la manipulation des en-têtes de routage. Elle prend en arguments le type de l'extension de routage (en l'occurrence la constante <tt>IPV6_RTHDR_TYPE_0</tt> dont la valeur numérique est 0 est qui est définie dans <tt><netinet/in.h></tt>) et le nombre de relais contenus dans cette extension (pour ce type d'extension, ce nombre doit être compris entre 0 et 127 inclus). Elle retourne la taille en octets nécessaire pour contenir cette en-tête de routage. Ici cette routine va permettre d'initialiser, via la variable <tt>rthsp</tt> et à l'aide de la macro <tt>CMSG_SPACE</tt>, la variable <tt>cmsgspace</tt> à la taille en octets de la donnée auxiliaire associée à cette extension de routage.
  
En lignes [[See à See]] , la longueur des données auxiliaires et la structure <tt>cmsg</tt> sont initialisés au moyen de la macro <tt>CMSG_LEN</tt> pour le champ <tt>cmsg_len</tt>.
+
En lignes 45 à 47, la longueur des données auxiliaires et la structure <tt>cmsg</tt> sont initialisés au moyen de la macro <tt>CMSG_LEN</tt> pour le champ <tt>cmsg_len</tt>.
  
Il faut maintenant initialiser les données transmises par la donnée auxiliaire avec l'en-tête routage (lignes [[See à See]] ). Nous allons nous servir de la routine <tt>inet6_rth_init</tt> fournie par l'API avancée. Celle-ci prend en premier argument un pointeur vers la zone mémoire qui contiendra l'en-tête de routage, le deuxième argument étant la taille en octets de cette zone mémoire. Les deux derniers arguments sont identiques à ceux de la routine <tt>inet6_rth_space</tt>. <tt>inet6_rth_init</tt> retourne un pointeur vers cette zone mémoire ou le pointeur <tt>NULL</tt> si la taille de celle-ci est insuffisante.
+
Il faut maintenant initialiser les données transmises par la donnée auxiliaire avec l'en-tête routage (lignes 48 à 53). Nous allons nous servir de la routine <tt>inet6_rth_init</tt> fournie par l'API avancée. Celle-ci prend en premier argument un pointeur vers la zone mémoire qui contiendra l'en-tête de routage, le deuxième argument étant la taille en octets de cette zone mémoire. Les deux derniers arguments sont identiques à ceux de la routine <tt>inet6_rth_space</tt>. <tt>inet6_rth_init</tt> retourne un pointeur vers cette zone mémoire ou le pointeur <tt>NULL</tt> si la taille de celle-ci est insuffisante.
  
 
Après ces diverses initialisations, la donnée auxiliaire est représentée à la figure Initialisation de l'en-tête de routage où l'on a supposé, afin de fixer les idées, que l'on est en présence d'une architecture 32 bits et que l'alignement se fait sur 32 bits également (autrement dit il n'y a pas de bourrage entre la structure cmsg et le début des données transmises, cf. [[figure Structure des données auxiliaires]]).
 
Après ces diverses initialisations, la donnée auxiliaire est représentée à la figure Initialisation de l'en-tête de routage où l'on a supposé, afin de fixer les idées, que l'on est en présence d'une architecture 32 bits et que l'alignement se fait sur 32 bits également (autrement dit il n'y a pas de bourrage entre la structure cmsg et le début des données transmises, cf. [[figure Structure des données auxiliaires]]).
Line 197: Line 198:
 
[[image:CS197.gif]]
 
[[image:CS197.gif]]
  
Dans la boucle qui suit (lignes [[See à See]] ), l'initialisation de l'en-tête de routage se termine en ajoutant successivement les adresses IPv6 des relais du routage par la source. Ces ajouts se font au moyen de la fonction inet6_rth_add qui prend en premier argument la zone mémoire contenant l'en-tête de routage et en deuxième argument un pointeur (de type <tt>struct in6_addr *</tt>) vers l'adresse du relais à ajouter.
+
Dans la boucle qui suit (lignes 54 à 58), l'initialisation de l'en-tête de routage se termine en ajoutant successivement les adresses IPv6 des relais du routage par la source. Ces ajouts se font au moyen de la fonction <tt>inet6_rth_add</tt> qui prend en premier argument la zone mémoire contenant l'en-tête de routage et en deuxième argument un pointeur (de type <tt>struct in6_addr *</tt>) vers l'adresse du relais à ajouter.
  
 
A l'issue de cette boucle, si l'on reprend l'exemple qui nous a servi à présenter la nouvelle version de la commande one_ping6 :
 
A l'issue de cette boucle, si l'on reprend l'exemple qui nous a servi à présenter la nouvelle version de la commande one_ping6 :
Line 203: Line 204:
 
  $ '''xapi_ping6 www.kame.net relais.imag.fr ipv6.imag.fr'''
 
  $ '''xapi_ping6 www.kame.net relais.imag.fr ipv6.imag.fr'''
  
la donnée auxiliaire sera maintenant comme représentée à la figure Adjonction des deux relais dans l'en-tête de routage, (avec les mêmes hypothèses sur l'architecture et l'alignement). Le message ainsi construit est expédié tout en gérant les erreurs éventuelles54.
+
la donnée auxiliaire sera maintenant comme représentée à la figure Adjonction des deux relais dans l'en-tête de routage, (avec les mêmes hypothèses sur l'architecture et l'alignement). Le message ainsi construit est expédié tout en gérant les erreurs éventuelles (nous laissons le soin au lecteur l'adaptation de la fonction main afin de prendre en compte les nouveaux arguments (optionnels) du programme <tt>one_ping6</tt>).
  
 
[[image:CS198.gif]]
 
[[image:CS198.gif]]
  
On remarque que la donnée auxiliaire contient les adresses des relais intermédiaires, alors que dans un paquet IPv6, [[Les extensions#routage|l'en-tête de routage]] contient les adresses à partir du deuxième relais et l'adresse destination finale, l'adresse du premier relais étant dans l'en-tête IPv6. Le noyau lors du sendmsg va permuter les adresses pour rétablir l'ordre correct.
+
On remarque que la donnée auxiliaire contient les adresses des relais intermédiaires, alors que dans un paquet IPv6, [[Les extensions#routage|l'en-tête de routage]] contient les adresses à partir du deuxième relais et l'adresse destination finale, l'adresse du premier relais étant dans l'en-tête IPv6. Le noyau lors du <tt>sendmsg</tt> va permuter les adresses pour rétablir l'ordre correct.
  
 
===Portabilité du code===
 
===Portabilité du code===
  
Solaris définit des prototypes de <tt>sendmsg</tt> et <tt>recvmsg</tt> variables selon les modes de compilation. De plus, jusqu'à la version 9 incluse, il ne définit pas les macros <tt>CMSG_SPACE</tt> et <tt>CMSG_LEN</tt>. Les lignes [[See à See]] et [[See à See]] du programme [[(See )]] servent à éviter ces problèmes de compatibilité.
+
Solaris définit des prototypes de <tt>sendmsg</tt> et <tt>recvmsg</tt> variables selon les modes de compilation. De plus, jusqu'à la version 9 incluse, il ne définit pas les macros <tt>CMSG_SPACE</tt> et <tt>CMSG_LEN</tt>. Les lignes 1 à 4 et 13 à 19 du programme servent à éviter ces problèmes de compatibilité.
  
 
D'autre part, les fonctions <tt>inet6_rth_xxx</tt>, définies dans le RFC 3542 sont encore souvent absentes de la librairie système (c'est le cas pour Solaris 9, FreeBSD4.x, NetBSD1.x, et Linux). Le lecteur peut les remplacer par un codage à la main, ou récupérer leur texte, par exemple dans la distribution KAME.
 
D'autre part, les fonctions <tt>inet6_rth_xxx</tt>, définies dans le RFC 3542 sont encore souvent absentes de la librairie système (c'est le cas pour Solaris 9, FreeBSD4.x, NetBSD1.x, et Linux). Le lecteur peut les remplacer par un codage à la main, ou récupérer leur texte, par exemple dans la distribution KAME.
 +
{{suivi| L'implémentation | L'implémentation | Supervision | Supervision }}

Latest revision as of 13:52, 27 February 2006

L'implémentation Table des matières Supervision

Le programme one_ping6.c va être repris afin de lui ajouter deux fonctionnalités dont l'implémentation s'appuiera sur l'usage de données auxiliaires. On souhaite d'une part afficher le nombre de sauts (hop limit) du paquet ECHO_REPLY (éventuellement) reçu et d'autre part de permettre, à l'instar de la commande ping6, de passer une liste de relais par lesquels le paquet ECHO_REQUEST devra transiter avant d'être envoyé à l'hôte destinataire (routage par la source).

Par exemple, pour envoyer un paquet ECHO_REQUEST à la machine ipv6.imag.fr tout en transitant tout d'abord par les machines www.kame.net et relai.imag.fr, la commande xapi_ping6 sera :

$ xapi_ping6 www.kame.net relais.imag.fr ipv6.imag.fr
Sending ECHO REQUEST to: ipv6.imag.fr via:
www.kame.net
relais.imag.fr
Waiting for answer (timeout = 5s)...
Got answer from 2001:660:9510:25::632 (seq = 0, hoplimit = 241)

L'affichage du nombre de sauts a déjà été en grande partie traité dans l'exemple du paragraphe consacré à l'implémentation de l'API avancée. Nous indiquerons donc seulement les changements significatifs par rapport à la version originale. Ces changements concernent essentiellement la routine wait_for_echo_reply6. La première tâche à effectuer est, comme dans l'exemple précédent, de positionner l'option IPV6_RECVHOPLIMIT, juste après avoir mis en place le filtrage ICMPv6. L'instruction :

noc = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *) from, &from_len);

est remplacée par la nouvelle instruction :

noc = recv_data(sock, buf, sizeof(buf), 0, (struct sockaddr *) from, &from_len, &hoplimit);

hoplimit est un entier qui a été précédemment déclaré dans le corps de la fonction wait_for_echo_reply6 et recv_data a pour texte :


 1| #ifdef sun /* For Solaris */
 2| #define _XOPEN_SOURCE 500 /* correct recvmsg/sendmsg/msg/CMSG_xx syntax */
 3| #define __EXTENSIONS__
 4| #endif
 5| #include <stdio.h>
 6| #include <stdlib.h>
 7| #include <unistd.h>
 8| #include <sys/uio.h>
 9| #include <sys/types.h>
10| #include <sys/socket.h>
11| #include <netinet/in.h>
12| #include <netinet/ip6.h>
13| #ifndef CMSG_SPACE /* Solaris <= 9 */
14| #define CMSG_SPACE(l) ((size_t)_CMSG_HDR_ALIGN(sizeof (struct cmsghdr) + (l)))
15| #define CMSG_LEN(l) ((size_t)_CMSG_DATA_ALIGN(sizeof (struct cmsghdr)) + (l))
16| #endif
17|  
18| int recv_data(int sock, void *buf, int len, unsigned int flags,
19|               struct sockaddr *from, socklen_t *fromlen, int *hoplimit)
20| {
21| int ret, found = 0, cmsgspace = CMSG_SPACE(sizeof(int));
22| struct iovec iov = {buf, len};
23| struct cmsghdr *cmsg = (struct cmsghdr *) malloc(cmsgspace), *ptr;
24| struct msghdr msg = {
25|    (caddr_t) from, *fromlen, &iov, 1,
26|    (caddr_t) cmsg, cmsgspace
27| };
28|  
29| if (cmsg == NULL) {
30|    perror("recv_data: malloc");
31|    return -1;
32| }
33| ret = recvmsg(sock, &msg, flags);
34| if (ret < 0) {
35|    perror("recv_data: recvmsg");
36|    goto done;
37| }
38| if (msg.msg_flags & MSG_TRUNC) {
39|    fprintf(stderr, "recv_data: recvmsg: data discarded before delivery\n");
40|    goto bad;
41| }
42| if (msg.msg_flags & MSG_CTRUNC) {
43|    fprintf(stderr,
44|            "recv_data: recvmsg: control data lost before delivery\n");
45|    goto bad;
46| }
47| if (msg.msg_controllen)
48|    for (ptr = CMSG_FIRSTHDR(&msg); ptr; ptr = CMSG_NXTHDR(&msg, ptr)) {
49|       if (ptr->cmsg_level==IPPROTO_IPV6 && ptr->cmsg_type==IPV6_HOPLIMIT) {
50|          if (ptr->cmsg_len != CMSG_LEN(sizeof(int))) {
51|             fprintf(stderr,
52|                     "recvmsg: ancillary data with invalid length\n");
53|             goto bad;
54|          }
55|          *hoplimit = *((int *) CMSG_DATA(ptr));
56|          goto done;
57|       }
58|    }
59|    fprintf(stderr,
60|            "recv_data: recvmsg: hoplimit not found in ancillary data\n");
61|  bad:
62|    ret = -1;
63|  done:
64|    free(cmsg);
65|    return ret;
66| }

Le code de la fonction recv_data ne sera pas commenté car la réception du nombre de sauts via une donnée auxiliaire a été étudiée dans un précédent exemple, la seule différence étant que la gestion des erreurs est ici plus détaillée.

Il faut enfin modifier trivialement le code de la routine recv_icmp_pkt afin que celle-ci imprime le nombre de sauts du paquet ECHO_REPLY (éventuellement) reçu. Nous en laissons le soin au lecteur.

Pour l'autre donnée auxiliaire (cette fois ci en émission), à savoir le routage par la source, il faut naturellement tout d'abord modifier la fonction send_echo_request6 et en premier lieu son prototype qui devient :

int send_echo_request6(int sock, struct sockaddr_in6 *dst, uint16_t id,
                       uint16_t seq, char *opt_data, int opt_data_size,
                       struct in6_addr *seg, int nseg)

La routine send_echo_request6 modifié possède deux arguments supplémentaires ajoutés à la fin. Le premier de ces nouveaux arguments est un pointeur vers un tableau contenant les adresses IPv6 des relais par lesquels on souhaite effectuer le routage par la source. Le dernier argument est le nombre d'éléments de ce tableau, c'est-à-dire le nombre de relais.

Il faut ensuite substituer dans le corps de la fonction send_echo_request6 l'instruction suivante :

noc = sendto(sock, (char *) icmp, icmp_pkt_size, 0, (struct sockaddr *) dst,

par :

noc = send_data(sock, (void *) icmp, icmp_pkt_size, 0, 
                (struct sockaddr *) dst, sizeof(struct sockaddr_in6),
                seg, nseg);

Si la variable seg est le pointeur NULL, la liste des relais est vide. On fait appel à la fonction send_data, dont le code va être commenté en détails :


 1| #ifdef sun /* For Solaris */
 2| #define _XOPEN_SOURCE 500 /* correct recvmsg/sendmsg/msg/CMSG_xx syntax */
 3| #define __EXTENSIONS__
 4| #endif
 5| #include <stdio.h>
 6| #include <stdlib.h>
 7| #include <unistd.h>
 8| #include <sys/uio.h>
 9| #include <sys/types.h>
10| #include <sys/socket.h>
11| #include <netinet/in.h>
12| #include <netinet/ip6.h>
13| #ifndef CMSG_SPACE /* Solaris <= 9 */
14| #define CMSG_SPACE(l) ((size_t)_CMSG_HDR_ALIGN(sizeof (struct cmsghdr) + (l)))
15| #define CMSG_LEN(l) ((size_t)_CMSG_DATA_ALIGN(sizeof (struct cmsghdr)) + (l))
16| #endif
17| #ifndef IPV6_RECVHOPLIMIT
18| #define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
19| #endif
20|  
21| extern void * inet6_rth_init(); /* sometimes not in ip6.h */
22| 
23| int send_data(int sock, void *buf, int len, unsigned int flags,
24|               struct sockaddr *to, socklen_t tolen,
25|               struct in6_addr *seg, int nseg)
26| {
27| int ret = -1, rthsp, cmsgspace;
28| void *data;
29| struct in6_addr *in6;
30| struct iovec iov = {buf, len};
31| struct cmsghdr *cmsg = NULL;
32| struct msghdr msg = {
33|                     (caddr_t) to, tolen, &iov, 1,
34|                     NULL, 0, 0
35|                     };
36|  
37|    if (seg != NULL) {
38|       rthsp = inet6_rth_space(IPV6_RTHDR_TYPE_0, nseg);
39|       cmsgspace = CMSG_SPACE(rthsp);
40|       msg.msg_control = cmsg = (struct cmsghdr *) malloc(cmsgspace);
41|       if (cmsg == NULL) {
42|          perror("recv_data: malloc");
43|          goto bad;
44|       }
45|       cmsg->cmsg_level = IPPROTO_IPV6;
46|       msg.msg_controllen = cmsg->cmsg_len = CMSG_LEN(rthsp);
47|       cmsg->cmsg_type = IPV6_RTHDR;
48|       data = CMSG_DATA(cmsg);
49|       data = (void *)inet6_rth_init(data, rthsp, IPV6_RTHDR_TYPE_0, nseg);
50|       if (!data) {
51|          fprintf(stderr, "send_data: inet6_rth_init failed\n");
52|          goto bad;
53|       }
54|       for (in6 = seg; in6 - seg < nseg; in6++)
55|          if (inet6_rth_add(data, in6) == -1) {
56|             fprintf(stderr, "send_data: inet6_rth_add failed\n");
57|             goto bad;
58|          }
59|    }
60|    ret = sendmsg(sock, &msg, flags);
61|    if (ret < 0) {
62|       perror("send_data: sendmsg");
63|       goto bad;
64|    }
65| bad:
66|    if (cmsg)
67|    free(cmsg);
68|    return ret;
69| }

Les six premiers paramètres de la fonction send_data sont identiques à ceux de la primitive système sendto, les deux derniers étant quant à eux identiques aux deux derniers arguments de la routine send_echo_request6.

Si la liste de relais est vide, on appelle sendmsg sans données auxiliaires (msg.msg_control est nul). Sinon on alloue (ligne 40) un tampon pour contenir les données auxiliaires.

La routine inet6_rth_space est l'une des six nouvelles routines proposées par l'API avancée afin de faciliter la tâche du programmeur lors de la manipulation des en-têtes de routage. Elle prend en arguments le type de l'extension de routage (en l'occurrence la constante IPV6_RTHDR_TYPE_0 dont la valeur numérique est 0 est qui est définie dans <netinet/in.h>) et le nombre de relais contenus dans cette extension (pour ce type d'extension, ce nombre doit être compris entre 0 et 127 inclus). Elle retourne la taille en octets nécessaire pour contenir cette en-tête de routage. Ici cette routine va permettre d'initialiser, via la variable rthsp et à l'aide de la macro CMSG_SPACE, la variable cmsgspace à la taille en octets de la donnée auxiliaire associée à cette extension de routage.

En lignes 45 à 47, la longueur des données auxiliaires et la structure cmsg sont initialisés au moyen de la macro CMSG_LEN pour le champ cmsg_len.

Il faut maintenant initialiser les données transmises par la donnée auxiliaire avec l'en-tête routage (lignes 48 à 53). Nous allons nous servir de la routine inet6_rth_init fournie par l'API avancée. Celle-ci prend en premier argument un pointeur vers la zone mémoire qui contiendra l'en-tête de routage, le deuxième argument étant la taille en octets de cette zone mémoire. Les deux derniers arguments sont identiques à ceux de la routine inet6_rth_space. inet6_rth_init retourne un pointeur vers cette zone mémoire ou le pointeur NULL si la taille de celle-ci est insuffisante.

Après ces diverses initialisations, la donnée auxiliaire est représentée à la figure Initialisation de l'en-tête de routage où l'on a supposé, afin de fixer les idées, que l'on est en présence d'une architecture 32 bits et que l'alignement se fait sur 32 bits également (autrement dit il n'y a pas de bourrage entre la structure cmsg et le début des données transmises, cf. figure Structure des données auxiliaires).

CS197.gif

Dans la boucle qui suit (lignes 54 à 58), l'initialisation de l'en-tête de routage se termine en ajoutant successivement les adresses IPv6 des relais du routage par la source. Ces ajouts se font au moyen de la fonction inet6_rth_add qui prend en premier argument la zone mémoire contenant l'en-tête de routage et en deuxième argument un pointeur (de type struct in6_addr *) vers l'adresse du relais à ajouter.

A l'issue de cette boucle, si l'on reprend l'exemple qui nous a servi à présenter la nouvelle version de la commande one_ping6 :

$ xapi_ping6 www.kame.net relais.imag.fr ipv6.imag.fr

la donnée auxiliaire sera maintenant comme représentée à la figure Adjonction des deux relais dans l'en-tête de routage, (avec les mêmes hypothèses sur l'architecture et l'alignement). Le message ainsi construit est expédié tout en gérant les erreurs éventuelles (nous laissons le soin au lecteur l'adaptation de la fonction main afin de prendre en compte les nouveaux arguments (optionnels) du programme one_ping6).

CS198.gif

On remarque que la donnée auxiliaire contient les adresses des relais intermédiaires, alors que dans un paquet IPv6, l'en-tête de routage contient les adresses à partir du deuxième relais et l'adresse destination finale, l'adresse du premier relais étant dans l'en-tête IPv6. Le noyau lors du sendmsg va permuter les adresses pour rétablir l'ordre correct.

Portabilité du code

Solaris définit des prototypes de sendmsg et recvmsg variables selon les modes de compilation. De plus, jusqu'à la version 9 incluse, il ne définit pas les macros CMSG_SPACE et CMSG_LEN. Les lignes 1 à 4 et 13 à 19 du programme servent à éviter ces problèmes de compatibilité.

D'autre part, les fonctions inet6_rth_xxx, définies dans le RFC 3542 sont encore souvent absentes de la librairie système (c'est le cas pour Solaris 9, FreeBSD4.x, NetBSD1.x, et Linux). Le lecteur peut les remplacer par un codage à la main, ou récupérer leur texte, par exemple dans la distribution KAME.

L'implémentation Table des matières Supervision
Personal tools