OpenSSL DTLS 连接从不建立

OpenSSL DTLS connection never establishes

对于这么大的代码块,我很抱歉,但我对发生的事情一头雾水,我不知道问题出在哪里...

我正在尝试运行一个非常小的 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() 正常,直到尝试连接。一旦客户端尝试连接(发送 Client Hello),DTLSv1_accept() return -1。调用 SSL_get_error 得到 5,这是一个 SSL_ERROR_SYSCALL.

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

AFAIK DTLSv1_listen 的全部要点是监听 any 传入的客户端问候,并在握手完成后用客户端地址填充 BIO_ADDR ...

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

openssl s_client -dtls -connect 127.0.0.1:1114

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

非常感谢任何帮助。

P.S。代码是完整的,因为它应该编译并且 运行 如果你想尝试一下。

谢谢!

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

首先,您生成 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 行,一切都会按预期开始工作。