Petit manuel de passage d'IPv4 à IPv6

(Module SOI)


Vue d'ensemble

L'API socket proprement dite

Structures de données dédiées aux adresses

Conversion des noms en adresses

Fonctions de formatage des adresses

Conclusion


Vue d'ensemble

Nous allons nous intéresser dans ce qui suit à l'interface de programmation constituant le standard de fait sous UNIX et dans bon nombre d'autres plates-formes, l'API socket.

Comme nous l'avons vu l'adressage à changé dans IPv6 par rapport à IPv4. Plus particulièrement, sa taille a augmenté, or les API sockets requièrent de l'utilisateur qu'il ait une connaissance de la taille de ces adresses dans de nombreux cas ce qui impose des modifications du code des applications anciennes. De plus les nouvelles caractéristiques disponibles ne peuvent être exploitées qu'en connaissance de cause.

Les changements apportés aux APIs ont été pensés de manière à simplifier cette migration. Il est néanmoins conseillé, tant que faire se peut, de faire cette évolution en n'utilisant que les fonctionnalités qui permettent la communication avec des hôtes IPv6 ou IPv4 de manière indifférente.

Il faut donc modifier les parties suivantes de l'API socket

Le noyau de l'API socket proprement dit

A la création d'une socket, il faut spécifier PF_INET6 comme famille de socket. Dès lors les autres fonctions de l'API (bind, connect, sendmsg, sendto, accept, recvfrom, recvmsg, getpeername, getsockname) savent à quelle type de socket, elles ont affaire et s'adaptent en conséquence.

D'autre part, setsockopt offre de nouvelles options IPV6_* sont toutes valables au niveau IPPROTO_IPV6. Entre autre, on peut changer le type d'une socket grâce à l'option IPV6_ADDFORM. IPV6_UNICAST_HOPS permet quant à elle de limiter la portée des paquets qui sont envoyés via cette socket. Les options multicast sont encore disponibles sous leur forme IPV6_* mais on peut désormais s'en passer, puisqu'ils sont inclus dans l'adresse.

Structures de données dédiées aux adresses

Les structures d'adresses définies dans <netinet/in.h> que l'on doit utiliser ne sont plus sockaddr_in mais sockaddr_in6 et in_addr devient in6_addr.

On doit particulièrement faire attention au changement de taille de sockaddr_in qui était traditionnellement de 16 octets et qui est désormais avec sockaddr_in6 de 24 octets. Les programmeurs avaient en effet l'habitude d'en présumer à tel point que les implémentations qui ne l'assurait pas normalement mettaient des zéros de bourrages à l'intérieur pour maintenir cette longueur.

Les nouvelles structures sont désormais, sur les systèmes compatibles 4.3BSD :

struct sockaddr_in6 {
    u_short sin6_family;       /* AF_INET6 */
    u_short sin6_port;         /* Transport layer port # */
    u_long sin6_flowinfo;      /* IPv6 flow label */
    struct in_addr6 sin6_addr; /* IPv6 address */
};
et
struct in6_addr {
    u_char s6_addr[16];        /* IPv6 address */
};

Nous remarquons que le champ sin6_flowinfo a été rajouté pour coder deux informations, 24 bits pour les étiquettes de flux et 4 bits pour la priorité.

Une nouvelle fonction qui n'est pour le moment pas définitivement figée, permet de récupérer des informations sur les capacités de noeuds dans le réseau :

int getaddrinfo(const char *hostname, const char *servname, const
struct addrinfo * hints, struct addrinfo **res);

struct addrinfo {
    int ai_flags;             /* AI_PASSIVE, AI_CANONNAME*/
    int ai_family;            /* PF_* */
    int ai_socktype;          /* SOCK_* */
    int ai_protocol;          /* 0 ou IPPROTO_* pour IPv4 et IPv6 */
    size_t ai_addrlen;        /* longueur de ai_addr */
    char *ai_canonname;       /* nom canonique pour hostname */
    struct sockaddr *ai_addr; /* adresse binaire */
    struct addrinfo *ai_next; /* pointeur sur la suivante */
};

hostname et servname sont des chaînes de caractères fournissant les noms des machines dont on veut obtenir des informations. Dans une optique serveur traditionnelle on ne remplira que le champ servname tandis que dans l'optique client, on remplit les deux champs servname et hostname pour savoir quelles est leur compatibilité. hints est un pointeur sur addrinfo qui contient des indices permettant de trouver de manière optimale les informations voulues, il donne des précisions sur les capacités de l'appelant. res permet de récupérer l'adresse d'une liste chaînée qui contient les informations demandées.

Un exemple pour une machine ne supportant que TCP :

