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

如何在SOCKS5转发完成后释放accept_conn_cb中创建的bev?

问题:Libevent SOCKS5代理无Connection: close时Bufferevent无法释放

我用Libevent实现了SOCKS5转发代理,但当客户端请求头不含Connection: close字段时,accept_conn_cb中创建的bev无法被释放。尝试修改event_cb同时释放bev和对应server bufferevent无效,目前通过设置5秒超时临时解决,求更优方案。

相关代码

核心实现代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/tcp.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>

#define BUFFER_SIZE 1024
#define SOCKS_ATYP_IPV6 4
#define RESPONSE_SIZE 10
#define SOCKS_VERSION 0x05
#define AUTH_METHOD_NO_AUTH 0x00
#define SOCKS_CMD_CONNECT 0x01
#define SOCKS_CMD_ASSOCIATE 0x03
#define SOCKS_ATYP_IPV4 0x01
#define SOCKS_ATYP_DOMAINNAME 0x03


void socks5_request(struct bufferevent *bev, void *ctx);
void event_cb(struct bufferevent *bev, short events, void *ctx);
void socks5_handshake(struct bufferevent *bev, void *ctx);
void write_cb(struct bufferevent *bev, void *ctx);
void forward_to_server(struct bufferevent *bev, void *ctx);
void forward_to_client(struct bufferevent *bev, void *ctx);
void accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void *ctx);
void accept_error_cb(struct evconnlistener *listener, void *ctx);

void event_cb(struct bufferevent *bev, short events, void *ctx) {
    if (events & BEV_EVENT_ERROR) {
        perror("Error on bufferevent");
    }
    if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
        // Connection closed, free bufferevent
        printf("free bufferevent bev\n");
        bufferevent_free(bev);
    }
}

// socks5 handshake 
void socks5_handshake(struct bufferevent *bev, void *ctx) {
    struct evbuffer *input = bufferevent_get_input(bev);
    size_t len = evbuffer_get_length(input);
    unsigned char buffer[2];
    
    if (len < 2) {
        printf("need more data\n");
        return;
    }
    evbuffer_copyout(input, buffer, 2);
    if (buffer[0] != SOCKS_VERSION) {
        bufferevent_free(bev);
        evbuffer_drain(input, evbuffer_get_length(input));
        return;
    }

    // no auth
    unsigned char response[2] = {SOCKS_VERSION, AUTH_METHOD_NO_AUTH};
    bufferevent_write(bev, response, 2);

    // clean input
    evbuffer_drain(input, evbuffer_get_length(input));

    // handle socks5 request
    bufferevent_setcb(bev, socks5_request, NULL, event_cb, ctx);
}

// forward to server
void forward_to_server(struct bufferevent *bev, void *ctx) {
    struct bufferevent *server = ctx;
    struct evbuffer *input = bufferevent_get_input(bev);
    struct evbuffer *output = bufferevent_get_output(server);
    evbuffer_add_buffer(output, input);
}

// forward to client
void forward_to_client(struct bufferevent *bev, void *ctx) {
    struct bufferevent *client = ctx;
    struct evbuffer *input = bufferevent_get_input(bev);
    struct evbuffer *output = bufferevent_get_output(client);
    evbuffer_add_buffer(output, input);
}

// handle request
void socks5_request(struct bufferevent *bev, void *ctx) {
    struct evbuffer *input = bufferevent_get_input(bev);
    size_t len = evbuffer_get_length(input);
    unsigned char atyp;
    unsigned char buffer[BUFFER_SIZE];

    if (len < 5) {
        printf("need more data\n");
        return;
    }

    evbuffer_copyout(input, buffer, sizeof(buffer));
    if (buffer[1] != SOCKS_CMD_CONNECT) {
        printf("unsupported command %d\n", buffer[1]);
        bufferevent_free(bev);
        evbuffer_drain(input, len);
        return;
    }

    atyp = buffer[3]; // Address type (ATYP)

    if (atyp == SOCKS_ATYP_IPV4) {
        if (len < 10) return;

        unsigned char addr[4];
        unsigned char port[2];
        evbuffer_copyout(input, buffer, 4); // First 4 bytes of request
        evbuffer_drain(input, 4);
        evbuffer_remove(input, addr, 4); // Copy IPv4 address
        evbuffer_remove(input, port, 2); // Copy port

        char target_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, addr, target_ip, INET_ADDRSTRLEN);
        int target_port = (port[0] << 8) | port[1];

        // connect dst
        struct event_base *base = bufferevent_get_base(bev);
        struct bufferevent *server = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
        bufferevent_socket_connect_hostname(server, NULL, AF_INET, target_ip, target_port);

        // response success
        unsigned char response[10] = {SOCKS_VERSION, 0x00, 0x00, SOCKS_ATYP_IPV4, 0, 0, 0, 0, 0, 0};
        bufferevent_write(bev, response, 10);

        bufferevent_setcb(bev, forward_to_server, NULL, event_cb, server);
        bufferevent_setcb(server, forward_to_client, NULL, event_cb, bev);
        bufferevent_enable(bev, EV_READ | EV_WRITE);
        bufferevent_enable(server, EV_READ | EV_WRITE);

        
    } else if (atyp == SOCKS_ATYP_DOMAINNAME) {
        
        int domain_len = buffer[4];
        char domain[256];
        memcpy(domain, &buffer[5], domain_len);
        domain[domain_len] = '\0';

        struct hostent *he = gethostbyname(domain);
        if (he == NULL) {
            printf("Failed to resolve domain: %s\n", domain);
            bufferevent_free(bev);
            evbuffer_drain(input, len);
            return;
        }

        struct sockaddr_in ipv4;
        memset(&ipv4, 0, sizeof(ipv4));
        ipv4.sin_family = AF_INET;
        memcpy(&ipv4.sin_addr, he->h_addr, he->h_length);
        memcpy(&ipv4.sin_port, &buffer[5 + domain_len], 2);

        struct event_base *base = bufferevent_get_base(bev);
        struct bufferevent *server = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
        bufferevent_socket_connect(server, (struct sockaddr *)&ipv4, sizeof(struct sockaddr_in));
        evbuffer_drain(input, 5 + domain_len + 2);

        // response success
        unsigned char response[10] = {SOCKS_VERSION, 0x00, 0x00, SOCKS_ATYP_IPV4, 0, 0, 0, 0, 0, 0};
        bufferevent_write(bev, response, 10);

        bufferevent_setcb(bev, forward_to_server, NULL, event_cb, server);
        bufferevent_setcb(server, forward_to_client, NULL, event_cb, bev);
        bufferevent_enable(bev, EV_READ | EV_WRITE);
        bufferevent_enable(server, EV_READ | EV_WRITE);
    } else {
        bufferevent_free(bev);
    }

    evbuffer_drain(input, len);
}

