You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于lwIP与mbed TLS的嵌入式HTTPS服务器实现技术问询

实现基于lwIP + mbed TLS的嵌入式HTTPS服务器方案

我之前在几个嵌入式项目里刚好折腾过这个——用lwIP自带的HTTPD结合mbed TLS改造成HTTPS服务器,给你分享下具体的思路和代码片段,应该能帮你少踩点坑。

核心改造逻辑

lwIP的httpd本质是基于TCP连接做HTTP解析,我们要做的就是给TCP套一层TLS加密:在TCP连接建立后先完成TLS握手,把原来直接操作TCP socket的读写,替换成经过mbed TLS加密的读写。核心步骤大概是:

  • 在httpd处理新连接的阶段(比如httpd_accept回调)创建mbed TLS的SSL上下文
  • 把TCP的socket绑定到SSL上下文的IO回调上
  • 完成TLS握手(一定要用非阻塞方式,别卡死lwIP的线程)
  • 后续httpd的所有数据收发都通过SSL上下文走,替换原来的recv/send为mbedtls_ssl_read/mbedtls_ssl_write

关键代码片段示例

1. 全局初始化mbed TLS环境

首先要在系统启动时初始化mbed TLS的基础组件,比如随机数生成器、证书和私钥:

#include "mbedtls/ssl.h"
#include "mbedtls/certs.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"

// 全局mbed TLS上下文(可以根据需求改成动态分配)
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_config ssl_cfg;
mbedtls_x509_crt cert;
mbedtls_pk_context pkey;

void mbedtls_server_init(void) {
    mbedtls_entropy_init(&entropy);
    mbedtls_ctr_drbg_init(&ctr_drbg);
    
    // 初始化随机数生成器,有硬件随机源的话这里可以替换成硬件接口
    mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0);
    
    mbedtls_x509_crt_init(&cert);
    mbedtls_pk_init(&pkey);
    
    // 加载服务器证书和私钥,实际项目里建议从加密Flash读取,别硬编码
    // 这里用mbed TLS自带的测试证书做示例
    int ret = mbedtls_x509_crt_parse(&cert, (const unsigned char*) mbedtls_test_crt, mbedtls_test_crt_len);
    if (ret != 0) {
        // 证书加载失败,做错误处理
        return;
    }
    ret = mbedtls_pk_parse_key(&pkey, (const unsigned char*) mbedtls_test_key, mbedtls_test_key_len, NULL, 0);
    if (ret != 0) {
        // 私钥加载失败,做错误处理
        return;
    }
    
    mbedtls_ssl_config_init(&ssl_cfg);
    mbedtls_ssl_config_defaults(&ssl_cfg, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
    
    // 绑定证书和私钥到SSL配置
    mbedtls_ssl_conf_ca_chain(&ssl_cfg, cert.next, NULL);
    mbedtls_ssl_conf_own_cert(&ssl_cfg, &cert, &pkey);
    
    // 绑定随机数生成器
    mbedtls_ssl_conf_rng(&ssl_cfg, mbedtls_ctr_drbg_random, &ctr_drbg);
}

2. 改造lwIP httpd的新连接处理

lwIP的httpd默认在httpd.c的httpd_accept函数处理新TCP连接,我们要在这里插入TLS握手的逻辑:

// 自定义结构体,把httpd连接和SSL上下文绑定起来
typedef struct {
    struct httpd_state *conn;
    mbedtls_ssl_context ssl;
} httpd_ssl_conn_t;

err_t httpd_accept(void *arg, struct tcp_pcb *newpcb, err_t err) {
    struct httpd_state *conn;
    httpd_ssl_conn_t *ssl_conn;
    
    // 保留lwIP httpd原来的连接初始化逻辑
    conn = (struct httpd_state *)mem_malloc(sizeof(struct httpd_state));
    if (conn == NULL) {
        return ERR_MEM;
    }
    memset(conn, 0, sizeof(struct httpd_state));
    conn->pcb = newpcb;
    tcp_arg(newpcb, conn);
    tcp_recv(newpcb, httpd_recv);
    tcp_err(newpcb, httpd_err);
    tcp_poll(newpcb, httpd_poll, 4);
    
    // 初始化SSL上下文
    ssl_conn = (httpd_ssl_conn_t *)mem_malloc(sizeof(httpd_ssl_conn_t));
    if (ssl_conn == NULL) {
        mem_free(conn);
        return ERR_MEM;
    }
    memset(ssl_conn, 0, sizeof(httpd_ssl_conn_t));
    ssl_conn->conn = conn;
    mbedtls_ssl_init(&ssl_conn->ssl);
    
    // 绑定全局SSL配置到当前连接的SSL上下文
    mbedtls_ssl_setup(&ssl_conn->ssl, &ssl_cfg);
    
    // 把TCP socket绑定到SSL的IO回调,需要实现下面的send/recv回调
    mbedtls_ssl_set_bio(&ssl_conn->ssl, newpcb, httpd_ssl_send, httpd_ssl_recv, NULL);
    
    // 启动非阻塞TLS握手,注意处理WANT_READ/WANT_WRITE的情况
    int ret = mbedtls_ssl_handshake(&ssl_conn->ssl);
    if (ret != 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
        // 握手失败,清理资源
        mbedtls_ssl_free(&ssl_conn->ssl);
        mem_free(ssl_conn);
        mem_free(conn);
        tcp_close(newpcb);
        return ERR_CONN;
    }
    
    // 把SSL上下文绑定到httpd连接结构体(需要修改httpd_state,加一个void* ssl_ptr字段)
    conn->ssl_ptr = ssl_conn;
    
    return ERR_OK;
}