{
struct addrinfo hints = {0,0,SOCK_STREAM,0,0,NULL,NULL,NULL};
struct addrinfo *res;
getaddrinfo(hostname, servname, &hints, &res);
...
}

Conversion des noms en adresses

La fonction de résolution de noms struct hostent *gethostbyname(const char *name) subsiste même si son implémentation a changé ce qui est transparent pour le programmeur, de même la structure hostent reste inchangée.

Une nouvelle fonction a été ajoutée pour prendre en compte les nouvelles et les anciennes adresses :

struct hostent *gethostbyname2(const char *name, int af). La valeur de af peut être AF_INET ou AF_INET6 et dans le premier cas on recherche une adresse IPv4 alors que dans le second on retourne une adresse IPv6. D'autre part une nouvelle option à appliquer au champ option de la structure _res : RES_USE_INET6.

Si cette option est activée gethostbyname appelle gethostbyname2 dans ses deux modes disponibles, sinon elle se comporte comme avant. Le tableau suivant résume le fonctionnement de la résolution de noms dans tous les cas suscités :

 
 

RES_USE_INET6 off

RES_USE_INET6 on

gethostbyname (host) elle renvoie une adresse IPv4 sinon une erreur elle renvoie une adresse IPv6 et si elle n’en trouve pas elle renvoie une adresse IPv4 mapped IPv6 sinon une erreur
gethostbyname2(host,AF_INET) elle renvoie une adresse IPv4 sinon une erreur elle renvoie une adresse IPv4-mapped IPv6 sinon une erreur
gethostbyname2(host,AF_INET6) elle renvoie une adresse IPv6 sinon une erreur elle renvoie une adresse IPv6 sinon une erreur

la fonction inverse gethostbyaddr ne change pas puisqu’elle contient déjà un argument af et il suffit de lui donner la valeur AF_INET6 pour qu'elle retourne des noms correspondant à des adresses IPv6.

Fonctions de formatage des adresses

Les fonctions inet_addr et inet_ntoa sont utilisées en IPv4 pour convertir des adresses entre les formes binaires et texte imprimable.

IPv6 s'est doté d'outils similaires qui convertissent à la fois des adresses IPv4 et IPv6 :

int inet_pton(int af, const char *src, void *dst) et const char *inet_ntop(int af, const void *src, char *dst, size_t size). inet_pton convertit une adresse texte vers une forme binaire.. La variable af spécifie le type d'adresse utilisé IPv4 ou IPv6, src est une chaine de caratères contenant la représentation imprimable de l'adresse à convertir en binaire, et dst recevra la forme binaire après conversion. Il faut prendre garde à assurer une taille suffisante à dst, dans le premier cas, pour que toutes les adresses puissent y entrer. Elle retourne 1 si la conversion marche, 0 si l'adresse n'est pas valide et -1 si af est inconnu

inet_ntop convertit une forme binaire vers une adresse texte. La variable af spécifie le type d'adresse utilisé IPv4 ou IPv6, src est un tableau d'octets contenant la représentation binaire de l'adresse à convertir en ascii, dst recevra la forme texte après conversion, et size contient la taille du tableau d'octets src. Elle retourne NULL en cas d'échec ou le pointeur sur la chaîne résultante qui a la même valeur que dst.

Exemple :

void exemple (char *printable_form) {
    int result;
    char return[16];
    char ascii_buffer[BUFSIZE];

    result = inet_pton(AF_INET6, printable_form, return);
    switch(result) {
        case 0 : printf("  inet_pton : adresse invalide \n");

        case 1 : printf(" inet_pton : conversion réussie \n");

        case -1: printf(" inet_pton : af inconnu \n");
    }
    result = (int) inet_ntop(AF_INET6, return, ascii_buffer, sizeof(ascii_buffer));
    if (result == 0)
        printf(" inet_ntop : échec \n ");
    else
        printf(" %s\n ",ascii_buffer);
}
 

Conclusion

La migration des programmes d'IPv4 vers IPv6 est relativement simple dans la mesure où cette nouvelle version du protocole a été conçue avec cette préoccupation à l'esprit. La nouvelle API socket a été pensée de manière a introduire le moins de modifications possibles par rapport aux versions IPv4 tout en assurant au maximum l'interopérabilité entre les deux versions du protocole.

Néanmoins il convient de faire attention aux nombreuses modifications des structures utilisées qui impliquent pour le programmeur des modifications de ses habitudes, et qui lui imposent de réviser toutes les suppositions implicites faites sur les particularités de celles-ci (par exemple de prendre garde au fait que sizeof(struct sockaddr) est différent de sizeof(struct sockaddr_in6) alors qu'il sont de même taille en IPv4).


François Beillouin, Frédéric Godefroy --