Browse Source

transport: SOCKS5-прокси для ETCP-линков (CONNECT + UDP ASSOCIATE + пинги)

v2
evgeny 3 weeks ago
parent
commit
d0dfa97a47
  1. 2
      src/Makefile.am
  2. 42
      src/config_parser.c
  3. 8
      src/config_parser.h
  4. 169
      src/transport_layer/etcp_connections.c
  5. 3
      src/transport_layer/etcp_connections.h
  6. 493
      src/transport_layer/socks_client.c
  7. 75
      src/transport_layer/socks_client.h
  8. 48
      src/transport_layer/stcp_client.c
  9. 7
      src/transport_layer/stcp_client.h
  10. 15
      src/transport_layer/stcp_link.c
  11. 5
      tests/Makefile.am
  12. 527
      tests/test_socks_client.c
  13. 16
      tests/test_stcp.c
  14. 4
      tests/test_stcp_link.c

2
src/Makefile.am

@ -45,6 +45,7 @@ utun_CORE_SOURCES = \
transport_layer/stcp.c \
transport_layer/stcp_server.c \
transport_layer/stcp_client.c \
transport_layer/socks_client.c \
transport_layer/pkt_normalizer.c \
transport_layer/packet_dump.c \
transport_layer/etcp_api.c \
@ -135,6 +136,7 @@ libutun_a_SOURCES = \
transport_layer/stcp.c \
transport_layer/stcp_server.c \
transport_layer/stcp_client.c \
transport_layer/socks_client.c \
transport_layer/pkt_normalizer.c \
transport_layer/packet_dump.c \
transport_layer/etcp_api.c \

42
src/config_parser.c

