14 changed files with 1360 additions and 54 deletions
@ -0,0 +1,493 @@
|
||||
// socks_client.c — SOCKS5 клиент: CONNECT (TCP) + UDP ASSOCIATE (UDP)
|
||||
#include "socks_client.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/platform_compat.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/mem.h" |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
|
||||
#define SOCKS_CTRL_TIMEOUT_TB 100000 // 10 с в 0.1 мс (connect+greeting+auth+request)
|
||||
#define SOCKS_CTRL_RBUF_SIZE 1024 |
||||
#define SOCKS_UDP_RECONNECT_MIN_TB 10000 // 1 с в 0.1 мс
|
||||
#define SOCKS_UDP_RECONNECT_MAX_TB 300000 // 30 с
|
||||
#define SOCKS_UDP_MAX_PAYLOAD 2048 |
||||
|
||||
// ── Общий control-канал: TCP к прокси + greeting + auth ───────────────
|
||||
// Обе фазы (dial и udp) используют эту базу; после greeting+auth вызывается on_ready(c),
|
||||
// где каждая сторона шлёт свой запрос (CONNECT / UDP ASSOCIATE) и подменяет process().
|
||||
struct socks_ctrl { |
||||
struct UASYNC *ua; |
||||
struct socks_cfg cfg; |
||||
socket_t sock; |
||||
void *socket_id; |
||||
void *timer; |
||||
uint8_t have_auth; |
||||
uint8_t done; // dial: guard против повторного finish
|
||||
uint8_t rbuf[SOCKS_CTRL_RBUF_SIZE]; |
||||
size_t rbuf_len; |
||||
uint8_t sbuf[512]; |
||||
size_t sbuf_len, sbuf_off; |
||||
void (*on_ready)(struct socks_ctrl *c); // greeting+auth OK → фаза 2
|
||||
void (*on_error)(struct socks_ctrl *c, int err); // любая фатальная ошибка
|
||||
void (*process)(struct socks_ctrl *c); // текущий обработчик входящих
|
||||
}; |
||||
|
||||
static void socks_ctrl_send(struct socks_ctrl *c, const uint8_t *data, size_t len); |
||||
static void socks_ctrl_flush(struct socks_ctrl *c); |
||||
static void socks_ctrl_process(struct socks_ctrl *c); |
||||
static void socks_ctrl_auth_process(struct socks_ctrl *c); |
||||
static void socks_ctrl_after_connect(struct socks_ctrl *c); |
||||
static void socks_ctrl_read_cb(socket_t sock, void *arg); |
||||
static void socks_ctrl_write_cb(socket_t sock, void *arg); |
||||
static void socks_ctrl_connect_wait_cb(socket_t sock, void *arg); |
||||
static void socks_ctrl_timeout_cb(void *arg); |
||||
|
||||
static void socks_ctrl_flush(struct socks_ctrl *c) { |
||||
while (c->sbuf_off < c->sbuf_len) { |
||||
ssize_t sent = send(c->sock, c->sbuf + c->sbuf_off, c->sbuf_len - c->sbuf_off, 0); |
||||
if (sent < 0) { |
||||
int err = socket_get_error(); |
||||
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) { uasync_set_socket_write(c->ua, c->socket_id, 1); return; } |
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: send failed err=%d", err); |
||||
c->on_error(c, err); |
||||
return; |
||||
} |
||||
c->sbuf_off += (size_t)sent; |
||||
} |
||||
c->sbuf_len = 0; |
||||
c->sbuf_off = 0; |
||||
uasync_set_socket_write(c->ua, c->socket_id, 0); |
||||
} |
||||
|
||||
static void socks_ctrl_send(struct socks_ctrl *c, const uint8_t *data, size_t len) { |
||||
memcpy(c->sbuf, data, len); |
||||
c->sbuf_len = len; |
||||
c->sbuf_off = 0; |
||||
socks_ctrl_flush(c); |
||||
} |
||||
|
||||
static void socks_ctrl_read_cb(socket_t sock, void *arg) { |
||||
struct socks_ctrl *c = (struct socks_ctrl *)arg; |
||||
(void)sock; |
||||
if (c->rbuf_len >= sizeof(c->rbuf)) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: rbuf overflow"); c->on_error(c, ENOBUFS); return; } |
||||
ssize_t n = recv(c->sock, c->rbuf + c->rbuf_len, sizeof(c->rbuf) - c->rbuf_len, 0); |
||||
if (n < 0) { |
||||
int err = socket_get_error(); |
||||
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) return; |
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: recv failed err=%d", err); |
||||
c->on_error(c, err); |
||||
return; |
||||
} |
||||
if (n == 0) { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks: control closed by proxy (EOF)"); c->on_error(c, ECONNRESET); return; } |
||||
c->rbuf_len += (size_t)n; |
||||
c->process(c); |
||||
} |
||||
|
||||
static void socks_ctrl_write_cb(socket_t sock, void *arg) { |
||||
struct socks_ctrl *c = (struct socks_ctrl *)arg; |
||||
(void)sock; |
||||
socks_ctrl_flush(c); |
||||
} |
||||
|
||||
// Ответ на greeting: выбор метода (или auth).
|
||||
static void socks_ctrl_process(struct socks_ctrl *c) { |
||||
if (c->rbuf_len < 2) return; |
||||
uint8_t ver = c->rbuf[0], method = c->rbuf[1]; |
||||
memmove(c->rbuf, c->rbuf + 2, c->rbuf_len - 2); c->rbuf_len -= 2; |
||||
if (ver != 0x05) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: bad greeting ver=%d", ver); c->on_error(c, EPROTO); return; } |
||||
if (method == 0x00) { c->on_ready(c); return; } |
||||
if (method == 0x02 && c->have_auth) { |
||||
size_t ulen = strlen(c->cfg.user), plen = strlen(c->cfg.pass); |
||||
if (ulen > 255 || plen > 255) { c->on_error(c, EINVAL); return; } |
||||
uint8_t ab[2 + 255 + 1 + 255]; |
||||
size_t n = 0; |
||||
ab[n++] = 0x01; ab[n++] = (uint8_t)ulen; |
||||
memcpy(ab + n, c->cfg.user, ulen); n += ulen; |
||||
ab[n++] = (uint8_t)plen; |
||||
memcpy(ab + n, c->cfg.pass, plen); n += plen; |
||||
c->process = socks_ctrl_auth_process; |
||||
socks_ctrl_send(c, ab, n); |
||||
return; |
||||
} |
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: no acceptable auth (offered=%d have_auth=%d)", method, c->have_auth); |
||||
c->on_error(c, EACCES); |
||||
} |
||||
|
||||
// Ответ на auth (RFC1929).
|
||||
static void socks_ctrl_auth_process(struct socks_ctrl *c) { |
||||
if (c->rbuf_len < 2) return; |
||||
uint8_t ver = c->rbuf[0], status = c->rbuf[1]; |
||||
memmove(c->rbuf, c->rbuf + 2, c->rbuf_len - 2); c->rbuf_len -= 2; |
||||
if (ver != 0x01 || status != 0x00) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: auth failed ver=%d status=%d", ver, status); c->on_error(c, EACCES); return; } |
||||
c->on_ready(c); |
||||
} |
||||
|
||||
static void socks_ctrl_after_connect(struct socks_ctrl *c) { |
||||
uint8_t g[4]; |
||||
size_t n; |
||||
g[0] = 0x05; |
||||
if (c->have_auth) { g[1] = 0x02; g[2] = 0x00; g[3] = 0x02; n = 4; } |
||||
else { g[1] = 0x01; g[2] = 0x00; n = 3; } |
||||
c->process = socks_ctrl_process; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "socks: connected to proxy %s:%u, greeting (auth=%d)", c->cfg.host, c->cfg.port, c->have_auth); |
||||
socks_ctrl_send(c, g, n); |
||||
} |
||||
|
||||
static void socks_ctrl_connect_wait_cb(socket_t sock, void *arg) { |
||||
struct socks_ctrl *c = (struct socks_ctrl *)arg; |
||||
int err = 0; |
||||
socklen_t el = sizeof(err); |
||||
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &el) < 0 || err != 0) { |
||||
int e = err ? err : ECONNREFUSED; |
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: connect to %s:%u failed err=%d", c->cfg.host, c->cfg.port, e); |
||||
c->on_error(c, e); |
||||
return; |
||||
} |
||||
uasync_remove_socket_t(c->ua, sock); |
||||
c->socket_id = uasync_add_socket_t(c->ua, sock, socks_ctrl_read_cb, socks_ctrl_write_cb, NULL, c); |
||||
if (!c->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: re-add socket failed"); c->on_error(c, ENOMEM); return; } |
||||
socks_ctrl_after_connect(c); |
||||
} |
||||
|
||||
static void socks_ctrl_timeout_cb(void *arg) { |
||||
struct socks_ctrl *c = (struct socks_ctrl *)arg; |
||||
c->timer = NULL; |
||||
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks: connect/handshake timeout to %s:%u", c->cfg.host, c->cfg.port); |
||||
c->on_error(c, ETIMEDOUT); |
||||
} |
||||
|
||||
// Подключить control-канал к прокси (resolve + socket + connect + таймер).
|
||||
// При немедленной ошибке возвращает -1 (сокет закрыт), cb не вызывается.
|
||||
static int socks_ctrl_start(struct socks_ctrl *c, int timeout_tb) { |
||||
char port_str[16]; |
||||
snprintf(port_str, sizeof(port_str), "%u", c->cfg.port); |
||||
struct addrinfo hints; |
||||
memset(&hints, 0, sizeof(hints)); |
||||
hints.ai_family = AF_UNSPEC; |
||||
hints.ai_socktype = SOCK_STREAM; |
||||
struct addrinfo *res = NULL; |
||||
if (getaddrinfo(c->cfg.host, port_str, &hints, &res) != 0 || !res) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: getaddrinfo(%s) failed", c->cfg.host); |
||||
return -1; |
||||
} |
||||
struct sockaddr_storage pa; |
||||
socklen_t palen = (socklen_t)res->ai_addrlen; |
||||
memcpy(&pa, res->ai_addr, res->ai_addrlen); |
||||
freeaddrinfo(res); |
||||
|
||||
c->sock = socket(pa.ss_family, SOCK_STREAM, 0); |
||||
if (c->sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: socket() failed err=%d", socket_get_error()); return -1; } |
||||
socket_set_nonblocking(c->sock); |
||||
|
||||
int cr = connect(c->sock, (struct sockaddr *)&pa, palen); |
||||
if (cr < 0) { |
||||
int err = socket_get_error(); |
||||
if (err != EINPROGRESS && err != ERR_WOULDBLOCK) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: connect to %s:%u failed err=%d", c->cfg.host, c->cfg.port, err); |
||||
socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID; |
||||
return -1; |
||||
} |
||||
c->socket_id = uasync_add_socket_t(c->ua, c->sock, NULL, socks_ctrl_connect_wait_cb, NULL, c); |
||||
} else { |
||||
c->socket_id = uasync_add_socket_t(c->ua, c->sock, socks_ctrl_read_cb, socks_ctrl_write_cb, NULL, c); |
||||
if (c->socket_id) socks_ctrl_after_connect(c); |
||||
} |
||||
if (!c->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: add_socket failed"); socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID; return -1; } |
||||
c->timer = uasync_set_timeout(c->ua, timeout_tb > 0 ? timeout_tb : SOCKS_CTRL_TIMEOUT_TB, c, socks_ctrl_timeout_cb, "socks_ctrl"); |
||||
return 0; |
||||
} |
||||
|
||||
// ── SOCKS5 CONNECT (dial) ─────────────────────────────────────────────
|
||||
|
||||
struct socks_dial { |
||||
struct socks_ctrl ctrl; // первый член — каст (struct socks_ctrl*)==&ctrl
|
||||
char target_host[256]; |
||||
uint16_t target_port; |
||||
socks_dial_cb cb; |
||||
void *arg; |
||||
}; |
||||
|
||||
static void dial_on_error(struct socks_ctrl *c, int err); |
||||
static void dial_reply_process(struct socks_ctrl *c); |
||||
|
||||
static void dial_finish(struct socks_dial *d, int err) { |
||||
struct socks_ctrl *c = &d->ctrl; |
||||
if (c->done) return; |
||||
c->done = 1; |
||||
socket_t out = c->sock; |
||||
if (c->timer) { uasync_cancel_timeout(c->ua, c->timer); c->timer = NULL; } |
||||
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; } |
||||
socks_dial_cb cb = d->cb; |
||||
void *arg = d->arg; |
||||
if (err == 0) { c->sock = SOCKET_INVALID; } // сокет переходит вызывающему
|
||||
else if (out != SOCKET_INVALID) { socket_close_wrapper(out); } |
||||
u_free(d); |
||||
cb(err == 0 ? out : SOCKET_INVALID, err, arg); |
||||
} |
||||
|
||||
static void dial_on_ready(struct socks_ctrl *c) { |
||||
struct socks_dial *d = (struct socks_dial *)c; |
||||
uint8_t buf[4 + 1 + 255 + 2]; |
||||
size_t n = 0; |
||||
buf[n++] = 0x05; buf[n++] = 0x01; buf[n++] = 0x00; // CONNECT
|
||||
struct in_addr a4; |
||||
struct in6_addr a6; |
||||
if (inet_pton(AF_INET, d->target_host, &a4) == 1) { |
||||
buf[n++] = 0x01; memcpy(buf + n, &a4, 4); n += 4; |
||||
} else if (inet_pton(AF_INET6, d->target_host, &a6) == 1) { |
||||
buf[n++] = 0x04; memcpy(buf + n, &a6, 16); n += 16; |
||||
} else { |
||||
size_t hl = strlen(d->target_host); |
||||
if (hl > 255) { c->on_error(c, EINVAL); return; } |
||||
buf[n++] = 0x03; buf[n++] = (uint8_t)hl; memcpy(buf + n, d->target_host, hl); n += hl; |
||||
} |
||||
uint16_t pb = htons(d->target_port); |
||||
memcpy(buf + n, &pb, 2); n += 2; |
||||
c->process = dial_reply_process; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "socks_dial: CONNECT %s:%u", d->target_host, d->target_port); |
||||
socks_ctrl_send(c, buf, n); |
||||
} |
||||
|
||||
static void dial_reply_process(struct socks_ctrl *c) { |
||||
if (c->rbuf_len < 4) return; |
||||
uint8_t ver = c->rbuf[0], rep = c->rbuf[1], atyp = c->rbuf[3]; |
||||
size_t need = 4; |
||||
if (atyp == 0x01) need += 4 + 2; |
||||
else if (atyp == 0x04) need += 16 + 2; |
||||
else if (atyp == 0x03) { if (c->rbuf_len < 5) return; need += 1 + (size_t)c->rbuf[4] + 2; } |
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_dial: bad reply atyp=%d", atyp); c->on_error(c, EPROTO); return; } |
||||
if (c->rbuf_len < need) return; |
||||
if (ver != 0x05) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_dial: bad reply ver=%d", ver); c->on_error(c, EPROTO); return; } |
||||
if (rep != 0x00) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_dial: CONNECT rejected rep=%d", rep); c->on_error(c, ECONNREFUSED); return; } |
||||
struct socks_dial *d = (struct socks_dial *)c; |
||||
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_dial: CONNECT %s:%u established via %s:%u", |
||||
d->target_host, d->target_port, c->cfg.host, c->cfg.port); |
||||
dial_finish(d, 0); |
||||
} |
||||
|
||||
static void dial_on_error(struct socks_ctrl *c, int err) { |
||||
dial_finish((struct socks_dial *)c, err); |
||||
} |
||||
|
||||
void *socks_dial(struct UASYNC *ua, const struct socks_cfg *cfg, |
||||
const char *target_host, uint16_t target_port, |
||||
socks_dial_cb cb, void *arg, int timeout_tb) { |
||||
if (!ua || !cfg || !cfg->host[0] || !cfg->port || !target_host || !target_host[0] || !cb) |
||||
return NULL; |
||||
struct socks_dial *d = u_calloc(1, sizeof(*d)); |
||||
if (!d) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_dial: alloc failed"); return NULL; } |
||||
d->ctrl.ua = ua; |
||||
d->ctrl.cfg = *cfg; |
||||
d->ctrl.have_auth = cfg->user[0] != '\0'; |
||||
d->ctrl.sock = SOCKET_INVALID; |
||||
d->ctrl.on_ready = dial_on_ready; |
||||
d->ctrl.on_error = dial_on_error; |
||||
d->ctrl.process = socks_ctrl_process; |
||||
strncpy(d->target_host, target_host, sizeof(d->target_host) - 1); |
||||
d->target_port = target_port; |
||||
d->cb = cb; |
||||
d->arg = arg; |
||||
if (socks_ctrl_start(&d->ctrl, timeout_tb) < 0) { u_free(d); return NULL; } |
||||
return d; |
||||
} |
||||
|
||||
void socks_dial_cancel(void *h) { |
||||
if (!h) return; |
||||
struct socks_dial *d = (struct socks_dial *)h; |
||||
struct socks_ctrl *c = &d->ctrl; |
||||
if (c->done) return; |
||||
c->done = 1; |
||||
if (c->timer) { uasync_cancel_timeout(c->ua, c->timer); c->timer = NULL; } |
||||
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; } |
||||
if (c->sock != SOCKET_INVALID) socket_close_wrapper(c->sock); |
||||
u_free(d); |
||||
} |
||||
|
||||
// ── SOCKS5 UDP ASSOCIATE ──────────────────────────────────────────────
|
||||
|
||||
struct socks_udp { |
||||
struct socks_ctrl ctrl; // первый член
|
||||
socket_t local_udp_fd; |
||||
struct sockaddr_storage relay; |
||||
uint8_t ready; |
||||
uint8_t closing; |
||||
void *reconnect_timer; |
||||
uint32_t reconnect_delay_tb; |
||||
socks_udp_ready_cb ready_cb; |
||||
void *ready_arg; |
||||
}; |
||||
|
||||
static void udp_on_error(struct socks_ctrl *c, int err); |
||||
static void udp_reply_process(struct socks_ctrl *c); |
||||
static void udp_idle_process(struct socks_ctrl *c); |
||||
static void udp_reconnect_cb(void *arg); |
||||
|
||||
static void udp_schedule_reconnect(struct socks_udp *s) { |
||||
if (s->closing) return; |
||||
s->ready = 0; |
||||
if (s->ctrl.socket_id) { uasync_remove_socket_t(s->ctrl.ua, s->ctrl.sock); s->ctrl.socket_id = NULL; } |
||||
if (s->ctrl.timer) { uasync_cancel_timeout(s->ctrl.ua, s->ctrl.timer); s->ctrl.timer = NULL; } |
||||
if (s->ctrl.sock != SOCKET_INVALID) { socket_close_wrapper(s->ctrl.sock); s->ctrl.sock = SOCKET_INVALID; } |
||||
s->ctrl.done = 0; |
||||
s->ctrl.rbuf_len = 0; |
||||
s->ctrl.sbuf_len = 0; |
||||
s->ctrl.sbuf_off = 0; |
||||
s->ctrl.process = socks_ctrl_process; |
||||
if (s->reconnect_delay_tb == 0) s->reconnect_delay_tb = SOCKS_UDP_RECONNECT_MIN_TB; |
||||
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_udp: reconnect in %u ms", s->reconnect_delay_tb / 10); |
||||
s->reconnect_timer = uasync_set_timeout(s->ctrl.ua, (int)s->reconnect_delay_tb, s, udp_reconnect_cb, "socks_udp_rc"); |
||||
s->reconnect_delay_tb *= 2; |
||||
if (s->reconnect_delay_tb > SOCKS_UDP_RECONNECT_MAX_TB) s->reconnect_delay_tb = SOCKS_UDP_RECONNECT_MAX_TB; |
||||
} |
||||
|
||||
static void udp_reconnect_cb(void *arg) { |
||||
struct socks_udp *s = (struct socks_udp *)arg; |
||||
s->reconnect_timer = NULL; |
||||
if (s->closing) return; |
||||
if (socks_ctrl_start(&s->ctrl, 0) < 0) udp_schedule_reconnect(s); |
||||
} |
||||
|
||||
static void udp_on_ready(struct socks_ctrl *c) { |
||||
uint8_t buf[10]; |
||||
buf[0] = 0x05; buf[1] = 0x03; buf[2] = 0x00; // UDP ASSOCIATE
|
||||
buf[3] = 0x01; // ATYP=IPv4, DST=0.0.0.0:0
|
||||
memset(buf + 4, 0, 6); |
||||
c->process = udp_reply_process; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "socks_udp: sending UDP ASSOCIATE"); |
||||
socks_ctrl_send(c, buf, 10); |
||||
} |
||||
|
||||
static void udp_reply_process(struct socks_ctrl *c) { |
||||
struct socks_udp *s = (struct socks_udp *)c; |
||||
if (c->rbuf_len < 4) return; |
||||
uint8_t ver = c->rbuf[0], rep = c->rbuf[1], atyp = c->rbuf[3]; |
||||
size_t need = 4; |
||||
if (atyp == 0x01) need += 4 + 2; |
||||
else if (atyp == 0x04) need += 16 + 2; |
||||
else if (atyp == 0x03) { if (c->rbuf_len < 5) return; need += 1 + (size_t)c->rbuf[4] + 2; } |
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: bad reply atyp=%d", atyp); c->on_error(c, EPROTO); return; } |
||||
if (c->rbuf_len < need) return; |
||||
if (ver != 0x05) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: bad reply ver=%d", ver); c->on_error(c, EPROTO); return; } |
||||
if (rep != 0x00) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: ASSOCIATE rejected rep=%d", rep); c->on_error(c, ECONNREFUSED); return; } |
||||
|
||||
memset(&s->relay, 0, sizeof(s->relay)); |
||||
if (atyp == 0x01) { |
||||
struct sockaddr_in *sa = (struct sockaddr_in *)&s->relay; |
||||
sa->sin_family = AF_INET; |
||||
memcpy(&sa->sin_addr.s_addr, c->rbuf + 4, 4); |
||||
memcpy(&sa->sin_port, c->rbuf + 8, 2); |
||||
} else if (atyp == 0x04) { |
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&s->relay; |
||||
sa6->sin6_family = AF_INET6; |
||||
memcpy(&sa6->sin6_addr, c->rbuf + 4, 16); |
||||
memcpy(&sa6->sin6_port, c->rbuf + 20, 2); |
||||
} else { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: unsupported relay family"); c->on_error(c, EAFNOSUPPORT); return; } |
||||
|
||||
memmove(c->rbuf, c->rbuf + need, c->rbuf_len - need); c->rbuf_len -= need; |
||||
s->ready = 1; |
||||
s->reconnect_delay_tb = 0; |
||||
if (c->timer) { uasync_cancel_timeout(c->ua, c->timer); c->timer = NULL; } |
||||
c->process = udp_idle_process; |
||||
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_udp: ASSOCIATE ready, relay=%s", sockaddr_storage_to_str(&s->relay).str); |
||||
if (s->ready_cb) s->ready_cb(s, 0, s->ready_arg); |
||||
} |
||||
|
||||
static void udp_idle_process(struct socks_ctrl *c) { |
||||
c->rbuf_len = 0; // неожиданные данные на control-канале после READY — игнорируем
|
||||
} |
||||
|
||||
static void udp_on_error(struct socks_ctrl *c, int err) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_udp: control error err=%d → reconnect", err); |
||||
udp_schedule_reconnect((struct socks_udp *)c); |
||||
} |
||||
|
||||
struct socks_udp *socks_udp_associate(struct UASYNC *ua, const struct socks_cfg *cfg, |
||||
socket_t local_udp_fd, socks_udp_ready_cb cb, void *arg) { |
||||
if (!ua || !cfg || !cfg->host[0] || !cfg->port || local_udp_fd == SOCKET_INVALID) return NULL; |
||||
struct socks_udp *s = u_calloc(1, sizeof(*s)); |
||||
if (!s) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: alloc failed"); return NULL; } |
||||
s->ctrl.ua = ua; |
||||
s->ctrl.cfg = *cfg; |
||||
s->ctrl.have_auth = cfg->user[0] != '\0'; |
||||
s->ctrl.sock = SOCKET_INVALID; |
||||
s->ctrl.on_ready = udp_on_ready; |
||||
s->ctrl.on_error = udp_on_error; |
||||
s->ctrl.process = socks_ctrl_process; |
||||
s->local_udp_fd = local_udp_fd; |
||||
s->ready_cb = cb; |
||||
s->ready_arg = arg; |
||||
if (socks_ctrl_start(&s->ctrl, 0) < 0) udp_schedule_reconnect(s); |
||||
return s; |
||||
} |
||||
|
||||
void socks_udp_destroy(struct socks_udp *s) { |
||||
if (!s) return; |
||||
s->closing = 1; |
||||
if (s->reconnect_timer) { uasync_cancel_timeout(s->ctrl.ua, s->reconnect_timer); s->reconnect_timer = NULL; } |
||||
if (s->ctrl.timer) { uasync_cancel_timeout(s->ctrl.ua, s->ctrl.timer); s->ctrl.timer = NULL; } |
||||
if (s->ctrl.socket_id) { uasync_remove_socket_t(s->ctrl.ua, s->ctrl.sock); s->ctrl.socket_id = NULL; } |
||||
if (s->ctrl.sock != SOCKET_INVALID) socket_close_wrapper(s->ctrl.sock); |
||||
u_free(s); |
||||
} |
||||
|
||||
ssize_t socks_udp_sendto(struct socks_udp *s, const uint8_t *data, size_t len, |
||||
const struct sockaddr_storage *dst) { |
||||
if (!s || !data || !dst || !s->ready) return -1; |
||||
if (dst->ss_family != AF_INET && dst->ss_family != AF_INET6) return -1; |
||||
uint8_t buf[SOCKS_UDP_MAX_PAYLOAD + 32]; |
||||
size_t off = 0; |
||||
buf[off++] = 0; buf[off++] = 0; // RSV
|
||||
buf[off++] = 0; // FRAG
|
||||
if (dst->ss_family == AF_INET) { |
||||
const struct sockaddr_in *sa = (const struct sockaddr_in *)dst; |
||||
buf[off++] = 0x01; |
||||
memcpy(buf + off, &sa->sin_addr.s_addr, 4); off += 4; |
||||
memcpy(buf + off, &sa->sin_port, 2); off += 2; |
||||
} else { |
||||
const struct sockaddr_in6 *sa6 = (const struct sockaddr_in6 *)dst; |
||||
buf[off++] = 0x04; |
||||
memcpy(buf + off, &sa6->sin6_addr, 16); off += 16; |
||||
memcpy(buf + off, &sa6->sin6_port, 2); off += 2; |
||||
} |
||||
if (off + len > sizeof(buf)) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: payload too large %zu", len); return -1; } |
||||
memcpy(buf + off, data, len); off += len; |
||||
socklen_t rl = (s->relay.ss_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); |
||||
ssize_t sent = socket_sendto(s->local_udp_fd, buf, off, (struct sockaddr *)&s->relay, rl); |
||||
if (sent < 0) DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: sendto relay failed err=%d", socket_get_error()); |
||||
return sent; |
||||
} |
||||
|
||||
ssize_t socks_udp_unwrap(const struct socks_udp *s, const uint8_t *raw, size_t raw_len, |
||||
uint8_t *payload, size_t cap, struct sockaddr_storage *src) { |
||||
(void)s; |
||||
if (!raw || raw_len < 10 || !src) return -1; |
||||
if (raw[0] != 0 || raw[1] != 0 || raw[2] != 0) { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_udp: bad RSV/FRAG"); return -1; } |
||||
uint8_t atyp = raw[3]; |
||||
size_t hlen; |
||||
if (atyp == 0x01) hlen = 4 + 4 + 2; |
||||
else if (atyp == 0x04) hlen = 4 + 16 + 2; |
||||
else if (atyp == 0x03) { if (raw_len < 5) return -1; hlen = 4 + 1 + (size_t)raw[4] + 2; } |
||||
else { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_udp: bad atyp=%d", atyp); return -1; } |
||||
if (raw_len < hlen) return -1; |
||||
|
||||
memset(src, 0, sizeof(*src)); |
||||
if (atyp == 0x01) { |
||||
struct sockaddr_in *sa = (struct sockaddr_in *)src; |
||||
sa->sin_family = AF_INET; |
||||
memcpy(&sa->sin_addr.s_addr, raw + 4, 4); |
||||
memcpy(&sa->sin_port, raw + 8, 2); |
||||
} else if (atyp == 0x04) { |
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)src; |
||||
sa6->sin6_family = AF_INET6; |
||||
memcpy(&sa6->sin6_addr, raw + 4, 16); |
||||
memcpy(&sa6->sin6_port, raw + 20, 2); |
||||
} else { |
||||
return -1; // domain — для ETCP-пиров не встречается
|
||||
} |
||||
size_t plen = raw_len - hlen; |
||||
if (plen > cap) return -1; |
||||
if (plen > 0) memcpy(payload, raw + hlen, plen); |
||||
return (ssize_t)plen; |
||||
} |
||||
@ -0,0 +1,75 @@
|
||||
// socks_client.h — SOCKS5 клиент (для исходящих ETCP-линков через SOCKS-прокси)
|
||||
//
|
||||
// Модуль позволяет подключаться к пиру не напрямую, а через SOCKS5-прокси:
|
||||
// - SOCKS5 CONNECT (TCP/STCP-линки) — socks_dial();
|
||||
// - SOCKS5 UDP ASSOCIATE (UDP-линки) — socks_udp_associate() и далее.
|
||||
//
|
||||
// Модуль не знает про ETCP — только транспорт.
|
||||
#ifndef SOCKS_CLIENT_H |
||||
#define SOCKS_CLIENT_H |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#include <stdint.h> |
||||
#include "../lib/socket_compat.h" |
||||
|
||||
struct UASYNC; |
||||
|
||||
// Конфигурация прокси. host — ip или домен; user/pass пустые = no-auth (метод 0x00).
|
||||
struct socks_cfg { |
||||
char host[64]; |
||||
uint16_t port; |
||||
char user[64]; |
||||
char pass[64]; |
||||
}; |
||||
|
||||
// ── SOCKS5 CONNECT (TCP) ──────────────────────────────────────────────
|
||||
|
||||
// Коллбэк завершения подключения:
|
||||
// sock — готовый сокет (на нём можно делать STCP-хендшейк), err == 0;
|
||||
// sock == SOCKET_INVALID при ошибке, err — код ошибки (errno).
|
||||
typedef void (*socks_dial_cb)(socket_t sock, int err, void *arg); |
||||
|
||||
// Асинхронно подключиться к прокси и выполнить SOCKS5 CONNECT к target_host:target_port.
|
||||
// target_host может быть числовым IP или доменом (домен резолвит сам прокси).
|
||||
// Возвращает handle (NULL при немедленной ошибке — тогда cb НЕ вызывается, ошибка залогирована).
|
||||
void *socks_dial(struct UASYNC *ua, const struct socks_cfg *cfg, |
||||
const char *target_host, uint16_t target_port, |
||||
socks_dial_cb cb, void *arg, int timeout_tb); |
||||
|
||||
// Отменить незавершённый dial: закрывает сокет, освобождает состояние, cb НЕ вызывается.
|
||||
void socks_dial_cancel(void *h); |
||||
|
||||
// ── SOCKS5 UDP ASSOCIATE (UDP) ────────────────────────────────────────
|
||||
|
||||
struct socks_udp; |
||||
|
||||
// Коллбэк готовности ассоциации: err==0 → relay получен, можно слать через socks_udp_sendto.
|
||||
typedef void (*socks_udp_ready_cb)(struct socks_udp *s, int err, void *arg); |
||||
|
||||
// Открыть UDP ASSOCIATE к прокси (persistent TCP-control + relay). local_udp_fd — уже
|
||||
// забинденный локальный UDP-сокет (reserved port), через него шлём на relay.
|
||||
// При обрыве control-канала сессия автоматически пересоздаёт ассоциацию (backoff).
|
||||
// Возвращает сессию (NULL только при ошибке аллокации/аргументов).
|
||||
struct socks_udp *socks_udp_associate(struct UASYNC *ua, const struct socks_cfg *cfg, |
||||
socket_t local_udp_fd, socks_udp_ready_cb cb, void *arg); |
||||
|
||||
// Отправить датаграмму пиру dst: обёртка в SOCKS-UDP заголовок + sendto(relay).
|
||||
// -1 пока ассоциация не готова (или ошибка).
|
||||
ssize_t socks_udp_sendto(struct socks_udp *s, const uint8_t *data, size_t len, |
||||
const struct sockaddr_storage *dst); |
||||
|
||||
// Снять SOCKS-UDP заголовок с входящей датаграммы: вернуть payload и реальный src пира.
|
||||
// Возвращает длину payload (<=0 при битом/неподходящем пакете).
|
||||
ssize_t socks_udp_unwrap(const struct socks_udp *s, const uint8_t *raw, size_t raw_len, |
||||
uint8_t *payload, size_t cap, struct sockaddr_storage *src); |
||||
|
||||
// Закрыть сессию: control-TCP, таймеры, память. Локальный UDP-сокет НЕ закрывается (чужой).
|
||||
void socks_udp_destroy(struct socks_udp *s); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif // SOCKS_CLIENT_H
|
||||
@ -0,0 +1,527 @@
|
||||
// test_socks_client.c — SOCKS5-клиент: dial через mock-прокси (no-auth, auth, reject)
|
||||
#include "socks_client.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/mem.h" |
||||
#include "../lib/socket_compat.h" |
||||
#include "../lib/platform_compat.h" |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
|
||||
static int test_failed = 0; |
||||
#define TASSERT(cond) do { \ |
||||
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return test_failed; } \
|
||||
} while(0) |
||||
|
||||
#define MOCK_PORT 24771 |
||||
|
||||
enum { MOCK_NOAUTH = 0, MOCK_AUTH = 1, MOCK_REJECT = 2 }; |
||||
|
||||
struct mock { |
||||
struct UASYNC *ua; |
||||
socket_t listen_sock; |
||||
void *listen_id; |
||||
socket_t conn_sock; |
||||
void *conn_id; |
||||
uint8_t mode; |
||||
uint8_t state; // 0=greeting, 1=auth, 2=request, 3=relay
|
||||
uint8_t buf[512]; |
||||
size_t buf_len; |
||||
int got_connect; |
||||
}; |
||||
|
||||
static void mock_send(struct mock *m, const uint8_t *d, size_t n) { |
||||
ssize_t s = send(m->conn_sock, d, n, 0); |
||||
(void)s; |
||||
} |
||||
|
||||
static void mock_process(struct mock *m) { |
||||
for (;;) { |
||||
if (m->state == 0) { // greeting
|
||||
if (m->buf_len < 3) return; |
||||
uint8_t nm = m->buf[1]; |
||||
if (m->buf_len < (size_t)(2 + nm)) return; |
||||
if (m->mode == MOCK_AUTH) { uint8_t r[] = {0x05, 0x02}; mock_send(m, r, 2); m->state = 1; } |
||||
else { uint8_t r[] = {0x05, 0x00}; mock_send(m, r, 2); m->state = 2; } |
||||
size_t c = 2 + nm; memmove(m->buf, m->buf + c, m->buf_len - c); m->buf_len -= c; |
||||
continue; |
||||
} |
||||
if (m->state == 1) { // auth (RFC1929)
|
||||
if (m->buf_len < 2) return; |
||||
size_t ulen = m->buf[1]; |
||||
if (m->buf_len < 2 + ulen + 1) return; |
||||
size_t plen = m->buf[2 + ulen]; |
||||
size_t total = 2 + ulen + 1 + plen; |
||||
if (m->buf_len < total) return; |
||||
uint8_t r[] = {0x01, 0x00}; mock_send(m, r, 2); m->state = 2; |
||||
memmove(m->buf, m->buf + total, m->buf_len - total); m->buf_len -= total; |
||||
continue; |
||||
} |
||||
if (m->state == 2) { // CONNECT request
|
||||
if (m->buf_len < 4) return; |
||||
uint8_t atyp = m->buf[3]; |
||||
size_t need = 4; |
||||
if (atyp == 0x01) need += 6; |
||||
else if (atyp == 0x03) { if (m->buf_len < 5) return; need += 1 + (size_t)m->buf[4] + 2; } |
||||
else if (atyp == 0x04) need += 18; |
||||
else return; |
||||
if (m->buf_len < need) return; |
||||
if (m->mode == MOCK_REJECT) { uint8_t r[] = {0x05, 0x05, 0x00, 0x01, 0,0,0,0, 0,0}; mock_send(m, r, 10); } |
||||
else { uint8_t r[] = {0x05, 0x00, 0x00, 0x01, 0,0,0,0, 0,0}; mock_send(m, r, 10); m->got_connect = 1; } |
||||
m->state = 3; |
||||
memmove(m->buf, m->buf + need, m->buf_len - need); m->buf_len -= need; |
||||
continue; |
||||
} |
||||
while (m->buf_len > 0) { // relay: echo
|
||||
mock_send(m, m->buf, 1); |
||||
memmove(m->buf, m->buf + 1, m->buf_len - 1); m->buf_len -= 1; |
||||
} |
||||
return; |
||||
} |
||||
} |
||||
|
||||
static void mock_read_cb(socket_t sock, void *arg) { |
||||
struct mock *m = (struct mock *)arg; |
||||
(void)sock; |
||||
uint8_t tmp[512]; |
||||
ssize_t n = recv(m->conn_sock, tmp, sizeof(tmp), 0); |
||||
if (n <= 0) return; |
||||
memcpy(m->buf + m->buf_len, tmp, (size_t)n); |
||||
m->buf_len += (size_t)n; |
||||
mock_process(m); |
||||
} |
||||
|
||||
static void mock_accept_cb(socket_t sock, void *arg) { |
||||
struct mock *m = (struct mock *)arg; |
||||
struct sockaddr_storage sa; socklen_t sl = sizeof(sa); |
||||
m->conn_sock = accept(sock, (struct sockaddr *)&sa, &sl); |
||||
if (m->conn_sock == SOCKET_INVALID) return; |
||||
socket_set_nonblocking(m->conn_sock); |
||||
m->conn_id = uasync_add_socket_t(m->ua, m->conn_sock, mock_read_cb, NULL, NULL, m); |
||||
} |
||||
|
||||
static int mock_start(struct mock *m, struct UASYNC *ua, int port, uint8_t mode) { |
||||
memset(m, 0, sizeof(*m)); |
||||
m->ua = ua; m->mode = mode; m->conn_sock = SOCKET_INVALID; |
||||
m->listen_sock = socket(AF_INET, SOCK_STREAM, 0); |
||||
if (m->listen_sock == SOCKET_INVALID) return -1; |
||||
socket_set_reuseaddr(m->listen_sock, 1); |
||||
socket_set_nonblocking(m->listen_sock); |
||||
struct sockaddr_in a; memset(&a, 0, sizeof(a)); |
||||
a.sin_family = AF_INET; a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); a.sin_port = htons((uint16_t)port); |
||||
if (bind(m->listen_sock, (struct sockaddr *)&a, sizeof(a)) < 0) return -1; |
||||
if (listen(m->listen_sock, 8) < 0) return -1; |
||||
m->listen_id = uasync_add_socket_t(ua, m->listen_sock, mock_accept_cb, NULL, NULL, m); |
||||
return m->listen_id ? 0 : -1; |
||||
} |
||||
|
||||
static void mock_stop(struct mock *m) { |
||||
if (m->conn_id) uasync_remove_socket_t(m->ua, m->conn_sock); |
||||
if (m->conn_sock != SOCKET_INVALID) socket_close_wrapper(m->conn_sock); |
||||
if (m->listen_id) uasync_remove_socket_t(m->ua, m->listen_sock); |
||||
socket_close_wrapper(m->listen_sock); |
||||
} |
||||
|
||||
// ── Mock SOCKS5 UDP ASSOCIATE: TCP-control (greeting+associate) + UDP-релей с эхом ──
|
||||
|
||||
struct udp_mock { |
||||
struct UASYNC *ua; |
||||
socket_t listen_sock; // TCP-control listener
|
||||
void *listen_id; |
||||
socket_t ctrl_sock; // принятый control-коннект
|
||||
void *ctrl_id; |
||||
uint8_t cbuf[512]; |
||||
size_t cblen; |
||||
uint8_t cstate; // 0=greeting, 1=associate
|
||||
socket_t udp_sock; // UDP-релей
|
||||
void *udp_id; |
||||
uint16_t udp_port; |
||||
int got_associate; |
||||
int echo_count; |
||||
uint8_t last_payload[256]; |
||||
size_t last_payload_len; |
||||
struct sockaddr_storage last_peer; |
||||
}; |
||||
|
||||
static void umock_ctrl_process(struct udp_mock *m) { |
||||
for (;;) { |
||||
if (m->cstate == 0) { // greeting
|
||||
if (m->cblen < 3) return; |
||||
uint8_t nm = m->cbuf[1]; |
||||
if (m->cblen < (size_t)(2 + nm)) return; |
||||
uint8_t r[] = {0x05, 0x00}; // no-auth
|
||||
send(m->ctrl_sock, r, 2, 0); |
||||
m->cstate = 1; |
||||
size_t c = 2 + nm; memmove(m->cbuf, m->cbuf + c, m->cblen - c); m->cblen -= c; |
||||
continue; |
||||
} |
||||
if (m->cstate == 1) { // UDP ASSOCIATE request
|
||||
if (m->cblen < 4) return; |
||||
uint8_t atyp = m->cbuf[3]; |
||||
size_t need = 4; |
||||
if (atyp == 0x01) need += 6; |
||||
else if (atyp == 0x04) need += 18; |
||||
else return; |
||||
if (m->cblen < need) return; |
||||
uint8_t r[10]; |
||||
r[0] = 0x05; r[1] = 0x00; r[2] = 0x00; r[3] = 0x01; |
||||
uint32_t ip = htonl(INADDR_LOOPBACK); |
||||
memcpy(r + 4, &ip, 4); |
||||
uint16_t port = htons(m->udp_port); |
||||
memcpy(r + 8, &port, 2); |
||||
send(m->ctrl_sock, r, 10, 0); |
||||
m->got_associate++; |
||||
m->cstate = 2; |
||||
memmove(m->cbuf, m->cbuf + need, m->cblen - need); m->cblen -= need; |
||||
continue; |
||||
} |
||||
m->cblen = 0; // idle
|
||||
return; |
||||
} |
||||
} |
||||
|
||||
static void umock_ctrl_read_cb(socket_t sock, void *arg) { |
||||
struct udp_mock *m = (struct udp_mock *)arg; |
||||
(void)sock; |
||||
uint8_t tmp[512]; |
||||
ssize_t n = recv(m->ctrl_sock, tmp, sizeof(tmp), 0); |
||||
if (n <= 0) return; |
||||
memcpy(m->cbuf + m->cblen, tmp, (size_t)n); m->cblen += (size_t)n; |
||||
umock_ctrl_process(m); |
||||
} |
||||
|
||||
static void umock_accept_cb(socket_t sock, void *arg) { |
||||
struct udp_mock *m = (struct udp_mock *)arg; |
||||
struct sockaddr_storage sa; socklen_t sl = sizeof(sa); |
||||
socket_t cs = accept(sock, (struct sockaddr *)&sa, &sl); |
||||
if (cs == SOCKET_INVALID) return; |
||||
socket_set_nonblocking(cs); |
||||
m->ctrl_sock = cs; |
||||
m->cstate = 0; |
||||
m->cblen = 0; |
||||
m->ctrl_id = uasync_add_socket_t(m->ua, cs, umock_ctrl_read_cb, NULL, NULL, m); |
||||
} |
||||
|
||||
// Релей: принять обёрнутую датаграмму, сохранить dst (peer) + payload, отправить эхо с SRC=peer.
|
||||
static void umock_udp_read_cb(socket_t sock, void *arg) { |
||||
struct udp_mock *m = (struct udp_mock *)arg; |
||||
(void)sock; |
||||
uint8_t raw[2048]; |
||||
struct sockaddr_storage from; socklen_t fl = sizeof(from); |
||||
ssize_t n = recvfrom(m->udp_sock, raw, sizeof(raw), 0, (struct sockaddr *)&from, &fl); |
||||
if (n < 10 || raw[0] || raw[1] || raw[2]) return; |
||||
uint8_t atyp = raw[3]; |
||||
size_t hlen; |
||||
if (atyp == 0x01) hlen = 10; |
||||
else if (atyp == 0x04) hlen = 22; |
||||
else return; |
||||
if ((size_t)n < hlen) return; |
||||
|
||||
memset(&m->last_peer, 0, sizeof(m->last_peer)); |
||||
if (atyp == 0x01) { |
||||
struct sockaddr_in *sa = (struct sockaddr_in *)&m->last_peer; |
||||
sa->sin_family = AF_INET; |
||||
memcpy(&sa->sin_addr.s_addr, raw + 4, 4); |
||||
memcpy(&sa->sin_port, raw + 8, 2); |
||||
} else { |
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&m->last_peer; |
||||
sa6->sin6_family = AF_INET6; |
||||
memcpy(&sa6->sin6_addr, raw + 4, 16); |
||||
memcpy(&sa6->sin6_port, raw + 20, 2); |
||||
} |
||||
size_t plen = (size_t)n - hlen; |
||||
memcpy(m->last_payload, raw + hlen, plen); |
||||
m->last_payload_len = plen; |
||||
m->echo_count++; |
||||
|
||||
uint8_t out[2048]; |
||||
size_t o = 0; |
||||
out[o++] = 0; out[o++] = 0; out[o++] = 0; |
||||
if (m->last_peer.ss_family == AF_INET) { |
||||
struct sockaddr_in *sa = (struct sockaddr_in *)&m->last_peer; |
||||
out[o++] = 0x01; |
||||
memcpy(out + o, &sa->sin_addr.s_addr, 4); o += 4; |
||||
memcpy(out + o, &sa->sin_port, 2); o += 2; |
||||
} else { |
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&m->last_peer; |
||||
out[o++] = 0x04; |
||||
memcpy(out + o, &sa6->sin6_addr, 16); o += 16; |
||||
memcpy(out + o, &sa6->sin6_port, 2); o += 2; |
||||
} |
||||
memcpy(out + o, m->last_payload, plen); o += plen; |
||||
sendto(m->udp_sock, out, o, 0, (struct sockaddr *)&from, fl); |
||||
} |
||||
|
||||
static int umock_start(struct udp_mock *m, struct UASYNC *ua, int listen_port) { |
||||
memset(m, 0, sizeof(*m)); |
||||
m->ua = ua; |
||||
m->ctrl_sock = SOCKET_INVALID; |
||||
m->listen_sock = socket(AF_INET, SOCK_STREAM, 0); |
||||
if (m->listen_sock == SOCKET_INVALID) return -1; |
||||
socket_set_reuseaddr(m->listen_sock, 1); |
||||
socket_set_nonblocking(m->listen_sock); |
||||
struct sockaddr_in a; memset(&a, 0, sizeof(a)); |
||||
a.sin_family = AF_INET; a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); a.sin_port = htons((uint16_t)listen_port); |
||||
if (bind(m->listen_sock, (struct sockaddr *)&a, sizeof(a)) < 0) return -1; |
||||
if (listen(m->listen_sock, 8) < 0) return -1; |
||||
m->listen_id = uasync_add_socket_t(ua, m->listen_sock, umock_accept_cb, NULL, NULL, m); |
||||
|
||||
m->udp_sock = socket(AF_INET, SOCK_DGRAM, 0); |
||||
if (m->udp_sock == SOCKET_INVALID) return -1; |
||||
socket_set_nonblocking(m->udp_sock); |
||||
struct sockaddr_in ua_addr; memset(&ua_addr, 0, sizeof(ua_addr)); |
||||
ua_addr.sin_family = AF_INET; ua_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
||||
if (bind(m->udp_sock, (struct sockaddr *)&ua_addr, sizeof(ua_addr)) < 0) return -1; |
||||
struct sockaddr_in got; socklen_t gl = sizeof(got); |
||||
getsockname(m->udp_sock, (struct sockaddr *)&got, &gl); |
||||
m->udp_port = ntohs(got.sin_port); |
||||
m->udp_id = uasync_add_socket_t(ua, m->udp_sock, umock_udp_read_cb, NULL, NULL, m); |
||||
return (m->listen_id && m->udp_id) ? 0 : -1; |
||||
} |
||||
|
||||
static void umock_stop(struct udp_mock *m) { |
||||
if (m->ctrl_id) uasync_remove_socket_t(m->ua, m->ctrl_sock); |
||||
if (m->ctrl_sock != SOCKET_INVALID) socket_close_wrapper(m->ctrl_sock); |
||||
if (m->listen_id) uasync_remove_socket_t(m->ua, m->listen_sock); |
||||
socket_close_wrapper(m->listen_sock); |
||||
if (m->udp_id) uasync_remove_socket_t(m->ua, m->udp_sock); |
||||
socket_close_wrapper(m->udp_sock); |
||||
} |
||||
|
||||
static int g_su_ready = 0, g_su_err = 0; |
||||
static void su_ready_cb(struct socks_udp *s, int err, void *arg) { |
||||
(void)s; (void)arg; |
||||
g_su_ready++; |
||||
g_su_err = err; |
||||
} |
||||
|
||||
static socket_t g_sock = SOCKET_INVALID; |
||||
static int g_err = 0; |
||||
static int g_done = 0; |
||||
static void dial_cb(socket_t sock, int err, void *arg) { |
||||
(void)arg; |
||||
g_sock = sock; g_err = err; g_done = 1; |
||||
} |
||||
static void read_echo_cb(socket_t sock, void *arg) { |
||||
int *got = (int *)arg; |
||||
uint8_t b; |
||||
if (recv(sock, &b, 1, 0) == 1) *got = b; |
||||
} |
||||
|
||||
// Запустить dial, дождаться результата, вернуть 0 (успех) или err.
|
||||
static int run_dial(struct UASYNC *ua, struct socks_cfg *cfg, const char *target, uint16_t tport) { |
||||
g_done = 0; g_sock = SOCKET_INVALID; g_err = 0; |
||||
void *h = socks_dial(ua, cfg, target, tport, dial_cb, NULL, 0); |
||||
if (!h) return -1; |
||||
int ticks = 0; |
||||
while (!g_done && ticks < 20000) { uasync_poll(ua, 10); ticks++; } |
||||
if (!g_done) return -1; |
||||
return g_err; |
||||
} |
||||
|
||||
static int test1_noauth_v4(void) { |
||||
struct UASYNC *ua = uasync_create(); TASSERT(ua); |
||||
struct mock m; |
||||
TASSERT(mock_start(&m, ua, MOCK_PORT, MOCK_NOAUTH) == 0); |
||||
struct socks_cfg cfg; memset(&cfg, 0, sizeof(cfg)); |
||||
strcpy(cfg.host, "127.0.0.1"); cfg.port = MOCK_PORT; |
||||
|
||||
TASSERT(run_dial(ua, &cfg, "127.0.0.1", 12345) == 0); |
||||
TASSERT(g_sock != SOCKET_INVALID); |
||||
TASSERT(m.got_connect == 1); |
||||
|
||||
int got = 0; |
||||
void *sid = uasync_add_socket_t(ua, g_sock, read_echo_cb, NULL, NULL, &got); |
||||
TASSERT(sid != NULL); |
||||
uint8_t x = 'A'; |
||||
send(g_sock, &x, 1, 0); |
||||
int ticks = 0; |
||||
while (got == 0 && ticks < 5000) { uasync_poll(ua, 10); ticks++; } |
||||
TASSERT(got == 'A'); |
||||
|
||||
uasync_remove_socket_t(ua, g_sock); |
||||
socket_close_wrapper(g_sock); |
||||
mock_stop(&m); |
||||
uasync_destroy(ua, 1); |
||||
return 0; |
||||
} |
||||
|
||||
static int test2_auth_domain(void) { |
||||
struct UASYNC *ua = uasync_create(); TASSERT(ua); |
||||
struct mock m; |
||||
TASSERT(mock_start(&m, ua, MOCK_PORT, MOCK_AUTH) == 0); |
||||
struct socks_cfg cfg; memset(&cfg, 0, sizeof(cfg)); |
||||
strcpy(cfg.host, "127.0.0.1"); cfg.port = MOCK_PORT; |
||||
strcpy(cfg.user, "alice"); strcpy(cfg.pass, "secret"); |
||||
|
||||
TASSERT(run_dial(ua, &cfg, "example.com", 443) == 0); |
||||
TASSERT(g_sock != SOCKET_INVALID); |
||||
TASSERT(m.got_connect == 1); |
||||
|
||||
socket_close_wrapper(g_sock); |
||||
mock_stop(&m); |
||||
uasync_destroy(ua, 1); |
||||
return 0; |
||||
} |
||||
|
||||
static int test3_reject(void) { |
||||
struct UASYNC *ua = uasync_create(); TASSERT(ua); |
||||
struct mock m; |
||||
TASSERT(mock_start(&m, ua, MOCK_PORT, MOCK_REJECT) == 0); |
||||
struct socks_cfg cfg; memset(&cfg, 0, sizeof(cfg)); |
||||
strcpy(cfg.host, "127.0.0.1"); cfg.port = MOCK_PORT; |
||||
|
||||
int err = run_dial(ua, &cfg, "127.0.0.1", 12345); |
||||
TASSERT(err != 0); |
||||
TASSERT(g_sock == SOCKET_INVALID); |
||||
|
||||
mock_stop(&m); |
||||
uasync_destroy(ua, 1); |
||||
return 0; |
||||
} |
||||
|
||||
static int test4_udp_associate(void) { |
||||
struct UASYNC *ua = uasync_create(); TASSERT(ua); |
||||
struct udp_mock m; |
||||
TASSERT(umock_start(&m, ua, MOCK_PORT) == 0); |
||||
|
||||
socket_t lfd = socket(AF_INET, SOCK_DGRAM, 0); |
||||
TASSERT(lfd != SOCKET_INVALID); |
||||
socket_set_nonblocking(lfd); |
||||
struct sockaddr_in lb; memset(&lb, 0, sizeof(lb)); |
||||
lb.sin_family = AF_INET; lb.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
||||
TASSERT(bind(lfd, (struct sockaddr *)&lb, sizeof(lb)) == 0); |
||||
|
||||
struct socks_cfg cfg; memset(&cfg, 0, sizeof(cfg)); |
||||
strcpy(cfg.host, "127.0.0.1"); cfg.port = MOCK_PORT; |
||||
|
||||
g_su_ready = 0; g_su_err = 0; |
||||
struct socks_udp *su = socks_udp_associate(ua, &cfg, lfd, su_ready_cb, NULL); |
||||
TASSERT(su != NULL); |
||||
|
||||
int ticks = 0; |
||||
while (g_su_ready == 0 && ticks < 20000) { uasync_poll(ua, 10); ticks++; } |
||||
TASSERT(g_su_ready == 1); |
||||
TASSERT(g_su_err == 0); |
||||
TASSERT(m.got_associate == 1); |
||||
|
||||
struct sockaddr_storage peer; memset(&peer, 0, sizeof(peer)); |
||||
struct sockaddr_in *p = (struct sockaddr_in *)&peer; |
||||
p->sin_family = AF_INET; p->sin_addr.s_addr = htonl(INADDR_LOOPBACK); p->sin_port = htons(9999); |
||||
|
||||
ssize_t snd = socks_udp_sendto(su, (const uint8_t *)"hello", 5, &peer); |
||||
TASSERT(snd > 0); |
||||
|
||||
uint8_t raw[2048]; |
||||
struct sockaddr_storage from; socklen_t fl = sizeof(from); |
||||
ssize_t got = -1; |
||||
ticks = 0; |
||||
while (ticks < 5000) { |
||||
got = recvfrom(lfd, raw, sizeof(raw), 0, (struct sockaddr *)&from, &fl); |
||||
if (got > 0) break; |
||||
uasync_poll(ua, 10); ticks++; |
||||
} |
||||
TASSERT(got > 0); |
||||
|
||||
uint8_t payload[256]; |
||||
struct sockaddr_storage src; |
||||
ssize_t plen = socks_udp_unwrap(su, raw, (size_t)got, payload, sizeof(payload), &src); |
||||
TASSERT(plen == 5); |
||||
TASSERT(memcmp(payload, "hello", 5) == 0); |
||||
TASSERT(src.ss_family == AF_INET); |
||||
TASSERT(((struct sockaddr_in *)&src)->sin_port == p->sin_port); |
||||
|
||||
socks_udp_destroy(su); |
||||
socket_close_wrapper(lfd); |
||||
umock_stop(&m); |
||||
uasync_destroy(ua, 1); |
||||
return 0; |
||||
} |
||||
|
||||
static int test5_udp_reassociate(void) { |
||||
struct UASYNC *ua = uasync_create(); TASSERT(ua); |
||||
struct udp_mock m; |
||||
TASSERT(umock_start(&m, ua, MOCK_PORT) == 0); |
||||
|
||||
socket_t lfd = socket(AF_INET, SOCK_DGRAM, 0); |
||||
TASSERT(lfd != SOCKET_INVALID); |
||||
socket_set_nonblocking(lfd); |
||||
struct sockaddr_in lb; memset(&lb, 0, sizeof(lb)); |
||||
lb.sin_family = AF_INET; lb.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
||||
TASSERT(bind(lfd, (struct sockaddr *)&lb, sizeof(lb)) == 0); |
||||
|
||||
struct socks_cfg cfg; memset(&cfg, 0, sizeof(cfg)); |
||||
strcpy(cfg.host, "127.0.0.1"); cfg.port = MOCK_PORT; |
||||
|
||||
g_su_ready = 0; g_su_err = 0; |
||||
struct socks_udp *su = socks_udp_associate(ua, &cfg, lfd, su_ready_cb, NULL); |
||||
TASSERT(su != NULL); |
||||
|
||||
int ticks = 0; |
||||
while (g_su_ready == 0 && ticks < 20000) { uasync_poll(ua, 10); ticks++; } |
||||
TASSERT(g_su_ready == 1); |
||||
TASSERT(m.got_associate == 1); |
||||
|
||||
// Обрыв control-канала: закрываем серверную сторону → клиент должен пересоздать ассоциацию.
|
||||
if (m.ctrl_id) { uasync_remove_socket_t(m.ua, m.ctrl_sock); m.ctrl_id = NULL; } |
||||
if (m.ctrl_sock != SOCKET_INVALID) { socket_close_wrapper(m.ctrl_sock); m.ctrl_sock = SOCKET_INVALID; } |
||||
|
||||
int ready_before = g_su_ready; |
||||
ticks = 0; |
||||
while (g_su_ready == ready_before && ticks < 50000) { uasync_poll(ua, 10); ticks++; } // backoff ~1с
|
||||
TASSERT(g_su_ready > ready_before); |
||||
TASSERT(m.got_associate >= 2); |
||||
|
||||
// После re-associate канал снова работает.
|
||||
struct sockaddr_storage peer; memset(&peer, 0, sizeof(peer)); |
||||
struct sockaddr_in *p = (struct sockaddr_in *)&peer; |
||||
p->sin_family = AF_INET; p->sin_addr.s_addr = htonl(INADDR_LOOPBACK); p->sin_port = htons(7777); |
||||
|
||||
TASSERT(socks_udp_sendto(su, (const uint8_t *)"ping", 4, &peer) > 0); |
||||
uint8_t raw[2048]; |
||||
struct sockaddr_storage from; socklen_t fl = sizeof(from); |
||||
ssize_t got = -1; |
||||
ticks = 0; |
||||
while (ticks < 5000) { |
||||
got = recvfrom(lfd, raw, sizeof(raw), 0, (struct sockaddr *)&from, &fl); |
||||
if (got > 0) break; |
||||
uasync_poll(ua, 10); ticks++; |
||||
} |
||||
TASSERT(got > 0); |
||||
uint8_t payload[256]; |
||||
struct sockaddr_storage src; |
||||
ssize_t plen = socks_udp_unwrap(su, raw, (size_t)got, payload, sizeof(payload), &src); |
||||
TASSERT(plen == 4); |
||||
TASSERT(memcmp(payload, "ping", 4) == 0); |
||||
|
||||
socks_udp_destroy(su); |
||||
socket_close_wrapper(lfd); |
||||
umock_stop(&m); |
||||
uasync_destroy(ua, 1); |
||||
return 0; |
||||
} |
||||
|
||||
int main(void) { |
||||
debug_set_level(DEBUG_LEVEL_ERROR); |
||||
socket_platform_init(); |
||||
|
||||
struct { const char *name; int (*fn)(void); } tests[] = { |
||||
{"test1_noauth_v4", test1_noauth_v4}, |
||||
{"test2_auth_domain", test2_auth_domain}, |
||||
{"test3_reject", test3_reject}, |
||||
{"test4_udp_associate", test4_udp_associate}, |
||||
{"test5_udp_reassociate", test5_udp_reassociate}, |
||||
}; |
||||
int n = (int)(sizeof(tests) / sizeof(tests[0])); |
||||
int passed = 0; |
||||
for (int i = 0; i < n; i++) { |
||||
test_failed = 0; |
||||
int r = tests[i].fn(); |
||||
if (r == 0) { passed++; DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "PASS: %s", tests[i].name); } |
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "FAIL: %s", tests[i].name); } |
||||
} |
||||
socket_platform_cleanup(); |
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Results: %d/%d passed", passed, n); |
||||
return passed == n ? 0 : 1; |
||||
} |
||||
Loading…
Reference in new issue