Raisonnement derrière les sockets C sockaddr et sockaddr_storage

Je regarde les fonctions telles que connect() et bind() dans les sockets C et remarquez qu’elles amènent un pointeur sur une structure sockaddr . J’ai lu et pour rendre votre application indépendante de AF, il est utile d’utiliser le pointeur de structure sockaddr_storage et de le sockaddr un pointeur sockaddr cause de tout l’espace supplémentaire dont il dispose pour les adresses plus grandes.

Ce que je me demande, c’est comment des fonctions comme connect() et bind() qui demandent un pointeur sockaddr vont accéder aux données d’un pointeur qui pointe vers une structure plus grande que celle à laquelle il s’attend. Bien sûr, vous lui transmettez la taille de la structure que vous lui fournissez, mais quelle est la syntaxe réelle utilisée par les fonctions pour extraire l’adresse IP des pointeurs vers des structures plus grandes que vous avez converties en struct *sockaddr ?

C’est probablement parce que je viens de langages OOP, mais cela semble être un peu un bidouillage et un peu de désordre.

Les fonctions qui attendent un pointeur sur struct sockaddr probablement le pointeur que vous les envoyez à sockaddr lorsque vous leur envoyez un pointeur sur struct sockaddr_storage . De cette façon, ils y accèdent comme s’il s’agissait d’un struct sockaddr .

struct sockaddr_storage est conçu pour tenir dans une struct sockaddr_in et struct sockaddr_in6

Vous ne créez pas votre propre struct sockaddr , vous créez généralement une struct sockaddr_in ou une struct sockaddr_in6 selon la version IP que vous utilisez. Pour éviter d’essayer de savoir quelle version IP vous allez utiliser, vous pouvez utiliser une struct sockaddr_storage qui peut contenir l’une ou l’autre. Ceci sera à son tour typecasted pour struct sockaddr par les fonctions connect (), bind (), etc et sera accédé de cette manière.

Vous pouvez voir toutes ces structures ci-dessous (le remplissage est spécifique à l’implémentation, à des fins d’alignement):

 struct sockaddr { unsigned short sa_family; // address family, AF_xxx char sa_data[14]; // 14 bytes of protocol address }; struct sockaddr_in { short sin_family; // eg AF_INET, AF_INET6 unsigned short sin_port; // eg htons(3490) struct in_addr sin_addr; // see struct in_addr, below char sin_zero[8]; // zero this if you want to }; struct sockaddr_in6 { u_int16_t sin6_family; // address family, AF_INET6 u_int16_t sin6_port; // port number, Network Byte Order u_int32_t sin6_flowinfo; // IPv6 flow information struct in6_addr sin6_addr; // IPv6 address u_int32_t sin6_scope_id; // Scope ID }; struct sockaddr_storage { sa_family_t ss_family; // address family // all this is padding, implementation specific, ignore it: char __ss_pad1[_SS_PAD1SIZE]; int64_t __ss_align; char __ss_pad2[_SS_PAD2SIZE]; }; 

Comme vous pouvez le voir, si la fonction attend une adresse IPv4, elle lira simplement les 4 premiers octets (car elle suppose que la structure est de type struct sockaddr . Sinon, elle lit les 16 octets complets pour IPv6).

Dans C ++, les classes avec au moins une fonction virtuelle reçoivent un TAG. Cette balise vous permet de dynamic_cast<>() de toutes les classes dont votre classe est issue et vice versa. Le TAG est ce qui permet à dynamic_cast<>() de fonctionner. Plus ou moins, cela peut être un nombre ou une chaîne …

En C, nous sums limités aux structures. Cependant, les structures peuvent également recevoir un TAG. En fait, si vous regardez toutes les structures que la structure a affichées dans sa réponse, vous remarquerez qu’elles commencent toutes par 2 octets (un court non signé) qui représente ce que nous appelons la famille de l’adresse. Ceci définit exactement quelle est la structure et donc sa taille, ses champs, etc.

Par conséquent, vous pouvez faire quelque chose comme ceci:

 int bind(int fd, struct sockaddr *in, socklen_t len) { switch(in->sa_family) { case AF_INET: if(len < sizeof(struct sockaddr_in)) { errno = EINVAL; // wrong size return -1; } { struct sockaddr_in *p = (struct sockaddr_in *) in; ... } break; case AF_INET6: if(len < sizeof(struct sockaddr_in6)) { errno = EINVAL; // wrong size return -1; } { struct sockaddr_in6 *p = (struct sockaddr_in6 *) in; ... } break; [...other cases...] default: errno = EINVAL; // family not supported return -1; } } 

Comme vous pouvez le voir, la fonction peut vérifier le paramètre len pour s’assurer que la longueur est suffisante pour s’adapter à la structure attendue et par conséquent, elle peut reinterpret_cast<>() (comme on l’appelle en C ++) votre pointeur. Que les données soient correctes dans la structure appartient à l'appelant. Il n'y a pas beaucoup de choix à cette fin. Ces fonctions sont censées vérifier toutes sortes de choses avant d’utiliser les données et de renvoyer -1 et errno chaque fois qu’un problème est détecté.

Donc, en fait, vous avez une struct sockaddr_in ou struct sockaddr_in6 que vous (réinterprétez) converti en une struct sockaddr et la fonction bind() (et les autres) retransmet ce pointeur à une struct sockaddr_in ou struct sockaddr_in6 après avoir vérifié le membre sa_family et vérifié la taille.