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

基于OpenSSL的TLS代理纯代理模式优化及连接关闭问题咨询

TLS代理优化方案及SIGSEGV问题解决

需求与当前问题

实现一款TLS代理应用:

  • 监听TLS客户端连接,处理初始若干请求后转发修改后的数据到后端服务器,同时修改后端响应返回给客户端
  • 完成初始处理后进入纯代理模式,双向转发客户端与后端的数据

当前采用双线程阻塞BIO方案,存在两个问题:

  1. 双线程方案存在线程切换开销,且易引发线程安全问题
  2. 双线程下执行SSL_shutdown/SSL_free时间歇性出现SIGSEGV错误

一、更优的纯代理实现方案:单线程非阻塞IO

方案优势

  • 避免线程切换开销,提升性能
  • 消除线程同步带来的复杂度与潜在问题
  • 更适合高并发场景,可通过epoll/select批量处理连接

实现步骤

  1. 将SSL对象的BIO设置为非阻塞模式
  2. 使用select/epoll监听两个SSL连接的读写事件
  3. 单线程内循环处理双向数据转发

代码示例

#include <openssl/ssl.h>
#include <sys/select.h>
#include <errno.h>

#define BUF_LEN 4096
#define MAX(a,b) ((a) > (b) ? (a) : (b))

void single_thread_proxy(SSL *client_ssl, SSL *backend_ssl) {
    fd_set read_fds;
    int client_fd = SSL_get_fd(client_ssl);
    int backend_fd = SSL_get_fd(backend_ssl);
    int max_fd = MAX(client_fd, backend_fd);
    char buf[BUF_LEN];
    int ret;

    // 设置非阻塞模式
    SSL_set_mode(client_ssl, SSL_MODE_AUTO_RETRY);
    SSL_set_mode(backend_ssl, SSL_MODE_AUTO_RETRY);
    BIO_set_nbio(SSL_get_rbio(client_ssl), 1);
    BIO_set_nbio(SSL_get_wbio(client_ssl), 1);
    BIO_set_nbio(SSL_get_rbio(backend_ssl), 1);
    BIO_set_nbio(SSL_get_wbio(backend_ssl), 1);

    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(client_fd, &read_fds);
        FD_SET(backend_fd, &read_fds);

        // 等待可读事件
        ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
        if (ret < 0) {
            if (errno == EINTR) continue;
            break;
        }

        // 处理客户端到后端的转发
        if (FD_ISSET(client_fd, &read_fds)) {
            ssize_t len = SSL_read(client_ssl, buf, BUF_LEN);
            if (len > 0) {
                SSL_write(backend_ssl, buf, len);
            } else if (len == 0 || SSL_get_error(client_ssl, len) == SSL_ERROR_ZERO_RETURN) {
                // 客户端关闭连接,触发后端关闭
                SSL_shutdown(backend_ssl);
                break;
            } else if (SSL_get_error(client_ssl, len) != SSL_ERROR_WANT_READ) {
                // 发生其他错误,退出循环
                break;
            }
        }

        // 处理后端到客户端的转发
        if (FD_ISSET(backend_fd, &read_fds)) {
            ssize_t len = SSL_read(backend_ssl, buf, BUF_LEN);
            if (len > 0) {
                SSL_write(client_ssl, buf, len);
            } else if (len == 0 || SSL_get_error(backend_ssl, len) == SSL_ERROR_ZERO_RETURN) {
                // 后端关闭连接,触发客户端关闭
                SSL_shutdown(client_ssl);
                break;
            } else if (SSL_get_error(backend_ssl, len) != SSL_ERROR_WANT_READ) {
                // 发生其他错误,退出循环
                break;
            }
        }
    }

    // 统一清理资源
    SSL_shutdown(client_ssl);
    SSL_shutdown(backend_ssl);
    SSL_free(client_ssl);
    SSL_free(backend_ssl);
}

二、双线程模式下SIGSEGV错误的解决办法

错误原因

  • 多线程同时操作同一个SSL对象,违反OpenSSL的线程安全要求(SSL对象本身并非线程安全)
  • 一个线程已执行SSL_free释放资源,另一个线程仍在访问该SSL对象

解决措施

  1. 线程同步:用互斥锁保护所有SSL对象的操作(SSL_read/SSL_write/SSL_shutdown/SSL_free)
  2. 连接关闭协调:设置全局退出标志,当任意线程检测到连接关闭时,通知另一线程停止操作
  3. 资源原子释放:统一在主线程等待子线程结束后,再执行SSL资源的关闭与释放

修改后的代码示例

#include <openssl/ssl.h>
#include <pthread.h>

#define BUF_LEN 4096

typedef struct proxy_args_t {
    SSL* source;
    SSL* target;
    pthread_mutex_t* ssl_mutex;
    volatile int* stop_flag;
} proxy_args_t;

void do_proxy(SSL *source, SSL *target, pthread_mutex_t* ssl_mutex, volatile int* stop_flag) {
    char buf[BUF_LEN];
    ssize_t len;

    while (!*stop_flag) {
        pthread_mutex_lock(ssl_mutex);
        len = SSL_read(source, buf, BUF_LEN);
        if (len > 0) {
            SSL_write(target, buf, len);
            pthread_mutex_unlock(ssl_mutex);
        } else {
            pthread_mutex_unlock(ssl_mutex);
            // 检测到连接关闭,设置停止标志
            *stop_flag = 1;
            break;
        }
    }
}

void *proxy_handler(void *args) {
    proxy_args_t *proxy_args = (proxy_args_t *)args;
    do_proxy(proxy_args->source, proxy_args->target, proxy_args->ssl_mutex, proxy_args->stop_flag);
    return NULL;
}

int pure_proxy(SSL *source, SSL *target) {
    pthread_t pthread;
    proxy_args_t proxy_args;
    pthread_mutex_t ssl_mutex;
    volatile int stop_flag = 0;

    // 初始化互斥锁
    if (pthread_mutex_init(&ssl_mutex, NULL) != 0) {
        return -1;
    }

    proxy_args.source = source;
    proxy_args.target = target;
    proxy_args.ssl_mutex = &ssl_mutex;
    proxy_args.stop_flag = &stop_flag;

    // 创建子线程
    if (pthread_create(&pthread, NULL, proxy_handler, &proxy_args) != 0) {
        pthread_mutex_destroy(&ssl_mutex);
        return -1;
    }

    // 主线程处理反向转发
    do_proxy(target, source, &ssl_mutex, &stop_flag);

    // 等待子线程结束
    pthread_join(pthread, NULL);

    // 统一在主线程执行SSL关闭与资源释放
    pthread_mutex_lock(&ssl_mutex);
    SSL_shutdown(source);
    SSL_shutdown(target);
    SSL_free(source);
    SSL_free(target);
    pthread_mutex_unlock(&ssl_mutex);

    // 销毁互斥锁
    pthread_mutex_destroy(&ssl_mutex);
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 04:10:35