3. 实现mbed TLS的IO回调函数

mbed TLS需要我们提供读写TCP的回调,把lwIP的TCP操作转成mbed TLS能识别的接口:

static int httpd_ssl_send(void *ctx, const unsigned char *buf, size_t len) {
    struct tcp_pcb *pcb = (struct tcp_pcb *)ctx;
    err_t err;
    u16_t sent_bytes;
    
    // 用lwIP的tcp_write发送数据
    err = tcp_write(pcb, buf, len, TCP_WRITE_FLAG_COPY);
    if (err != ERR_OK) {
        return MBEDTLS_ERR_SSL_IO;
    }
    
    // 触发TCP发送
    sent_bytes = tcp_output(pcb);
    if (sent_bytes != len) {
        // 数据没发完,告诉mbed TLS稍后再试
        return MBEDTLS_ERR_SSL_WANT_WRITE;
    }
    
    return len;
}

static int httpd_ssl_recv(void *ctx, unsigned char *buf, size_t len) {
    struct tcp_pcb *pcb = (struct tcp_pcb *)ctx;
    err_t err;
    u16_t recv_bytes;
    
    // 从TCP读取数据
    err = tcp_recv(pcb, buf, len, &recv_bytes);
    if (err != ERR_OK) {
        return MBEDTLS_ERR_SSL_IO;
    }
    
    if (recv_bytes == 0) {
        // 对方关闭连接
        return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY;
    }
    
    return recv_bytes;
}

4. 改写httpd的接收和发送逻辑

原来的httpd_recv直接从TCP读取数据,现在要改成从SSL上下文读取:

err_t httpd_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
    struct httpd_state *conn = (struct httpd_state *)arg;
    httpd_ssl_conn_t *ssl_conn = (httpd_ssl_conn_t *)conn->ssl_ptr;
    unsigned char recv_buf[1024]; // 根据你的MTU调整大小
    int ret;
    
    // 先处理未完成的TLS握手(非阻塞模式下可能需要多次调用)
    ret = mbedtls_ssl_handshake(&ssl_conn->ssl);
    if (ret != 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
        // 握手失败,关闭连接
        httpd_close_conn(conn);
        return ERR_OK;
    }
    
    // 从SSL读取加密后的数据
    ret = mbedtls_ssl_read(&ssl_conn->ssl, recv_buf, sizeof(recv_buf));
    if (ret <= 0) {
        if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
            // 对方正常关闭连接
            httpd_close_conn(conn);
        }
        return ERR_OK;
    }
    
    // 把读取到的明文转成lwIP的pbuf,交给原来的httpd处理逻辑
    struct pbuf *new_p = pbuf_alloc(PBUF_TRANSPORT, ret, PBUF_RAM);
    if (new_p != NULL) {
        memcpy(new_p->payload, recv_buf, ret);
        // 调用httpd的原始处理函数,比如httpd_process_input
        httpd_process_input(conn, new_p);
        pbuf_free(new_p);
    }
    
    return ERR_OK;
}

同样,原来的httpd发送响应的函数(比如httpd_send)也要改成用mbedtls_ssl_write来发送数据,逻辑和接收类似。

踩坑提示

  • 内存限制:嵌入式设备内存紧张,mbed TLS的SSL上下文占内存不小,建议调整mbed TLS的编译配置(比如减少支持的加密套件、关闭不必要的TLS扩展)
  • 非阻塞必须严格遵守:lwIP通常是单线程或轻量级线程环境,绝对不能用阻塞的TLS操作,否则会卡死整个系统
  • 证书安全:服务器证书和私钥别硬编码在代码里,建议存在加密的Flash分区,或者用设备的安全存储模块
  • 性能优化:如果设备有硬件加密加速模块,一定要让mbed TLS调用硬件接口,不然加密解密会拖慢系统

替代方案补充

如果实在不想改lwIP httpd,可以看看lwIP的contrib库有没有第三方HTTPS实现,或者试试mongoose——轻量级嵌入式Web服务器,原生支持HTTPS,也能和lwIP、mbed TLS配合。不过你说暂时没找到合适的,那改造lwIP httpd确实是最直接的方案。

内容的提问来源于stack exchange,提问作者wildfireheart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:30:05