56

I know that sockaddr_in is for IPv4, and sockaddr_in6 for IPv6. The confusion to me is the difference between sockaddr and sockaddr_in[6].

Some functions accept sockaddr and some functions accept sockaddr_in or sockaddr_in6, so:

  • what's the rule?
  • And why is there a need for two different structures?

And because the sizeof(sockaddr_in6) > sizeof(sockaddr) == sizeof(sockaddr_in).

  • Does that mean we should always use sockaddr_in6 to allocate memory in stack and cast to sockaddr and sockaddr_in if we need to support ipv4 and ipv6?

One example is: we have a socket, and we want to get the string ip address of it (it can be ipv4 or ipv6).

We first call getsockname to get an addr and then call inet_ntop based on the addr.sa_family.

Is there anything wrong with this code snippet?

char ipStr[256];
sockaddr_in6 addr_inv6;
sockaddr* addr = (sockaddr*)&addr_inv6;
sockaddr_in* addr_in = (sockaddr_in*)&addr_inv6;

socklen_t len = sizeof(addr_inv6);
getsockname(_socket, addr, &len);

if (addr->sa_family == AF_INET6) {
    inet_ntop(addr_inv6.sin6_family, &addr_inv6.sin6_addr, ipStr, sizeof(ipStr)); 
    // <<<<<<<<IS THIS LINE VALID, getsockname expected a sockaddr, but we use 
    // it output parameter as sockaddr_in6.
} else {
    inet_ntop(addr_in->sin_family, &addr_in->sin_addr, ipStr, sizeof(ipStr));
}
pevik
  • 4,523
  • 3
  • 33
  • 44
ZijingWu
  • 3,350
  • 3
  • 25
  • 40
  • Somewhat related - http://stackoverflow.com/questions/21099041/why-do-we-cast-sockaddr-in-to-sockaddr-when-calling-bind – sashoalm Jul 30 '15 at 14:01

2 Answers2

34

sockaddr_in and sockaddr_in6 are both structures where first member is a sockaddr structure.

According to the C standard, the address of a structure and its first member are the same, so you can cast the pointer to sockaddr_in(6) in a pointer to sockaddr.

Functions taking sockaddr_in(6) as parameter may modify the sockaddr part, and functions taking sockaddr as parameter just care about that part.

It's a bit like inheritance.

linkdd
  • 1,015
  • 1
  • 10
  • 24
  • 1
    That's interesting i didn't see the inheritance relationship in definition of sockaddr_in and `sockaddr`. I would expected there is an `sockaddr` type member in `sockaddr_in` definition as the common way for C language. But i didn't see this, at least in MAC. And how about the code snippet correctness, if it is valid, then `getsockname` expected an `sockaddr`, but change the value outside `sockaddr` for IPV6. – ZijingWu Sep 04 '13 at 10:31
  • 2
    *sockaddr_in and sockaddr_in6 are both structures where first member is a sockaddr structure.*. When looking at the headerfiles, I don't see `sockaddr_in` having a member of type `sockaddr` member. – MikeMB Jun 11 '15 at 17:27
  • 4
    I don't think any implementation actually embeds struct sockaddr as a member of the other struct sockaddr derivatives, such as struct sockaddr_in. All they have in common is the 1. member `sa_family` which when given a struct sockaddr, you use to figure out which actual socket address structure it is. – nos Aug 20 '15 at 08:18
  • @nos: In the days before compilers got absurdly aggressive with optimization, the CIS rule was widely interpreted in a way that wouldn't require sockaddr to be embedded within the other types. C99 added a requirement that a complete union *type* containing the structures be visible at the point of access, but compilers like gcc pretend that the Standard requires that access be performed directly through the structure type even though it says no such thing. – supercat Feb 14 '17 at 00:56
  • an inspection of `sockaddr_in` struct , it seems it has no member called `sockaddr` – interesting Nov 10 '22 at 10:43
28

In order to give more information other people may find useful, I have decided to answer my question although I initially did not intend to.

After some digging into the linux source code I have found the following : There are multiple protocols and they all implement getsockname. And each one has an underlying address data structure. For example, IPv4 has sockaddr_in, IPV6 has sockaddr_in6, the AF_UNIX socket has sockaddr_un. sockaddr is used as the common data struct in the signature of the linux networking

That API will copy the the socketaddr_inor sockaddr_in6 or sockaddr_un to a sockaddr base on another parameter length by memcpy.

And all those data structures begin with same type field sa_family.

Because of all this, the code snippet is valid, because both sockaddr_in and sockaddr_in6 have a sa_family field and then can be cast into the correct data structure to be used after a check on that sa_family field.

BTW, I'm not sure why the sizeof(sockaddr_in6) > sizeof(sockaddr), which cause allocate memory based on size of sockaddr is not enough for ipv6 (that is error-prone), but I guess it is because of history reason.

cassepipe
  • 371
  • 6
  • 16
ZijingWu
  • 3,350
  • 3
  • 25
  • 40
  • 9
    Note that you're not supposed to, ever, allocate storage for a struct sockaddr You always allocate a concrete struct such as struct sockaddr_in, struct sockaddr_in6. Or you allocate a struct sockaddr_storage which is guaranteed to be big enough for any struct sockaddr(There are many more than just sockaddr_in and sockaddr_in6) – nos Aug 20 '15 at 08:15