API using sockaddr_storage

19,115

Solution 1

The problem with jointly doing IPV6 and IPV4 programming is that a pure sockaddr struct itself is not big enough to hold a sockaddr_in6. So if you need to blindly pass around an address that could be either sockaddr_in or sockaddr_in6, sockaddr_storage is a bit easier to use.

At the end of the day, whether you are using sockaddr_in, sockaddr_in6, or sockaddr_storage, you'll have to cast those pointers to make a call to sendto, recvfrom, connect, accept, and many other socket functions. It's just a known nuance of socket programming. Just let go of the feeling of doing something unsafe. Your code will be ok.

Now when writing network code that is meant to work for both IPV4 and IPV6, you can easily get into a trap of having an abundance of switch statements to handle the different network types. Code then gets messy like the following:

if (addr.ss_family == AF_INET)
    sendto(sock, buffer, len, 0, (sockaddr*)&addr, sizeof(sockaddr_in))
else (addr.ss_family == AF_INET6)
    sendto(sock, buffer, len, 0, (sockaddr*)&addr, sizeof(sockaddr_in6));

And then that type of "if family == AF_INET" expression can easily start to repeat itself over and over. That's what you want to avoid.

Assuming you are using C++, you'll find that an abstraction class for a socket address object is incredibly useful. I have an example on github here and here. The CSocketAddress class is backed by a union of {sockaddr, sockaddr_in, sockaddr_in6} and can be constructed with a sockaddr_storage. If I had known about sockaddr_storage before I had started this class, I would have used that instead of the union thing. In any case, it allows me to write code as follows:

CSocketAddress addr;
...
sendto(sock, buffer, len, 0, addr.GetSockAddr(), addr.GetSockAddrLength());

Likewise, an "accept" statement looks like this:

sockaddr_storage addrstorage = {};
int len = sizeof(sockaddr_storage);
accept(sock, (sockaddr*)&addrstorage, &len);

CSocketAdddress addr(addrstorage); // construct an address object to pass around everywhere else

This was incredibly helpful for the code paths that call bind, send, and recv. Now my STUN server and client code paths no longer have to know anything about the family type of the socket address. They just work with "CSocketAddress" objects. The only IPV4 and IPV6 specific code is during the client and server initialization - when the address objects are actually constructed. Fortunately, that was partially abstracted out as well.

You might also want to peruse the helper functions here. There's some more useful stuff for resolving hostnames, enumerating adapters, etc... in an IP agnostic way. It's Linux code, but some of it should map ok to Windows and winsock.

I am almost done adding TCP support to this code base. In the process of adding support for SOCK_STREAM, I have not had to make a single change nor add any new code to deal with the differences in IPV4 and IPV6 address structures.

Solution 2

I don't generally see the need for struct sockaddr_storage. It's purpose is to allocate enough space for the sockaddr structure of any given protocol, but how often do you need to do that in IP-version-agnostic code? Generally you call getaddrinfo() and it gives you a bunch of struct sockaddr *s, and you don't care whether they're sockaddr_in or sockaddr_in6, you just pass them along as-is to bind() and connect() (no casting required).

In typical client/server code, the main place I can think of where struct sockaddr_storage is useful is to reserve space for the second parameter to accept(). In this case I agree it's ugly to have to cast it to struct sockaddr * once for accept() and again for getnameinfo(). But I can't see a way around those casts. This is C. Structure inheritance always involves lots of casts.

Share:
19,115
Nanda
Author by

Nanda

Updated on June 22, 2022

Comments

  • Nanda
    Nanda almost 2 years

    I'm trying to do some IP agnostic coding and as suggested by various sources I tried to use sockaddr_storage. However all the API calls (getaddrinfo, getnameinfo) still depend on struct sockaddr. And casting between them isn't exactly a good option, gves rise to a lot of other problems.

    And casting to sockaddr_in and sockaddr_in6 separately sort of defeats the purpose of me trying to use sockaddr_storage.

    Anybody who has effectively used sockaddr_storage in devloping a simple client server socket application.

  • Jon Watte
    Jon Watte over 7 years
    Note that what gethostbyname() returns isn't compatible with sockaddr_in -- it returns pointers to four-byte host addresses, not full sockaddr instances. Thus, you copy the h_addr_list[0] field into the sin_addr field of a sockaddr_in for IPV6 and into the sin6_addr field of a sockaddr_ipv6.
  • Celada
    Celada over 7 years
    @JonWatte: true, and that's just one more reason to not use gethostbyname(). Use getaddrinfo() instead.
  • Jon Watte
    Jon Watte over 7 years
    Yes, in code bases where I have that freedom, that's better.