@ -53,7 +53,8 @@ typedef enum {
SECTION_GUI,
SECTION_NTP,
SECTION_LOG_UDP,
SECTION_REALITY
SECTION_REALITY,
SECTION_SOCKS
} section_type_t;
static char* trim(char *str) {
@ -703,7 +704,11 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
srv->reality_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local, transport, reality", filename, line_num, key);
if (strcmp(key, "socks") == 0) {
srv->socks_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local, transport, reality, socks", filename, line_num, key);
return -1;
}
@ -912,9 +917,35 @@ static int parse_reality(const char *key, const char *value, struct reality_conf
return -1;
}
// Разбор [socks] секции: прокси для исходящих ETCP-линков.
static int parse_socks(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "enabled") == 0) {
global->socks_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
return 0;
}
if (strcmp(key, "addr") == 0) {
const char *colon = strrchr(value, ':');
if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: socks addr must be 'host:port'", filename, line_num); return -1; }
int port = atoi(colon + 1);
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: bad socks port '%s'", filename, line_num, colon + 1); return -1; }
char host[64];
size_t hlen = (size_t)(colon - value);
if (hlen >= sizeof(host)) hlen = sizeof(host) - 1;
memcpy(host, value, hlen); host[hlen] = '\0';
if (hlen >= 2 && host[0] == '[' && host[hlen - 1] == ']') { host[hlen - 1] = '\0'; memmove(host, host + 1, strlen(host)); }
strncpy(global->socks_host, host, sizeof(global->socks_host) - 1);
global->socks_host[sizeof(global->socks_host) - 1] = '\0';
global->socks_port = (uint16_t)port;
return 0;
}
if (strcmp(key, "username") == 0) return assign_string(global->socks_username, sizeof(global->socks_username), value);
if (strcmp(key, "password") == 0) return assign_string(global->socks_password, sizeof(global->socks_password), value);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown socks option '%s'. Valid: enabled, addr, username, password", filename, line_num, key);
return -1;
}
static section_type_t parse_section_header(const char *line, char *name, size_t name_len) {
if (line[0] != '[') return SECTION_UNKNOWN;
size_t line_len = strlen(line);
if (line[line_len - 1] != ']') return SECTION_UNKNOWN;
@ -940,6 +971,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "gui") == 0) return SECTION_GUI;
if (strcasecmp(section, "log_udp") == 0) return SECTION_LOG_UDP;
if (strcasecmp(section, "reality") == 0) return SECTION_REALITY;
if (strcasecmp(section, "socks") == 0) return SECTION_SOCKS;
char *colon = strchr(section, ':');
if (!colon) return SECTION_UNKNOWN;
@ -1208,6 +1240,9 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
case SECTION_REALITY:
parse_reality(key, value, &cfg->global.reality, filename, line_num);
break;
case SECTION_SOCKS:
parse_socks(key, value, &cfg->global, filename, line_num);
break;
default:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
break;
@ -1359,6 +1394,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
case SECTION_CHATSERVER: cfg->global.chatserver_enabled = 1; cfg->global.db_sync_enabled = 1; if (strcmp(key, "db_path") == 0) assign_string(cfg->global.db_path, sizeof(cfg->global.db_path), value); else if (parse_chatserver(key, value, &cfg->global, filename, line_num) < 0 && chat_setting_set(key, value) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown chatserver option '%s'. Valid: db_path, join_password, allowed_groups, storage_total_size, storage_unit_size, storage_autoload, opus_codec_preset, compressor_enabled, compressor_max_gain_db, compressor_rise_rate, media_download_max_peers", filename, line_num, key); } break;
case SECTION_GUI: break;
case SECTION_REALITY: parse_reality(key, value, &cfg->global.reality, filename, line_num); break;
case SECTION_SOCKS: parse_socks(key, value, &cfg->global, filename, line_num); break;
case SECTION_LOG_UDP:
if (strcmp(key, "ip") == 0) strncpy(cfg->global.log_udp_ip, value, sizeof(cfg->global.log_udp_ip) - 1);
else if (strcmp(key, "port") == 0) cfg->global.log_udp_port = atoi(value);

8
src/config_parser.h

@ -50,6 +50,7 @@ struct CFG_SERVER {
uint8_t type; // public/nat/private
uint8_t transport; // 0=udp (default), 1=tcp
uint8_t reality_enabled; // 1 = TCP-порт с REALITY-камуфляжем (только transport=1)
uint8_t socks_enabled; // 1 = UDP-сокет туннелировать через SOCKS5 (только transport=0)
uint8_t ipv6_mode; // CFG_IPV6_MODE_*
int mtu;
uint8_t only_local; // 1 = only local connections, no forwarding
@ -223,6 +224,13 @@ struct global_config {
// Reality-камуфляж ([reality] section)
struct reality_config reality;
// SOCKS5-прокси для исходящих ETCP-линков ([socks] section)
int socks_enabled; // 1 = TCP/STCP-линки подключаются через SOCKS5
char socks_host[64]; // host прокси (ip или домен)
uint16_t socks_port; // порт прокси
char socks_username[64]; // пусто = no-auth
char socks_password[64];
};
struct utun_config {

169
src/transport_layer/etcp_connections.c

@ -18,6 +18,7 @@
#include "stcp_link.h"
#include "stcp.h"
#include "stcp_client.h"
#include "socks_client.h"
#include "topo_node.h"
#include "topo_group.h"
#include "node_conn_direct.h"
@ -52,6 +53,41 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset, uint8_t c
static void etcp_link_init_timer_cbk(void* arg);
static void burst_resp_timeout_cb(void* arg);
static int etcp_tcp_send(struct ETCP_DGRAM* dgram);
static void etcp_process_packet(struct ETCP_SOCKET* e_sock, uint8_t* data, ssize_t recv_len, struct sockaddr_storage addr);
static void socks_etcp_read_callback(socket_t sock, void* arg);
static void socks_relay_ready_cb(struct socks_udp* s, int err, void* arg);
// Коллбэк готовности SOCKS5 UDP ASSOCIATE: только логирование (sendto сам ждёт ready).
static void socks_relay_ready_cb(struct socks_udp* s, int err, void* arg) {
struct ETCP_SOCKET* e_sock = (struct ETCP_SOCKET*)arg;
(void)s;
if (err != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks UDP associate error for socket %s err=%d", e_sock->name, err); return; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks UDP relay ready for socket %s", e_sock->name);
}
// Чтение с локального UDP-сокета SOCKS5 UDP ASSOCIATE: снять SOCKS-заголовок → реальный src,
// дальше обычный разбор через etcp_process_packet.
static void socks_etcp_read_callback(socket_t sock, void* arg) {
struct ETCP_SOCKET* e_sock = (struct ETCP_SOCKET*)arg;
if (!e_sock || !e_sock->socks_udp) return;
uint8_t raw[PACKET_DATA_SIZE + 64];
uint8_t payload[PACKET_DATA_SIZE];
struct sockaddr_storage relay;
socklen_t rl = sizeof(relay);
memset(&relay, 0, sizeof(relay));
ssize_t raw_len = socket_recvfrom(sock, raw, sizeof(raw), (struct sockaddr*)&relay, &rl);
if (raw_len <= 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "socks recvfrom failed, error=%zd, sock_err=%d", raw_len, socket_get_error());
return;
}
struct sockaddr_storage src;
ssize_t plen = socks_udp_unwrap(e_sock->socks_udp, raw, (size_t)raw_len, payload, sizeof(payload), &src);
if (plen <= 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "socks unwrap failed len=%zd from %s", plen, sockaddr_storage_to_str(&relay).str);
return;
}
etcp_process_packet(e_sock, payload, plen, src);
}
// STCP-сервер: новое входящее TCP-соединение → найти/создать ETCP_CONN по node_id
// pubkey пира, создать TCP-линк и поднять его (ready).
@ -174,7 +210,11 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset, uint8_t c
req->socket_id = link->conn ? link->conn->sock_id : 0;
req->only_local = link->conn ? link->conn->only_local : 0;
req->type = link->conn ? link->conn->type : CFG_SERVER_TYPE_UNKNOWN;
if (link->conn && link->conn->interface_addr.ss_family == AF_INET) {
// SOCKS5-прокси: пир не может дотянуться до нашего interface_addr — не рекламируем src.
if (link->conn && link->conn->socks_udp) {
memset(req->src_ipv4, 0, 4);
memset(req->src_port, 0, 2);
} else if (link->conn && link->conn->interface_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&link->conn->interface_addr;
memcpy(req->src_ipv4, &sin->sin_addr.s_addr, 4);
*(uint16_t*)req->src_port = sin->sin_port;
@ -506,6 +546,7 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S
int mtu = server->mtu ? server->mtu : instance->config->global.mtu;
if (mtu == 0 || mtu > PACKET_DATA_MAX_MTU) mtu = PACKET_DATA_MAX_MTU;
uint8_t only_local = server->only_local;
uint8_t socks_enabled = server->socks_enabled;
char* name = server->name;
struct ETCP_SOCKET* e_sock = u_calloc(1, sizeof(struct ETCP_SOCKET));
@ -730,10 +771,44 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "Add Socket type=%s", server_type_str(type));
e_sock->next = instance->etcp_sockets;
instance->etcp_sockets = e_sock;
e_sock->socket_id = uasync_add_socket_t(instance->ua, e_sock->fd, etcp_connections_read_callback_socket, NULL, NULL, e_sock);
if (socks_enabled) {
// SOCKS5 UDP ASSOCIATE: весь сокет туннелируется через прокси.
struct global_config* gc = &instance->config->global;
if (!gc->socks_host[0] || !gc->socks_port) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socket %s socks=yes but [socks] addr not configured", e_sock->name);
socket_close_wrapper(e_sock->fd);
queue_free(e_sock->links_queue);
instance->etcp_sockets = e_sock->next;
u_free(e_sock);
return NULL;
}
struct socks_cfg scfg;
memset(&scfg, 0, sizeof(scfg));
strncpy(scfg.host, gc->socks_host, sizeof(scfg.host) - 1);
scfg.port = gc->socks_port;
strncpy(scfg.user, gc->socks_username, sizeof(scfg.user) - 1);
strncpy(scfg.pass, gc->socks_password, sizeof(scfg.pass) - 1);
e_sock->socks_udp = socks_udp_associate(instance->ua, &scfg, e_sock->fd, socks_relay_ready_cb, e_sock);
if (!e_sock->socks_udp) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_udp_associate failed for socket %s", e_sock->name);
socket_close_wrapper(e_sock->fd);
queue_free(e_sock->links_queue);
instance->etcp_sockets = e_sock->next;
u_free(e_sock);
return NULL;
}
e_sock->socket_id = uasync_add_socket_t(instance->ua, e_sock->fd, socks_etcp_read_callback, NULL, NULL, e_sock);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket %s tunneled via SOCKS5 UDP (proxy=%s:%u)", e_sock->name, gc->socks_host, gc->socks_port);
} else {
e_sock->socket_id = uasync_add_socket_t(instance->ua, e_sock->fd, etcp_connections_read_callback_socket, NULL, NULL, e_sock);
}
if (!e_sock->socket_id) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "Failed to register socket with uasync");
if (e_sock->socks_udp) { socks_udp_destroy(e_sock->socks_udp); e_sock->socks_udp = NULL; }
socket_close_wrapper(e_sock->fd);
queue_free(e_sock->links_queue);
instance->etcp_sockets = e_sock->next;
u_free(e_sock);
return NULL;
}
@ -761,6 +836,7 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] Closed socket");
}
if (conn->socks_udp) { socks_udp_destroy(conn->socks_udp); conn->socks_udp = NULL; }
if (conn->links_queue) {
struct ll_entry* entry;
@ -1229,9 +1305,11 @@ static int etcp_tcp_send(struct ETCP_DGRAM* dgram) {
}
// Единая точка отправки UDP: через send_hook (если задан) или socket_sendto напрямую.
// Единая точка отправки UDP: SOCKS5-релей (если сокет proxied) → send_hook (dummynet) → socket_sendto.
ssize_t etcp_udp_send(struct ETCP_LINK* link, socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len) {
if (link && link->conn && link->conn->socks_udp)
return socks_udp_sendto(link->conn->socks_udp, buf, len, (const struct sockaddr_storage*)addr);
if (link && link->send_hook)
return link->send_hook(fd, buf, len, addr, addr_len, link, link->send_hook_ctx);
return socket_sendto(fd, buf, len, addr, addr_len);
@ -1288,9 +1366,10 @@ es_err:
return -1;
}
// Шифрует и отправляет готовый PING/PONG-датаграмм без линка (сырой sendto по fd).
static int etcp_send_ping_raw(struct ETCP_DGRAM* dgram, socket_t fd, sc_context_t* sc, const struct sockaddr_storage* addr) {
if (!dgram || !sc || !addr) {
// Шифрует и отправляет готовый PING/PONG-датаграмм без линка (сырой sendto по сокету).
// Для proxied-сокета идёт через SOCKS5 UDP-релей (dst = addr пира).
static int etcp_send_ping_raw(struct ETCP_DGRAM* dgram, struct ETCP_SOCKET* e_sock, sc_context_t* sc, const struct sockaddr_storage* addr) {
if (!dgram || !e_sock || !sc || !addr) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Null pointer in ping send");
return -1;
}
@ -1313,16 +1392,23 @@ static int etcp_send_ping_raw(struct ETCP_DGRAM* dgram, socket_t fd, sc_context_
return -1;
}
memcpy(enc_buf + enc_buf_len, dgram->data + len, dgram->noencrypt_len);
size_t total = enc_buf_len + dgram->noencrypt_len;
ssize_t sent;
if (e_sock->socks_udp) {
sent = socks_udp_sendto(e_sock->socks_udp, enc_buf, total, addr);
if (sent < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "ping via socks failed addr=%s len=%zu", sockaddr_storage_to_str(addr).str, total);
return -1;
}
return (int)sent;
}
socklen_t addr_len = (addr->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
ssize_t sent = enc_buf_len + dgram->noencrypt_len;
// uint8_t* xaddr=&((struct sockaddr_in*)addr)->sin_addr;
//xaddr[0]=192; xaddr[1]=168; xaddr[2]=10; xaddr[3]=1;
sent = socket_sendto(fd, enc_buf, enc_buf_len + dgram->noencrypt_len, (struct sockaddr*)addr, addr_len);
sent = socket_sendto(e_sock->fd, enc_buf, total, (struct sockaddr*)addr, addr_len);
if (sent < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "sendto failed for ping, err=%d addr=%s fd=%d len=%d", socket_get_error(), sockaddr_storage_to_str(addr).str, fd, enc_buf_len + dgram->noencrypt_len);
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "sendto failed for ping, err=%d addr=%s fd=%d len=%zu", socket_get_error(), sockaddr_storage_to_str(addr).str, e_sock->fd, total);
return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ping sendto succeeded to %s sent=%zd bytes fd=%d", sockaddr_storage_to_str(addr).str, sent, fd);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ping sendto succeeded to %s sent=%zd bytes fd=%d", sockaddr_storage_to_str(addr).str, sent, e_sock->fd);
return (int)sent;
}
@ -1440,7 +1526,7 @@ int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET*
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ping send nonce=%016llx timeout=%d ulen=%zu",
(unsigned long long)ctx->nonce, timeout_ms, user_data_len);
int send_rc = etcp_send_ping_raw(dgram, e_sock->fd, &sc, addr);
int send_rc = etcp_send_ping_raw(dgram, e_sock, &sc, addr);
u_free(dgram);
if (send_rc < 0) {
if (ctx->user_data) u_free(ctx->user_data);
@ -1518,9 +1604,22 @@ int etcp_send_tcp_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubke
struct tcp_ping_adapter* a = u_malloc(sizeof(struct tcp_ping_adapter));
if (!a) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "malloc adapter"); return -3; }
a->cb = cb; a->arg = user_arg;
struct socks_cfg socks_buf;
const struct socks_cfg* socks = NULL;
if (instance->config && instance->config->global.socks_enabled &&
instance->config->global.socks_host[0] && instance->config->global.socks_port) {
memset(&socks_buf, 0, sizeof(socks_buf));
strncpy(socks_buf.host, instance->config->global.socks_host, sizeof(socks_buf.host) - 1);
socks_buf.port = instance->config->global.socks_port;
strncpy(socks_buf.user, instance->config->global.socks_username, sizeof(socks_buf.user) - 1);
strncpy(socks_buf.pass, instance->config->global.socks_password, sizeof(socks_buf.pass) - 1);
socks = &socks_buf;
}
struct stcp_client* cli = stcp_ping_send(instance->ua, addr_str, port, &instance->my_keys, peer_pubkey_bin,
instance->my_ed25519_pubkey, instance->client_type,
instance->keepalive_interval, timeout_ms, tcp_ping_cb_adapter, a);
instance->keepalive_interval, timeout_ms, tcp_ping_cb_adapter, a, socks);
if (!cli) { u_free(a); return -4; }
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "tcp ping to %s:%u timeout=%d", addr_str, (unsigned)port, timeout_ms);
return 0;
@ -1577,7 +1676,7 @@ static int handle_ping(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const
if (sc_set_peer_public_key(&resp_sc, decrypted_pubkey, SC_PEER_PUBKEY_BIN) == SC_OK) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "PONG send nonce=%016llx flags=%02x to=%s fd=%d",
(unsigned long long)nonce, (unsigned)pong_flags, sockaddr_storage_to_str(addr).str, e_sock->fd);
etcp_send_ping_raw(resp, e_sock->fd, &resp_sc, addr);
etcp_send_ping_raw(resp, e_sock, &resp_sc, addr);
}
u_free(resp);
}
@ -1909,17 +2008,33 @@ static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_D
// Главный приёмник UDP-кодограмм: normal/init decrypt, диспетчеризация PING/PONG/INIT,
// создание/переиспользование линков и коннектов, обработка коллизий.
// Приём прямого UDP-пакета: recvfrom → etcp_process_packet.
void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback fd=%d, socket=%p", fd, arg);
// !!!!!! DANGER: в этой функции ПРЕДЕЛЬНАЯ АККУРАТНОСТЬ. Если кажется что не туда указатель то невнимательно аланизировал !!!!!
struct ETCP_SOCKET* e_sock = (struct ETCP_SOCKET*)arg;
if (!e_sock) return;
struct sockaddr_storage addr;
uint8_t data[PACKET_DATA_SIZE];
socklen_t addr_len = sizeof(addr);
memset(&addr, 0, sizeof(addr));
ssize_t recv_len = socket_recvfrom(sock, data, PACKET_DATA_SIZE, (struct sockaddr*)&addr, &addr_len);
if (recv_len <= 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "recvfrom failed, error=%zd, sock_err=%d", recv_len, socket_get_error());
return;
}
etcp_process_packet(e_sock, data, recv_len, addr);
}
// Обработка принятого сырого UDP-пакета: расшифровка и диспетчеризация.
// Выделена из read_callback, чтобы один путь обслуживал прямой приём и приём через
// SOCKS5 UDP ASSOCIATE (socks_etcp_read_callback подставляет реальный src пира).
// !!!!!! DANGER: в этой функции ПРЕДЕЛЬНАЯ АККУРАТНОСТЬ !!!!!
// НЕ РУИНИТЬ (uint8_t*)&pkt->timestamp - это правильно !!!!
//
// Ошибки функции (errorcode):
// 1 - пакет слишком маленький для init (< SC_PUBKEY_SIZE)
// 2 - не удалось установить peer public key при init
// 3 - не удалось расшифровать init пакет
// 4 - не init/p ing пакет (неверный код)
// 4 - не init/ping пакет (неверный код)
// 5 - коллизия peer ID и ключей
// 6 - не удалось расшифровать обычный пакет
// 7 - слишком короткий пакет
@ -1928,19 +2043,9 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
// 46 - расшифрованный пакет слишком маленький (< 3 байта)
// 55 - не удалось создать подключение
// 66 - не удалось создать линк
struct ETCP_SOCKET* e_sock = (struct ETCP_SOCKET*)arg;
if (!e_sock) return;
struct sockaddr_storage addr;
uint8_t data[PACKET_DATA_SIZE];
socklen_t addr_len=sizeof(addr);
memset(&addr, 0, sizeof(addr));
ssize_t recv_len = socket_recvfrom(sock, data, PACKET_DATA_SIZE, (struct sockaddr*)&addr, &addr_len);
if (recv_len <= 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "recvfrom failed, error=%zd, sock_err=%d", recv_len, socket_get_error());
return;
}
static void etcp_process_packet(struct ETCP_SOCKET* e_sock, uint8_t* data,
ssize_t recv_len, struct sockaddr_storage addr) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
// DUMP: Show received packet content
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "RECV in:", data, recv_len);

