Quelle est la bonne façon de convertir une struct sockaddr * en struct sockaddr_in6 * avec du code C valide?

Voici un programme simple qui montre comment, normalement, nous transtypons cast struct sockaddr * en struct sockaddr_in * ou struct sockaddr_in6 * en écrivant des programmes de socket.

 #include  #include  #include  #include  #include  #include  int main() { struct addrinfo *ai; printf("sizeof (struct sockaddr): %zu\n", sizeof (struct sockaddr)); printf("sizeof (struct sockaddr_in): %zu\n", sizeof (struct sockaddr_in)); printf("sizeof (struct sockaddr_in6): %zu\n", sizeof (struct sockaddr_in6)); if (getaddrinfo("localhost", "http", NULL, &ai) != 0) { printf("error\n"); return EXIT_FAILURE; } if (ai->ai_family == AF_INET) { struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr; printf("IPv4 port: %d\n", addr->sin_port); } else if (ai->ai_family == AF_INET6) { struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai->ai_addr; printf("IPv6 port: %d\n", addr->sin6_port); } return 0; } 

Le Guide de programmation en réseau de Beej le recommande également à la page 10.

Pour faire face à struct sockaddr, les programmeurs ont créé une structure parallèle: struct sockaddr_in («in» pour «Internet») à utiliser avec IPv4.

Et c’est le bit important: un pointeur sur une structure sockaddr_in peut être converti en un pointeur sur une structure sockaddr et inversement. Donc, même si connect () veut une struct sockaddr *, vous pouvez quand même utiliser une struct sockaddr_in et la lancer à la dernière minute!

Mais d’après la discussion à une autre question , il apparaît qu’il s’agit simplement d’un code C valide, non valide, conformément à la norme C.

En particulier, voir la réponse de AnT qui mentionne,

En ce qui concerne la technique populaire avec les conversions entre struct sockaddr *, struct sockaddr_in * et struct sockaddr_in6 * – ce ne sont que des bidouilles qui n’ont rien à voir avec le langage C. Ils travaillent simplement dans la pratique, mais en ce qui concerne le langage C, la technique est invalide.

Donc, si cette technique que nous utilisons pour faire de la programmation de socket (et ce qui est également recommandé par les livres) n’est pas valide, quel est le moyen de réécrire valide le code ci-dessus afin qu’il soit également un code C valide conformément au standard C?

Donc, si la façon dont nous procédons à la programmation des sockets (et ce qui est également recommandé par les livres) est un hack, quelle est la bonne façon de réécrire le code ci-dessus afin qu’il soit également un code C valide conformément au standard C?

TL; DR: continuez à faire ce que vous présentez dans votre exemple.

Le code que vous avez présenté semble être syntaxiquement correct. Il peut ou non présenter un comportement indéfini dans certaines circonstances. Cela dépend du comportement de getaddrinfo() .

Il n’existe aucun moyen de le faire en C qui réponde à toutes les exigences fonctionnelles et soit mieux protégé contre les comportements indéfinis que la technique standard que vous avez présentée. C’est pourquoi c’est la technique standard. Le problème ici est que la fonction doit prendre en charge tous les types d’adresses concevables, y compris les types qui n’ont pas encore été définis. Il pourrait déclarer le pointeur d’adresse de socket comme un void * , ce qui ne nécessiterait pas de transtypage, mais cela ne changerait rien au fait qu’un programme donné présente un comportement non défini.

Pour sa part, getaddrinfo() est conçu avec ce type d’utilisation en tête. Le problème est donc que l’utilisation de la getaddrinfo() attendue sur le résultat autorise un comportement incorrect. De plus, getaddrinfo() ne fait pas partie de la bibliothèque du standard C, il est normalisé (uniquement) par POSIX, qui intègre également le standard C. L’parsing de cette fonction à la lumière de C seul démontre donc un hyperfocus inapproprié. Bien que les conversions soulèvent certaines inquiétudes à la lumière de C seulement, vous devriez vous attendre à ce que, dans le contexte de getaddrinfo() et d’autres fonctions réseau POSIX utilisant struct sockaddr * , le transtypage vers le type d’adresse spécifique correct et l’access à l’object référencé produisent des résultats fiables.

De plus, je pense que la réponse de AnT à votre autre question est trop simpliste et trop négative. J’envisage de rédiger une réponse contrastée.

La norme POSIX garantit qu’un pointeur sur n’importe quel type de socket peut être struct sockaddr* . Ainsi, vous pouvez convertir un pointeur sur n’importe quel type de socket vers une struct sockaddr* pour l’utiliser dans bind() ou connect() ; la bibliothèque sait quels bits vérifier. Vous pouvez également vérifier le champ sa_family de votre socket pour voir ce qu’il en est réellement, en supposant qu’il contienne des données valides, puis transtyper vers le type de pointeur approprié. Si vous devez allouer un assez grand bloc de mémoire pour stocker en toute sécurité tout type de socket, utilisez sockaddr_storage . Un transtypage d’un sockaddr_storage* vers un autre pointeur de sockets est garanti pour s’aligner correctement et le champ contenant la famille de sockets est garanti pour continuer à fonctionner.

