Difference between revisions of "L'implémentation"

From Livre IPv6

m
Line 1: Line 1:
 +
{{suivi| Programmation avancée | Programmation avancée | L'exemple « mini-ping » revisité | L'exemple « mini-ping » revisité }}
 
Comme indiqué précédemment, l'implémentation de l'API avancée repose principalement sur les primitives <tt>sendmsg</tt> et <tt>recvmsg</tt> dont les prototypes sont les suivants :
 
Comme indiqué précédemment, l'implémentation de l'API avancée repose principalement sur les primitives <tt>sendmsg</tt> et <tt>recvmsg</tt> dont les prototypes sont les suivants :
  

Revision as of 13:07, 27 February 2006

Programmation avancée Table des matières L'exemple « mini-ping » revisité

Comme indiqué précédemment, l'implémentation de l'API avancée repose principalement sur les primitives sendmsg et recvmsg dont les prototypes sont les suivants :

int sendmsg(int s, const struct msghdr *msg, int flags);
int recvmsg(int s, struct msghdr *msg, unsigned int flags);

Le premier paramètre s désigne le descripteur d'E/S associée à la socket et le dernier paramètre flags est identique au 3ème paramètre des primitives sendto et recvfrom. Le second paramètre est une structure définie (dans <sys/socket.h>) comme suit :

struct msghdr {
   void *msg_name;           /* pointeur vers l'adresse de la socket */
   socklen_t msg_namelen;    /* longueur de l'adresse de la socket */
   struct iovec *msg_iov;    /* tampon mémoire vectoriel (scatter/gather array) */
   int msg_iovlen;           /* nombre d'éléments de msg_iov */
   void *msg_control;        /* données auxiliaires */
   socklen_t msg_controllen; /* longueur des données auxiliaires */
   int msg_flags;            /* drapeaux des messages reçus */
};

Les deux premiers champs spécifient pour sendmsg (respectivement recvmsg) l'adresse de destination (respectivement d'origine). Le premier champ peut être le pointeur NULL en mode connecté. Les deux champs suivants contiennent le tampon mémoire vectoriel en émission ou en réception suivant le cas (voir le manuel de la primitive readv).

Les champs msg_control et msg_controllen spécifient le tableau des données auxiliaires reçues ou émises, le champ msg_control pouvant être le pointeur NULL s'il n'y a aucune donnée auxiliaire à émettre ou recevoir. Chaque donnée auxiliaire se présente sous la forme d'une structure de type struct cmsghdr définie (dans sys/socket.h) :

struct cmsghdr {
   socklen_t cmsg_len; /* longueur en octet, en-tête inclus */
   int cmsg_level;     /* protocole (IPPROTO_IPV6, ...) */
   int cmsg_type;      /* sous-type dans le protocole (IPV6_RTHDR, ...) */
                       /* suivi par unsigned char cmsg_data[]; */
};

En raison de problèmes d'alignement (cf. figure Structure des données auxiliaires), l'accès au tableau des données auxiliaires ainsi que la manipulation de ces dernières ne doivent se faire qu'au moyen de cinq macros appropriées, définies dans <sys/socket.h> :

CS196.gif

  • struct cmsghdr *CMSG_FIRSTHDR(const struct msghdr *msgh);
    CMSG_FIRSTHDR renvoie un pointeur vers la première donnée auxiliaire contenue dans la structure de type struct msghdr pointée par msgh.
  • struct cmsghdr *CMSG_NXTHDR(const struct msghdr *msgh, const struct cmsghdr *cmsg);
    CMSG_NXTHDR renvoie un pointeur vers la donnée auxiliaire qui suit celle pointée par cmsg ou le pointeur NULL s'il n'y en a pas. Si cmsg est le pointeur NULL, CMSG_NXTHDR renvoie un pointeur vers la première donnée auxiliaire. Ainsi, CMSG_NXTHDR(msgh, NULL) est équivalent à CMSG_FIRSTHDR(msgh).
  • socklen_t CMSG_SPACE(socklen_t length);
    CMSG_SPACE renvoie le nombre d'octets occupés par une donnée auxiliaire dont la taille des données transmises est length, tout en tenant compte des alignements.
  • socklen_t CMSG_LEN(socklen_t length);
    CMSG_LEN retourne la valeur à stocker dans le champ cmsg_len de la structure (de type struct cmsghdr) associée à une donnée auxiliaire dont la taille des données transmises est length, ceci en tenant compte des alignements.
  • unsigned char *CMSG_DATA(const struct cmsghdr *cmsg);
    CMSG_DATA retourne un pointeur vers les données contenues dans la donnée auxiliaire pointée par le paramètre cmsg.

Le dernier champ msg_flags de la structure msghdr est rempli au retour de recvmsg(). Plusieurs drapeaux peuvent avoir été levés dont le drapeau MSG_TRUNC pour indiquer que les données ont été tronquées ou le drapeau MSG_CTRUNC pour indiquer que les données auxiliaires ont été tronquées.

Afin de recevoir toute donnée auxiliaire sur une socket, il faut auparavant le demander en positionnant l'option correspondante. Plus précisément, le RFC 3542 liste de manière exhaustive des options disponibles et comment les positionner :