3
src/transport_layer/etcp_connections.h

@ -164,6 +164,7 @@ struct ETCP_SOCKET {
uint8_t nat_type; // NAT_TYPE_*
uint32_t local_defaultroute_ip; // auto-detected IPv4 (network byte order)
uint8_t local_defaultroute_ip6[16];
struct socks_udp* socks_udp; // SOCKS5 UDP ASSOCIATE (NULL = напрямую)
};
// NAT check status
@ -198,6 +199,8 @@ typedef ssize_t (*etcp_udp_send_fn_t)(socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len,
struct ETCP_LINK* link, void* context);
struct socks_udp;
// ETCP Link - одно динамическое соединение (один путь)
struct ETCP_LINK {

493
src/transport_layer/socks_client.c

@ -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;
}

75
src/transport_layer/socks_client.h

@ -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

48
src/transport_layer/stcp_client.c

@ -31,6 +31,9 @@ struct stcp_client {
// reality-камуфляж (клиент): перед STCP-хендшейком шлём ClientHello и читаем ServerHello
uint8_t reality_enabled;
struct reality_client_config reality_cfg;
// SOCKS5-прокси: подключение к прокси + CONNECT выполняется до STCP-хендшейка
void *socks_handle; // handle socks_dial (NULL = не через прокси)
int hs_timeout_tb; // таймаут хендшейка (0.1ms), для установки после dial
};
static void client_connect_write_cb(socket_t sock, void *arg);
@ -213,6 +216,23 @@ static void client_connect_write_cb(socket_t sock, void *arg) {
client_after_connect(c);
}
// SOCKS5 CONNECT завершён (или не удался): продолжаем STCP-хендшейк на готовом сокете.
static void client_socks_ready_cb(socket_t sock, int err, void *arg) {
struct stcp_client *cli = (struct stcp_client *)arg;
struct stcp_conn *c = &cli->conn;
cli->socks_handle = NULL;
if (err != 0 || sock == SOCKET_INVALID) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client: SOCKS dial failed err=%d", err);
stcp_conn_do_close(c, err ? err : ECONNREFUSED);
return;
}
c->sock = sock;
c->socket_id = uasync_add_socket_t(cli->ua, sock, client_conn_read_cb, stcp_write_cb, NULL, c);
if (!c->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client: add_socket after socks failed"); stcp_conn_do_close(c, ENOMEM); return; }
c->hs_timer = uasync_set_timeout(cli->ua, cli->hs_timeout_tb, c, hs_timeout_cb, "stcp_hs");
client_after_connect(c);
}
struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port,
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
const uint8_t *my_ed25519_pubkey,
@ -225,7 +245,8 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
stcp_close_cb close_cb, void *close_arg,
const struct sockaddr_storage *local_addr,
int timeout_ms,
const struct reality_client_config *reality) {
const struct reality_client_config *reality,
const struct socks_cfg *socks) {
if (!ua || !addr || !keys || !peer_pubkey || (!ready_cb && !ping_cb)) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "invalid args"); return NULL; }
struct stcp_client *cli = u_calloc(1, sizeof(struct stcp_client));
if (!cli) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "calloc failed"); return NULL; }
@ -246,7 +267,15 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
c->keepalive_interval = keepalive_interval;
c->hs_flags = hs_flags;
int hs_tb = (timeout_ms > 0) ? timeout_ms * 10 : STCP_CONNECT_TIMEOUT;
cli->hs_timeout_tb = (timeout_ms > 0) ? timeout_ms * 10 : STCP_CONNECT_TIMEOUT;
// SOCKS5-прокси: подключение к прокси + CONNECT вместо прямого connect().
if (socks) {
cli->socks_handle = socks_dial(ua, socks, addr, port, client_socks_ready_cb, cli, cli->hs_timeout_tb);
if (!cli->socks_handle) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client: socks_dial init failed for %s:%u", addr, port); u_free(cli); return NULL; }
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: connecting %s:%u via SOCKS5 %s:%u", addr, port, socks->host, socks->port);
return cli;
}
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
@ -275,11 +304,11 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
}
c->socket_id = uasync_add_socket_t(ua, c->sock, NULL, client_connect_write_cb, NULL, cli);
if (!c->socket_id) { socket_close_wrapper(c->sock); u_free(cli); return NULL; }
c->hs_timer = uasync_set_timeout(ua, hs_tb, c, hs_timeout_cb, "stcp_hs");
c->hs_timer = uasync_set_timeout(ua, cli->hs_timeout_tb, c, hs_timeout_cb, "stcp_hs");
} else {
c->socket_id = uasync_add_socket_t(ua, c->sock, client_conn_read_cb, stcp_write_cb, NULL, c);
if (!c->socket_id) { socket_close_wrapper(c->sock); u_free(cli); return NULL; }
c->hs_timer = uasync_set_timeout(ua, hs_tb, c, hs_timeout_cb, "stcp_hs");
c->hs_timer = uasync_set_timeout(ua, cli->hs_timeout_tb, c, hs_timeout_cb, "stcp_hs");
client_after_connect(c);
}
return cli;
@ -287,6 +316,12 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
void stcp_client_destroy(struct stcp_client *cli) {
if (!cli) return;
if (cli->socks_handle) {
socks_dial_cancel(cli->socks_handle);
cli->socks_handle = NULL;
u_free(cli);
return;
}
cli->conn.free_on_close = cli;
stcp_conn_do_close(&cli->conn, 0);
}
@ -309,11 +344,12 @@ struct stcp_client *stcp_ping_send(struct UASYNC *ua, const char *addr, uint16_t
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
const uint8_t *my_ed25519_pubkey,
uint8_t device_type, uint16_t keepalive_interval,
int timeout_ms, stcp_ping_cb cb, void *arg) {
int timeout_ms, stcp_ping_cb cb, void *arg,
const struct socks_cfg *socks) {
struct stcp_client *cli = stcp_client_connect(ua, addr, port, keys, peer_pubkey, my_ed25519_pubkey,
0, NULL, device_type, keepalive_interval,
STCP_HANDSHAKE_FLAG_PING,
NULL, NULL, cb, arg, ping_close_cb, NULL, NULL, timeout_ms, NULL);
NULL, NULL, cb, arg, ping_close_cb, NULL, NULL, timeout_ms, NULL, socks);
if (cli) cli->conn.free_on_close = cli;
return cli;
}

