As @Celada pointed out, getpeername()
works on a struct sockaddr *
which size is, in your case, the one of a struct sockaddr_in
, not big enough to hold information about an IPv6 address.
This struct sockaddr
will either be implemented as a struct sockaddr_in
or a struct sockaddr_in6
depending on if the socket is dealing with an IPv4 or an IPv6 address.
You are allocating a struct sockaddr_in
which is 16 bytes (space for an IPv4 address only). An IPv6 needs a struct sockaddr_in6
which is 28 bytes and stores its IP address after the 16 first bytes. Thus reading a struct sockaddr_in
as a struct sockaddr_in6
The next part explains why you might read 0.0.0.0:0
. If you are more interested in the solution, go directly to the last section.
Why do you read some zeroed IP addresses with your current code?
Here is the definition of those structures:
struct sockaddr_in {
short sin_family; // e.g. AF_INET, AF_INET6 <-- 2 bytes
unsigned short sin_port; // e.g. htons(3490) <-- 2 bytes
struct in_addr sin_addr; // (only contains IPv4 address) <-- 4 bytes
char sin_zero[8]; // zero this if you want to <-- 8 bytes
};
struct sockaddr_in6 {
u_int16_t sin6_family; // address family, AF_INET6 <-- 2 bytes
u_int16_t sin6_port; // port number, Network Byte Order <-- 2 bytes
u_int32_t sin6_flowinfo; // IPv6 flow information <-- 4 bytes
struct in6_addr sin6_addr; // IPv6 address <-- 16 bytes
u_int32_t sin6_scope_id; // Scope ID <-- 4 bytes
};
Source: Beej's Guide to Network Programming's struct sockaddr and pals page
Thus, when you try to read the IP address from a struct sockaddr_in
while getpeername()
filled it with struct sockaddr_in6
information, you are actually reading the sin6_flowinfo
part of the struct sockaddr_in6
. That flow information is usually zero, unless the connection is explicitely flagged with a flow ID. That is why you would read a zeroed IP address.
How to provide a generic storage so that getpeername()
works in either case, without the possibility of having ahead intel on the peer's connection type (indeed)?
That is where struct sockaddr_storage
kicks in!
It is the type you should use to initialize space to provide getpeername()
with, casting it to struct sockaddr *
. It has been designed to hold information of whatever connection type.
Your code should be:
socklen_t socklen;
struct sockaddr_storage client_addr;
socklen = sizeof(client_addr);
retval = getpeername(connfd, (struct sockaddr *)&client_addr, &socklen);
if(retval == -1) perror("getpeername error!\n");
To use the structure, you should then cast it back to either struct sockaddr_in
or struct sockaddr_in6
, depending on the value of the ss_family
field of your struct sockaddr_storage
.
if (addr.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
port = ntohs(s->sin_port);
inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
} else if(addr.ss_family == AF_INET6) {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
port = ntohs(s->sin6_port);
inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
}
Source (modified): Beej's Guide to Network Programming's getpeername() page
Yeah, since the IP address length is different from IPv4 to IPv6, there is no generic function doing that for you, returning an IP address... since you would still need to know if you received 32 or 128 bits, thus checking for the address family again!