The casting works.
Looking at the two structures:
struct sockaddr_in {
__uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
struct sockaddr {
__uint8_t sa_len; /* total length */
sa_family_t sa_family; /* [XSI] address family */
char sa_data[14]; /* [XSI] addr value (actually larger) */
};
First two members, sin_len
and sa_len
, sin_family
and sa_family
will not be problematic as those are of the same data type. The padding for sa_family_t
works exactly the same on both ends.
Looking at the reference,
in_port_t
Equivalent to the type uint16_t
as described in <inttypes.h>
in_addr_t
Equivalent to the type uint32_t
as described in <inttypes.h>
For windows, struct in_addr
looks like below:
struct in_addr {
union {
struct {
u_char s_b1;
u_char s_b2;
u_char s_b3;
u_char s_b4;
} S_un_b;
struct {
u_short s_w1;
u_short s_w2;
} S_un_w;
u_long S_addr;
} S_un;
};
and that for a linux is:
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
The whole confusion you might have is because of how the contents align. However, it is a well-thought historic design. It is intended to accommodate implementation-dependent aspects in the design.
When I Secondly, implementation-dependent -- it refers to the fact that implementation of in_addr_t
is not consistent across all systems, as seen above.
In a nutshell, this entire magic works, because of the 2 things: The exact size and padding nature of the first two members and then lastly the data type of sa_data[14]
is char
, or more precisely an array of a 1-byte data-type. This design trick with union
inside a struct
has been widely used.
Unix Network Programming Volume 1 states:
The reason the sin_addr member is a structure, and not just an in_addr_t, is historical. Earlier releases (4.2BSD) defined the in_addr structure as a union of various structures, to allow access to each of the 4 bytes and to both of the 16-bit values contained within the 32-bit IPv4 address. This was used with class A, B, and C addresses to fetch the appropriate bytes of the address. But with the advent of subnetting and then the disappearance of the various address classes with classless addressing, the need for the union disappeared. Most systems today have done away with the union and just define in_addr as a structure with a single in_addr_t member.
Not what you asked for, but good to know:
The same header states:
The sockaddr_in
structure is used to store addresses for the Internet address family. Values of this type shall be cast by applications to struct sockaddr
for use with socket functions.
So, sockaddr_in
is a struct specific to IP-based communication and sockaddr
is more of a generic structure for socket operations.
Just a try:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(void)
{
printf("sizeof(struct sockaddr_in) = %zu bytes\n", sizeof(struct sockaddr_in));
printf("sizeof(struct sockaddr) = %zu bytes\n", sizeof(struct sockaddr));
return 0;
}
Prints:
sizeof(struct sockaddr_in) = 16 bytes
sizeof(struct sockaddr) = 16 bytes