Pour obtenir un socket IPv6 à partir de sockaddr_in , vous pouvez convertir l’adresse IPv4 en notation IPv6 et utiliser getaddrinfo() . Cependant, les fonctions de recherche modernes vous donnent probablement une liste chaînée comprenant à la fois un socket IPv4 et IPv6.

La réponse est dans man getaddrinfo et sys/socket.h . man getaddrinfo fournit la raison derrière en utilisant une struct sockaddr commune: struct sockaddr :

 Given node and service, which identify an Internet host and a service, getaddrinfo() returns one or more addrinfo structures, each of which contains an Internet address that can be specified in a call to bind(2) or connect(2). The getaddrinfo() function combines the functionality provided by the gethostbyname(3) and getservbyname(3) functions into a single interface, but unlike the latter functions, getaddrinfo() is reentrant and allows programs to eliminate IPv4-versus-IPv6 dependencies. 

Il n’y a qu’un seul struct sockaddr . Il semble que les différents types sont tous simplement utilisés dans une union transparente pour fournir toute struct sockaddr_X nécessaire. Par exemple:

 /* This is the type we use for generic socket address arguments. With GCC 2.7 and later, the funky union causes redeclarations or uses with any of the listed types to be allowed without complaint. G++ 2.7 does not support transparent unions so there we want the old-style declaration, too. */ #if defined __cplusplus || !__GNUC_PREREQ (2, 7) || !defined __USE_GNU # define __SOCKADDR_ARG struct sockaddr *__ressortingct # define __CONST_SOCKADDR_ARG const struct sockaddr * #else /* Add more `struct sockaddr_AF' types here as necessary. These are all the ones I found on NetBSD and Linux. */ # define __SOCKADDR_ALLTYPES \ __SOCKADDR_ONETYPE (sockaddr) \ __SOCKADDR_ONETYPE (sockaddr_at) \ __SOCKADDR_ONETYPE (sockaddr_ax25) \ __SOCKADDR_ONETYPE (sockaddr_dl) \ __SOCKADDR_ONETYPE (sockaddr_eon) \ __SOCKADDR_ONETYPE (sockaddr_in) \ __SOCKADDR_ONETYPE (sockaddr_in6) \ __SOCKADDR_ONETYPE (sockaddr_inarp) \ __SOCKADDR_ONETYPE (sockaddr_ipx) \ __SOCKADDR_ONETYPE (sockaddr_iso) \ __SOCKADDR_ONETYPE (sockaddr_ns) \ __SOCKADDR_ONETYPE (sockaddr_un) \ __SOCKADDR_ONETYPE (sockaddr_x25) # define __SOCKADDR_ONETYPE(type) struct type *__ressortingct __##type##__; typedef union { __SOCKADDR_ALLTYPES } __SOCKADDR_ARG __atsortingbute__ ((__transparent_union__)); # undef __SOCKADDR_ONETYPE # define __SOCKADDR_ONETYPE(type) const struct type *__ressortingct __##type##__; typedef union { __SOCKADDR_ALLTYPES } __CONST_SOCKADDR_ARG __atsortingbute__ ((__transparent_union__)); # undef __SOCKADDR_ONETYPE #endif 

Je n’ai pas encore mangé toute la macro-soupe, mais il semble que vous soyez en sécurité avec l’un ou l’autre type.

Référence à ce lien et à l’autre lien Est-il légal de typer des pointeurs de différents types de structure (par exemple, struct sockaddr * à struct sockaddr_in6 *)? . Ce ne sont pas exactement des hacks. Pour faire ce que vous voulez faire, si j’ai bien compris, je ferais quelque chose comme:

 struct base { int a; char b; double *n; } struct derived { struct base b; //(no pointer, but the whole struct) int c; int d; } 

De cette manière, lorsque vous transformez le dérivé en base, vous êtes certain que les n premiers octets du dérivé recouvrent exactement la base. Le code fonctionne et est entièrement portable. Différents problèmes différentes solutions. En fait, dans mon expérience, j’ai toujours préféré que la base contienne des dérivés, et non pas l’inverse. Donc, avoir une structure “polymorphe”. Mais 1) si cela fonctionne, 2) les gens vont lire le code va comprendre 3) vous vous sentez utile … pourquoi pas? tout dépend de toi. Probablement c ++ implémente le hinerhitance exactement de cette façon! qui peut le dire?
Il suffit de faire attention à la gamme d’entre eux, d’indexer avec le bon type et d’être toujours à la première place. (mais C ++ a aussi des problèmes avec le tableau d’objects polymorphes, il ne peut en faire que des pointeurs)