【问题标题】:OpenSSL DTLS connection never establishesOpenSSL DTLS 连接永远不会建立
【发布时间】:2019-07-26 04:27:16
【问题描述】:

对于这么大的代码块我很抱歉,但我对正在发生的事情感到迷茫,我不知道问题可能出在哪里......

我正在尝试让一个非常小的 dtls 服务器运行,但我无法让客户端和服务器完成握手。

这是我的代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/opensslv.h>

int generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len)
{
    cookie = "cookie";
    cookie_len = 5;

    return 1;
}

int verify_cookie(SSL *ssl, const unsigned char *cookie, unsigned int cookie_len)
{
    return 1;
}

int dtls_verify_callback (int ok, X509_STORE_CTX *ctx) {
    /* This function should ask the user
     * if he trusts the received certificate.
     * Here we always trust.
     */
    return 1;
}

int main() {
    char buff[FILENAME_MAX];
    getcwd(buff, FILENAME_MAX);

    union {
        struct sockaddr_storage ss;
        struct sockaddr_in s4;
        struct sockaddr_in6 s6;
    } client_addr;

    struct sockaddr_in server_addr;

    const int on = 1, off = 0;

    memset(&server_addr, 0, sizeof(server_addr));
    memset(&client_addr, 0, sizeof(client_addr));

    int res;

    SSL *ssl;
    BIO *bio;
    int sock;
    struct timeval timeout;

    SSL_CTX *ctx = SSL_CTX_new(DTLS_server_method());
    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);

    if(!SSL_CTX_use_certificate_file(ctx, "/home/matthew/CLionProjects/OpenSSL.Test/cmake-build-debug/certs/cert.crt", SSL_FILETYPE_PEM))
    {
        perror("cert");
        exit(EXIT_FAILURE);
    }

    if(!SSL_CTX_use_PrivateKey_file(ctx, "/home/matthew/CLionProjects/OpenSSL.Test/cmake-build-debug/certs/key.key", SSL_FILETYPE_PEM))
    {
        perror("key");
        exit(EXIT_FAILURE);
    }

    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, dtls_verify_callback);
    SSL_CTX_set_read_ahead(ctx, 1);
    SSL_CTX_set_cookie_generate_cb(ctx, generate_cookie);
    SSL_CTX_set_cookie_verify_cb(ctx, &verify_cookie);

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(1114);

    if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*) &on, (socklen_t) sizeof(on)) < 0)
    {
        perror("set reuse address");
        exit(EXIT_FAILURE);
    }

    if(setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*) &on, (socklen_t) sizeof(on)) < 0)
    {
        perror("set reuse port");
        exit(EXIT_FAILURE);
    }

    if(bind(sock, (const struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
    {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    memset(&client_addr, 0, sizeof(struct sockaddr_storage));

    /* Create BIO */
    bio = BIO_new_dgram(sock, BIO_NOCLOSE);

    /* Set and activate timeouts */
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;
    BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout);

    ssl = SSL_new(ctx);

    SSL_set_bio(ssl, bio, bio);
    SSL_set_options(ssl, SSL_OP_COOKIE_EXCHANGE);

    if(!SSL_set_fd(ssl, sock))
    {
        perror("set fd");
        exit(EXIT_FAILURE);
    }

res = 0;
while(res <= 0)
{
    res = DTLSv1_listen(ssl, (BIO_ADDR *) &client_addr);
    if(res < 0)
    {
        perror("dtls listen"); <--- "Destination address required"
        exit(EXIT_FAILURE);
    }
}

    SSL_accept(ssl);

    printf("Hello, World!\n");
    return 0;
}

我在我能想到的每个角落都添加了错误检查...

发生的情况是,它在DTLSv1_accept() 上循环,直到尝试连接。一旦客户端尝试连接(向客户端发送 Hello),DTLSv1_accept() 返回 -1。对SSL_get_error 的调用给了我5,即SSL_ERROR_SYSCALL

所以我做perror 并得到Destination address required...

AFAIK DTLSv1_listen 的全部意义在于监听任何传入的客户问候,并在握手完成后用客户的地址填充BIO_ADDR...

我正在使用它来测试服务器:

openssl s_client -dtls -connect 127.0.0.1:1114

我在这方面花了很多时间。我即将放弃 OpenSSL 并尝试 libressl...

非常感谢任何帮助。

附:代码是完整的,如果你想尝试一下,它应该编译并运行。

谢谢!

【问题讨论】:

  • 你能看看我的帖子here 非常感谢,因为你似乎在同一领域工作过。

标签: c sockets openssl dtls


【解决方案1】:

您的代码示例中有两个重大问题。

首先,您生成 cookie 的代码不正确。你应该创建 cookie并将其存储在cookie指向的位置,然后在*cookie_len中填写cookie的长度。所以代码应该是这样的:

int generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len)
{
    memcpy(cookie,  "cookie", 6);
    *cookie_len = 6;

    return 1;
}

但是,在设置 SSL 对象以供使用时,最重要的错误出现在后面。 OpenSSL 具有用于抽象底层传输层的 BIO 概念。为了通过 UDP 进行 DTLS,您需要使用“dgram”BIO。这一点你做得对:

/* Create BIO */
bio = BIO_new_dgram(sock, BIO_NOCLOSE);

/* Set and activate timeouts */
timeout.tv_sec = 5;
timeout.tv_usec = 0;
BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout);

