【Linux 网络基础】libwebsockets HTTPS 客户端实现机制详解
·
libwebsockets HTTPS 客户端实现机制详解
模块架构图
关键数据结构
struct lws_context/struct lws_vhost(lib/core/context.c、lib/core-net/private-lib-core-net.h)- 客户端TLS配置来源(
info.client_*字段),以及复用的SSL_CTX哈希与持有者列表。
- 客户端TLS配置来源(
struct lws(lib/core-net/private-lib-core-net.h)wsi->tls.ssl:SSL会话句柄wsi->alpn、client_h2_alpn:ALPN选择与HTTP/2标记desc.sockfd:底层套接字
struct lws_tls_client_reuse(lib/tls/openssl/openssl-client.c)- 指纹哈希(配置指纹)、引用计数、共享的
SSL_CTX。
- 指纹哈希(配置指纹)、引用计数、共享的
核心函数调用流程图
典型交互时序图
1. SSL/TLS握手过程实现
1.1 初始化SSL上下文(SSL_CTX)
- 入口:
lib/tls/openssl/openssl-client.c:690lws_tls_client_create_vhost_context(...) - 行为:
- 选择方法:
TLS_client_method()(或回退到TLSv1_2_client_method) - 为相同配置指纹复用客户端
SSL_CTX(减少加载系统CA的内存开销) - 设置选项:
SSL_OP_NO_COMPRESSION、SSL_OP_CIPHER_SERVER_PREFERENCE、SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_RELEASE_BUFFERS - 套件:TLS1.2
SSL_CTX_set_cipher_list;TLS1.3SSL_CTX_set_ciphersuites - CA加载:默认OS路径、指定文件路径或内存DER注入到
X509_STORE(参见SSL_CTX_set_default_verify_paths、SSL_CTX_load_verify_locations、X509_STORE_add_cert) - 如有客户端证书与私钥,使用
SSL_CTX_use_certificate_chain_file与SSL_CTX_use_PrivateKey_file或*_ASN1内存版本,并校验匹配SSL_CTX_check_private_key
- 选择方法:
1.2 证书验证机制(含自签名证书)
- 验证入口:握手成功后
lib/tls/openssl/openssl-client.c:581lws_tls_client_confirm_peer_cert使用SSL_get_verify_result - 错误分类与处理:
X509_V_OK:通过X509_V_ERR_HOSTNAME_MISMATCH:主机名不匹配,可通过LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK跳过X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT/X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:自签名或链含自签名,可通过LCCSCF_ALLOW_SELFSIGNED允许- 证书未生效/过期:可通过
LCCSCF_ALLOW_EXPIRED允许
- 额外信任:
lws_tls_client_vhost_extra_cert_mem(...)可在运行时向SSL_CTX注入DER证书增强验证
1.3 完整TLS握手消息交换流程
- 客户端驱动:
lib/tls/openssl/openssl-client.c:488lws_tls_client_connect调用SSL_connect - 非阻塞推进:根据返回
SSL_ERROR_WANT_READ/WRITE切换读写事件,继续调用直至成功或错误 - 握手成功:记录套件与ALPN,通过
SSL_get0_alpn_selected获取服务器选择并调用lws_role_call_alpn_negotiated - TLS1.2与TLS1.3握手消息:由OpenSSL处理(ClientHello、ServerHello、EncryptedExtensions、Certificate、Finished等),lws侧只负责驱动与后续处理
2. 客户端连接建立过程
2.1 从TCP连接到HTTPS升级的完整时序
- 创建套接字并非阻塞连接(事件循环)
- 关联SSL:
SSL_new、SSL_set_fd、BIO_set_nbio(1) - 执行握手:
SSL_connect非阻塞循环 - 握手成功后进入HTTP/WS客户端状态机(发送HTTP请求或WS握手)
2.2 SNI(Server Name Indication)实现细节
- 客户端在
SSL_connect期间自动附带SNI(由OpenSSL根据目标主机名) - 服务器端在
lws_ssl_server_name_cb根据servername选择匹配vhost并切换SSL_CTX
2.3 ALPN(Application-Layer Protocol Negotiation)协商过程
- 客户端设置ALPN列表:
lib/tls/openssl/openssl-client.c:408SSL_set_alpn_protos(由lws_alpn_comma_to_openssl将逗号分隔字符串转换) - 服务器设置选择回调:
lib/tls/tls.c:228SSL_CTX_set_alpn_select_cb - 握手成功后客户端获取选择:
SSL_get0_alpn_selected并调用lws_role_call_alpn_negotiated驱动角色逻辑(如HTTP/2)
3. 数据加密传输机制
3.1 记录层分帧与加密
- 明文写:
lws_write→SSL_write(OpenSSL记录层分片、MAC或AEAD、加密)→ 发送 - 密文读:socket → 读BIO →
SSL_read(解密与完整性校验)→ 明文进入HTTP/WS解析
3.2 流量控制与窗口管理
- TLS层流控由记录层与BIO非阻塞驱动;应用层的HTTP/2流控与窗口在H2角色中实现(
lib/roles/h2/*),不直接由TLS层控制 - lws内置RX flow控制(
rxflow_bitmap等)避免应用层过快消费导致拥塞(private-lib-core-net.h)
3.3 会话恢复(Session Resumption)
- 客户端端:握手成功后如检测到
SSL_session_reused(wsi->tls.ssl),调整SSL_SESSION_set_time延长有效期(openssl-client.c:532起) - 服务端缓存管理:
openssl-session.c维护LRU缓存;客户端复用由OpenSSL自动完成,lws负责延寿与状态标记
4. 安全特性实现
4.1 证书链验证流程
- 加载系统CA或指定CA;握手后
SSL_get_verify_result判断,结合允许标志处理自签名/过期/主机名不匹配等场景
4.2 OCSP装订(Stapling)
- 代码库未见到直接启用OCSP Stapling的绑定代码;如需,可在OpenSSL层启用并通过自定义回调接入,lws层不阻碍(建议在服务器侧与客户端侧通过OpenSSL配置统一处理)
4.3 密钥交换算法选择策略
- 由OpenSSL与套件配置控制;TLS1.3中密钥交换独立于套件(如X25519),TLS1.2由套件定义;lws通过
cipher_list/ciphersuites配置影响选择
5. 性能优化措施
5.1 零拷贝发送
- lws在用户态构建发送缓冲并尽量减少复制;TLS层仍需加密处理(不可完全零拷贝)。可利用发送缓冲对齐与合并减少系统调用次数(参考
lib/core-net/output.c)。
5.2 SSL会话缓存管理
- 服务器端的会话缓存由
openssl-session.c管理,客户端在SSL_session_reused情况下延寿;可调整tls_session_cache_max与超时策略。
5.3 硬件加速支持
- OpenSSL在运行时自动检测并使用AES-NI、PCLMUL、VAES等指令集;lws不直接控制,受OpenSSL构建与运行环境影响。
错误处理机制说明
- 握手错误:
lws_tls_client_connect中根据SSL_ERROR_*分支处理,写入错误缓冲并返回LWS_SSL_CAPABLE_ERROR - 证书错误:
lws_tls_client_confirm_peer_cert按类型记录与可允许标志决定是否继续 - 连接错误:非阻塞连接失败由事件循环与
errno判断处理
关键配置参数说明表
info.client_ssl_ca_filepath:客户端CA文件路径info.client_ssl_cert_filepath/info.client_ssl_private_key_filepath:客户端证书与私钥文件info.client_ssl_cipher_list:TLS1.2套件列表(OpenSSL格式)info.client_tls_1_3_plus_cipher_list:TLS1.3套件列表info.alpn:默认ALPN列表(逗号分隔)info.ssl_client_options_set/clear:OpenSSL选项位设置与清除(例如禁用旧版本、启用单次DH使用)info.tls_session_cache_max/info.tls_session_timeout:服务器端缓存容量与超时(客户端延寿受用)
版本兼容性说明
- OpenSSL分支差异:BoringSSL/AWS-LC部分API名与行为不同,代码已做条件编译适配(如
SSL_SESSION_set_time参数类型) - TLS1.2与TLS1.3:套件列表与握手消息组织差异由OpenSSL处理;lws通过配置影响选择,不直接编写握手消息
- 可选后端:mbedTLS路径在
lib/tls/mbedtls/*,接口语义对齐
常见问题解决方案附录(FAQ)
- 自签名证书无法通过验证:
- 设置允许标志
LCCSCF_ALLOW_SELFSIGNED或在SSL_CTX注入自有CA(lws_tls_client_vhost_extra_cert_mem)
- 设置允许标志
- 主机名不匹配:
- 设置
LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK或确保服务器证书CN/SAN包含目标域名
- 设置
- 握手阻塞或失败:
- 检查事件循环是否正确转发
WANT_READ/WRITE;确认BIO_set_nbio(1)已设
- 检查事件循环是否正确转发
- ALPN未协商到期望协议:
- 检查客户端ALPN列表与服务器ALPN选择回调;确保双方包含相同的候选值(如
h2,http/1.1)
- 检查客户端ALPN列表与服务器ALPN选择回调;确保双方包含相同的候选值(如
可运行示例:最小HTTPS客户端(HTTP/1.1 GET)
#include <libwebsockets.h>
static int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len) {
switch (reason) {
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_err("conn error: %s\n", in ? (char*)in : "");
break;
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
lwsl_notice("client http established\n");
break;
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
fwrite(in, 1, len, stdout);
break;
default:
break;
}
return 0;
}
int main(void) {
struct lws_context_creation_info info; memset(&info, 0, sizeof(info));
static const struct lws_protocols protocols[] = {
{ "http", callback_http, 0, 0, 0, NULL, 0 },
LWS_PROTOCOL_LIST_TERM
};
info.port = CONTEXT_PORT_NO_LISTEN;
info.protocols = protocols;
info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.client_ssl_ca_filepath = "./ca-bundle.crt"; // 或者系统CA
info.client_ssl_cipher_list = "DEFAULT"; // TLS1.2
info.client_tls_1_3_plus_cipher_list = "DEFAULT"; // TLS1.3
info.alpn = "h2,http/1.1";
struct lws_context *cx = lws_create_context(&info);
if (!cx) return 1;
struct lws_client_connect_info ci; memset(&ci, 0, sizeof(ci));
ci.context = cx;
ci.address = "www.example.com";
ci.port = 443;
ci.path = "/";
ci.host = ci.address;
ci.origin = ci.address;
ci.ssl_connection = LCCSCF_USE_SSL; // 使用TLS
ci.alpn = "h2,http/1.1";
ci.protocol = protocols[0].name;
struct lws *wsi = lws_client_connect_via_info(&ci);
if (!wsi) { lwsl_err("connect failed\n"); }
while (lws_service(cx, 0) >= 0) { }
lws_context_destroy(cx);
return 0;
}
说明:示例依赖libwebsockets构建的客户端HTTP支持;若目标站点使用自签名证书,可在
info中注入自有CA或设置允许标志(不建议用于生产)。
以上文档从架构、数据结构、流程与时序图全面说明了libwebsockets在客户端HTTPS的实现机制,并提供可运行的最小示例与关键配置说明。你可在IDE中根据函数路径直接跳转源码以进一步确认。
更多推荐




所有评论(0)