diff --git a/doc/tasks.md b/doc/tasks.md index 4e391287..a924b77f 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -4,6 +4,14 @@ ## Текущие задачи +[+] **test_reality_tls: коллизии порта слушателя** — 10.10.2026: + В общем прогоне 140 passed, 1 failed: stcp_server_create не смог занять порт 34922, bind errno=98 (EADDRINUSE). + Причина: выбор порта как 34000 + time(NULL) % 1000 без гарантии его доступности. + Слушатель создаётся с port=0; stcp_server_get_port возвращает назначенный ОС порт для подключения клиента. + Логи показывают фактические порты REALITY и HTTPS. Сборка прошла; 8 параллельных TLS/HTTP-тестов прошли + при занятом всём прежнем диапазоне 34000–34999. ./check.sh: 141 passed, 0 failed, 0 skipped; + proxy/full, burst и load — PASS. + [ ] **Member files: исчерпание кеша результатов массовых изменений** — 10.10.2026: Воспроизведено test_member_files_browser::recursiveDeletion(server-busy): из 76 объектов удаляются 64, следующая команда получает MF_BUSY, остаются 12. storage.c: STORE_RECEIPTS=64, RECEIPT_TB=1200000 (120 секунд). diff --git a/src/transport_layer/stcp_server.c b/src/transport_layer/stcp_server.c index 788d432a..1de57f34 100644 --- a/src/transport_layer/stcp_server.c +++ b/src/transport_layer/stcp_server.c @@ -335,11 +335,27 @@ struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port, DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "listen failed err=%d", socket_get_error()); socket_close_wrapper(srv->listen_sock); u_free(srv); return NULL; } + uint16_t bound_port = stcp_server_get_port(srv); + if (!bound_port) { socket_close_wrapper(srv->listen_sock); u_free(srv); return NULL; } srv->listen_id = uasync_add_socket_t(ua, srv->listen_sock, server_accept_cb, NULL, NULL, "stcp_listen", srv); - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_server: listening on port %u family=%s", port, use_family == AF_INET ? "v4" : "v6"); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_server: listening on port %u family=%s", bound_port, use_family == AF_INET ? "v4" : "v6"); return srv; } +uint16_t stcp_server_get_port(const struct stcp_server *srv) { + if (!srv) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_server_get_port: invalid server"); return 0; } + struct sockaddr_storage address; + socklen_t size = sizeof(address); + if (getsockname(srv->listen_sock, (struct sockaddr *)&address, &size) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_server_get_port: getsockname failed err=%d", socket_get_error()); + return 0; + } + if (address.ss_family == AF_INET) return ntohs(((struct sockaddr_in *)&address)->sin_port); + if (address.ss_family == AF_INET6) return ntohs(((struct sockaddr_in6 *)&address)->sin6_port); + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_server_get_port: unexpected family=%d", address.ss_family); + return 0; +} + void stcp_server_destroy(struct stcp_server *srv) { if (!srv) return; if (srv->listen_id) uasync_remove_socket_t(srv->ua, srv->listen_sock); diff --git a/src/transport_layer/stcp_server.h b/src/transport_layer/stcp_server.h index 51f104d6..47fbdb40 100644 --- a/src/transport_layer/stcp_server.h +++ b/src/transport_layer/stcp_server.h @@ -24,6 +24,8 @@ struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port, stcp_connect_cb connect_cb, void *arg, stcp_close_cb close_cb, void *close_arg, int family); +// Фактический TCP-порт слушателя (port=0 при create выбирает ОС); 0 при ошибке. +uint16_t stcp_server_get_port(const struct stcp_server *srv); void stcp_server_destroy(struct stcp_server *srv); // Включить REALITY-камуфляж на этом TCP-сервере: перед STCP-хендшейком сервер diff --git a/tests/Makefile.am b/tests/Makefile.am index 3f87c670..b4950e19 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -691,8 +691,14 @@ check-local: $(check_PROGRAMS) total=$$((total + 1)); \ logfile="$(TEST_LOG_DIR)/$$test.log"; \ if test -f "./$$test"; then \ + set -- "./$$test"; \ + if test "$$test" = test_auto_socket_dynamic && test "$$(id -u)" -ne 0 \ + && command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then \ + set -- sudo -n -- "./$$test"; \ + printf "[INFO] %s: running as root via sudo -n\n" "$$test"; \ + fi; \ start=$$(date +%s%N); \ - if "./$$test" > "$$logfile" 2>&1; then \ + if "$$@" > "$$logfile" 2>&1; then \ end=$$(date +%s%N); \ elapsed_ms=$$(( (end - start) / 1000000 )); \ printf "$${G}[PASS]$${N} %s (%sms)\n" "$$test" "$$elapsed_ms"; \ @@ -700,7 +706,7 @@ check-local: $(check_PROGRAMS) elif test $$? -eq 77; then \ end=$$(date +%s%N); \ elapsed_ms=$$(( (end - start) / 1000000 )); \ - printf "$${Y}[SKIP]$${N} %s (requires root)\n" "$$test"; \ + printf "$${Y}[SKIP]$${N} %s (see %s)\n" "$$test" "$$logfile"; \ skipped=$$((skipped + 1)); \ else \ end=$$(date +%s%N); \ diff --git a/tests/test_reality_tls.c b/tests/test_reality_tls.c index 456e838f..c12987ff 100644 --- a/tests/test_reality_tls.c +++ b/tests/test_reality_tls.c @@ -8,7 +8,6 @@ #include #include #include -#include #define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "FAIL line=%d: %s", __LINE__, #x); return 1; } } while (0) struct https_peer { @@ -111,9 +110,10 @@ int main(void) { cfg.enabled = 1; cfg.short_id_count = 1; uint8_t pub[32]; CHECK(!reality_generate_keypair(cfg.private_key, pub)); snprintf(cfg.dest, sizeof(cfg.dest), "127.0.0.1:%u", ntohs(address.sin_port)); - uint16_t port = 34000 + (uint16_t)(time(NULL) % 1000); - struct stcp_server *reality = stcp_server_create(ua, port, &keys, NULL, &inst, unexpected_stcp, NULL, NULL, NULL, AF_INET); + struct stcp_server *reality = stcp_server_create(ua, 0, &keys, NULL, &inst, unexpected_stcp, NULL, NULL, NULL, AF_INET); CHECK(reality); stcp_server_set_reality(reality, &cfg); + uint16_t port = stcp_server_get_port(reality); CHECK(port); + DEBUG_INFO(DEBUG_CATEGORY_REALITY, "test listeners: REALITY=127.0.0.1:%u HTTPS=%s", port, cfg.dest); socket_t sock = socket(AF_INET, SOCK_STREAM, 0); CHECK(sock != SOCKET_INVALID); address.sin_port = htons(port); CHECK(!connect(sock, (struct sockaddr *)&address, sizeof(address))); socket_set_nonblocking(sock); diff --git a/tests/test_uasync_regressions.c b/tests/test_uasync_regressions.c index 12e1d744..c1516100 100644 --- a/tests/test_uasync_regressions.c +++ b/tests/test_uasync_regressions.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -102,10 +103,41 @@ static int test_fd_mapping(int param) { socket_cleanup(&t); return 0; } static int test_lookup(int param) { + const int min_fd = FD_SETSIZE + 16; + if (param & 4) { + struct rlimit limit; + if (getrlimit(RLIMIT_NOFILE, &limit) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "getrlimit(RLIMIT_NOFILE): %s", strerror(errno)); + return 1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "High-fd test: min_fd=%d, soft=%llu, hard=%llu", min_fd, + (unsigned long long)limit.rlim_cur, (unsigned long long)limit.rlim_max); + if (limit.rlim_max <= (rlim_t)min_fd) { + DEBUG_WARN(DEBUG_CATEGORY_SYS, "Skipping high-fd test: fd >= %d requires RLIMIT_NOFILE > %d, hard=%llu", + min_fd, min_fd, (unsigned long long)limit.rlim_max); + return 77; + } + // Каждый случай работает в дочернем процессе; лимит родителя не меняется. + if (limit.rlim_cur <= (rlim_t)min_fd) { + limit.rlim_cur = (rlim_t)min_fd + 1; + if (setrlimit(RLIMIT_NOFILE, &limit) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "setrlimit(RLIMIT_NOFILE, %llu): %s", + (unsigned long long)limit.rlim_cur, strerror(errno)); + return 1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "High-fd test: raised soft limit to %llu", (unsigned long long)limit.rlim_cur); + } + } struct socket_test t; CHECK_EQ(socket_setup(&t, param & 3, 1, 0, 0), 0); if (param & 4) { CHECK_EQ(uasync_remove_socket(t.ua, t.id), ERR_OK); t.id = NULL; - int high = fcntl(t.fd[0], F_DUPFD, FD_SETSIZE + 16); CHECK(high >= FD_SETSIZE); + int high = fcntl(t.fd[0], F_DUPFD, min_fd); + if (high < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "fcntl(fd=%d, F_DUPFD, min_fd=%d): %s", t.fd[0], min_fd, strerror(errno)); + socket_cleanup(&t); return 1; + } + CHECK(high >= min_fd); + DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "High-fd test: allocated fd=%d", high); close(t.fd[0]); t.fd[0] = high; t.id = uasync_add_socket(t.ua, high, read_cb, NULL, NULL, "high fd", &t); CHECK(t.id); }