ssl = SSL_new(ctx);

SSL_set_bio(ssl, bio, bio);

因此,此时 SSL 对象已为 DTLSv1_listen() 调用正确设置。但是,您可以这样做:

if(!SSL_set_fd(ssl, sock))
{
    perror("set fd");
    exit(EXIT_FAILURE);
}

SSL_set_fd 函数是SSL_set_bio 函数的替代函数。它所做的是获取提供的fd,将其包装在“socket”BIO 中,然后调用SSL_set_bio() 并返回结果。套接字 BIO 主要用于标准 TLS,不能用于 DTLS。所以上面代码的效果就是把你之前设置的dgram BIO扔掉了。

在我的测试中,如果我对上面建议的generate_cookie 进行了更改,并删除了SSL_set_fd 行,那么一切都开始按预期工作。

【讨论】:

  • 完美!非常感谢!
  • @Matt Caswell “为了通过 UDP 执行 DTLS,您需要使用 'dgram' BIO。”这真的是强制性的吗?我需要将我的非阻塞传出套接字与 BIO 分离。我正在考虑在读取时使用内存 BIO,在写入端(写入套接字)使用自定义 BIO 来解决 MTU 超出的问题,因为内存 BIO 中的片段被附加并且写入套接字时可能超过 MTU 并得到掉了。除了使用自定义 BIO 之外,还有其他解决方案吗?我只对握手感兴趣,因为该应用程序是获取 SRTP 的密钥。
  • @asinix 这不是强制性的,尽管它是最常见的方法。您不能使用标准的 OpenSSL mem bio,因为它不尊重数据报语义(最重要的是数据包边界和 MTU 发现)。实现做这些事情的自定义 mem bio 并不会太难 - 但你不能开箱即用。你必须自己做。您在评论中谈到了 MTU - 标准 dgram BIO 具有自动发现 MTU 的能力 - 或者您可以明确设置它。
  • 啊...对,只是重新阅读您的评论。我意识到你说 mem bio 用于读取,所以 MTU 发现在那里不是问题......但数据包边界是。
  • @MattCaswell 感谢您的回复。因此,如果我正在读取和写入非阻塞 UDP 套接字,则 rbio 和 wbio 的 dgram BIO 就可以了。不需要使用任何自定义 BIO? dgram BIO 是最近添加的吗?我看到较旧的(2015-2016)DTLS 1.2 实现使用自定义 BIO 来解决数据包边界问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-14
  • 2022-08-12
  • 1970-01-01
  • 2021-11-05
  • 1970-01-01
  • 2012-11-24
相关资源
最近更新 更多