【问题标题】:How to get peer address from OpenSSL BIO object如何从 OpenSSL BIO 对象中获取对等地址
【发布时间】:2012-12-18 21:27:42
【问题描述】:

我正在使用 OpenSSL 1.0.0 库对 TLS 服务器进行编程,因此我使用的是 BIO* 对象,而不是 SSL* 对象(我使用的是 IBM 文档:part 1part 2part 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 对象时获取对等地址?

【问题讨论】:

    标签: c cryptography openssl


    【解决方案1】:

    原型是:

    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 的字节数存储在 addr_len 中。

    在您的辩护中,文档在这一点上可能不清楚。当我在 Google 上搜索 getpeername 时,顶部条目是 this one。在我看来,它未能充分解释 address_len 论点。但是,next entry 说明了一切。

    【讨论】:

    • 谢谢!初始化addr_len 后,它可以正常工作。有时是小事让你绊倒......
    【解决方案2】:

    尽管最佳答案解决了初始化 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
      ....
    

    【讨论】:

      猜你喜欢
      • 2018-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多