Zephyr Socket API结合MbedTLS实现DTLS及自定义PSK回调问题
Zephyr Socket + MbedTLS DTLS 自定义PSK回调与UDP BIO实现方案
核心需求
基于Zephyr 3.5.99,结合Zephyr Socket API与MbedTLS实现DTLS功能,核心要求是通过自定义回调函数在DTLS握手阶段动态修改PSK ID。此前使用Zephyr内置的DTLS套接字(zsock_socket(af, SOCK_DGRAM, IPPROTO_DTLS_1_2))无法支持自定义回调,因此改为手动对接UDP套接字与MbedTLS,但遇到握手失败问题。
现有问题
手动对接代码运行时出现握手失败,错误码-28928(对应MBEDTLS_ERR_SSL_BAD_INPUT_DATA)。核心原因是UDP为无连接协议,原BIO收发函数使用zsock_send/zsock_recv无法处理目标地址信息,必须改用zsock_sendto/zsock_recvfrom;同时缺少UDP套接字绑定步骤,且未实现自定义PSK回调逻辑。
解决方案
1. 自定义BIO上下文结构体
将UDP套接字、目标地址、地址长度封装为结构体,作为MbedTLS BIO的上下文参数,让收发函数能获取到地址信息:
typedef struct { int sock_fd; struct sockaddr_storage dest_addr; socklen_t dest_addr_len; } dtls_bio_ctx_t;
2. 实现支持地址传递的BIO收发函数
替换原有的mbedtls_net_send/mbedtls_net_recv,改用zsock_sendto/zsock_recvfrom:
int dtls_bio_send(void *ctx, const unsigned char *buf, size_t len) { dtls_bio_ctx_t *bio_ctx = (dtls_bio_ctx_t *)ctx; return zsock_sendto(bio_ctx->sock_fd, buf, len, 0, (struct sockaddr *)&bio_ctx->dest_addr, bio_ctx->dest_addr_len); } int dtls_bio_recv(void *ctx, unsigned char *buf, size_t len) { dtls_bio_ctx_t *bio_ctx = (dtls_bio_ctx_t *)ctx; return zsock_recvfrom(bio_ctx->sock_fd, buf, len, 0, (struct sockaddr *)&bio_ctx->dest_addr, &bio_ctx->dest_addr_len); }
3. 实现自定义PSK回调函数
通过mbedtls_ssl_conf_psk_cb注册回调,在握手时动态修改PSK ID:
int psk_callback(void *ctx, mbedtls_ssl_context *ssl, const unsigned char *hint, size_t hint_len, unsigned char *psk_id, size_t *psk_id_len, unsigned char *psk, size_t *psk_len) { // 此处可根据业务逻辑动态生成或修改PSK ID const unsigned char custom_psk_id[] = "Dynamic_PSK_Identity"; const unsigned char custom_psk[] = {0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x01}; if (*psk_id_len < sizeof(custom_psk_id) - 1) { return MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL; } memcpy(psk_id, custom_psk_id, sizeof(custom_psk_id) - 1); *psk_id_len = sizeof(custom_psk_id) - 1; if (*psk_len < sizeof(custom_psk)) { return MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL; } memcpy(psk, custom_psk, sizeof(custom_psk)); *psk_len = sizeof(custom_psk); return 0; }
4. 修正主流程代码
添加UDP套接字绑定,初始化自定义BIO上下文,注册PSK回调并完成DTLS握手:
mbedtls_ssl_context ssl; mbedtls_ssl_config conf; dtls_bio_ctx_t bio_ctx; int ret; mbedtls_ssl_init(&ssl); mbedtls_ssl_config_init(&conf); // 配置DTLS参数(服务器角色) if (mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_DATAGRAM, MBEDTLS_SSL_PRESET_DEFAULT) != 0) { printf("Failed to configure SSL\n"); return -1; } // 创建并绑定UDP套接字 bio_ctx.sock_fd = zsock_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (bio_ctx.sock_fd < 0) { printf("Failed to create UDP socket\n"); goto end; } struct sockaddr_in6 server_addr = {0}; server_addr.sin6_family = AF_INET6; server_addr.sin6_port = htons(4433); server_addr.sin6_addr = in6addr_any; if (zsock_bind(bio_ctx.sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) { printf("Failed to bind UDP socket\n"); goto end; } // 注册自定义PSK回调 mbedtls_ssl_conf_psk_cb(&conf, psk_callback, NULL); // 绑定自定义BIO上下文 mbedtls_ssl_set_bio(&ssl, &bio_ctx, dtls_bio_send, dtls_bio_recv, NULL); if (mbedtls_ssl_setup(&ssl, &conf) != 0) { printf("Failed to set up SSL\n"); goto end; } // 执行DTLS握手(UDP需先接收客户端初始报文,获取目标地址) unsigned char recv_buf[1024]; ret = zsock_recvfrom(bio_ctx.sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&bio_ctx.dest_addr, &bio_ctx.dest_addr_len); if (ret <= 0) { printf("Failed to receive client initial message\n"); goto end; } // 将接收到的初始报文喂给MbedTLS ret = mbedtls_ssl_input(&ssl, recv_buf, ret); if (ret != 0) { printf("Failed to process input: %d\n", ret); goto end; } // 完成握手 ret = mbedtls_ssl_handshake(&ssl); if (ret != 0) { printf("Handshake failed: %d\n", ret); goto end; } printf("DTLS handshake succeeded\n"); end: mbedtls_ssl_free(&ssl); mbedtls_ssl_config_free(&conf); zsock_close(bio_ctx.sock_fd);
关键说明
- UDP无连接特性:服务器端需先通过
zsock_recvfrom获取客户端地址,再执行握手;客户端则需先设置目标服务器地址再调用握手。 - PSK回调时机:回调会在握手阶段被MbedTLS触发,可根据
hint参数或上下文信息动态生成PSK ID和密钥。 - 错误码排查:若仍出现
MBEDTLS_ERR_SSL_BAD_INPUT_DATA,可启用MbedTLS调试日志(配置CONFIG_MBEDTLS_DEBUG)查看更详细的错误原因。
内容的提问来源于stack exchange,提问作者yeetAE13
相关产品推荐
相关产品推荐

