Hubert Kario
Hubert Kario

Reputation: 22880

How to get peer address from OpenSSL BIO object

I'm programming TLS server using OpenSSL 1.0.0 library, as such I'm using BIO* objects, not SSL* objects (I'm using IBM documentation: part 1, part 2 and part 3).

To get a socket to remote client I run following code:

BIO *new_client;

while(1)
{
    if (BIO_do_accept(socket) <= 0)
        { handle error }
    new_client = BIO_pop(socket);
    BIO_do_handshake(new_client);

    // fire a thread and do rest of communication
}

This works without problem, I can send data to client, client can respond. If I don't provide my custom CA cert file to client, client refuses connection because failure of verification of certificate, etc. In short, everything looks perfectly well.

Problem is, I can't get peer host address.

I can't find any OpenSSL specific API to do that. Then I tried to get the file descriptor of the socket and invoke getpeername() using following code:

// get peer address
int sock_fd;
if (BIO_get_fd(socket, &sock_fd) == -1)
  {
    fprintf(stderr, "Uninitialized socket passed to worker");
    goto listen_cleanup;
  }
printf("socket fd: %i\n", sock_fd);
struct sockaddr addr;
socklen_t addr_len;
// make enough space for ipv6 address and few extra chars
ctx->hostname = malloc(sizeof(char) * 80);
if (!ctx->hostname)
  {
    fprintf(stderr, "Out of memory\n");
    goto internal_error;
  }

// ignore failures, as any problem will be caught in TLS handshake
getpeername(sock_fd, &addr, &addr_len);
if (addr.sa_family == AF_INET)
  inet_ntop(AF_INET, &((struct sockaddr_in *)&addr)->sin_addr,
      ctx->hostname, 40);
else if (addr.sa_family == AF_INET6)
  inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr,
      ctx->hostname, 40);
else
  {
    fprintf(stderr, "Unknown socket type passed to worker(): %i\n",
        addr.sa_family);
    goto internal_error;
  }

but both before and after BIO_do_handshake(), it fails while checking sa_family, I get Unknown socket type passed to worker(): 50576.

How to get peer address while using OpenSSL BIO objects that wrap TLS?

Upvotes: 2

Views: 2733

Answers (2)

greg
greg

Reputation: 562

Although the top answer addresses the issue of initializing addrlen, your code will not work for IPv6 as there is not enough room being provided to getpeername for a sockaddr_in6. Running this code:

#include    <stdio.h>
#include    <netinet/in.h>

int main( int argc, char *argv[] ) {

printf("sizeof( struct sockaddr ) = %d\n", sizeof( struct sockaddr ));
printf("sizeof( struct sockaddr_in )  = %d\n", sizeof( struct sockaddr_in));
printf("sizeof( struct sockaddr_in6 ) = %d\n", sizeof( struct sockaddr_in6));
}

results in:

sizeof( struct sockaddr ) = 16
sizeof( struct sockaddr_in )  = 16
sizeof( struct sockaddr_in6 ) = 28

IMHO the best way to call the network functions in a protocol agnostic way is to use a union:

union address {
    struct sockaddr     addr;
    struct sockaddr_in  addr4;
    struct sockaddr_in6 addr6;
};

and then invoke getpeername by:

union address peer;
socklen_t addrlen = sizeof( peer );
getpeername( sock_fd, &peer.addr, &addrlen );

Rounding out the code you've used addr.sa_family as the family selector which is great, but simplifying the use of inet_ntop:

if (peer.addr.sa_family == AF_INET)
  inet_ntop(AF_INET, peer.addr4.sin_addr, ctx->hostname, 80 );
else if (peer.addr.sa_family == AF_INET6)
  inet_ntop(AF_INET6, peer.addr6.sin6_addr, ctx->hostname, 80 );
else
  ....

Upvotes: 2

President James K. Polk
President James K. Polk

Reputation: 42018

The prototype is:

int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

The addrlen argument is actually an in/out argument, and this is where you problem is. You need to intialize it to the size in bytes of the sockaddr argument, i.e.

addr_len = sizeof(addr);  // this is what's missing
getpeername(sock_fd, &addr, &addr_len);

The getpeername function then stores in addr_len the number of bytes it wants to write into addr.

In your defense the documentation can be unclear on this point. When I do a google search for getpeername the top entry is this one. In my opinion it fails to adequately explain the address_len argument. The next entry, however, makes it all clear.

Upvotes: 2

Related Questions