int on = 1;
/* interface de réception / adresse destination */
setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on));
/* nombre de sauts */
setsockopt(s, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on));
/* en-tête de routage */
setsockopt(s, IPPROTO_IPV6, IPV6_RECVRTHDR, &on, sizeof(on));
/* options proche-en-proche */
setsockopt(s, IPPROTO_IPV6, IPV6_RECVHOPOPTS, &on, sizeof(on));
/* option destination */
setsockopt(s, IPPROTO_IPV6, IPV6_RECVDSTOPTS, &on, sizeof(on));
/* classe de trafic */
setsockopt(s, IPPROTO_IPV6, IPV6_RECVTCLASS, &on, sizeof(on));

En ce qui concerne l'émission d'une donnée auxiliaire, deux possibilités s'offrent au programmeur :

  • soit il fait appel à la primitive setsockopt pour positionner l'option correspondante avec les données adéquates. Ce sont alors des options dites permanentes (sticky) car elles s'appliquent à tous les paquets transmis par la suite et ce jusqu'à un nouvel appel à setsockopt ou une surcharge par une donnée auxiliaire.
  • soit il utilise sendmsg et les données auxiliaires affectent uniquement le datagramme concerné (non applicable au socket de type SOCK_STREAM)

Le tableau Options de données auxiliaires en émission, extrait du RFC 3542, donne la liste des options disponibles en émission (avec leur type de données associées) :

Options de données auxiliaires en émission
opt level / cmsg_level optname / cmsg_type optval / cmsg_data[]
IPPROTO_IPV6 IPV6_PKTINFO structure in6_pktinfo
IPPROTO_IPV6 IPV6_HOPLIMIT int
IPPROTO_IPV6 IPV6_NEXTHOP structure sockaddr_in6
IPPROTO_IPV6 IPV6_RTHDR structure ip6_rthdr
IPPROTO_IPV6 IPV6_HOPOPTS (prochain saut / next hop) structure ip6_hbh
IPPROTO_IPV6 IPV6_DSTOPTS structure ip6_dest
IPPROTO_IPV6 IPV6_RTHDRDSTOPTS structure ip6_dest
IPPROTO_IPV6 IPV6_TCLASS int

Les options proposées par cette API avancée, ne seront pas toutes détaillées dans ce chapitre. Nous recommandons au lecteur intéressé de se reporter au RFC 3542. L'exemple simple qui suit, met en oeuvre ces notions. Il s'agit d'un extrait de programme qui, outre les données habituelles, souhaite recevoir également deux données auxiliaires :

  • index de l'interface de réception du paquet / adresse destination du paquet reçu (option IPV6_RECVPKTINFO) et
  • le nombre de sauts (hop limit) du paquet reçu (option IPV6_RECVHOPLIMIT).

La première partie de ce programme, où les variables sont déclarées et initialisées ne présente aucune difficulté. On notera l'usage de la macro CMSG_SPACE afin d'initialiser la variable cmsg_buf destinée à accueillir les données auxiliaires demandées.

int noc, o = 1, s;
struct sockaddr_storage from;
char buf[1024];
struct iovec iov = {buf, sizeof(buf)};
char cmsg_buf[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))];
struct cmsghdr *cmsg;
struct msghdr msg = {(void *) &from, sizeof(from), &iov, 1,
                     (void *) cmsg_buf, sizeof(cmsg_buf), 0};

Actuellement, un grand nombre d'implémentations ne sont pas à jour du RFC 3542 (bien que publié en mai 2003). En particulier, certaines implémentations ne distinguent toujours pas entre les options de réception et les options d'émission. Si bien qu'il peut être nécessaire d'ajouter les lignes suivantes :

#ifndef IPV6_RECVHOPLIMIT
#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
#endif
#ifndef IPV6_RECVPKTINFO
#define IPV6_RECVPKTINFO IPV6_PKTINFO
#endif

Ensuite on indique par l'intermédiaire de la primitive setsockopt que les données auxiliaires mentionnées plus haut doivent être reçues. La variable s est un descripteur d'entrées/sorties associé à une socket PF_INET6.

if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &o, sizeof(o)) ||
   setsockopt(s, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &o, sizeof(o))) {
     /* traitement de l'erreur */
}

La primitive recvmsg est exécutée et les erreurs éventuelles sont traitées :

if ((noc = recvmsg(s, &msg, 0)) < 0) {
/* traitement de l'erreur */
}
if (msg.msg_flags & MSG_TRUNC) {
/* traitement de l'erreur (les données sont tronquées) */
}
if (msg.msg_flags & MSG_CTRUNC) {
/* traitement de l'erreur (les données auxiliaires sont tronquées) */
}

Finalement, au moyen des macros CMSG_FIRSTHDR et CMSG_NXTHDR précédemment décrites, une boucle traite les données auxiliaires reçues :

for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
   if ((cmsg->cmsg_level == IPPROTO_IPV6) && (cmsg->cmsg_type == IPV6_PKTINFO)) {
      struct in6_pktinfo *pi = (struct in6_pktinfo *) CMSG_DATA(cmsg);
      /* suite du traitement */
   }
   if ((cmsg->cmsg_level == IPPROTO_IPV6) && (cmsg->cmsg_type == IPV6_HOPLIMIT)) {
       int hlim = *(int *)CMSG_DATA(cmsg);
       /* suite du traitement */
   }
/* suite du programme */
}
Personal tools