transport: SOCKS5-прокси для ETCP-линков (CONNECT + UDP ASSOCIATE + пинги)
This commit is contained in:
1 parent
7c8d65db78
commit
d0dfa97a47
14 files changed
+1360
-54
No files matched your search
@@ -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 \
|
||||
|
||||
+39
-3
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,493 @@
|
||||
// socks_client.c — SOCKS5 клиент: CONNECT (TCP) + UDP ASSOCIATE (UDP)
|
||||
#include "socks_client.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define SOCKS_CTRL_TIMEOUT_TB 100000 // 10 с в 0.1 мс (connect+greeting+auth+request)
|
||||
#define SOCKS_CTRL_RBUF_SIZE 1024
|
||||
#define SOCKS_UDP_RECONNECT_MIN_TB 10000 // 1 с в 0.1 мс
|
||||
#define SOCKS_UDP_RECONNECT_MAX_TB 300000 // 30 с
|
||||
#define SOCKS_UDP_MAX_PAYLOAD 2048
|
||||
|
||||
// ── Общий control-канал: TCP к прокси + greeting + auth ───────────────
|
||||
// Обе фазы (dial и udp) используют эту базу; после greeting+auth вызывается on_ready(c),
|
||||
// где каждая сторона шлёт свой запрос (CONNECT / UDP ASSOCIATE) и подменяет process().
|
||||
struct socks_ctrl {
|
||||
struct UASYNC *ua;
|
||||
struct socks_cfg cfg;
|
||||
socket_t sock;
|
||||
void *socket_id;
|
||||
void *timer;
|
||||
uint8_t have_auth;
|
||||
uint8_t done; // dial: guard против повторного finish
|
||||
uint8_t rbuf[SOCKS_CTRL_RBUF_SIZE];
|
||||
size_t rbuf_len;
|
||||
uint8_t sbuf[512];
|
||||
size_t sbuf_len, sbuf_off;
|
||||
void (*on_ready)(struct socks_ctrl *c); // greeting+auth OK → фаза 2
|
||||
void (*on_error)(struct socks_ctrl *c, int err); // любая фатальная ошибка
|
||||
void (*process)(struct socks_ctrl *c); // текущий обработчик входящих
|
||||
};
|
||||
|
||||
static void socks_ctrl_send(struct socks_ctrl *c, const uint8_t *data, size_t len);
|
||||
static void socks_ctrl_flush(struct socks_ctrl *c);
|
||||
static void socks_ctrl_process(struct socks_ctrl *c);
|
||||
static void socks_ctrl_auth_process(struct socks_ctrl *c);
|
||||
static void socks_ctrl_after_connect(struct socks_ctrl *c);
|
||||
static void socks_ctrl_read_cb(socket_t sock, void *arg);
|
||||
static void socks_ctrl_write_cb(socket_t sock, void *arg);
|
||||
static void socks_ctrl_connect_wait_cb(socket_t sock, void *arg);
|
||||
static void socks_ctrl_timeout_cb(void *arg);
|
||||
|
||||
static void socks_ctrl_flush(struct socks_ctrl *c) {
|
||||
while (c->sbuf_off < c->sbuf_len) {
|
||||
ssize_t sent = send(c->sock, c->sbuf + c->sbuf_off, c->sbuf_len - c->sbuf_off, 0);
|
||||
if (sent < 0) {
|
||||
int err = socket_get_error();
|
||||
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) { uasync_set_socket_write(c->ua, c->socket_id, 1); return; }
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: send failed err=%d", err);
|
||||
c->on_error(c, err);
|
||||
return;
|
||||
}
|
||||
c->sbuf_off += (size_t)sent;
|
||||
}
|
||||
c->sbuf_len = 0;
|
||||
c->sbuf_off = 0;
|
||||
uasync_set_socket_write(c->ua, c->socket_id, 0);
|
||||
}
|
||||
|
||||
static void socks_ctrl_send(struct socks_ctrl *c, const uint8_t *data, size_t len) {
|
||||
memcpy(c->sbuf, data, len);
|
||||
c->sbuf_len = len;
|
||||
c->sbuf_off = 0;
|
||||
socks_ctrl_flush(c);
|
||||
}
|
||||
|
||||
static void socks_ctrl_read_cb(socket_t sock, void *arg) {
|
||||
struct socks_ctrl *c = (struct socks_ctrl *)arg;
|
||||
(void)sock;
|
||||
if (c->rbuf_len >= sizeof(c->rbuf)) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: rbuf overflow"); c->on_error(c, ENOBUFS); return; }
|
||||
ssize_t n = recv(c->sock, c->rbuf + c->rbuf_len, sizeof(c->rbuf) - c->rbuf_len, 0);
|
||||
if (n < 0) {
|
||||
int err = socket_get_error();
|
||||
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: recv failed err=%d", err);
|
||||
c->on_error(c, err);
|
||||
return;
|
||||
}
|
||||
if (n == 0) { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks: control closed by proxy (EOF)"); c->on_error(c, ECONNRESET); return; }
|
||||
c->rbuf_len += (size_t)n;
|
||||
c->process(c);
|
||||
}
|
||||
|
||||
static void socks_ctrl_write_cb(socket_t sock, void *arg) {
|
||||
struct socks_ctrl *c = (struct socks_ctrl *)arg;
|
||||
(void)sock;
|
||||
socks_ctrl_flush(c);
|
||||
}
|
||||
|
||||
// Ответ на greeting: выбор метода (или auth).
|
||||
static void socks_ctrl_process(struct socks_ctrl *c) {
|
||||
if (c->rbuf_len < 2) return;
|
||||
uint8_t ver = c->rbuf[0], method = c->rbuf[1];
|
||||
memmove(c->rbuf, c->rbuf + 2, c->rbuf_len - 2); c->rbuf_len -= 2;
|
||||
if (ver != 0x05) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: bad greeting ver=%d", ver); c->on_error(c, EPROTO); return; }
|
||||
if (method == 0x00) { c->on_ready(c); return; }
|
||||
if (method == 0x02 && c->have_auth) {
|
||||
size_t ulen = strlen(c->cfg.user), plen = strlen(c->cfg.pass);
|
||||
if (ulen > 255 || plen > 255) { c->on_error(c, EINVAL); return; }
|
||||
uint8_t ab[2 + 255 + 1 + 255];
|
||||
size_t n = 0;
|
||||
ab[n++] = 0x01; ab[n++] = (uint8_t)ulen;
|
||||
memcpy(ab + n, c->cfg.user, ulen); n += ulen;
|
||||
ab[n++] = (uint8_t)plen;
|
||||
memcpy(ab + n, c->cfg.pass, plen); n += plen;
|
||||
c->process = socks_ctrl_auth_process;
|
||||
socks_ctrl_send(c, ab, n);
|
||||
return;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: no acceptable auth (offered=%d have_auth=%d)", method, c->have_auth);
|
||||
c->on_error(c, EACCES);
|
||||
}
|
||||
|
||||
// Ответ на auth (RFC1929).
|
||||
static void socks_ctrl_auth_process(struct socks_ctrl *c) {
|
||||
if (c->rbuf_len < 2) return;
|
||||
uint8_t ver = c->rbuf[0], status = c->rbuf[1];
|
||||
memmove(c->rbuf, c->rbuf + 2, c->rbuf_len - 2); c->rbuf_len -= 2;
|
||||
if (ver != 0x01 || status != 0x00) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: auth failed ver=%d status=%d", ver, status); c->on_error(c, EACCES); return; }
|
||||
c->on_ready(c);
|
||||
}
|
||||
|
||||
static void socks_ctrl_after_connect(struct socks_ctrl *c) {
|
||||
uint8_t g[4];
|
||||
size_t n;
|
||||
g[0] = 0x05;
|
||||
if (c->have_auth) { g[1] = 0x02; g[2] = 0x00; g[3] = 0x02; n = 4; }
|
||||
else { g[1] = 0x01; g[2] = 0x00; n = 3; }
|
||||
c->process = socks_ctrl_process;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "socks: connected to proxy %s:%u, greeting (auth=%d)", c->cfg.host, c->cfg.port, c->have_auth);
|
||||
socks_ctrl_send(c, g, n);
|
||||
}
|
||||
|
||||
static void socks_ctrl_connect_wait_cb(socket_t sock, void *arg) {
|
||||
struct socks_ctrl *c = (struct socks_ctrl *)arg;
|
||||
int err = 0;
|
||||
socklen_t el = sizeof(err);
|
||||
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &el) < 0 || err != 0) {
|
||||
int e = err ? err : ECONNREFUSED;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: connect to %s:%u failed err=%d", c->cfg.host, c->cfg.port, e);
|
||||
c->on_error(c, e);
|
||||
return;
|
||||
}
|
||||
uasync_remove_socket_t(c->ua, sock);
|
||||
c->socket_id = uasync_add_socket_t(c->ua, sock, socks_ctrl_read_cb, socks_ctrl_write_cb, NULL, c);
|
||||
if (!c->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: re-add socket failed"); c->on_error(c, ENOMEM); return; }
|
||||
socks_ctrl_after_connect(c);
|
||||
}
|
||||
|
||||
static void socks_ctrl_timeout_cb(void *arg) {
|
||||
struct socks_ctrl *c = (struct socks_ctrl *)arg;
|
||||
c->timer = NULL;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks: connect/handshake timeout to %s:%u", c->cfg.host, c->cfg.port);
|
||||
c->on_error(c, ETIMEDOUT);
|
||||
}
|
||||
|
||||
// Подключить control-канал к прокси (resolve + socket + connect + таймер).
|
||||
// При немедленной ошибке возвращает -1 (сокет закрыт), cb не вызывается.
|
||||
static int socks_ctrl_start(struct socks_ctrl *c, int timeout_tb) {
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%u", c->cfg.port);
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
struct addrinfo *res = NULL;
|
||||
if (getaddrinfo(c->cfg.host, port_str, &hints, &res) != 0 || !res) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: getaddrinfo(%s) failed", c->cfg.host);
|
||||
return -1;
|
||||
}
|
||||
struct sockaddr_storage pa;
|
||||
socklen_t palen = (socklen_t)res->ai_addrlen;
|
||||
memcpy(&pa, res->ai_addr, res->ai_addrlen);
|
||||
freeaddrinfo(res);
|
||||
|
||||
c->sock = socket(pa.ss_family, SOCK_STREAM, 0);
|
||||
if (c->sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: socket() failed err=%d", socket_get_error()); return -1; }
|
||||
socket_set_nonblocking(c->sock);
|
||||
|
||||
int cr = connect(c->sock, (struct sockaddr *)&pa, palen);
|
||||
if (cr < 0) {
|
||||
int err = socket_get_error();
|
||||
if (err != EINPROGRESS && err != ERR_WOULDBLOCK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: connect to %s:%u failed err=%d", c->cfg.host, c->cfg.port, err);
|
||||
socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID;
|
||||
return -1;
|
||||
}
|
||||
c->socket_id = uasync_add_socket_t(c->ua, c->sock, NULL, socks_ctrl_connect_wait_cb, NULL, c);
|
||||
} else {
|
||||
c->socket_id = uasync_add_socket_t(c->ua, c->sock, socks_ctrl_read_cb, socks_ctrl_write_cb, NULL, c);
|
||||
if (c->socket_id) socks_ctrl_after_connect(c);
|
||||
}
|
||||
if (!c->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks: add_socket failed"); socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID; return -1; }
|
||||
c->timer = uasync_set_timeout(c->ua, timeout_tb > 0 ? timeout_tb : SOCKS_CTRL_TIMEOUT_TB, c, socks_ctrl_timeout_cb, "socks_ctrl");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── SOCKS5 CONNECT (dial) ─────────────────────────────────────────────
|
||||
|
||||
struct socks_dial {
|
||||
struct socks_ctrl ctrl; // первый член — каст (struct socks_ctrl*)==&ctrl
|
||||
char target_host[256];
|
||||
uint16_t target_port;
|
||||
socks_dial_cb cb;
|
||||
void *arg;
|
||||
};
|
||||
|
||||
static void dial_on_error(struct socks_ctrl *c, int err);
|
||||
static void dial_reply_process(struct socks_ctrl *c);
|
||||
|
||||
static void dial_finish(struct socks_dial *d, int err) {
|
||||
struct socks_ctrl *c = &d->ctrl;
|
||||
if (c->done) return;
|
||||
c->done = 1;
|
||||
socket_t out = c->sock;
|
||||
if (c->timer) { uasync_cancel_timeout(c->ua, c->timer); c->timer = NULL; }
|
||||
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; }
|
||||
socks_dial_cb cb = d->cb;
|
||||
void *arg = d->arg;
|
||||
if (err == 0) { c->sock = SOCKET_INVALID; } // сокет переходит вызывающему
|
||||
else if (out != SOCKET_INVALID) { socket_close_wrapper(out); }
|
||||
u_free(d);
|
||||
cb(err == 0 ? out : SOCKET_INVALID, err, arg);
|
||||
}
|
||||
|
||||
static void dial_on_ready(struct socks_ctrl *c) {
|
||||
struct socks_dial *d = (struct socks_dial *)c;
|
||||
uint8_t buf[4 + 1 + 255 + 2];
|
||||
size_t n = 0;
|
||||
buf[n++] = 0x05; buf[n++] = 0x01; buf[n++] = 0x00; // CONNECT
|
||||
struct in_addr a4;
|
||||
struct in6_addr a6;
|
||||
if (inet_pton(AF_INET, d->target_host, &a4) == 1) {
|
||||
buf[n++] = 0x01; memcpy(buf + n, &a4, 4); n += 4;
|
||||
} else if (inet_pton(AF_INET6, d->target_host, &a6) == 1) {
|
||||
buf[n++] = 0x04; memcpy(buf + n, &a6, 16); n += 16;
|
||||
} else {
|
||||
size_t hl = strlen(d->target_host);
|
||||
if (hl > 255) { c->on_error(c, EINVAL); return; }
|
||||
buf[n++] = 0x03; buf[n++] = (uint8_t)hl; memcpy(buf + n, d->target_host, hl); n += hl;
|
||||
}
|
||||
uint16_t pb = htons(d->target_port);
|
||||
memcpy(buf + n, &pb, 2); n += 2;
|
||||
c->process = dial_reply_process;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "socks_dial: CONNECT %s:%u", d->target_host, d->target_port);
|
||||
socks_ctrl_send(c, buf, n);
|
||||
}
|
||||
|
||||
static void dial_reply_process(struct socks_ctrl *c) {
|
||||
if (c->rbuf_len < 4) return;
|
||||
uint8_t ver = c->rbuf[0], rep = c->rbuf[1], atyp = c->rbuf[3];
|
||||
size_t need = 4;
|
||||
if (atyp == 0x01) need += 4 + 2;
|
||||
else if (atyp == 0x04) need += 16 + 2;
|
||||
else if (atyp == 0x03) { if (c->rbuf_len < 5) return; need += 1 + (size_t)c->rbuf[4] + 2; }
|
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_dial: bad reply atyp=%d", atyp); c->on_error(c, EPROTO); return; }
|
||||
if (c->rbuf_len < need) return;
|
||||
if (ver != 0x05) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_dial: bad reply ver=%d", ver); c->on_error(c, EPROTO); return; }
|
||||
if (rep != 0x00) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_dial: CONNECT rejected rep=%d", rep); c->on_error(c, ECONNREFUSED); return; }
|
||||
struct socks_dial *d = (struct socks_dial *)c;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_dial: CONNECT %s:%u established via %s:%u",
|
||||
d->target_host, d->target_port, c->cfg.host, c->cfg.port);
|
||||
dial_finish(d, 0);
|
||||
}
|
||||
|
||||
static void dial_on_error(struct socks_ctrl *c, int err) {
|
||||
dial_finish((struct socks_dial *)c, err);
|
||||
}
|
||||
|
||||
void *socks_dial(struct UASYNC *ua, const struct socks_cfg *cfg,
|
||||
const char *target_host, uint16_t target_port,
|
||||
socks_dial_cb cb, void *arg, int timeout_tb) {
|
||||
if (!ua || !cfg || !cfg->host[0] || !cfg->port || !target_host || !target_host[0] || !cb)
|
||||
return NULL;
|
||||
struct socks_dial *d = u_calloc(1, sizeof(*d));
|
||||
if (!d) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_dial: alloc failed"); return NULL; }
|
||||
d->ctrl.ua = ua;
|
||||
d->ctrl.cfg = *cfg;
|
||||
d->ctrl.have_auth = cfg->user[0] != '\0';
|
||||
d->ctrl.sock = SOCKET_INVALID;
|
||||
d->ctrl.on_ready = dial_on_ready;
|
||||
d->ctrl.on_error = dial_on_error;
|
||||
d->ctrl.process = socks_ctrl_process;
|
||||
strncpy(d->target_host, target_host, sizeof(d->target_host) - 1);
|
||||
d->target_port = target_port;
|
||||
d->cb = cb;
|
||||
d->arg = arg;
|
||||
if (socks_ctrl_start(&d->ctrl, timeout_tb) < 0) { u_free(d); return NULL; }
|
||||
return d;
|
||||
}
|
||||
|
||||
void socks_dial_cancel(void *h) {
|
||||
if (!h) return;
|
||||
struct socks_dial *d = (struct socks_dial *)h;
|
||||
struct socks_ctrl *c = &d->ctrl;
|
||||
if (c->done) return;
|
||||
c->done = 1;
|
||||
if (c->timer) { uasync_cancel_timeout(c->ua, c->timer); c->timer = NULL; }
|
||||
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; }
|
||||
if (c->sock != SOCKET_INVALID) socket_close_wrapper(c->sock);
|
||||
u_free(d);
|
||||
}
|
||||
|
||||
// ── SOCKS5 UDP ASSOCIATE ──────────────────────────────────────────────
|
||||
|
||||
struct socks_udp {
|
||||
struct socks_ctrl ctrl; // первый член
|
||||
socket_t local_udp_fd;
|
||||
struct sockaddr_storage relay;
|
||||
uint8_t ready;
|
||||
uint8_t closing;
|
||||
void *reconnect_timer;
|
||||
uint32_t reconnect_delay_tb;
|
||||
socks_udp_ready_cb ready_cb;
|
||||
void *ready_arg;
|
||||
};
|
||||
|
||||
static void udp_on_error(struct socks_ctrl *c, int err);
|
||||
static void udp_reply_process(struct socks_ctrl *c);
|
||||
static void udp_idle_process(struct socks_ctrl *c);
|
||||
static void udp_reconnect_cb(void *arg);
|
||||
|
||||
static void udp_schedule_reconnect(struct socks_udp *s) {
|
||||
if (s->closing) return;
|
||||
s->ready = 0;
|
||||
if (s->ctrl.socket_id) { uasync_remove_socket_t(s->ctrl.ua, s->ctrl.sock); s->ctrl.socket_id = NULL; }
|
||||
if (s->ctrl.timer) { uasync_cancel_timeout(s->ctrl.ua, s->ctrl.timer); s->ctrl.timer = NULL; }
|
||||
if (s->ctrl.sock != SOCKET_INVALID) { socket_close_wrapper(s->ctrl.sock); s->ctrl.sock = SOCKET_INVALID; }
|
||||
s->ctrl.done = 0;
|
||||
s->ctrl.rbuf_len = 0;
|
||||
s->ctrl.sbuf_len = 0;
|
||||
s->ctrl.sbuf_off = 0;
|
||||
s->ctrl.process = socks_ctrl_process;
|
||||
if (s->reconnect_delay_tb == 0) s->reconnect_delay_tb = SOCKS_UDP_RECONNECT_MIN_TB;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_udp: reconnect in %u ms", s->reconnect_delay_tb / 10);
|
||||
s->reconnect_timer = uasync_set_timeout(s->ctrl.ua, (int)s->reconnect_delay_tb, s, udp_reconnect_cb, "socks_udp_rc");
|
||||
s->reconnect_delay_tb *= 2;
|
||||
if (s->reconnect_delay_tb > SOCKS_UDP_RECONNECT_MAX_TB) s->reconnect_delay_tb = SOCKS_UDP_RECONNECT_MAX_TB;
|
||||
}
|
||||
|
||||
static void udp_reconnect_cb(void *arg) {
|
||||
struct socks_udp *s = (struct socks_udp *)arg;
|
||||
s->reconnect_timer = NULL;
|
||||
if (s->closing) return;
|
||||
if (socks_ctrl_start(&s->ctrl, 0) < 0) udp_schedule_reconnect(s);
|
||||
}
|
||||
|
||||
static void udp_on_ready(struct socks_ctrl *c) {
|
||||
uint8_t buf[10];
|
||||
buf[0] = 0x05; buf[1] = 0x03; buf[2] = 0x00; // UDP ASSOCIATE
|
||||
buf[3] = 0x01; // ATYP=IPv4, DST=0.0.0.0:0
|
||||
memset(buf + 4, 0, 6);
|
||||
c->process = udp_reply_process;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "socks_udp: sending UDP ASSOCIATE");
|
||||
socks_ctrl_send(c, buf, 10);
|
||||
}
|
||||
|
||||
static void udp_reply_process(struct socks_ctrl *c) {
|
||||
struct socks_udp *s = (struct socks_udp *)c;
|
||||
if (c->rbuf_len < 4) return;
|
||||
uint8_t ver = c->rbuf[0], rep = c->rbuf[1], atyp = c->rbuf[3];
|
||||
size_t need = 4;
|
||||
if (atyp == 0x01) need += 4 + 2;
|
||||
else if (atyp == 0x04) need += 16 + 2;
|
||||
else if (atyp == 0x03) { if (c->rbuf_len < 5) return; need += 1 + (size_t)c->rbuf[4] + 2; }
|
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: bad reply atyp=%d", atyp); c->on_error(c, EPROTO); return; }
|
||||
if (c->rbuf_len < need) return;
|
||||
if (ver != 0x05) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: bad reply ver=%d", ver); c->on_error(c, EPROTO); return; }
|
||||
if (rep != 0x00) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: ASSOCIATE rejected rep=%d", rep); c->on_error(c, ECONNREFUSED); return; }
|
||||
|
||||
memset(&s->relay, 0, sizeof(s->relay));
|
||||
if (atyp == 0x01) {
|
||||
struct sockaddr_in *sa = (struct sockaddr_in *)&s->relay;
|
||||
sa->sin_family = AF_INET;
|
||||
memcpy(&sa->sin_addr.s_addr, c->rbuf + 4, 4);
|
||||
memcpy(&sa->sin_port, c->rbuf + 8, 2);
|
||||
} else if (atyp == 0x04) {
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&s->relay;
|
||||
sa6->sin6_family = AF_INET6;
|
||||
memcpy(&sa6->sin6_addr, c->rbuf + 4, 16);
|
||||
memcpy(&sa6->sin6_port, c->rbuf + 20, 2);
|
||||
} else { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: unsupported relay family"); c->on_error(c, EAFNOSUPPORT); return; }
|
||||
|
||||
memmove(c->rbuf, c->rbuf + need, c->rbuf_len - need); c->rbuf_len -= need;
|
||||
s->ready = 1;
|
||||
s->reconnect_delay_tb = 0;
|
||||
if (c->timer) { uasync_cancel_timeout(c->ua, c->timer); c->timer = NULL; }
|
||||
c->process = udp_idle_process;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_udp: ASSOCIATE ready, relay=%s", sockaddr_storage_to_str(&s->relay).str);
|
||||
if (s->ready_cb) s->ready_cb(s, 0, s->ready_arg);
|
||||
}
|
||||
|
||||
static void udp_idle_process(struct socks_ctrl *c) {
|
||||
c->rbuf_len = 0; // неожиданные данные на control-канале после READY — игнорируем
|
||||
}
|
||||
|
||||
static void udp_on_error(struct socks_ctrl *c, int err) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_udp: control error err=%d → reconnect", err);
|
||||
udp_schedule_reconnect((struct socks_udp *)c);
|
||||
}
|
||||
|
||||
struct socks_udp *socks_udp_associate(struct UASYNC *ua, const struct socks_cfg *cfg,
|
||||
socket_t local_udp_fd, socks_udp_ready_cb cb, void *arg) {
|
||||
if (!ua || !cfg || !cfg->host[0] || !cfg->port || local_udp_fd == SOCKET_INVALID) return NULL;
|
||||
struct socks_udp *s = u_calloc(1, sizeof(*s));
|
||||
if (!s) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: alloc failed"); return NULL; }
|
||||
s->ctrl.ua = ua;
|
||||
s->ctrl.cfg = *cfg;
|
||||
s->ctrl.have_auth = cfg->user[0] != '\0';
|
||||
s->ctrl.sock = SOCKET_INVALID;
|
||||
s->ctrl.on_ready = udp_on_ready;
|
||||
s->ctrl.on_error = udp_on_error;
|
||||
s->ctrl.process = socks_ctrl_process;
|
||||
s->local_udp_fd = local_udp_fd;
|
||||
s->ready_cb = cb;
|
||||
s->ready_arg = arg;
|
||||
if (socks_ctrl_start(&s->ctrl, 0) < 0) udp_schedule_reconnect(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
void socks_udp_destroy(struct socks_udp *s) {
|
||||
if (!s) return;
|
||||
s->closing = 1;
|
||||
if (s->reconnect_timer) { uasync_cancel_timeout(s->ctrl.ua, s->reconnect_timer); s->reconnect_timer = NULL; }
|
||||
if (s->ctrl.timer) { uasync_cancel_timeout(s->ctrl.ua, s->ctrl.timer); s->ctrl.timer = NULL; }
|
||||
if (s->ctrl.socket_id) { uasync_remove_socket_t(s->ctrl.ua, s->ctrl.sock); s->ctrl.socket_id = NULL; }
|
||||
if (s->ctrl.sock != SOCKET_INVALID) socket_close_wrapper(s->ctrl.sock);
|
||||
u_free(s);
|
||||
}
|
||||
|
||||
ssize_t socks_udp_sendto(struct socks_udp *s, const uint8_t *data, size_t len,
|
||||
const struct sockaddr_storage *dst) {
|
||||
if (!s || !data || !dst || !s->ready) return -1;
|
||||
if (dst->ss_family != AF_INET && dst->ss_family != AF_INET6) return -1;
|
||||
uint8_t buf[SOCKS_UDP_MAX_PAYLOAD + 32];
|
||||
size_t off = 0;
|
||||
buf[off++] = 0; buf[off++] = 0; // RSV
|
||||
buf[off++] = 0; // FRAG
|
||||
if (dst->ss_family == AF_INET) {
|
||||
const struct sockaddr_in *sa = (const struct sockaddr_in *)dst;
|
||||
buf[off++] = 0x01;
|
||||
memcpy(buf + off, &sa->sin_addr.s_addr, 4); off += 4;
|
||||
memcpy(buf + off, &sa->sin_port, 2); off += 2;
|
||||
} else {
|
||||
const struct sockaddr_in6 *sa6 = (const struct sockaddr_in6 *)dst;
|
||||
buf[off++] = 0x04;
|
||||
memcpy(buf + off, &sa6->sin6_addr, 16); off += 16;
|
||||
memcpy(buf + off, &sa6->sin6_port, 2); off += 2;
|
||||
}
|
||||
if (off + len > sizeof(buf)) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: payload too large %zu", len); return -1; }
|
||||
memcpy(buf + off, data, len); off += len;
|
||||
socklen_t rl = (s->relay.ss_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
|
||||
ssize_t sent = socket_sendto(s->local_udp_fd, buf, off, (struct sockaddr *)&s->relay, rl);
|
||||
if (sent < 0) DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_udp: sendto relay failed err=%d", socket_get_error());
|
||||
return sent;
|
||||
}
|
||||
|
||||
ssize_t socks_udp_unwrap(const struct socks_udp *s, const uint8_t *raw, size_t raw_len,
|
||||
uint8_t *payload, size_t cap, struct sockaddr_storage *src) {
|
||||
(void)s;
|
||||
if (!raw || raw_len < 10 || !src) return -1;
|
||||
if (raw[0] != 0 || raw[1] != 0 || raw[2] != 0) { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_udp: bad RSV/FRAG"); return -1; }
|
||||
uint8_t atyp = raw[3];
|
||||
size_t hlen;
|
||||
if (atyp == 0x01) hlen = 4 + 4 + 2;
|
||||
else if (atyp == 0x04) hlen = 4 + 16 + 2;
|
||||
else if (atyp == 0x03) { if (raw_len < 5) return -1; hlen = 4 + 1 + (size_t)raw[4] + 2; }
|
||||
else { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "socks_udp: bad atyp=%d", atyp); return -1; }
|
||||
if (raw_len < hlen) return -1;
|
||||
|
||||
memset(src, 0, sizeof(*src));
|
||||
if (atyp == 0x01) {
|
||||
struct sockaddr_in *sa = (struct sockaddr_in *)src;
|
||||
sa->sin_family = AF_INET;
|
||||
memcpy(&sa->sin_addr.s_addr, raw + 4, 4);
|
||||
memcpy(&sa->sin_port, raw + 8, 2);
|
||||
} else if (atyp == 0x04) {
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)src;
|
||||
sa6->sin6_family = AF_INET6;
|
||||
memcpy(&sa6->sin6_addr, raw + 4, 16);
|
||||
memcpy(&sa6->sin6_port, raw + 20, 2);
|
||||
} else {
|
||||
return -1; // domain — для ETCP-пиров не встречается
|
||||
}
|
||||
size_t plen = raw_len - hlen;
|
||||
if (plen > cap) return -1;
|
||||
if (plen > 0) memcpy(payload, raw + hlen, plen);
|
||||
return (ssize_t)plen;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// socks_client.h — SOCKS5 клиент (для исходящих ETCP-линков через SOCKS-прокси)
|
||||
//
|
||||
// Модуль позволяет подключаться к пиру не напрямую, а через SOCKS5-прокси:
|
||||
// - SOCKS5 CONNECT (TCP/STCP-линки) — socks_dial();
|
||||
// - SOCKS5 UDP ASSOCIATE (UDP-линки) — socks_udp_associate() и далее.
|
||||
//
|
||||
// Модуль не знает про ETCP — только транспорт.
|
||||
#ifndef SOCKS_CLIENT_H
|
||||
#define SOCKS_CLIENT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
|
||||
struct UASYNC;
|
||||
|
||||
// Конфигурация прокси. host — ip или домен; user/pass пустые = no-auth (метод 0x00).
|
||||
struct socks_cfg {
|
||||
char host[64];
|
||||
uint16_t port;
|
||||
char user[64];
|
||||
char pass[64];
|
||||
};
|
||||
|
||||
// ── SOCKS5 CONNECT (TCP) ──────────────────────────────────────────────
|
||||
|
||||
// Коллбэк завершения подключения:
|
||||
// sock — готовый сокет (на нём можно делать STCP-хендшейк), err == 0;
|
||||
// sock == SOCKET_INVALID при ошибке, err — код ошибки (errno).
|
||||
typedef void (*socks_dial_cb)(socket_t sock, int err, void *arg);
|
||||
|
||||
// Асинхронно подключиться к прокси и выполнить SOCKS5 CONNECT к target_host:target_port.
|
||||
// target_host может быть числовым IP или доменом (домен резолвит сам прокси).
|
||||
// Возвращает handle (NULL при немедленной ошибке — тогда cb НЕ вызывается, ошибка залогирована).
|
||||
void *socks_dial(struct UASYNC *ua, const struct socks_cfg *cfg,
|
||||
const char *target_host, uint16_t target_port,
|
||||
socks_dial_cb cb, void *arg, int timeout_tb);
|
||||
|
||||
// Отменить незавершённый dial: закрывает сокет, освобождает состояние, cb НЕ вызывается.
|
||||
void socks_dial_cancel(void *h);
|
||||
|
||||
// ── SOCKS5 UDP ASSOCIATE (UDP) ────────────────────────────────────────
|
||||
|
||||
struct socks_udp;
|
||||
|
||||
// Коллбэк готовности ассоциации: err==0 → relay получен, можно слать через socks_udp_sendto.
|
||||
typedef void (*socks_udp_ready_cb)(struct socks_udp *s, int err, void *arg);
|
||||
|
||||
// Открыть UDP ASSOCIATE к прокси (persistent TCP-control + relay). local_udp_fd — уже
|
||||
// забинденный локальный UDP-сокет (reserved port), через него шлём на relay.
|
||||
// При обрыве control-канала сессия автоматически пересоздаёт ассоциацию (backoff).
|
||||
// Возвращает сессию (NULL только при ошибке аллокации/аргументов).
|
||||
struct socks_udp *socks_udp_associate(struct UASYNC *ua, const struct socks_cfg *cfg,
|
||||
socket_t local_udp_fd, socks_udp_ready_cb cb, void *arg);
|
||||
|
||||
// Отправить датаграмму пиру dst: обёртка в SOCKS-UDP заголовок + sendto(relay).
|
||||
// -1 пока ассоциация не готова (или ошибка).
|
||||
ssize_t socks_udp_sendto(struct socks_udp *s, const uint8_t *data, size_t len,
|
||||
const struct sockaddr_storage *dst);
|
||||
|
||||
// Снять SOCKS-UDP заголовок с входящей датаграммы: вернуть payload и реальный src пира.
|
||||
// Возвращает длину payload (<=0 при битом/неподходящем пакете).
|
||||
ssize_t socks_udp_unwrap(const struct socks_udp *s, const uint8_t *raw, size_t raw_len,
|
||||
uint8_t *payload, size_t cap, struct sockaddr_storage *src);
|
||||
|
||||
// Закрыть сессию: control-TCP, таймеры, память. Локальный UDP-сокет НЕ закрывается (чужой).
|
||||
void socks_udp_destroy(struct socks_udp *s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // SOCKS_CLIENT_H
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user