// handle new accept
void accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void *ctx) {
    struct event_base *base = evconnlistener_get_base(listener);
    struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

    // set socks5_handshake
    bufferevent_setcb(bev, socks5_handshake, NULL, NULL, NULL);
    bufferevent_enable(bev, EV_READ | EV_WRITE);
}

// handle accept error
void accept_error_cb(struct evconnlistener *listener, void *ctx) {
    struct event_base *base = evconnlistener_get_base(listener);
    int err = EVUTIL_SOCKET_ERROR();
    fprintf(stderr, "Accept error %d (%s)\n", err, evutil_socket_error_to_string(err));
    event_base_loopexit(base, NULL);
}


int main()
{
    int port = 10802;
    
    struct event_base *base;
    struct evconnlistener *listener;
    struct sockaddr_in sin;

    // create base
    base = event_base_new();
    if (!base) {
        return 1;
    }

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(port);

    listener = evconnlistener_new_bind(base, accept_conn_cb, NULL, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, (struct sockaddr*)&sin, sizeof(sin));
    if (!listener) {
        perror("evconnlistener_new_bind()");
        return 1;
    }

    evconnlistener_set_error_cb(listener, accept_error_cb);
    event_base_dispatch(base);

    evconnlistener_free(listener);
    event_base_free(base);
    
    return 0;
}

尝试过的无效修改(event_cb)

void event_cb(struct bufferevent *bev, short events, void *ctx) {
    struct bufferevent *client = ctx;
    if (events & BEV_EVENT_ERROR) {
        perror("Error on bufferevent");
    }
    if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
        // Connection closed, free bufferevent
        bufferevent_free(bev);
        if (client) {
            bufferevent_free(client);
        }
    }
}

临时解决方法(超时)

在socks5_request中添加:

struct timeval read_timeout = {5, 0};  // 5 seconds
    struct timeval write_timeout = {5, 0}; // 5 seconds

    bufferevent_set_timeouts(bev, &read_timeout, &write_timeout);
    bufferevent_set_timeouts(server, &read_timeout, &write_timeout);

在event_cb中添加:

if (events & BEV_EVENT_TIMEOUT) {
        if (bev) {
            bufferevent_free(bev);
        }
    }

更优解决方案分析

问题根源在于:当客户端和服务端都使用长连接(不发送Connection: close)且没有数据传输时,Libevent不会触发BEV_EVENT_EOF或BEV_EVENT_ERROR,导致bufferevent一直被持有无法释放。

方案1:完善双向连接的关闭逻辑

之前修改event_cb无效的原因是,释放其中一个bufferevent时,另一个的ctx指针可能已失效或被重复释放。需要先解除两者的回调关联,避免野指针:

void event_cb(struct bufferevent *bev, short events, void *ctx) {
    struct bufferevent *peer_bev = ctx;
    if (events & BEV_EVENT_ERROR) {
        perror("Error on bufferevent");
    }
    if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
        // 先解除对方的回调关联,防止重复释放
        if (peer_bev) {
            bufferevent_setcb(peer_bev, NULL, NULL, NULL, NULL);
            bufferevent_free(peer_bev);
        }
        bufferevent_free(bev);
    }
}

方案2:启用TCP Keepalive

利用TCP层的Keepalive机制自动检测死连接,当连接断开时Libevent会触发关闭事件,从而释放bufferevent。在创建bufferevent后添加以下配置:

// 启用TCP Keepalive
int keepalive = 1;
int idle = 30; // 30秒无数据发送探测包
int interval = 5; // 探测包间隔5秒
int count = 3; // 3次探测无响应则关闭连接
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));
setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));

注:fd为对应bufferevent的socket描述符,在accept_conn_cb中是传入的fd,在socks5_request中可通过bufferevent_getfd(server)获取。

方案3:主动检测连接空闲状态

在读写回调中记录连接最后活跃时间,通过定时事件遍历所有连接,释放超过阈值的空闲连接。这种方式比固定超时更灵活,可根据业务调整空闲时长。


推荐方案

优先选择方案1 + 方案2的组合:

  1. 完善event_cb的释放逻辑,确保双向bufferevent都能被正确释放,避免野指针问题。
  2. 启用TCP Keepalive,让系统自动检测死连接,触发关闭事件。

该组合既解决了长连接空闲时的资源释放问题,又能处理异常断开的情况,比单纯的超时方案更可靠。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 14:50:54