libwebsockets HTTPS 客户端实现机制详解

模块架构图

App API
Context/VHost
TLS Client SSL_CTX
TCP Connect
SSL_new + set_fd + BIO_nbio
SSL_connect loop
Peer Cert Check
SNI / ALPN
Session Resumption
TLS Established
HTTP/WS Client

关键数据结构

  • struct lws_context / struct lws_vhostlib/core/context.clib/core-net/private-lib-core-net.h
    • 客户端TLS配置来源(info.client_* 字段),以及复用的 SSL_CTX 哈希与持有者列表。
  • struct lwslib/core-net/private-lib-core-net.h
    • wsi->tls.ssl:SSL会话句柄
    • wsi->alpnclient_h2_alpn:ALPN选择与HTTP/2标记
    • desc.sockfd:底层套接字
  • struct lws_tls_client_reuselib/tls/openssl/openssl-client.c
    • 指纹哈希(配置指纹)、引用计数、共享的 SSL_CTX

核心函数调用流程图

WANT_READ/WRITE
OK
lws_client_connect_via_info
bind vhost
lws_tls_client_create_vhost_context
socket/connect
SSL_new + SSL_set_fd + BIO_set_nbio
SSL_connect
lws_tls_client_confirm_peer_cert
ALPN selected
HTTP/WS client

典型交互时序图

App LWS SSL Net lws_client_connect_via_info 1 bind vhost / client config 2 lws_tls_client_create_vhost_context 3 socket/connect (non-blocking) 4 SSL_new + SSL_set_fd + BIO_set_nbio 5 WANT_READ / WANT_WRITE 6 resume SSL_connect 7 loop [SSL_connect (non-blocking)] handshake done + ALPN 8 lws_tls_client_confirm_peer_cert 9 enter HTTP/WS client 10 App LWS SSL Net

1. SSL/TLS握手过程实现

1.1 初始化SSL上下文(SSL_CTX)

  • 入口:lib/tls/openssl/openssl-client.c:690 lws_tls_client_create_vhost_context(...)
  • 行为:
    • 选择方法:TLS_client_method()(或回退到TLSv1_2_client_method
    • 为相同配置指纹复用客户端 SSL_CTX(减少加载系统CA的内存开销)
    • 设置选项:SSL_OP_NO_COMPRESSIONSSL_OP_CIPHER_SERVER_PREFERENCESSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_RELEASE_BUFFERS
    • 套件:TLS1.2 SSL_CTX_set_cipher_list;TLS1.3 SSL_CTX_set_ciphersuites
    • CA加载:默认OS路径、指定文件路径或内存DER注入到 X509_STORE(参见 SSL_CTX_set_default_verify_pathsSSL_CTX_load_verify_locationsX509_STORE_add_cert
    • 如有客户端证书与私钥,使用 SSL_CTX_use_certificate_chain_fileSSL_CTX_use_PrivateKey_file*_ASN1 内存版本,并校验匹配 SSL_CTX_check_private_key

1.2 证书验证机制(含自签名证书)

  • 验证入口:握手成功后 lib/tls/openssl/openssl-client.c:581 lws_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:488 lws_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_newSSL_set_fdBIO_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:408 SSL_set_alpn_protos(由 lws_alpn_comma_to_openssl 将逗号分隔字符串转换)
  • 服务器设置选择回调:lib/tls/tls.c:228 SSL_CTX_set_alpn_select_cb
  • 握手成功后客户端获取选择:SSL_get0_alpn_selected 并调用 lws_role_call_alpn_negotiated 驱动角色逻辑(如HTTP/2)

3. 数据加密传输机制

3.1 记录层分帧与加密

  • 明文写:lws_writeSSL_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

可运行示例:最小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中根据函数路径直接跳转源码以进一步确认。

Logo

更多推荐