Files
utun2/src/proxy/socks_proxy.c
T

960 lines
47 KiB
C

// socks_proxy.c — SOCKS5 / HTTP CONNECT proxy (client side)
#include "socks_proxy.h"
#include "http_proxy.h"
#include "tcp_proxy_server.h"
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "utun_instance.h"
#include "../routing_layer/topo_node.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/tcp_io.h"
#include "../lib/mem.h"
#include "../lib/async_dns.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
static void socks_flow_wake(void* arg);
static void socks_flushed_cb(struct tcp_conn* tc, void* arg);
static void on_accept_cb(socket_t sock, void* arg);
static void on_read_cb(struct ll_queue* q, void* arg);
static void on_fin_cb(struct tcp_conn* tc, void* arg);
static void on_error_cb(struct tcp_conn* tc, int err, void* arg);
static void on_closed_cb(struct tcp_conn* tc, void* arg);
static void socks_proxy_conn_free_deferred(void* arg);
static void socks_proxy_conn_free_soon(struct socks_proxy_conn* c);
static void tx_waiter_cb(struct ll_queue* q, void* arg);
static void tx_retry_timer_cb(void* arg);
static void socks_issue_dns(struct socks_proxy_conn* c, const char* host, uint8_t kind);
static void socks_finalize(struct socks_proxy_conn* c);
static void socks_dns_done_cb(const struct adns_result* res, void* arg);
static void socks_dns_error_and_close(struct socks_proxy_conn* c);
static void socks_bp_register(struct socks_proxy_conn* c);
static void socks_maybe_relay_fin(struct socks_proxy_conn* c);
static void http_finish(struct socks_proxy_conn* c);
static void http_fail(struct socks_proxy_conn* c, int status, const char* reason);
static void http_read(struct socks_proxy_conn* c);
static void http_receive(struct socks_proxy_conn* c, const uint8_t* data, size_t len);
static void http_deadline(void* arg);
struct socks_http_state {
struct http_proxy_message request;
struct http_proxy_message response;
struct http_proxy_request target;
void* timer;
unsigned busy;
uint32_t rx_forwarded;
uint8_t active;
uint8_t request_done;
uint8_t response_done;
uint8_t response_started;
uint8_t close_client;
uint8_t retire_cmd;
uint8_t input_eof;
uint8_t tunnel;
uint8_t stop_upload;
uint8_t sending;
};
static int send_msg(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len, int force);
enum { DNSK_SOCKS = 0, DNSK_HTTP_CONNECT = 1, DNSK_HTTP_PROXY = 2 };
struct listen_ctx {
socket_t listen_sock;
void* socket_id;
struct UASYNC* ua;
struct UTUN_INSTANCE* inst;
uint64_t via_node_id;
int is_http;
struct socks_proxy_conn** conns;
int* conn_count;
uint32_t* next_stream_id;
};
// ====================================================================
// Отправка сообщений через ETCP
// ====================================================================
static int send_msg(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len, int force) {
(void)group_id;
return proxy_send(inst, dst, ETCP_RT_ID_TCP_PROXY_SERVER, subcmd, sid, data, len, force);
}
static int send_connect(struct socks_proxy_conn* c) {
uint8_t buf[6];
memcpy(buf, c->dest_ip, 4); memcpy(buf + 4, &c->dest_port, 2);
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "SOCKS proxy: CONNECT sid=%08x to %d.%d.%d.%d:%d via_node=%016llx %s",
c->stream_id, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3],
ntohs(c->dest_port), (unsigned long long)c->via_node_id, c->http ? "http" : "socks");
return send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, TCP_PROXY_SUBCMD_CONNECT, c->stream_id, buf, 6, 1);
}
static int send_data(struct socks_proxy_conn* c, const uint8_t* data, uint16_t len, int force) {
return proxy_flow_send(&c->flow, data, len, force);
}
static void send_close(struct socks_proxy_conn* c) {
DEBUG_TRACE(DEBUG_CATEGORY_PROXY, "SOCKS send CLOSE sid=%08x", c->stream_id);
if (send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, TCP_PROXY_SUBCMD_CLOSE, c->stream_id, NULL, 0, 1) < 0) c->close_pending = 1;
else { c->close_pending = 0; c->close_sent = 1; }
}
// Удалить только разобранные байты, сохранив следующий запрос/данные того же recv().
static void consume_header(struct socks_proxy_conn* c, uint16_t len) {
c->buf_len -= len;
memmove(c->buf, c->buf + len, c->buf_len);
}
// ====================================================================
// Запись ответа в TCP клиенту через write_queue
// ====================================================================
static int write_to_client(struct socks_proxy_conn* c, const uint8_t* data, uint16_t len) {
struct ll_entry* e = queue_entry_new_from_pool(c->tc->entry_pool);
uint8_t* buf = memory_pool_alloc(c->tc->data_pool);
if (!e || !buf) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: write_to_client alloc failed sid=%08x", c->stream_id);
if (e) queue_entry_free(e); if (buf) memory_pool_free(c->tc->data_pool, buf);
return -1;
}
if (len > c->tc->data_pool->object_size) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: write_to_client len=%u > pool_sz=%zu sid=%08x",
len, c->tc->data_pool->object_size, c->stream_id);
queue_entry_free(e); memory_pool_free(c->tc->data_pool, buf);
return -1;
}
memcpy(buf, data, len);
e->dgram = buf; e->len = len;
c->bytes_to_client += len;
queue_data_put(c->tc->write_queue, e);
return 0;
}
// Deadline относится к фазе HTTP, а не к каждому принятому байту заголовка.
static void http_arm(struct socks_proxy_conn* c, int timeout_tb) {
if (c->http->timer) uasync_cancel_timeout(c->ua, c->http->timer);
c->http->timer = uasync_set_timeout(c->ua, timeout_tb, c, http_deadline, "http_deadline");
if (!c->http->timer) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "HTTP: deadline allocation failed sid=%08x", c->stream_id);
on_error_cb(c->tc, ENOMEM, c);
}
}
// Request output ограничен одним входным блоком плюс реконструированные заголовки.
static int http_request_output(void* arg, const uint8_t* data, size_t len, size_t source_len) {
(void)source_len;
struct socks_proxy_conn* c = arg;
if (!len) return 0;
size_t total = c->tx_len + len;
if (total > HTTP_PROXY_HEADER_LIMIT + HTTP_PROXY_LINE_LIMIT + TCP_PROXY_CHUNK) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "HTTP: request output limit sid=%08x bytes=%zu", c->stream_id, total); return -1;
}
uint8_t* buf = u_realloc(c->tx_buf, total);
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "HTTP: request allocation failed sid=%08x bytes=%zu", c->stream_id, total); return -1; }
memcpy(buf + c->tx_len, data, len); c->tx_buf = buf; c->tx_len = (uint16_t)total;
return 0;
}
// Deferred write consumer исключает flush посреди реконструкции одного header block.
static int http_response_output(void* arg, const uint8_t* data, size_t len, size_t source_len) {
struct socks_proxy_conn* c = arg;
struct socks_http_state* h = c->http;
if (h->response.status >= 200 || h->response.status == 101) h->response_started = 1;
for (size_t off = 0; off < len;) {
size_t n = len - off; if (n > TCP_PROXY_CHUNK) n = TCP_PROXY_CHUNK;
if (write_to_client(c, data + off, (uint16_t)n) < 0) return -1;
off += n;
}
h->rx_forwarded += (uint32_t)source_len;
tcp_conn_set_flushed(c->tc, socks_flushed_cb);
return 0;
}
// Завершение отдельного upstream не закрывает клиентский keep-alive.
static void http_finish(struct socks_proxy_conn* c) {
struct socks_http_state* h = c->http;
if (!h || h->busy || h->tunnel || c->freed || c->free_soon_id) return;
if (h->retire_cmd) {
uint8_t cmd = h->retire_cmd;
h->busy++;
int ret = send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, cmd, c->stream_id, NULL, 0, 1);
h->busy--;
if (ret < 0) {
socks_bp_register(c); return;
}
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "HTTP: upstream retired sid=%08x command=%u", c->stream_id, cmd);
h->retire_cmd = 0; h->active = 0; c->stream_id = 0;
if (!c->rem_closed) {
if (h->timer) uasync_cancel_timeout(c->ua, h->timer);
int eof = h->input_eof;
memset(h, 0, sizeof(*h)); h->input_eof = eof; c->state = HTTP_STATE_REQUEST;
http_arm(c, 600000);
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "HTTP: keep-alive ready buffered=%u", c->buf_len);
http_read(c); return;
}
}
if (c->rem_closed) {
if (!c->close_queued) {
c->close_queued = 1;
if (tcp_conn_push_close(c->tc) < 0) on_error_cb(c->tc, ENOMEM, c);
}
return;
}
if (h->input_eof && !h->active && !c->buf_len && !c->tc->read_queue->head) {
if (h->request.header_len) {
http_fail(c, 400, "EOF in request headers"); return;
}
c->rem_closed = 1; http_finish(c); return;
}
if (!h->active) return;
if (h->input_eof && c->flow.ready && !h->request_done && !h->stop_upload && !c->buf_len && !c->tc->read_queue->head) {
http_fail(c, 400, "EOF in request body"); return;
}
if ((h->request_done || h->stop_upload) && !c->tx_buf && !h->target.upgrade[0] && !c->flow.fin_sent && c->flow.ready) {
h->busy++;
c->flow.fin_pending = 1; proxy_flow_flush(&c->flow);
h->busy--;
if (c->rem_closed || c->free_soon_id) { http_finish(c); return; }
}
if (!h->response_done || c->tx_buf || c->tc->write_buf || c->tc->write_queue->head) return;
if (!h->request_done && !h->close_client) return;
h->retire_cmd = h->close_client && !h->request_done ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
proxy_flow_destroy(&c->flow); c->flow.ready = 0;
etcp_router_cancel_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER, &c->tx_waiter);
if (c->tx_retry_timer) { uasync_cancel_timeout(c->ua, c->tx_retry_timer); c->tx_retry_timer = NULL; }
int close = h->close_client || h->target.close || (h->input_eof && !c->buf_len && !c->tc->read_queue->head);
if (h->timer) { uasync_cancel_timeout(c->ua, h->timer); h->timer = NULL; }
// Ответ полностью передан в OS TCP: старый credit/FIN не может попасть в новую транзакцию.
if (close) c->rem_closed = 1;
http_finish(c);
if (h->retire_cmd || c->rem_closed) http_arm(c, 600000);
}
// Ошибка до final headers получает отдельный ответ; после них закрываем прерванное сообщение.
static void http_fail(struct socks_proxy_conn* c, int status, const char* reason) {
struct socks_http_state* h = c->http;
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "HTTP: fail sid=%08x status=%d reason=%s active=%u request_done=%u response_started=%u",
c->stream_id, status, reason, h->active, h->request_done, h->response_started);
if (c->rem_closed) { socks_proxy_conn_free_soon(c); return; }
c->rem_closed = 1;
if (c->dns_q) { adns_cancel(c->dns_q); c->dns_q = NULL; }
proxy_flow_destroy(&c->flow); c->flow.ready = 0;
etcp_router_cancel_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER, &c->tx_waiter);
h->stop_upload = 1;
if (c->tx_buf && !h->busy) { u_free(c->tx_buf); c->tx_buf = NULL; c->tx_len = 0; }
if (h->active) h->retire_cmd = TCP_PROXY_SUBCMD_ERROR;
if (!h->response_started) {
char reply[160];
const char* text = status == 431 ? "Request Header Fields Too Large" : status == 414 ? "URI Too Long" :
status == 408 ? "Request Timeout" : status == 504 ? "Gateway Timeout" :
status == 502 ? "Bad Gateway" : status == 500 ? "Internal Server Error" : "Bad Request";
int n = snprintf(reply, sizeof(reply), "HTTP/1.1 %d %s\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", status, text);
if (write_to_client(c, (const uint8_t*)reply, (uint16_t)n) < 0) { on_error_cb(c->tc, ENOMEM, c); return; }
}
http_arm(c, 600000); http_finish(c);
}
// Ожидание заголовков/connect ограничено; зависшее закрытие освобождается принудительно.
static void http_deadline(void* arg) {
struct socks_proxy_conn* c = arg;
c->http->timer = NULL;
if (c->http->tunnel) return;
if (c->rem_closed) {
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "HTTP: close drain deadline expired sid=%08x", c->stream_id);
socks_proxy_conn_free_soon(c); return;
}
if (!c->http->active && !c->http->request.header_len && !c->buf_len) {
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "HTTP: idle connection expired");
c->rem_closed = 1; http_finish(c); return;
}
http_fail(c, c->http->active ? 504 : 408, "deadline expired");
}
// Один read block может содержать конец тела и начало следующего запроса.
static void http_read(struct socks_proxy_conn* c) {
struct socks_http_state* h = c->http;
if (c->rem_closed || c->free_soon_id || c->tx_buf || h->stop_upload || (h->active && (!c->flow.ready || h->request_done))) return;
h->busy++;
if (!c->buf_len) {
struct ll_entry* e = queue_data_get(c->tc->read_queue);
if (!e) { h->busy--; http_finish(c); return; }
memcpy(c->buf, e->dgram, e->len); c->buf_len = e->len;
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
}
size_t used = 0;
int ret;
if (!h->active) {
if (!h->request.header_len) http_arm(c, 300000);
ret = http_proxy_headers_feed(&h->request, c->buf, c->buf_len, &used);
consume_header(c, (uint16_t)used);
if (ret > 0) {
ret = http_proxy_request_parse(&h->request, &h->target, http_request_output, c);
if (ret == 0) {
c->stream_id = ++(*c->next_stream_id);
if (!c->stream_id) c->stream_id = ++(*c->next_stream_id);
proxy_flow_init(&c->flow, c->inst, c->ua, c->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER,
c->stream_id, socks_flow_wake, c);
h->active = 1; h->close_client = h->target.close; h->request_done = h->request.body == HTTP_BODY_DONE;
c->dest_port = htons(h->target.port); http_arm(c, 150000);
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "HTTP: request sid=%08x host=%s port=%u connect=%u framing=%u length=%llu",
c->stream_id, h->target.host, h->target.port, h->target.connect, h->request.body,
(unsigned long long)h->request.length);
socks_issue_dns(c, h->target.host, h->target.connect ? DNSK_HTTP_CONNECT : DNSK_HTTP_PROXY);
}
}
} else {
ret = http_proxy_body_feed(&h->request, c->buf, c->buf_len, &used, http_request_output, c);
consume_header(c, (uint16_t)used); http_arm(c, 600000);
if (ret > 0) h->request_done = 1;
}
h->busy--;
if (ret < 0) { http_fail(c, -ret, "request parse/output"); return; }
if (c->flow.ready && (c->tx_buf || (c->buf_len && !h->request_done))) socks_flow_wake(c);
else if (!h->active || (!h->request_done && !c->tx_buf)) queue_resume_callback(c->tc->read_queue);
http_finish(c);
}
// Принятые ETCP bytes остаются charged до доставки реконструированного результата.
static void http_receive(struct socks_proxy_conn* c, const uint8_t* data, size_t len) {
struct socks_http_state* h = c->http;
if (!h->active || h->response_done) { http_fail(c, 502, "unsolicited response bytes"); return; }
h->busy++;
int ret = 0;
while (len && !h->response_done) {
size_t used = 0;
if (!h->response.headers_done) {
if (!h->response.header_len) http_arm(c, 300000);
ret = http_proxy_headers_feed(&h->response, data, len, &used);
data += used; len -= used;
if (ret <= 0) break;
int early = h->response.headers[9] >= '2' && (!h->request_done || c->tx_buf);
if (early) {
h->close_client = 1;
h->stop_upload = 1;
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "HTTP: early final response sid=%08x; stop upload", c->stream_id);
}
ret = http_proxy_response_parse(&h->response, &h->target, h->close_client, http_response_output, c);
if (ret < 0) break;
http_arm(c, 600000);
if (h->response.status == 101) {
if (!h->request_done || c->tx_buf) { ret = -502; break; }
h->tunnel = 1;
if (h->timer) { uasync_cancel_timeout(c->ua, h->timer); h->timer = NULL; }
if (len && http_response_output(c, data, len, len) < 0) { ret = -500; break; }
if (c->buf_len) {
c->tx_buf = u_malloc(c->buf_len);
if (!c->tx_buf) { ret = -500; break; }
memcpy(c->tx_buf, c->buf, c->buf_len); c->tx_len = c->buf_len; c->buf_len = 0;
}
len = 0; c->state = SOCKS_STATE_RELAY; socks_flow_wake(c); break;
}
if (h->response.status < 200) { memset(&h->response, 0, sizeof(h->response)); continue; }
h->close_client |= h->response.close;
}
if (h->response.body == HTTP_BODY_DONE) h->response_done = 1;
else {
ret = http_proxy_body_feed(&h->response, data, len, &used, http_response_output, c);
data += used; len -= used; http_arm(c, 600000);
if (ret < 0) break;
if (ret > 0) h->response_done = 1;
}
}
if (len && ret >= 0 && !h->tunnel) ret = -502;
h->busy--;
if (ret < 0) {
if (ret == -500) on_error_cb(c->tc, ENOMEM, c);
else http_fail(c, 502, "response parse/boundary");
return;
}
if (h->stop_upload && c->tx_buf && !h->busy) socks_flow_wake(c);
if (h->rx_forwarded && !c->tc->write_buf && !c->tc->write_queue->head) socks_flushed_cb(c->tc, c);
http_finish(c);
}
// ====================================================================
// SOCKS5 handshake
// ====================================================================
static void process_socks_greeting(struct socks_proxy_conn* c) {
if (c->buf_len < 2) return;
uint8_t ver = c->buf[0], nmethods = c->buf[1];
if (ver != 5 || !nmethods) {
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_proxy: invalid greeting sid=%08x ver=%u methods=%u", c->stream_id, ver, nmethods);
c->rem_closed = 1; tcp_conn_push_close(c->tc); return;
}
if (c->buf_len < (uint16_t)(2 + nmethods)) return;
if (!memchr(c->buf + 2, 0, nmethods)) {
uint8_t reject[] = {5, 255};
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_proxy: no supported authentication sid=%08x", c->stream_id);
write_to_client(c, reject, sizeof(reject)); c->rem_closed = 1; tcp_conn_push_close(c->tc); return;
}
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "socks_proxy: greeting ver=%d nmethods=%d", ver, nmethods);
uint8_t reply[] = { 0x05, 0x00 };
write_to_client(c, reply, 2);
consume_header(c, 2 + nmethods);
c->state = SOCKS_STATE_REQUEST;
}
static void process_socks_request(struct socks_proxy_conn* c) {
if (c->buf_len < 4) return;
uint8_t ver = c->buf[0], cmd = c->buf[1], atyp = c->buf[3];
if (ver != 5 || c->buf[2] != 0) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: bad ver=%d", ver); goto error; }
if (cmd != 1) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: unsupported cmd=%d (only CONNECT supported)", cmd); goto error; }
uint16_t need;
if (atyp == 1) need = 10; // IPv4: 4+2=6 more bytes
else if (atyp == 3) { // domain: 1+len+2
if (c->buf_len < 5) return;
if (!c->buf[4]) { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_proxy: empty domain"); goto error; }
need = (uint16_t)(5 + c->buf[4] + 2);
}
else if (atyp == 4) need = 22; // IPv6: 16+2=18 more
else { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: unsupported atyp=%d", atyp); goto error; }
if (c->buf_len < need) return;
if (atyp == 1) {
memcpy(c->dest_ip, c->buf + 4, 4);
memcpy(&c->dest_port, c->buf + 8, 2);
consume_header(c, need);
socks_finalize(c);
return;
} else if (atyp == 4) {
uint8_t reply[] = {5, 8, 0, 1, 0, 0, 0, 0, 0, 0};
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_proxy: IPv6 destination unsupported sid=%08x", c->stream_id);
write_to_client(c, reply, sizeof(reply)); c->rem_closed = 1; tcp_conn_push_close(c->tc); return;
}
// atyp == 3 (domain)
uint8_t dlen = c->buf[4];
char domain[256]; memcpy(domain, c->buf + 5, dlen); domain[dlen] = '\0';
memcpy(&c->dest_port, c->buf + 5 + dlen, 2);
consume_header(c, need);
socks_issue_dns(c, domain, DNSK_SOCKS);
return;
error: {
uint8_t err_reply[] = { 0x05, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
write_to_client(c, err_reply, 10);
tcp_conn_push_close(c->tc);
}
}
// ====================================================================
// DNS-резолвинг (неблокирующий) + финализация рукопожатия
// ====================================================================
// DNS готов: запрашиваем exit, успех клиенту сообщаем только после CONNECTED.
static void socks_finalize(struct socks_proxy_conn* c) {
c->state = SOCKS_STATE_CONNECTING;
if (send_connect(c) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: send_connect failed sid=%08x", c->stream_id);
socks_dns_error_and_close(c);
}
}
// Ответ CONNECTED открывает релей и выпускает сохранённый хвост рукопожатия.
static void socks_connected(struct socks_proxy_conn* c) {
if (c->state != SOCKS_STATE_CONNECTING || c->flow.ready) {
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_proxy: unexpected CONNECTED sid=%08x state=%u", c->stream_id, c->state);
return;
}
c->flow.ready = 1;
if (c->http && !c->http->target.connect) {
c->state = HTTP_STATE_RELAY;
http_arm(c, 600000);
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "HTTP: upstream connected sid=%08x buffered=%u", c->stream_id, c->tx_len);
socks_flow_wake(c); return;
}
if (c->dns_kind == DNSK_SOCKS) {
const uint8_t reply[] = {5, 0, 0, 1, 0, 0, 0, 0, 0, 0};
if (write_to_client(c, reply, sizeof(reply)) < 0) { on_error_cb(c->tc, ENOMEM, c); return; }
} else if (c->dns_kind == DNSK_HTTP_CONNECT) {
char reply[64];
int len = snprintf(reply, sizeof(reply), "HTTP/1.%u 200 Connection Established\r\n\r\n", c->http->target.minor);
if (write_to_client(c, (const uint8_t*)reply, (uint16_t)len) < 0) { on_error_cb(c->tc, ENOMEM, c); return; }
c->http->tunnel = 1;
if (c->http->timer) { uasync_cancel_timeout(c->ua, c->http->timer); c->http->timer = NULL; }
}
c->state = SOCKS_STATE_RELAY;
if (c->buf_len) {
c->tx_buf = u_malloc(c->buf_len);
if (!c->tx_buf) { on_error_cb(c->tc, ENOMEM, c); return; }
memcpy(c->tx_buf, c->buf, c->buf_len); c->tx_len = c->buf_len; c->buf_len = 0;
}
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: established sid=%08x buffered=%u", c->stream_id, c->tx_len);
socks_flow_wake(c);
}
static void socks_dns_error_and_close(struct socks_proxy_conn* c) {
if (c->http) { http_fail(c, 502, "DNS/connect failed"); return; }
uint8_t err[] = { 0x05, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
write_to_client(c, err, sizeof(err));
c->rem_closed = 1;
tcp_conn_push_close(c->tc);
}
static void socks_issue_dns(struct socks_proxy_conn* c, const char* host, uint8_t kind) {
c->dns_kind = kind;
if (inet_pton(AF_INET, host, &c->dest_ip) == 1) {
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "socks_proxy: literal IP %s sid=%08x", host, c->stream_id);
socks_finalize(c);
return;
}
c->state = SOCKS_STATE_CONNECTING;
c->dns_q = adns_resolve(c->ua, host, NULL, socks_dns_done_cb, c);
if (!c->dns_q) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: DNS resolve start failed for %s sid=%08x", host, c->stream_id);
socks_dns_error_and_close(c);
}
}
static void socks_dns_done_cb(const struct adns_result* res, void* arg) {
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
c->dns_q = NULL;
if (res->status == ADNS_OK && res->count > 0) {
memcpy(c->dest_ip, &res->addrs[0].sin_addr, 4);
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: resolved → %d.%d.%d.%d:%d sid=%08x",
c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], ntohs(c->dest_port), c->stream_id);
socks_finalize(c);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: DNS failed status=%d sid=%08x", res->status, c->stream_id);
socks_dns_error_and_close(c);
}
}
// ====================================================================
// tcp_io read callback — парсинг рукопожатия + релей данных
// ====================================================================
static void on_read_cb(struct ll_queue* q, void* arg) {
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
if (c->http && !c->http->tunnel) { http_read(c); return; }
if (c->state == SOCKS_STATE_CONNECTING || c->tx_buf || c->rem_closed) return;
struct ll_entry* e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
if (c->rem_closed) {
queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(q); return;
}
if (c->state == SOCKS_STATE_GREETING || c->state == SOCKS_STATE_REQUEST) {
// накапливаем данные для рукопожатия
uint16_t space = sizeof(c->buf) - c->buf_len;
if (space < e->len) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: handshake buffer overflow sid=%08x", c->stream_id);
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
tcp_conn_push_close(c->tc); queue_resume_callback(q); return;
}
memcpy(c->buf + c->buf_len, e->dgram, e->len);
c->buf_len += e->len;
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
if (c->state == SOCKS_STATE_GREETING) process_socks_greeting(c);
if (!c->rem_closed && c->state == SOCKS_STATE_REQUEST) process_socks_request(c);
if (c->state != SOCKS_STATE_CONNECTING && !c->rem_closed) queue_resume_callback(q);
return;
}
int ret = send_data(c, e->dgram, e->len, 0);
DEBUG_TRACE(DEBUG_CATEGORY_PROXY, "SOCKS PROXY SEND sid=%08x len=%u ret=%d http=%d", c->stream_id, e->len, ret, c->http != NULL);
if (ret == 0) {
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
queue_resume_callback(q);
} else {
c->tx_buf = u_malloc(e->len);
if (c->tx_buf) { memcpy(c->tx_buf, e->dgram, e->len); c->tx_len = e->len; }
else { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: tx_buf allocation failed sid=%08x", c->stream_id); }
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
if (!c->tx_buf) { on_error_cb(c->tc, ENOMEM, c); return; }
socks_bp_register(c);
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "SOCKS PROXY BP: sid=%08x tx_buf=%u waiter_reg", c->stream_id, c->tx_len);
}
}
// Отправка буфера частями сохраняет HTTP headers/body, независимо от размера одного DATA.
static void socks_flow_wake(void* arg) {
struct socks_proxy_conn* c = arg;
if (c->http && c->http->retire_cmd) { http_finish(c); return; }
if (c->http && c->http->sending) return;
if (c->rem_closed || c->freed || !c->flow.ready) return;
if (c->http) c->http->busy++;
while (c->tx_buf) {
if (c->http && c->http->stop_upload) { u_free(c->tx_buf); c->tx_buf = NULL; c->tx_len = 0; break; }
uint16_t len = c->tx_len > TCP_PROXY_CHUNK ? TCP_PROXY_CHUNK : c->tx_len;
uint8_t chunk[TCP_PROXY_CHUNK];
const uint8_t* data = c->tx_buf;
// Текущий chunk уже принадлежит send: синхронный 101 должен видеть только ещё не отправленный хвост.
if (c->http) {
memcpy(chunk, c->tx_buf, len); data = chunk;
c->tx_len -= len;
if (c->tx_len) memmove(c->tx_buf, c->tx_buf + len, c->tx_len);
else { u_free(c->tx_buf); c->tx_buf = NULL; }
c->http->sending = 1;
}
int ret = send_data(c, data, len, 0);
if (c->http) c->http->sending = 0;
if (c->http && (c->http->stop_upload || c->rem_closed || c->free_soon_id)) {
u_free(c->tx_buf); c->tx_buf = NULL; c->tx_len = 0;
c->http->busy--; http_finish(c); return;
}
if (ret < 0) {
if (c->http) {
uint8_t* restored = u_realloc(c->tx_buf, c->tx_len + len);
if (!restored) {
c->http->busy--;
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "HTTP: send rollback allocation failed sid=%08x", c->stream_id);
on_error_cb(c->tc, ENOMEM, c); return;
}
memmove(restored + len, restored, c->tx_len); memcpy(restored, chunk, len);
c->tx_buf = restored; c->tx_len += len;
}
socks_bp_register(c);
if (c->http) c->http->busy--;
return;
}
if (!c->http) {
c->tx_len -= len;
if (c->tx_len) memmove(c->tx_buf, c->tx_buf + len, c->tx_len);
else { u_free(c->tx_buf); c->tx_buf = NULL; }
}
}
if (c->http) c->http->busy--;
if (c->http && !c->http->tunnel) {
if (c->buf_len && !c->http->busy && !c->http->request_done) http_read(c);
else if (!c->http->request_done) queue_resume_callback(c->tc->read_queue);
http_finish(c);
} else {
queue_resume_callback(c->tc->read_queue);
socks_maybe_relay_fin(c);
}
}
static void tx_waiter_cb(struct ll_queue* q, void* arg) {
(void)q;
socks_flow_wake(arg);
}
static void tx_retry_timer_cb(void* arg) {
struct socks_proxy_conn* c = arg;
c->tx_retry_timer = NULL;
socks_flow_wake(c);
}
static void socks_bp_register(struct socks_proxy_conn* c) {
if (c->flow.ready && c->flow.tx_credit >= (c->tx_len > TCP_PROXY_CHUNK ? TCP_PROXY_CHUNK : c->tx_len))
etcp_router_on_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER, &c->tx_waiter, tx_waiter_cb, c);
if (!c->tx_retry_timer) c->tx_retry_timer = uasync_set_timeout(c->ua, 5000, c, tx_retry_timer_cb, "socks_retry");
}
// Обе половины закрываются независимо: write_queue не задерживает FIN прочитанного направления.
static void socks_maybe_relay_fin(struct socks_proxy_conn* c) {
if (c->rem_closed || c->close_queued || !c->flow.ready) return;
if (c->tc->fin_remote && !c->tx_buf && !c->tc->read_queue->head && !c->flow.fin_sent) {
c->flow.fin_pending = 1;
proxy_flow_flush(&c->flow);
}
if (c->flow.fin_sent && c->flow.fin_received && c->tc->fin_local) {
c->close_queued = 1;
tcp_conn_push_close(c->tc);
}
}
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
struct socks_proxy_conn* c = arg;
if (c->http && !c->http->tunnel) {
c->http->input_eof = tc->fin_remote; http_finish(c); return;
}
socks_maybe_relay_fin(arg);
}
static void socks_flushed_cb(struct tcp_conn* tc, void* arg) {
struct socks_proxy_conn* c = arg;
if (c->http && !c->http->tunnel) {
if (!tc->write_buf && !tc->write_queue->head) {
uint32_t credit = c->http->rx_forwarded; c->http->rx_forwarded = 0;
if (credit && c->http->active && !c->http->retire_cmd && !c->rem_closed) {
c->http->busy++;
proxy_flow_consume(&c->flow, credit);
c->http->busy--;
}
}
http_finish(c); return;
}
if (!tc->write_buf && !tc->write_queue->head)
proxy_flow_consume(&c->flow, TCP_PROXY_WINDOW - c->flow.rx_credit - c->flow.consumed);
socks_maybe_relay_fin(c);
}
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
(void)err;
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: tcp error sid=%08x err=%d state=%d fm_r=%d fm_l=%d rem_cl=%d cs=%d cp=%d "
"bytes_client=%u bytes_exit=%u wq=%d",
c->stream_id, err, c->state, tc->fin_remote, tc->fin_local, c->rem_closed, c->close_sent, c->close_pending,
c->bytes_to_client, c->bytes_from_exit, c->tc->write_queue->count);
int notify = !c->close_sent && !c->close_pending && (!c->http || c->http->active);
struct UTUN_INSTANCE* inst = c->inst;
uint64_t via = c->via_node_id;
uint32_t sid = c->stream_id;
c->rem_closed = 1;
socks_proxy_conn_free_soon(c);
if (notify && inst) send_msg(inst, TOPO_GROUP_UTUN, via, TCP_PROXY_SUBCMD_ERROR, sid, NULL, 0, 1);
}
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: tcp closed sid=%08x state=%d fm_r=%d fm_l=%d rem_cl=%d bytes_client=%u bytes_exit=%u",
c->stream_id, c->state, tc->fin_remote, tc->fin_local, c->rem_closed, c->bytes_to_client, c->bytes_from_exit);
if (c->http && c->http->active && !c->http->retire_cmd)
c->http->retire_cmd = TCP_PROXY_SUBCMD_ERROR;
if (!c->http && !c->flow.fin_sent && !c->rem_closed && !c->close_sent && !c->close_pending) send_close(c);
socks_proxy_conn_free_soon(c);
}
// ====================================================================
// Accept callback для слушающего сокета
// ====================================================================
static void on_accept_cb(socket_t sock, void* arg) {
struct listen_ctx* ctx = (struct listen_ctx*)arg;
struct sockaddr_in addr; socklen_t alen = sizeof(addr);
socket_t csock = accept(sock, (struct sockaddr*)&addr, &alen);
if (csock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: accept failed errno=%d", errno); return; }
socket_set_nonblocking(csock);
struct socks_proxy_conn* c = u_calloc(1, sizeof(struct socks_proxy_conn));
if (!c) { socket_close_wrapper(csock); DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: u_calloc failed"); return; }
c->stream_id = ++(*ctx->next_stream_id);
c->next_stream_id = ctx->next_stream_id;
c->ua = ctx->ua; c->inst = ctx->inst; c->via_node_id = ctx->via_node_id;
c->state = ctx->is_http ? HTTP_STATE_REQUEST : SOCKS_STATE_GREETING;
c->head = ctx->conns; c->count = ctx->conn_count;
proxy_flow_init(&c->flow, ctx->inst, ctx->ua, ctx->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER, c->stream_id, socks_flow_wake, c);
c->tc = tcp_conn_create(ctx->ua, csock, 4096, 4096, 8, 0, 0, on_fin_cb, on_error_cb, c);
if (!c->tc) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: tcp_conn_create failed"); u_free(c); socket_close_wrapper(csock); return; }
if (ctx->is_http) {
c->http = u_calloc(1, sizeof(*c->http));
if (!c->http) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "HTTP: state allocation failed"); tcp_conn_destroy(c->tc); u_free(c); return;
}
queue_set_callback_defer(c->tc->write_queue, 1);
http_arm(c, 300000);
}
c->tc->on_closed = on_closed_cb;
c->tc->on_fin_sent = on_fin_cb;
queue_set_callback(c->tc->read_queue, on_read_cb, c);
queue_set_waiter_defer(c->tc->read_queue, 1);
c->next = *ctx->conns; *ctx->conns = c; (*ctx->conn_count)++;
char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &addr.sin_addr, ip, sizeof(ip));
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: accepted %s conn from %s:%d sid=%08x total=%d",
ctx->is_http ? "http" : "socks", ip, ntohs(addr.sin_port), c->stream_id, *ctx->conn_count);
}
// ====================================================================
// Публичное API для tcp_proxy_client (etcp dispatch)
// ====================================================================
struct socks_proxy_conn* socks_proxy_find_conn(struct socks_proxy_conn* head, uint32_t stream_id) {
struct socks_proxy_conn* c;
for (c = head; c; c = c->next) if (c->stream_id == stream_id) return c;
return NULL;
}
int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
uint32_t stream_id, uint8_t subcmd,
const uint8_t* data, size_t data_len) {
struct socks_proxy_conn* c = socks_proxy_find_conn(*head, stream_id);
if (!c) return 0;
if (c->rem_closed || c->freed) return 1;
if (c->http && !c->http->active) return 1;
if (subcmd == TCP_PROXY_SUBCMD_CONNECTED) { socks_connected(c); return 1; }
if (subcmd == TCP_PROXY_SUBCMD_WINDOW) {
if (proxy_flow_window(&c->flow, data, data_len) < 0) on_error_cb(c->tc, EPROTO, c);
else socks_flow_wake(c);
return 1;
}
if (subcmd == TCP_PROXY_SUBCMD_DATA) {
DEBUG_TRACE(DEBUG_CATEGORY_PROXY, "SOCKS DATA <- sid=%08x len=%zu", stream_id, data_len);
if (proxy_flow_receive(&c->flow, data_len) < 0) { on_error_cb(c->tc, EPROTO, c); return 1; }
c->bytes_from_exit += (uint32_t)data_len;
if (c->http && !c->http->tunnel) { http_receive(c, data, data_len); return 1; }
if (data_len > 0) {
if (data_len > c->tc->data_pool->object_size) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: data_len=%zu > pool_sz=%zu, dropping sid=%08x",
data_len, c->tc->data_pool->object_size, stream_id);
return 1;
}
struct ll_entry* e = queue_entry_new_from_pool(c->tc->entry_pool);
uint8_t* buf = memory_pool_alloc(c->tc->data_pool);
if (e && buf) {
memcpy(buf, data, data_len); e->dgram = buf; e->len = (uint16_t)data_len;
c->bytes_to_client += (uint32_t)data_len;
tcp_conn_set_flushed(c->tc, socks_flushed_cb);
queue_data_put(c->tc->write_queue, e);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: handle_data alloc failed sid=%08x wq=%d bytes_in=%u bytes_out=%u",
stream_id, c->tc->write_queue->count, c->bytes_from_exit, c->bytes_to_client);
if (e) queue_entry_free(e); if (buf) memory_pool_free(c->tc->data_pool, buf);
on_error_cb(c->tc, ENOMEM, c);
}
}
return 1;
}
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) {
if (c->http && !c->http->tunnel) {
if (c->http->response_done) http_finish(c);
else http_fail(c, 502, "upstream CLOSE before completion");
return 1;
}
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: REM_CLOSED sid=%08x", stream_id);
c->rem_closed = 1;
etcp_router_cancel_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER, &c->tx_waiter);
tcp_conn_push_close(c->tc);
return 1;
}
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_proxy: ERROR from exit sid=%08x", stream_id);
if (c->http && !c->http->tunnel) { http_fail(c, 502, "upstream ERROR"); return 1; }
if (!c->flow.ready) { socks_dns_error_and_close(c); return 1; }
c->rem_closed = 1;
etcp_router_cancel_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER, &c->tx_waiter);
tcp_conn_push_close(c->tc);
return 1;
}
if (subcmd == TCP_PROXY_SUBCMD_FIN) {
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: FIN from exit sid=%08x", stream_id);
if (c->flow.fin_received) return 1;
c->fin_remote = 1; c->flow.fin_received = 1;
if (c->http && !c->http->tunnel) {
struct socks_http_state* h = c->http;
if (!h->response.headers_done) http_fail(c, 502, "EOF in response headers");
else if (h->response.body == HTTP_BODY_EOF) { h->response_done = 1; http_finish(c); }
else if (!h->response_done) http_fail(c, 502, "EOF in response body");
else http_finish(c);
return 1;
}
if (tcp_conn_push_fin(c->tc) < 0) on_error_cb(c->tc, ENOMEM, c);
return 1;
}
return 1;
}
static void socks_proxy_conn_free_deferred(void* arg) {
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
c->free_soon_id = NULL;
socks_proxy_conn_free(c);
}
static void socks_proxy_conn_free_soon(struct socks_proxy_conn* c) {
if (!c || c->free_soon_id) return;
c->free_soon_id = uasync_call_soon(c->ua, c, socks_proxy_conn_free_deferred);
}
void socks_proxy_conn_free(struct socks_proxy_conn* c) {
if (!c) return;
if (c->freed) return;
c->freed = 1;
if (c->http) {
if (c->http->timer) uasync_cancel_timeout(c->ua, c->http->timer);
if (c->http->active) {
uint8_t cmd = c->http->retire_cmd ? c->http->retire_cmd : TCP_PROXY_SUBCMD_ERROR;
if (send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, cmd, c->stream_id, NULL, 0, 1) < 0)
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "HTTP: upstream cancellation unavailable sid=%08x", c->stream_id);
}
u_free(c->http); c->http = NULL;
}
proxy_flow_destroy(&c->flow);
if (c->free_soon_id) { uasync_call_soon_cancel(c->ua, c->free_soon_id); c->free_soon_id = NULL; }
struct socks_proxy_conn** head = c->head; int* count = c->count;
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: FREE sid=%08x state=%d total=%d", c->stream_id, c->state, count ? *count : 0);
if (c->close_pending && c->inst) {
c->close_pending = 0;
uint8_t subcmd = c->tc && c->tc->error ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, subcmd, c->stream_id, NULL, 0, 1);
}
if (head) { struct socks_proxy_conn** prev = head; while (*prev) { if (*prev == c) { *prev = c->next; if (count) (*count)--; break; } prev = &(*prev)->next; } }
if (c->tx_buf) { u_free(c->tx_buf); c->tx_buf = NULL; c->tx_len = 0; }
if (c->dns_q) { adns_cancel(c->dns_q); c->dns_q = NULL; }
if (c->tx_retry_timer) { uasync_cancel_timeout(c->ua, c->tx_retry_timer); c->tx_retry_timer = NULL; }
if (c->tc) { tcp_conn_destroy(c->tc); c->tc = NULL; }
if (c->inst) etcp_router_cancel_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER, &c->tx_waiter);
u_free(c);
}
void socks_proxy_conn_free_all(struct socks_proxy_conn** head, int* count) {
while (*head) { struct socks_proxy_conn* next = (*head)->next; socks_proxy_conn_free(*head); }
}
// ====================================================================
// Слушающий сокет
// ====================================================================
struct listen_ctx* socks_proxy_init_listen(struct UASYNC* ua, const char* addr_str,
struct socks_proxy_conn** conns, int* count, uint32_t* next_stream_id,
struct UTUN_INSTANCE* inst, uint64_t via_node_id, int is_http) {
char ip[64]; const char* colon = strrchr(addr_str, ':');
if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: bad addr '%s' (need IP:PORT)", addr_str); return NULL; }
size_t ip_len = (size_t)(colon - addr_str);
if (ip_len > sizeof(ip) - 1) ip_len = sizeof(ip) - 1;
memcpy(ip, addr_str, ip_len); ip[ip_len] = '\0';
char* end;
long parsed = strtol(colon + 1, &end, 10);
if (end == colon + 1 || *end || parsed < 0 || parsed > 65535) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: bad port '%s'", colon + 1); return NULL;
}
int port = (int)parsed;
struct listen_ctx* ctx = u_calloc(1, sizeof(struct listen_ctx));
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: u_calloc failed"); return NULL; }
ctx->ua = ua; ctx->inst = inst; ctx->via_node_id = via_node_id;
ctx->is_http = is_http; ctx->conns = conns; ctx->conn_count = count;
ctx->next_stream_id = next_stream_id;
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: socket() failed errno=%d", errno); u_free(ctx); return NULL; }
socket_set_nonblocking(sock);
socket_set_reuseaddr(sock, 1);
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; addr.sin_port = htons((uint16_t)port);
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: inet_pton(%s) failed", ip); socket_close_wrapper(sock); u_free(ctx); return NULL; }
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: bind(%s:%d) failed errno=%d", ip, port, errno); socket_close_wrapper(sock); u_free(ctx); return NULL; }
if (listen(sock, 32) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: listen() failed errno=%d", errno); socket_close_wrapper(sock); u_free(ctx); return NULL; }
uint16_t bound_port = socket_get_bound_port(sock);
if (!bound_port) { socket_close_wrapper(sock); u_free(ctx); return NULL; }
ctx->listen_sock = sock;
ctx->socket_id = uasync_add_socket_t(ua, sock, on_accept_cb, NULL, NULL, "socks_proxy_listen", ctx);
if (!ctx->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: uasync_add_socket_t failed"); socket_close_wrapper(sock); u_free(ctx); return NULL; }
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: %s listening on %s:%d sock=%d",
is_http ? "HTTP" : "SOCKS", ip, bound_port, (int)sock);
return ctx;
}
uint16_t socks_proxy_get_port(const struct listen_ctx* ctx) {
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: missing listener"); return 0; }
return socket_get_bound_port(ctx->listen_sock);
}
void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out) {
if (!ctx) return;
if (ctx->socket_id) uasync_remove_socket_t(ua, ctx->listen_sock);
socket_close_wrapper(ctx->listen_sock);
if (sock_out) *sock_out = SOCKET_INVALID;
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: listener closed sock=%d", (int)ctx->listen_sock);
u_free(ctx);
}