diff --git a/src/Makefile.am b/src/Makefile.am index b0751274..3bf5e668 100644 --- a/src/Makefile.am +++ b/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 \ diff --git a/src/config_parser.c b/src/config_parser.c index f65593e2..6f8743bd 100644 --- a/src/config_parser.c +++ b/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); diff --git a/src/config_parser.h b/src/config_parser.h index 0301e154..22b11765 100644 --- a/src/config_parser.h +++ b/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 { diff --git a/src/transport_layer/etcp_connections.c b/src/transport_layer/etcp_connections.c index 35fde3c4..55187775 100644 --- a/src/transport_layer/etcp_connections.c +++ b/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); diff --git a/src/transport_layer/etcp_connections.h b/src/transport_layer/etcp_connections.h index cbcbbaba..ff46a994 100644 --- a/src/transport_layer/etcp_connections.h +++ b/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 { diff --git a/src/transport_layer/socks_client.c b/src/transport_layer/socks_client.c new file mode 100644 index 00000000..472ceae8 --- /dev/null +++ b/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 +#include +#include + +#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; +} diff --git a/src/transport_layer/socks_client.h b/src/transport_layer/socks_client.h new file mode 100644 index 00000000..b9f5ecb4 --- /dev/null +++ b/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 +#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 diff --git a/src/transport_layer/stcp_client.c b/src/transport_layer/stcp_client.c index 4a5edd03..2940a30d 100644 --- a/src/transport_layer/stcp_client.c +++ b/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; } diff --git a/src/transport_layer/stcp_client.h b/src/transport_layer/stcp_client.h index 7e7007b6..ef84638b 100644 --- a/src/transport_layer/stcp_client.h +++ b/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); diff --git a/src/transport_layer/stcp_link.c b/src/transport_layer/stcp_link.c index b8125f3b..d7f2ea5f 100644 --- a/src/transport_layer/stcp_link.c +++ b/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", diff --git a/tests/Makefile.am b/tests/Makefile.am index f60605cb..19ad976e 100644 --- a/tests/Makefile.am +++ b/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) diff --git a/tests/test_socks_client.c b/tests/test_socks_client.c new file mode 100644 index 00000000..456a6f48 --- /dev/null +++ b/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 +#include +#include + +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; +} diff --git a/tests/test_stcp.c b/tests/test_stcp.c index a53269a2..29ee71ed 100644 --- a/tests/test_stcp.c +++ b/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++; } diff --git a/tests/test_stcp_link.c b/tests/test_stcp_link.c index 171817e9..794048b0 100644 --- a/tests/test_stcp_link.c +++ b/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;