7
src/transport_layer/stcp_client.h

@ -8,6 +8,7 @@ extern "C" {
#include "stcp.h"
#include "socks_client.h"
typedef void (*stcp_ready_cb)(struct stcp_conn *conn, void *arg);
typedef void (*stcp_close_cb)(struct stcp_conn *conn, int err, void *arg);
@ -24,7 +25,8 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
stcp_close_cb close_cb, void *close_arg,
const struct sockaddr_storage *local_addr,
int timeout_ms,
const struct reality_client_config *reality);
const struct reality_client_config *reality,
const struct socks_cfg *socks);
/* TCP-ping: подключается и шлёт хендшейк с флагом STCP_HANDSHAKE_FLAG_PING.
* Ответчик отвечает обычным хендшейк-ответом и мягко закрывает соединение.
* По завершении хендшейка вызывается cb(success=1, rtt, arg); при ошибке/таймауте — cb(0, 0, arg). */
@ -32,7 +34,8 @@ struct stcp_client *stcp_ping_send(struct UASYNC *ua, const char *addr, uint16_t
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
const uint8_t *my_ed25519_pubkey,
uint8_t device_type, uint16_t keepalive_interval,
int timeout_ms, stcp_ping_cb cb, void *arg);
int timeout_ms, stcp_ping_cb cb, void *arg,
const struct socks_cfg *socks);
void stcp_client_destroy(struct stcp_client *cli);
struct stcp_conn *stcp_client_get_conn(struct stcp_client *cli);

