socket: Implement sockaddr_storage with an anonymous union
PATCH 1/1 socket: Implement sockaddr_storage with an anonymous union
The historical design of sockaddr_storage makes it impossible to use without breaking strict aliasing rules. Not only this type is unusable, but even the use of other sockaddr_* structures directly (when the programmer only cares about a single address family) is also incorrect, since at some point the structure will be accessed as a sockaddr, and that breaks strict aliasing rules too.
So, the only way for a programmer to not invoke Undefined Behavior is to declare a union that includes sockaddr and any sockaddr_* structures that are of interest, which allows later accessing as either the correct structure or plain sockaddr for the sa_family.
This patch fixes sockaddr_storage to remove UB on its uses and make it that structure that everybody should be using. It also allows removing many casts in code that needs to pass a sockaddr as a side effect.
The following is an example of how this improves both existing code and new code:
void
foo(foo)
{
struct old_sockaddr_storage oss;
struct new_sockaddr_storage nss;
// ... (initialize oss and nss)
inet_sockaddr2str(&nss.sa); // correct (and has no casts) inet_sockaddr2str((struct sockaddr_storage *)&oss); // UB inet_sockaddr2str((struct sockaddr_storage *)&nss); // correct
}
/* This function is correct, as far as the accessed object has the
* type we're using. That's only possible through a union, since
* we're accessing it with 2 different types: sockaddr for the
* sa_family and then the appropriate subtype for the address
* itself.
*/
const char *inet_sockaddr2str(const struct sockaddr *sa)
{
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
static char buf[MAXHOSTNAMELEN];
switch (sa->sa_family) {
case AF_INET:
sin = (struct sockaddr_in *) sa;
inet_ntop(AF_INET, &sin->sin_addr, buf, NITEMS(buf));
return buf;
case AF_INET6:
sin6 = (struct sockaddr_in6 *) sa;
inet_ntop(AF_INET6, &sin6->sin6_addr, buf, NITEMS(buf));
return buf;
default:
errno = EAFNOSUPPORT;
return NULL;
}
}While it's not necessary to do the same for sockaddr, it might still be interesting to so, since it will allow removing many casts in the implementation of many libc functions.
--- bits/socket.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-)
bits/socket.h +8 -3
@@ -168,9 +168,14 @@ struct sockaddrstruct sockaddr_storage { -__SOCKADDR_COMMON (ss_);/* Address family, etc. */ -char __ss_padding[_SS_PADSIZE]; -__ss_aligntype __ss_align;/* Force desired alignment. */ +union + { +__SOCKADDR_COMMON (ss_);/* Address family, etc. */ +struct sockaddr sa; +struct sockaddr_in sin; +struct sockaddr_in6 sin6; +struct sockaddr_un sun; + }; };
Signature · 1 line
2.39.0