我正在使用OpenSSL 1.0.0库编写TLS服务器,因此我使用的是BIO *对象,而不是SSL *对象(我正在使用IBM文档:part 1,part 2和part 3)。
要获取到远程客户端的套接字,我运行以下代码:
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
}
这没有问题,我可以将数据发送到客户端,客户端可以响应。如果我没有向客户提供我的自定义CA证书文件,客户拒绝连接,因为证书验证失败等等。简而言之,一切看起来都非常好。
问题是,我无法获得对等主机地址。
我找不到任何OpenSSL特定的API来做到这一点。然后我尝试获取套接字的文件描述符并使用以下代码调用getpeername()
:
// 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;
}
但在BIO_do_handshake()
之前和之后,在检查sa_family
时失败,我得到Unknown socket type passed to worker(): 50576
。
如何在使用包装TLS的OpenSSL BIO对象时获取对等地址?
答案 0 :(得分:2)
原型是:
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
addrlen
参数实际上是一个输入/输出参数,这就是问题所在。您需要将其初始化为sockaddr参数的字节大小,即
addr_len = sizeof(addr); // this is what's missing
getpeername(sock_fd, &addr, &addr_len);
getpeername函数然后在addr_len中存储它要写入addr
的字节数。
在你的辩护中,文件在这一点上可能不清楚。当我在谷歌搜索getpeername时,顶部条目为this one。在我看来,它无法充分解释address_len
论点。但是,next entry一目了然。
答案 1 :(得分:1)
虽然最佳答案解决了初始化 addrlen 的问题,但您的代码无法用于IPv6,因为没有足够的空间提供 getpeername 以获取 sockaddr_in6的即可。运行此代码:
#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));
}
结果:
sizeof( struct sockaddr ) = 16
sizeof( struct sockaddr_in ) = 16
sizeof( struct sockaddr_in6 ) = 28
恕我直言,以协议无关的方式调用网络功能的最佳方法是使用联合:
union address {
struct sockaddr addr;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
};
然后通过以下方式调用 getpeername
union address peer;
socklen_t addrlen = sizeof( peer );
getpeername( sock_fd, &peer.addr, &addrlen );
将您使用 addr.sa_family 作为家庭选择器的代码完善,这很好,但是简化了 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
....