3

in my program I register a EV_READ event for connfd in libevent event loop. when this event is triggered, I use getpeername to get the IP/PORT address of the peer

socklen_t socklen;
struct sockaddr_in client_addr;
socklen = sizeof(client_addr);
retval = getpeername(connfd, (struct sockaddr *)&client_addr, &socklen);
if(retval == -1) perror("getpeername error!\n");

but sometimes, it returns 0.0.0.0:0

and then I notice the error is Transport endpoint is not connected

but recv(connfd,buf,..) returns a 1273 , which means it receives 1273 bytes. if the connection is ended, how can recv() get bytes? and how can the event be triggered?

thanks!

user1944267
  • 1,557
  • 5
  • 20
  • 27
  • If `getpeername()` is successful, it should never report 0.0.0.0. Is `getpeername()` returning an error code? The code you showed is not checking for that. – Remy Lebeau Jun 20 '13 at 17:57
  • Transport endpoint is not connected – user1944267 Jun 20 '13 at 18:05
  • Could it be that the socket's family is sometimes `AF_INET6` instead of `AF_INET` as your code assumes it must be? Interpreting a `struct sockaddr_in6` as a `struct sockaddr` would lead to meaningless results, possible 0.0.0.0. – Celada Jun 20 '13 at 20:53
  • @Celada: I would expect that condition to cause an `EFAULT` or similar error, not an `ENOTCONN` error. – Remy Lebeau Jun 20 '13 at 21:33
  • @Remy Lebeau maybe the socket is closed by the program itself somewhere or by the remote peer? – misteryes Jun 20 '13 at 23:20
  • @misteryes: That would not explain why `recv()` still works even though `getpeername()` reports the socket is not connected. For a TCP socket, that should be impossible. Makes me think maybe the socket is actually UDP instead, which is connection-less by default. – Remy Lebeau Jun 20 '13 at 23:49
  • @RemyLebeau, if `getpeername()` is provided with a buffer that's too small it just truncates the result and returns success. Functions that return success don't set `errno`, so it contain a garbage value. If my hypothesis is correct, `getpeername` is not failing and the `ENOTCONN` `errno` value is not relevant. – Celada Jun 21 '13 at 00:31
  • @Celada: not all platforms truncate the buffer (Windows not not). On those that do, the third parameter is updated to a value greater than what was input. The OP's code is checking for `getpeername()` returning failure. – Remy Lebeau Jun 21 '13 at 08:43
  • @Celada what is the buffer for `getpeername()`? why it becomes small? because of memory leak? I checked the socklen, sometimes it is 16 and sometimes it is a garbage value. – user1944267 Jun 21 '13 at 10:09

1 Answers1

3

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_inor 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!

Bernard Rosset
  • 4,523
  • 6
  • 27
  • 29