15
src/transport_layer/stcp_link.c

@ -237,12 +237,25 @@ struct stcp_link *stcp_link_connect(struct ETCP_LINK *etcp_link,
// (etcp_link->reality_set). Глобальный [reality] для исходящих не используется.
const struct reality_client_config *reality = etcp_link->reality_set ? &etcp_link->reality : NULL;
// SOCKS5-прокси: глобальная секция [socks]. Если включена — исходящие TCP-линки идут через прокси.
struct socks_cfg socks_buf;
const struct socks_cfg *socks = NULL;
if (inst->config && inst->config->global.socks_enabled &&
inst->config->global.socks_host[0] && inst->config->global.socks_port) {
memset(&socks_buf, 0, sizeof(socks_buf));
strncpy(socks_buf.host, inst->config->global.socks_host, sizeof(socks_buf.host) - 1);
socks_buf.port = inst->config->global.socks_port;
strncpy(socks_buf.user, inst->config->global.socks_username, sizeof(socks_buf.user) - 1);
strncpy(socks_buf.pass, inst->config->global.socks_password, sizeof(socks_buf.pass) - 1);
socks = &socks_buf;
}
link->cli = stcp_client_connect(inst->ua, addr_str, rport, &inst->my_keys, pubkey,
inst->my_ed25519_pubkey,
etcp->got_initial_pkt, etcp,
inst->client_type, inst->keepalive_interval,
0,
client_ready_cb, link, NULL, NULL, NULL, NULL, local_addr, 0, reality);
client_ready_cb, link, NULL, NULL, NULL, NULL, local_addr, 0, reality, socks);
if (!link->cli) { u_free(link); return NULL; }
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_link: connecting to %s:%u pubkey=%016llx bind=%s",

