|
|
|
@ -9,6 +9,7 @@ |
|
|
|
#include "../lib/mem.h" |
|
|
|
#include "../lib/mem.h" |
|
|
|
#include "../lib/debug_config.h" |
|
|
|
#include "../lib/debug_config.h" |
|
|
|
#include "../lib/platform_compat.h" |
|
|
|
#include "../lib/platform_compat.h" |
|
|
|
|
|
|
|
#include "../lib/async_dns.h" |
|
|
|
#include <string.h> |
|
|
|
#include <string.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <errno.h> |
|
|
|
#include <errno.h> |
|
|
|
@ -32,6 +33,10 @@ struct reality_relay { |
|
|
|
void *dest_sid; |
|
|
|
void *dest_sid; |
|
|
|
int connecting; // 1 = ждём завершения connect() к dest
|
|
|
|
int connecting; // 1 = ждём завершения connect() к dest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char host[256]; // dest hostname (для async DNS и логирования)
|
|
|
|
|
|
|
|
uint16_t port; // dest port
|
|
|
|
|
|
|
|
struct adns_query *dns_q; // pending async DNS query (NULL если нет)
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t *c2d; size_t c2d_len, c2d_off; // pending client→dest
|
|
|
|
uint8_t *c2d; size_t c2d_len, c2d_off; // pending client→dest
|
|
|
|
uint8_t *d2c; size_t d2c_len, d2c_off; // pending dest→client
|
|
|
|
uint8_t *d2c; size_t d2c_len, d2c_off; // pending dest→client
|
|
|
|
|
|
|
|
|
|
|
|
@ -57,6 +62,7 @@ static void relay_free_cb(void *arg) { |
|
|
|
static void relay_free(struct reality_relay *r) { |
|
|
|
static void relay_free(struct reality_relay *r) { |
|
|
|
if (!r || r->closed) return; |
|
|
|
if (!r || r->closed) return; |
|
|
|
r->closed = 1; |
|
|
|
r->closed = 1; |
|
|
|
|
|
|
|
if (r->dns_q) { adns_cancel(r->dns_q); r->dns_q = NULL; } |
|
|
|
if (r->client_sid) { uasync_remove_socket_t(r->ua, r->client_sock); r->client_sid = NULL; } |
|
|
|
if (r->client_sid) { uasync_remove_socket_t(r->ua, r->client_sock); r->client_sid = NULL; } |
|
|
|
if (r->dest_sid) { uasync_remove_socket_t(r->ua, r->dest_sock); r->dest_sid = NULL; } |
|
|
|
if (r->dest_sid) { uasync_remove_socket_t(r->ua, r->dest_sock); r->dest_sid = NULL; } |
|
|
|
if (r->client_sock != SOCKET_INVALID) { socket_close_wrapper(r->client_sock); r->client_sock = SOCKET_INVALID; } |
|
|
|
if (r->client_sock != SOCKET_INVALID) { socket_close_wrapper(r->client_sock); r->client_sock = SOCKET_INVALID; } |
|
|
|
@ -182,6 +188,56 @@ static void relay_dest_write_cb(socket_t sock, void *arg) { |
|
|
|
relay_flush_c2d(r); |
|
|
|
relay_flush_c2d(r); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Создать неблокирующий dest-сокет и начать connect() к уже разрезолвенному адресу.
|
|
|
|
|
|
|
|
// Вызывается сразу (literal IP) или из коллбэка async DNS.
|
|
|
|
|
|
|
|
static void relay_connect_dest(struct reality_relay *r, const struct sockaddr_in *addr) { |
|
|
|
|
|
|
|
socket_t dest_sock = socket(AF_INET, SOCK_STREAM, 0); |
|
|
|
|
|
|
|
if (dest_sock == SOCKET_INVALID) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay: socket failed for %s:%u", r->host, r->port); |
|
|
|
|
|
|
|
relay_free(r); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
socket_set_nonblocking(dest_sock); |
|
|
|
|
|
|
|
r->dest_sock = dest_sock; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int cr = connect(dest_sock, (const struct sockaddr *)addr, sizeof(*addr)); |
|
|
|
|
|
|
|
if (cr < 0) { |
|
|
|
|
|
|
|
int err = socket_get_error(); |
|
|
|
|
|
|
|
if (err != EINPROGRESS && err != ERR_WOULDBLOCK) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay connect %s:%u failed err=%d", r->host, r->port, err); |
|
|
|
|
|
|
|
relay_free(r); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
r->connecting = 0; |
|
|
|
|
|
|
|
int opt = 1; setsockopt(dest_sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
r->dest_sid = uasync_add_socket_t(r->ua, dest_sock, relay_dest_read_cb, relay_dest_write_cb, NULL, "reality_dest", r); |
|
|
|
|
|
|
|
if (!r->dest_sid) { relay_free(r); return; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!r->connecting) { |
|
|
|
|
|
|
|
if (r->c2d) relay_flush_c2d(r); |
|
|
|
|
|
|
|
else uasync_set_socket_read(r->ua, r->client_sid, 1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void relay_dns_done_cb(const struct adns_result *res, void *arg) { |
|
|
|
|
|
|
|
struct reality_relay *r = (struct reality_relay *)arg; |
|
|
|
|
|
|
|
r->dns_q = NULL; |
|
|
|
|
|
|
|
if (res->status == ADNS_OK && res->count > 0) { |
|
|
|
|
|
|
|
struct sockaddr_in sin = res->addrs[0]; |
|
|
|
|
|
|
|
sin.sin_port = htons(r->port); |
|
|
|
|
|
|
|
const uint8_t *b = (const uint8_t *)&sin.sin_addr; |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "reality_relay: resolved %s -> %u.%u.%u.%u:%u", |
|
|
|
|
|
|
|
r->host, b[0], b[1], b[2], b[3], r->port); |
|
|
|
|
|
|
|
relay_connect_dest(r, &sin); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay: DNS failed for %s status=%d", r->host, res->status); |
|
|
|
|
|
|
|
relay_free(r); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int reality_relay_start(struct UASYNC *ua, socket_t client_sock, |
|
|
|
int reality_relay_start(struct UASYNC *ua, socket_t client_sock, |
|
|
|
const char *dest, |
|
|
|
const char *dest, |
|
|
|
const uint8_t *initial_data, size_t initial_len) { |
|
|
|
const uint8_t *initial_data, size_t initial_len) { |
|
|
|
@ -190,74 +246,59 @@ int reality_relay_start(struct UASYNC *ua, socket_t client_sock, |
|
|
|
return -1; |
|
|
|
return -1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// парсим "host:port"
|
|
|
|
|
|
|
|
char host[256]; |
|
|
|
|
|
|
|
uint16_t port = 443; |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
size_t dl = strlen(dest); |
|
|
|
|
|
|
|
if (dl >= sizeof(host)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: dest too long"); goto fail; } |
|
|
|
|
|
|
|
memcpy(host, dest, dl + 1); |
|
|
|
|
|
|
|
char *colon = strrchr(host, ':'); |
|
|
|
|
|
|
|
if (colon) { *colon = '\0'; port = (uint16_t)atoi(colon + 1); } |
|
|
|
|
|
|
|
if (!host[0] || port == 0) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: bad dest '%s'", dest); goto fail; } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct addrinfo hints = {0}; |
|
|
|
|
|
|
|
hints.ai_family = AF_UNSPEC; |
|
|
|
|
|
|
|
hints.ai_socktype = SOCK_STREAM; |
|
|
|
|
|
|
|
char port_str[16]; |
|
|
|
|
|
|
|
snprintf(port_str, sizeof(port_str), "%u", port); |
|
|
|
|
|
|
|
struct addrinfo *res; |
|
|
|
|
|
|
|
if (getaddrinfo(host, port_str, &hints, &res) != 0) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay: getaddrinfo %s failed", host); |
|
|
|
|
|
|
|
goto fail; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
socket_t dest_sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); |
|
|
|
|
|
|
|
if (dest_sock == SOCKET_INVALID) { freeaddrinfo(res); DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay: socket failed"); goto fail; } |
|
|
|
|
|
|
|
socket_set_nonblocking(dest_sock); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct reality_relay *r = u_calloc(1, sizeof(struct reality_relay)); |
|
|
|
struct reality_relay *r = u_calloc(1, sizeof(struct reality_relay)); |
|
|
|
if (!r) { socket_close_wrapper(dest_sock); freeaddrinfo(res); DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay: calloc failed"); goto fail; } |
|
|
|
if (!r) { socket_close_wrapper(client_sock); DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: calloc failed"); return -1; } |
|
|
|
r->ua = ua; |
|
|
|
r->ua = ua; |
|
|
|
r->client_sock = client_sock; |
|
|
|
r->client_sock = client_sock; |
|
|
|
r->dest_sock = dest_sock; |
|
|
|
r->dest_sock = SOCKET_INVALID; |
|
|
|
r->connecting = 1; |
|
|
|
r->connecting = 1; |
|
|
|
|
|
|
|
|
|
|
|
// берём владение client_sock: снимаем прежнюю регистрацию и ставим свою
|
|
|
|
// берём владение client_sock сразу (снимаем старую регистрацию) — до парсинга,
|
|
|
|
|
|
|
|
// чтобы любая ошибка ниже не оставила сокет зарегистрированным в uasync.
|
|
|
|
uasync_remove_socket_t(ua, client_sock); |
|
|
|
uasync_remove_socket_t(ua, client_sock); |
|
|
|
r->client_sid = uasync_add_socket_t(ua, client_sock, relay_client_read_cb, relay_client_write_cb, NULL, "reality_client", r); |
|
|
|
r->client_sid = uasync_add_socket_t(ua, client_sock, relay_client_read_cb, relay_client_write_cb, NULL, "reality_client", r); |
|
|
|
if (!r->client_sid) { relay_free(r); freeaddrinfo(res); return -1; } |
|
|
|
if (!r->client_sid) { relay_free(r); return -1; } |
|
|
|
|
|
|
|
// не читаем от клиента, пока dest не подключён (иначе буферили бы без границ во время DNS)
|
|
|
|
|
|
|
|
uasync_set_socket_read(ua, r->client_sid, 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// парсим "host:port"
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
size_t dl = strlen(dest); |
|
|
|
|
|
|
|
if (dl >= sizeof(r->host)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: dest too long"); relay_free(r); return -1; } |
|
|
|
|
|
|
|
memcpy(r->host, dest, dl + 1); |
|
|
|
|
|
|
|
char *colon = strrchr(r->host, ':'); |
|
|
|
|
|
|
|
if (colon) { *colon = '\0'; r->port = (uint16_t)atoi(colon + 1); } |
|
|
|
|
|
|
|
else r->port = 443; |
|
|
|
|
|
|
|
if (!r->host[0] || r->port == 0) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_relay_start: bad dest '%s'", dest); relay_free(r); return -1; } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (initial_len && initial_data) { |
|
|
|
if (initial_len && initial_data) { |
|
|
|
r->c2d = u_malloc(initial_len); |
|
|
|
r->c2d = u_malloc(initial_len); |
|
|
|
if (!r->c2d) { relay_free(r); freeaddrinfo(res); return -1; } |
|
|
|
if (!r->c2d) { relay_free(r); return -1; } |
|
|
|
memcpy(r->c2d, initial_data, initial_len); |
|
|
|
memcpy(r->c2d, initial_data, initial_len); |
|
|
|
r->c2d_len = initial_len; |
|
|
|
r->c2d_len = initial_len; |
|
|
|
r->c2d_off = 0; |
|
|
|
r->c2d_off = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int cr = connect(dest_sock, res->ai_addr, res->ai_addrlen); |
|
|
|
// literal IPv4 — без DNS
|
|
|
|
freeaddrinfo(res); |
|
|
|
struct sockaddr_in sin; |
|
|
|
if (cr < 0) { |
|
|
|
memset(&sin, 0, sizeof(sin)); |
|
|
|
int err = socket_get_error(); |
|
|
|
sin.sin_family = AF_INET; |
|
|
|
if (err != EINPROGRESS && err != ERR_WOULDBLOCK) { |
|
|
|
sin.sin_port = htons(r->port); |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay connect %s:%u failed err=%d", host, port, err); |
|
|
|
if (inet_pton(AF_INET, r->host, &sin.sin_addr) == 1) { |
|
|
|
relay_free(r); return -1; |
|
|
|
relay_connect_dest(r, &sin); |
|
|
|
} |
|
|
|
return 0; |
|
|
|
} else { |
|
|
|
|
|
|
|
r->connecting = 0; |
|
|
|
|
|
|
|
int opt = 1; setsockopt(dest_sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt)); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
r->dest_sid = uasync_add_socket_t(ua, dest_sock, relay_dest_read_cb, relay_dest_write_cb, NULL, "reality_dest", r); |
|
|
|
|
|
|
|
if (!r->dest_sid) { relay_free(r); return -1; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!r->connecting && r->c2d) relay_flush_c2d(r); |
|
|
|
// асинхронный A-запрос — не блокирует event loop
|
|
|
|
|
|
|
|
r->dns_q = adns_resolve(ua, r->host, NULL, relay_dns_done_cb, r); |
|
|
|
|
|
|
|
if (!r->dns_q) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_relay: async DNS start failed for %s", r->host); |
|
|
|
|
|
|
|
relay_free(r); |
|
|
|
|
|
|
|
return -1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "reality_relay started: client→%s:%u initial=%zu", host, port, initial_len); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "reality_relay started: client→%s:%u initial=%zu (resolving)", |
|
|
|
|
|
|
|
r->host, r->port, initial_len); |
|
|
|
return 0; |
|
|
|
return 0; |
|
|
|
|
|
|
|
|
|
|
|
fail: |
|
|
|
|
|
|
|
socket_close_wrapper(client_sock); |
|
|
|
|
|
|
|
return -1; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|