Reasoning behind C sockets sockaddr and sockaddr_storage
Solution 1:
Functions that expect a pointer to struct sockaddr
probably typecast the pointer you send them to sockaddr
when you send them a pointer to struct sockaddr_storage
. In that way, they access it as if it was a struct sockaddr
.
struct sockaddr_storage
is designed to fit in both a struct sockaddr_in
and struct sockaddr_in6
You don't create your own struct sockaddr
, you usually create a struct sockaddr_in
or a struct sockaddr_in6
depending on what IP version you're using. In order to avoid trying to know what IP version you will be using, you can use a struct sockaddr_storage
which can hold either. This will in turn be typecasted to struct sockaddr
by the connect(), bind(), etc functions and accessed that way.
You can see all of these structs below (the padding is implementation specific, for alignment purposes):
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; // e.g. AF_INET, AF_INET6
unsigned short sin_port; // e.g. 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];
};
So as you can see, if the function expects an IPv4 address, it will just read the first 4 bytes (because it assumes the struct is of type struct sockaddr
. Otherwise it will read the full 16 bytes for IPv6).
Solution 2:
In C++ classes with at least one virtual function are given a TAG. That tag allows you to dynamic_cast<>()
to any of the classes your class derives from and vice versa. The TAG is what allows dynamic_cast<>()
to work. More or less, this can be a number or a string...
In C we are limited to structures. However, structures can also be assigned a TAG. In fact, if you look at all the structures that theprole posted in his answer, you will notice that they all start with 2 bytes (an unsigned short) which represents what we call the family of the address. This defines exactly what the structure is and thus its size, fields, etc.
Therefore you can do something like this:
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;
}
}
As you can see, the function can check the len
parameter to make sure that the length is enough to fit the expected structure and therefore they can reinterpret_cast<>()
(as it would be called in C++) your pointer. Whether the data is correct in the structure is up to the caller. There is not much choice on that end. These functions are expected to verify all sorts of things before it uses the data and return -1 and errno
whenever a problem is found.
So in effect, you have a struct sockaddr_in
or struct sockaddr_in6
that you (reinterpret) cast to a struct sockaddr
and the bind()
function (and others) cast that pointer back to a struct sockaddr_in
or struct sockaddr_in6
after they checked the sa_family
member and verified the size.