我用
mbedtls制作了一个
epoll
HTTPS 服务器。它侦听两个端口:一个用于 HTTP,另一个用于 HTTPS。 HTTP 运行得非常好。但对于 HTTPS,当没有数据可供使用 EPOLLIN
读取时,我似乎会收到 mbedtls_ssl_read()
通知。
以下是代码的重要部分: 请注意,我知道这段代码在很多情况下都会失败。我只是做了一个例子来重现该错误。这段代码只是描述了当我接受单个客户端时发生的所有事情,然后在收到
EPOLLIN
后,尝试从中读取数据。
#define REQUEST_SIZE 8192
struct HTTP_server
{
mbedtls_net_context https_context;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_config conf;
mbedtls_x509_crt srvcert;
mbedtls_pk_context pkey;
const char *pers;
};
typedef struct client_data
{
char *request;
proto_t protocol;
union
{
int client_fd;
mbedtls_net_context https_context;
};
mbedtls_ssl_context ssl;
} client_data_t;
struct HTTP_server server;
server->pers = "ssl_server";
mbedtls_net_init(&server->https_context);
mbedtls_ssl_config_init(&server->conf);
mbedtls_x509_crt_init(&server->srvcert);
mbedtls_pk_init(&server->pkey);
mbedtls_entropy_init(&server->entropy);
mbedtls_ctr_drbg_init(&server->ctr_drbg);
if (mbedtls_ctr_drbg_seed(&server->ctr_drbg, mbedtls_entropy_func, &server->entropy,
(const unsigned char *) server->pers, strlen(server->pers)) != 0) {
return 0;
}
if (mbedtls_x509_crt_parse_file(&server->srvcert, "server.crt") != 0)
{
return 0;
}
if (mbedtls_pk_parse_keyfile(&server->pkey, "server.key", "pass_phrase", mbedtls_ctr_drbg_random, &server->ctr_drbg) != 0)
{
return 0;
}
if (mbedtls_net_bind(&server->https_context, NULL, "8000", MBEDTLS_NET_PROTO_TCP) != 0)
{
return 0;
}
mbedtls_net_set_nonblock(&server->https_context);
if (mbedtls_ssl_config_defaults(&server->conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT) != 0)
{
return 0;
}
mbedtls_ssl_conf_rng(&server->conf, mbedtls_ctr_drbg_random, &server->ctr_drbg);
if (mbedtls_ssl_conf_own_cert(&server->conf, &server->srvcert, &server->pkey) != 0)
{
return 0;
}
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1)
{
return;
}
event.data.fd = server->https_context.fd;
event.events = EPOLLIN;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server->https_context.fd, &event) == -1)
{
goto epoll_close;
}
while (1)
{
int nevents = epoll_wait(epoll_fd, events, 128, -1);
if (nevents == -1)
{
goto epoll_close;
}
else if (errno == EINTR)
{
goto epoll_close;
}
for (int i = 0; i < nevents; ++i)
{
if ((events[i].events & (EPOLLERR | EPOLLHUP)))
{
continue;
}
else if (events[i].data.fd == server->https_context.fd)
{
client_data_t client_status = malloc(sizeof(client_data_t));
mbedtls_net_context client_socket;
mbedtls_net_accept(&server->https_context, &client_socket, NULL, 0, NULL);
client_status->https_context = client_socket;
mbedtls_ssl_init(&client_status->ssl);
if (mbedtls_ssl_setup(&client_status->ssl, &server->conf) != 0)
{
return 0;
}
mbedtls_ssl_set_bio(&client_status->ssl, client_socket, mbedtls_net_send, mbedtls_net_recv, NULL);
int ret;
while (!program_interrupted && ((ret = mbedtls_ssl_handshake(&client_status->ssl)) != 0))
{
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
{
mbedtls_printf(" failed\n ! mbedtls_ssl_handshake returned %d\n\n", ret);
return 0;
}
}
client_status->request = malloc(REQUEST_SIZE + 1);
event.data.fd = client_socket.fd;
event.data.ptr = client_status;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket.fd, &event) == -1)
{
continue;
}
}
else
{
client_data_t *client_status = events[i].data.ptr;
// Every function call until this was successful, I checked, here nbytes == -69 or nbytes == -76
// mbedtls_ssl_get_bytes_avail(&client_status->ssl) gives 0
ssize_t nbytes = mbedtls_ssl_read(&client_status->ssl, (unsigned char *)(client_status->request), REQUEST_SIZE);
// other stuff
}
}
}
epoll_close:
close(epoll_fd);
mbedtls 调试错误消息如下所示:
ssl_msg.c:5662: => read
ssl_msg.c:4110: => read record
ssl_msg.c:2155: => fetch input
ssl_msg.c:2295: in_left: 0, nb_want: 5
ssl_msg.c:2315: in_left: 0, nb_want: 5
ssl_msg.c:2318: ssl->f_recv(_timeout)() returned -69 (-0x0045)
ssl_msg.c:4782: mbedtls_ssl_fetch_input() returned -69 (-0x0045)
ssl_msg.c:4141: ssl_get_next_record() returned -69 (-0x0045)
ssl_msg.c:5722: mbedtls_ssl_read_record() returned -69 (-0x0045)
TLS 在 TCP 之上添加了一层,该层具有自己的记录框架,并且还具有没有应用程序负载(握手、会话票证)的记录。
mbedtls_ssl_read
将仅在收到包含应用程序数据的完整记录后返回数据,因为只有这样数据才能被解密。
EPOLL 仅在 TCP 套接字级别工作。这意味着每当可以读取套接字上的某些内容时,无论这是部分 TLS 记录还是完整 TLS 记录,无论这些是应用程序数据还是只是控制信息,它都会发出活动信号。
这意味着您无法确定每当 EPOLL 返回套接字可读时您就可以
mbedtls_ssl_read
。更重要的是,即使 EPOLL 没有表明套接字可读,您也可以mbedtls_ssl_read
,因为最后一次读取的套接字获得了多个带有应用程序数据的 TLS 记录。不确定 Mbed-TLS,但对于 OpenSSL,SSL_read
仅包含来自单个 TLS 记录的数据,需要检查 SSL_pending
以了解是否有更多可用数据。 mbed-TLS 有类似的功能mbedtls_ssl_check_pending
。