Browse Source

tests: hold OS-assigned HTTP and SOCKS listener ports

master
evgeny 5 days ago
parent
commit
4e2cc0e35a
  1. 17
      src/proxy/socks_proxy.c
  2. 2
      src/proxy/socks_proxy.h
  3. 14
      tests/test_proxy_regressions.c
  4. 71
      tests/test_socks_http_proxy.c

17
src/proxy/socks_proxy.c

@ -909,8 +909,12 @@ struct listen_ctx* socks_proxy_init_listen(struct UASYNC* ua, const char* addr_s
size_t ip_len = (size_t)(colon - addr_str); size_t ip_len = (size_t)(colon - addr_str);
if (ip_len > sizeof(ip) - 1) ip_len = sizeof(ip) - 1; if (ip_len > sizeof(ip) - 1) ip_len = sizeof(ip) - 1;
memcpy(ip, addr_str, ip_len); ip[ip_len] = '\0'; memcpy(ip, addr_str, ip_len); ip[ip_len] = '\0';
int port = atoi(colon + 1); char* end;
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: bad port %d", port); return NULL; } long parsed = strtol(colon + 1, &end, 10);
if (end == colon + 1 || *end || parsed < 0 || parsed > 65535) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: bad port '%s'", colon + 1); return NULL;
}
int port = (int)parsed;
struct listen_ctx* ctx = u_calloc(1, sizeof(struct listen_ctx)); struct listen_ctx* ctx = u_calloc(1, sizeof(struct listen_ctx));
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: u_calloc failed"); return NULL; } if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: u_calloc failed"); return NULL; }
@ -928,16 +932,23 @@ struct listen_ctx* socks_proxy_init_listen(struct UASYNC* ua, const char* addr_s
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: inet_pton(%s) failed", ip); socket_close_wrapper(sock); u_free(ctx); return NULL; } if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: inet_pton(%s) failed", ip); socket_close_wrapper(sock); u_free(ctx); return NULL; }
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: bind(%s:%d) failed errno=%d", ip, port, errno); socket_close_wrapper(sock); u_free(ctx); return NULL; } if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: bind(%s:%d) failed errno=%d", ip, port, errno); socket_close_wrapper(sock); u_free(ctx); return NULL; }
if (listen(sock, 32) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: listen() failed errno=%d", errno); socket_close_wrapper(sock); u_free(ctx); return NULL; } if (listen(sock, 32) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: listen() failed errno=%d", errno); socket_close_wrapper(sock); u_free(ctx); return NULL; }
uint16_t bound_port = socket_get_bound_port(sock);
if (!bound_port) { socket_close_wrapper(sock); u_free(ctx); return NULL; }
ctx->listen_sock = sock; ctx->listen_sock = sock;
ctx->socket_id = uasync_add_socket_t(ua, sock, on_accept_cb, NULL, NULL, "socks_proxy_listen", ctx); ctx->socket_id = uasync_add_socket_t(ua, sock, on_accept_cb, NULL, NULL, "socks_proxy_listen", ctx);
if (!ctx->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: uasync_add_socket_t failed"); socket_close_wrapper(sock); u_free(ctx); return NULL; } if (!ctx->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: uasync_add_socket_t failed"); socket_close_wrapper(sock); u_free(ctx); return NULL; }
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: %s listening on %s:%d sock=%d", DEBUG_INFO(DEBUG_CATEGORY_PROXY, "socks_proxy: %s listening on %s:%d sock=%d",
is_http ? "HTTP" : "SOCKS", ip, port, (int)sock); is_http ? "HTTP" : "SOCKS", ip, bound_port, (int)sock);
return ctx; return ctx;
} }
uint16_t socks_proxy_get_port(const struct listen_ctx* ctx) {
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "socks_proxy: missing listener"); return 0; }
return socket_get_bound_port(ctx->listen_sock);
}
void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out) { void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out) {
if (!ctx) return; if (!ctx) return;
if (ctx->socket_id) uasync_remove_socket_t(ua, ctx->listen_sock); if (ctx->socket_id) uasync_remove_socket_t(ua, ctx->listen_sock);

2
src/proxy/socks_proxy.h

@ -83,6 +83,8 @@ struct listen_ctx* socks_proxy_init_listen(struct UASYNC* ua, const char* addr_s
// Закрыть слушающий сокет и освободить listen_ctx. // Закрыть слушающий сокет и освободить listen_ctx.
// Если sock_out задан, в него записывается SOCKET_INVALID: сокет уже закрыт. // Если sock_out задан, в него записывается SOCKET_INVALID: сокет уже закрыт.
void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out); void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out);
// Фактический порт активного listener (IP:0 выбирает ОС); 0 при ошибке.
uint16_t socks_proxy_get_port(const struct listen_ctx* ctx);
// Найти соединение по stream_id // Найти соединение по stream_id
struct socks_proxy_conn* socks_proxy_find_conn(struct socks_proxy_conn* head, uint32_t stream_id); struct socks_proxy_conn* socks_proxy_find_conn(struct socks_proxy_conn* head, uint32_t stream_id);

