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
行,一切都会按预期开始工作。
对于这么大的代码块,我很抱歉,但我对发生的事情一头雾水,我不知道问题出在哪里...
我正在尝试运行一个非常小的 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
行,一切都会按预期开始工作。