5
tests/Makefile.am

@ -47,6 +47,7 @@ check_PROGRAMS = \
test_icmp_proxy \
test_tcp_proxy_client \
test_socks_http_proxy \
test_socks_client \
test_bgp_route_exchange \
test_bgp_triangle \
test_broadcast \
@ -151,6 +152,10 @@ test_stcp_SOURCES = test_stcp.c
test_stcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_stcp_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_socks_client_SOURCES = test_socks_client.c
test_socks_client_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/transport_layer -I$(top_srcdir)/lib
test_socks_client_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_two_instances_SOURCES = test_etcp_two_instances.c
test_etcp_two_instances_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_two_instances_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)

527
tests/test_socks_client.c

@ -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;
}

16
tests/test_stcp.c

@ -104,7 +104,7 @@ static int test1_sizes(void) {
uint16_t port = BASE_PORT + 1;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL); TASSERT(sc);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
size_t sizes[] = {0, 1, 16, 17, 255, 256, 1000, 65535};
int n_sizes = 8;
@ -144,7 +144,7 @@ static int test2_many(void) {
uint16_t port = BASE_PORT + 2;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL); TASSERT(sc);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
int sent = 0, ticks = 0;
while (srv.msg_count < 200 && ticks < 200) {
@ -185,7 +185,7 @@ static int test3_wrong_key(void) {
struct SC_MYKEYS rogue;
TASSERT(sc_generate_keypair(&rogue) == SC_OK);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, rogue.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL); TASSERT(sc);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, rogue.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
int ticks = 0;
while (ticks < 200) {
@ -210,7 +210,7 @@ static int test4_close(void) {
uint16_t port = BASE_PORT + 4;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL); TASSERT(sc);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
int closed = 0, ticks = 0;
while (!srv.closed && ticks < 200) {
@ -260,7 +260,7 @@ static int test5_multi(void) {
struct stcp_client *clients[NCLI] = {0};
for (int i = 0; i < NCLI; i++) {
clients[i] = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &clip[i], NULL, NULL, peer_close_cb, &clip[i], NULL, 0, NULL);
clients[i] = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &clip[i], NULL, NULL, peer_close_cb, &clip[i], NULL, 0, NULL, NULL);
TASSERT(clients[i]);
}
@ -309,7 +309,7 @@ static int test6_interleaved(void) {
uint16_t port = BASE_PORT + 6;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL); TASSERT(sc);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
int round = 0, ticks = 0;
while (srv.msg_count < 50 || cli.msg_count < 50) {
@ -343,7 +343,7 @@ static int test7_bulk_4mb(void) {
uint16_t port = BASE_PORT + 7;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL); TASSERT(sc);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
#define N_BULK 64
#define SZ_BULK 65535
@ -382,7 +382,7 @@ static int test8_srv_recv_close(void) {
uint16_t port = BASE_PORT + 8;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL); TASSERT(sc);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
int ticks = 0;
while ((!srv.ready || !cli.ready) && ticks < 200) { uasync_poll(ua, 10); ticks++; }

4
tests/test_stcp_link.c

@ -52,7 +52,7 @@ static int test1_basic(void) {
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", BASE_PORT + 1, &c_keys,
s_keys.public_key, NULL,
0, NULL,
0, 200, 0, on_cli_ready, &cli_ready, NULL, NULL, NULL, NULL, NULL, 0, NULL);
0, 200, 0, on_cli_ready, &cli_ready, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL);
TASSERT(sc);
int ticks = 0;
@ -81,7 +81,7 @@ static int test2_wrong_key(void) {
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", BASE_PORT + 2, &c_keys,
rogue.public_key, NULL,
0, NULL,
0, 200, 0, on_cli_ready, &cli_ready, NULL, NULL, NULL, NULL, NULL, 0, NULL);
0, 200, 0, on_cli_ready, &cli_ready, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL);
TASSERT(sc);
int ticks = 0;

Loading…
Cancel
Save