14
tests/test_proxy_regressions.c

@ -115,13 +115,10 @@ static void deliver(struct UTUN_INSTANCE* inst, uint64_t peer, uint8_t svc, uint
} }
static void parser_tests(struct UTUN_INSTANCE* inst) { static void parser_tests(struct UTUN_INSTANCE* inst) {
uint16_t sp, hp; struct tcp_proxy_client* p = tcp_proxy_client_create(inst, ua, NULL, NULL, 1500, 1, NULL, 0, 42,
socket_t reserved = listen_local(&sp); socket_close_wrapper(reserved); 1, "127.0.0.1:0", 1, "127.0.0.1:0");
reserved = listen_local(&hp); socket_close_wrapper(reserved);
char socks[64], http[64];
snprintf(socks, sizeof(socks), "127.0.0.1:%u", sp); snprintf(http, sizeof(http), "127.0.0.1:%u", hp);
struct tcp_proxy_client* p = tcp_proxy_client_create(inst, ua, NULL, NULL, 1500, 1, NULL, 0, 42, 1, socks, 1, http);
CHECK(p); inst->tcp_proxy_client = p; CHECK(p); inst->tcp_proxy_client = p;
uint16_t sp = socks_proxy_get_port(p->socks_listen), hp = socks_proxy_get_port(p->http_listen); CHECK(sp && hp);
socket_t fd = connect_local(sp); socket_t fd = connect_local(sp);
// Greeting, CONNECT и ранние данные намеренно одним write. // Greeting, CONNECT и ранние данные намеренно одним write.
const uint8_t request[] = {5,1,0, 5,1,0,1,127,0,0,1,0,80, 'a','b','c'}; const uint8_t request[] = {5,1,0, 5,1,0,1,127,0,0,1,0,80, 'a','b','c'};
@ -238,10 +235,9 @@ static void upstream(struct UTUN_INSTANCE* inst, uint32_t sid, const char* text)
} }
static void http_tests(struct UTUN_INSTANCE* inst) { static void http_tests(struct UTUN_INSTANCE* inst) {
uint16_t port; socket_t reserved = listen_local(&port); socket_close_wrapper(reserved); struct tcp_proxy_client* p = tcp_proxy_client_create(inst, ua, NULL, NULL, 1500, 1, NULL, 0, 42, 0, NULL, 1, "127.0.0.1:0");
char addr[64]; snprintf(addr, sizeof(addr), "127.0.0.1:%u", port);
struct tcp_proxy_client* p = tcp_proxy_client_create(inst, ua, NULL, NULL, 1500, 1, NULL, 0, 42, 0, NULL, 1, addr);
CHECK(p); inst->tcp_proxy_client = p; CHECK(p); inst->tcp_proxy_client = p;
uint16_t port = socks_proxy_get_port(p->http_listen); CHECK(port);
char out[64000]; socket_t fd = connect_local(port); message_count = 0; char out[64000]; socket_t fd = connect_local(port); message_count = 0;
const char pipeline[] = "GET http://127.0.0.1:8080?x=1 HTTP/1.1\r\nHost: wrong\r\n" const char pipeline[] = "GET http://127.0.0.1:8080?x=1 HTTP/1.1\r\nHost: wrong\r\n"
"Proxy-Authorization: secret\r\nConnection: X-Hop\r\nX-Hop: secret\r\n\r\n" "Proxy-Authorization: secret\r\nConnection: X-Hop\r\nX-Hop: secret\r\n\r\n"

71
tests/test_socks_http_proxy.c

@ -20,6 +20,9 @@
#include <time.h> #include <time.h>
#include "../lib/platform_compat.h" #include "../lib/platform_compat.h"
#include "test_utils.h" #include "test_utils.h"
#include "test_net.h"
#include "../src/proxy/socks_proxy.h"
#include "../src/proxy/tcp_proxy_client.h"
#include "../lib/u_async.h" #include "../lib/u_async.h"
#include "../lib/debug_config.h" #include "../lib/debug_config.h"
#include "../lib/ll_queue.h" #include "../lib/ll_queue.h"
@ -58,18 +61,6 @@ static char g_cert[512], g_key[512];
static void* g_mon_id = NULL; static void* g_mon_id = NULL;
static int g_file_seed = 0; static int g_file_seed = 0;
static int alloc_port(void) {
int s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) return -1;
struct sockaddr_in a; memset(&a, 0, sizeof(a));
a.sin_family = AF_INET; a.sin_addr.s_addr = inet_addr("127.0.0.1");
for (int i = 0; i < 200; i++) {
a.sin_port = htons((uint16_t)(20000 + (rand() % 40000)));
if (bind(s, (struct sockaddr*)&a, sizeof(a)) == 0) { close(s); return ntohs(a.sin_port); }
}
close(s); return -1;
}
static void gen_file(const char* path, int size, int seed) { static void gen_file(const char* path, int size, int seed) {
FILE* f = fopen(path, "wb"); FILE* f = fopen(path, "wb");
if (!f) { fprintf(stderr, "gen_file: fopen(%s) failed\n", path); return; } if (!f) { fprintf(stderr, "gen_file: fopen(%s) failed\n", path); return; }
@ -83,7 +74,7 @@ static void gen_file(const char* path, int size, int seed) {
} }
// Сервер сообщает готовность после listen и настройки TLS; EOF обнаруживает ошибку запуска без паузы. // Сервер сообщает готовность после listen и настройки TLS; EOF обнаруживает ошибку запуска без паузы.
static int wait_server_ready(int fd, int port, const char* name) { static int wait_server_ready(int fd, int* port, const char* name) {
uint64_t start = get_time_us(), deadline = start + 5000000; uint64_t start = get_time_us(), deadline = start + 5000000;
struct pollfd pfd = {.fd = fd, .events = POLLIN}; struct pollfd pfd = {.fd = fd, .events = POLLIN};
int result; int result;
@ -91,21 +82,23 @@ static int wait_server_ready(int fd, int port, const char* name) {
uint64_t now = get_time_us(); uint64_t now = get_time_us();
result = poll(&pfd, 1, now < deadline ? (int)((deadline - now + 999) / 1000) : 0); result = poll(&pfd, 1, now < deadline ? (int)((deadline - now + 999) / 1000) : 0);
} while (result < 0 && errno == EINTR); } while (result < 0 && errno == EINTR);
char marker = 0; uint8_t assigned[2] = {0};
ssize_t bytes = result > 0 ? read(fd, &marker, 1) : -1; ssize_t bytes = result > 0 ? read(fd, assigned, sizeof(assigned)) : -1;
int error = result < 0 || (result > 0 && bytes < 0) ? errno : 0; int error = result < 0 || (result > 0 && bytes < 0) ? errno : 0;
close(fd); close(fd);
if (result <= 0 || bytes != 1 || marker != 'R') { int actual = ((int)assigned[0] << 8) | assigned[1];
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "test %s server startup failed: port=%d poll=%d events=%x bytes=%ld marker=%d errno=%d", if (result <= 0 || bytes != sizeof(assigned) || !actual) {
name, port, result, pfd.revents, (long)bytes, marker, error); DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "test %s server startup failed: poll=%d events=%x bytes=%ld port=%d errno=%d",
name, result, pfd.revents, (long)bytes, actual, error);
return -1; return -1;
} }
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "test %s server ready: port=%d elapsed=%lluus", name, port, *port = actual;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "test %s server ready: port=%d elapsed=%lluus", name, *port,
(unsigned long long)(get_time_us() - start)); (unsigned long long)(get_time_us() - start));
return 0; return 0;
} }
static int start_http_server(int port, const char* dir) { static int start_http_server(int* port, const char* dir) {
char script[512]; snprintf(script, sizeof(script), "%s/_srv.py", dir); char script[512]; snprintf(script, sizeof(script), "%s/_srv.py", dir);
FILE* sf = fopen(script, "w"); FILE* sf = fopen(script, "w");
if (!sf) return -1; if (!sf) return -1;
@ -130,7 +123,7 @@ static int start_http_server(int port, const char* dir) {
"port=int(sys.argv[1])\n" "port=int(sys.argv[1])\n"
"os.chdir(sys.argv[2])\n" "os.chdir(sys.argv[2])\n"
"httpd=HTTPServer(('127.0.0.1',port),H)\n" "httpd=HTTPServer(('127.0.0.1',port),H)\n"
"ready=int(sys.argv[3]);os.write(ready,b'R');os.close(ready)\n" "ready=int(sys.argv[3]);os.write(ready,httpd.server_port.to_bytes(2,'big'));os.close(ready)\n"
"httpd.serve_forever()\n" "httpd.serve_forever()\n"
); );
fclose(sf); fclose(sf);
@ -144,9 +137,8 @@ static int start_http_server(int port, const char* dir) {
} }
if (pid == 0) { if (pid == 0) {
close(ready[0]); close(ready[0]);
char port_str[16], ready_str[16]; char ready_str[16]; snprintf(ready_str, sizeof(ready_str), "%d", ready[1]);
snprintf(port_str, sizeof(port_str), "%d", port); snprintf(ready_str, sizeof(ready_str), "%d", ready[1]); execlp("python3", "python3", script, "0", dir, ready_str, (char*)NULL);
execlp("python3", "python3", script, port_str, dir, ready_str, (char*)NULL);
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "test HTTP python exec: %s", strerror(errno)); DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "test HTTP python exec: %s", strerror(errno));
_exit(1); _exit(1);
} }
@ -155,7 +147,7 @@ static int start_http_server(int port, const char* dir) {
return wait_server_ready(ready[0], port, "HTTP"); return wait_server_ready(ready[0], port, "HTTP");
} }
static int start_https_server(int port, const char* dir, const char* cert, const char* key) { static int start_https_server(int* port, const char* dir, const char* cert, const char* key) {
char script[512]; snprintf(script, sizeof(script), "%s/_srv_https.py", dir); char script[512]; snprintf(script, sizeof(script), "%s/_srv_https.py", dir);
FILE* sf = fopen(script, "w"); FILE* sf = fopen(script, "w");
if (!sf) return -1; if (!sf) return -1;
@ -187,7 +179,7 @@ static int start_https_server(int port, const char* dir, const char* cert, const
"httpd=HTTPServer(('127.0.0.1',int(sys.argv[4])),H)\n" "httpd=HTTPServer(('127.0.0.1',int(sys.argv[4])),H)\n"
"httpd.socket=ctx.wrap_socket(httpd.socket,server_side=True)\n" "httpd.socket=ctx.wrap_socket(httpd.socket,server_side=True)\n"
"httpd.timeout=None\n" "httpd.timeout=None\n"
"ready=int(sys.argv[5]);os.write(ready,b'R');os.close(ready)\n" "ready=int(sys.argv[5]);os.write(ready,httpd.server_port.to_bytes(2,'big'));os.close(ready)\n"
"httpd.serve_forever()\n" "httpd.serve_forever()\n"
); );
fclose(sf); fclose(sf);
@ -201,9 +193,8 @@ static int start_https_server(int port, const char* dir, const char* cert, const
} }
if (pid == 0) { if (pid == 0) {
close(ready[0]); close(ready[0]);
char port_str[16], ready_str[16]; char ready_str[16]; snprintf(ready_str, sizeof(ready_str), "%d", ready[1]);
snprintf(port_str, sizeof(port_str), "%d", port); snprintf(ready_str, sizeof(ready_str), "%d", ready[1]); execlp("python3", "python3", script, cert, key, dir, "0", ready_str, (char*)NULL);
execlp("python3", "python3", script, cert, key, dir, port_str, ready_str, (char*)NULL);
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "test HTTPS python exec: %s", strerror(errno)); DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "test HTTPS python exec: %s", strerror(errno));
_exit(1); _exit(1);
} }
@ -487,14 +478,6 @@ int main(void) {
utun_instance_set_tun_init_enabled(0); utun_instance_set_tun_init_enabled(0);
srand((unsigned)time(NULL)); srand((unsigned)time(NULL));
g_http_port = alloc_port(); g_https_port = alloc_port();
g_cli_port = alloc_port(); g_srv_port = alloc_port();
g_socks_port = alloc_port(); g_http_proxy_port = alloc_port();
if (g_http_port < 0 || g_https_port < 0 || g_cli_port < 0 || g_srv_port < 0 || g_socks_port < 0 || g_http_proxy_port < 0) {
printf("[FAIL] port allocation\n"); return 1;
}
printf(" ports: http=%d https=%d socks=%d http_proxy=%d\n", g_http_port, g_https_port, g_socks_port, g_http_proxy_port);
strcpy(g_tmpdir, "/tmp/utun_test_XXXXXX"); strcpy(g_tmpdir, "/tmp/utun_test_XXXXXX");
if (test_mkdtemp(g_tmpdir) < 0) { printf("[FAIL] mkdtemp: %s\n", strerror(errno)); return 1; } if (test_mkdtemp(g_tmpdir) < 0) { printf("[FAIL] mkdtemp: %s\n", strerror(errno)); return 1; }
@ -509,8 +492,8 @@ int main(void) {
} }
printf(" test files: 1KB, 100KB, 1MB, 10MB (total %.1fMB)\n", total_mb / (1024.0*1024.0)); fflush(stdout); printf(" test files: 1KB, 100KB, 1MB, 10MB (total %.1fMB)\n", total_mb / (1024.0*1024.0)); fflush(stdout);
if (start_http_server(g_http_port, g_tmpdir) < 0) { printf("[FAIL] start http server\n"); goto cleanup; } if (start_http_server(&g_http_port, g_tmpdir) < 0) { printf("[FAIL] start http server\n"); goto cleanup; }
if (start_https_server(g_https_port, g_tmpdir, g_cert, g_key) < 0) { printf("[FAIL] start https server\n"); goto cleanup; } if (start_https_server(&g_https_port, g_tmpdir, g_cert, g_key) < 0) { printf("[FAIL] start https server\n"); goto cleanup; }
printf(" HTTP/HTTPS servers started\n"); printf(" HTTP/HTTPS servers started\n");
g_ua = uasync_create(); g_ua = uasync_create();
@ -519,6 +502,16 @@ int main(void) {
g_srv = utun_instance_create_from_str(g_ua, make_cfg_server()); g_srv = utun_instance_create_from_str(g_ua, make_cfg_server());
g_cli = utun_instance_create_from_str(g_ua, make_cfg_client()); g_cli = utun_instance_create_from_str(g_ua, make_cfg_client());
if (!g_srv || !g_cli) { printf("[FAIL] instance create\n"); goto cleanup; } if (!g_srv || !g_cli) { printf("[FAIL] instance create\n"); goto cleanup; }
test_etcp_set_peer(g_cli, "c1", g_srv, "s1");
g_srv_port = ss_get_port(&test_etcp_bound_socket(g_srv, "s1")->local_addr);
g_cli_port = ss_get_port(&test_etcp_bound_socket(g_cli, "s1")->local_addr);
struct tcp_proxy_client* proxy = g_cli->tcp_proxy_client;
if (!proxy) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "test: proxy client missing"); goto cleanup; }
g_socks_port = socks_proxy_get_port(proxy->socks_listen);
g_http_proxy_port = socks_proxy_get_port(proxy->http_listen);
if (!g_socks_port || !g_http_proxy_port) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "test: proxy listeners missing"); goto cleanup; }
printf(" ports: http=%d https=%d socks=%d http_proxy=%d udp_client=%d udp_server=%d\n",
g_http_port, g_https_port, g_socks_port, g_http_proxy_port, g_cli_port, g_srv_port);
if (utun_instance_init(g_srv) < 0 || utun_instance_init(g_cli) < 0) { printf("[FAIL] instance init\n"); goto cleanup; } if (utun_instance_init(g_srv) < 0 || utun_instance_init(g_cli) < 0) { printf("[FAIL] instance init\n"); goto cleanup; }
printf(" waiting for ETCP...\n"); fflush(stdout); printf(" waiting for ETCP...\n"); fflush(stdout);

Loading…
Cancel
Save