Reputation: 22880
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
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
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