OpenSSL KTLS发送端生效但接收端未启用的原因及解决方法
KTLS仅发送端(TX)生效,接收端(RX)未启用的解决问题
问题背景
客户端与服务端通过已建立的TCP连接,使用SSL_set_fd()启动TLS握手。服务端核心代码如下:
auto ctx = SSL_CTX_new(TLS_server_method()); SSL_CTX_set_options(ctx, SSL_OP_ENABLE_KTLS); SSL_CTX_use_certificate_chain_file(ctx, "cert.pem"); SSL_CTX_use_PrivateKey_file(ctx, "priv.pem", SSL_FILETYPE_PEM); auto ssl = SSL_new(ctx); SSL_set_fd(ssl, sock); SSL_accept(ssl); // 客户端执行SSL_connect() std::cerr << BIO_get_ktls_send(SSL_get_wbio(ssl)) << "\n"; std::cerr << BIO_get_ktls_recv(SSL_get_rbio(ssl)) << "\n";
省略错误处理与证书校验后,发现KTLS仅TX端生效(输出1),RX端未启用(输出0)。通过strace可见OpenSSL仅设置了TLS_TX的setsockopt,未设置TLS_RX,内核tls模块已加载。
环境信息:Ubuntu 22.04.1 LTS,OpenSSL 3.0.2-ubuntu1.7,内核版本5.15.0-46-generic
解决方法
方法1:调整OpenSSL上下文配置,自动启用KTLS RX
OpenSSL 3.0.x中,KTLS RX的自动启用需要满足两个关键条件:使用内核支持的密码套件,且避免重协商干扰。修改服务端代码如下:
auto ctx = SSL_CTX_new(TLS_server_method()); // 启用KTLS并禁用重协商(重协商会破坏RX端的KTLS状态) SSL_CTX_set_options(ctx, SSL_OP_ENABLE_KTLS | SSL_OP_NO_RENEGOTIATION); // 指定内核原生支持的密码套件(优先选择AES-GCM系列) SSL_CTX_set_cipher_list(ctx, "AES128-GCM-SHA256:AES256-GCM-SHA384"); SSL_CTX_use_certificate_chain_file(ctx, "cert.pem"); SSL_CTX_use_PrivateKey_file(ctx, "priv.pem", SSL_FILETYPE_PEM); auto ssl = SSL_new(ctx); SSL_set_fd(ssl, sock); SSL_accept(ssl); // 再次检查KTLS状态 std::cerr << BIO_get_ktls_send(SSL_get_wbio(ssl)) << "\n"; std::cerr << BIO_get_ktls_recv(SSL_get_rbio(ssl)) << "\n";
修改后重新测试,OpenSSL应会自动调用setsockopt配置TLS_RX。
方法2:手动调用setsockopt配置KTLS RX
若自动配置失败,可在TLS握手完成后,从SSL会话中提取加密参数,手动设置TLS_RX选项。示例代码如下:
#include <linux/tls.h> #include <openssl/ssl.h> #include <openssl/evp.h> // 握手完成后执行 if (SSL_is_init_finished(ssl)) { SSL_SESSION *sess = SSL_get_session(ssl); const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); const EVP_CIPHER *evp_cipher = SSL_CIPHER_get_cipher(cipher); // 仅处理内核支持的AES-GCM套件(根据实际使用的套件调整) if (EVP_CIPHER_type(evp_cipher) == EVP_CIPH_AES_GCM_MODE) { struct tls_crypto_info_aes_gcm rx_info; memset(&rx_info, 0, sizeof(rx_info)); // 填充基础信息 rx_info.info.version = SSL_version(ssl) == TLS1_3_VERSION ? TLS_1_3_VERSION : TLS_1_2_VERSION; rx_info.info.cipher_type = TLS_CIPHER_AES_GCM; rx_info.info.cipher_len = EVP_CIPHER_key_length(evp_cipher); rx_info.info.iv_len = EVP_CIPHER_iv_length(evp_cipher); rx_info.info.record_seq_len = 8; // 提取主密钥(注意:实际需根据TLS版本提取正确的密钥材料) unsigned char master_key[EVP_MAX_KEY_LENGTH]; int master_key_len = SSL_SESSION_get_master_key(sess, master_key, sizeof(master_key)); if (master_key_len > 0) { memcpy(rx_info.key, master_key, rx_info.info.cipher_len); } // 提取IV和盐(不同TLS版本的提取方式不同,TLS1.2和TLS1.3需分别处理) // 此处示例仅做框架,需根据实际协议版本补充完整参数提取逻辑 // 设置TLS_RX选项 if (setsockopt(sock, SOL_TLS, TLS_RX, &rx_info, sizeof(rx_info)) < 0) { perror("setsockopt TLS_RX failed"); } } }
注意:密钥、IV等参数的提取需严格遵循TLS协议规范,不同版本(TLS1.2/TLS1.3)的密钥派生逻辑不同,需对应调整代码。
额外检查项
- 确认内核
tls模块已加载:执行modprobe tls(若未加载) - 验证密码套件兼容性:执行
cat /proc/crypto | grep tls查看内核支持的TLS算法 - 升级OpenSSL补丁:执行
apt update && apt upgrade openssl获取最新修复版本
内容的提问来源于stack exchange,提问作者Thomas
相关产品推荐
相关产品推荐

