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.