Specifically sin_addr seems to be located on different memory locations for IPv4 and IPv6 socket addressed. This results in weirdness:
#include <stdio.h>
#include <netinet/in.h>
int main(int argc, char ** argv) {
struct sockaddr_in sa;
printf("sin_addr in sockaddr_in = %p\n", &sa.sin_addr);
printf("sin_addr in sockaddr_in6 = %p\n", &((struct sockaddr_in6*)&sa)->sin6_addr);
};
Output:
sin_addr in sockaddr_in = 0x7fffa26102b4
sin_addr in sockaddr_in6 = 0x7fffa26102b8
Why aren’t these 2 values the same ?
Since this is pointing to the same data (the address to connect to), this should be located at the same address. Otherwise, how are you supposed to call inet_ntop with a sockaddr_in that you don’t know is IPv4 or IPv6 ?
sockaddr_inandsockaddr_in6are different structs used for different address families (IPv4 and IPv6, respectively). They are not required to be compatible with each other in any way except one – the first field must be a 16-bit integer to hold the address family.sockaddr_inalways has that field set toAF_INET, andsockaddr_in6always has that field set toAF_INET6. By standardizing the family field in this way, anysockaddr-based API can access that field and know how to interpret the rest of the struct data as needed. That is also whysockaddr-based APIs usually also have anintsize value as input/output as well, sincesockaddr_inandsockaddr_in6are different byte sizes, so APIs need to be able to validate the size of any buffers you pass around.No, it should not. The location of the address field within the struct is specific to the type of address family the struct belongs to. There is no requirement that
sockaddr_inandsockaddr_in6should store their addresses at the exact same offset.sockaddr_incan only be used with IPv4 and nothing else, andsockaddr_in6can only be used with IPv6 and nothing else. If you have asockaddr_inthen you implicitally know you have an IPv4 address, and if you have asockaddr_in6then you implicitally know you have an IPv6 address. You have to specify that information toinet_ntop()so it knows how to interpret the data you pass in to it:.
To help you write family-agnostic code, you should be using
sockaddr_storageinstead ofsockaddr_inorsockaddr_in6directly when possible.sockaddr_storageis large enough in size to hold bothsockaddr_inandsockaddr_in6structs. Since both structs define a family field at the same offset and size,sockaddr_storagecan be used with any API that operates onsockaddr*pointers (connect(),accept(),bind(),getsockname(),getpeername(), etc).However,
inet_ntop()does not fall into that category, so you have to pull apart asockaddr_storagemanually when usinginet_ntop(), eg: