From 60af36902ca3d95ab3cd8288fdb3500ddef99fd2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 31 Oct 2026 20:06:17 +0100 Subject: [PATCH] Skip unavailable automatic sockets and retain working transports --- src/transport_layer/auto_socket.c | 90 +++++++++++++++---------------- src/transport_layer/auto_socket.h | 8 ++- src/transport_layer/stcp_link.c | 2 +- 3 files changed, 49 insertions(+), 51 deletions(-) diff --git a/src/transport_layer/auto_socket.c b/src/transport_layer/auto_socket.c index f6443a2b..d23f92ca 100644 --- a/src/transport_layer/auto_socket.c +++ b/src/transport_layer/auto_socket.c @@ -271,6 +271,8 @@ static void auto_socket_post_sockets_changed(struct AUTO_SOCKET* as) { int old_v4 = as->v4_usable, old_v6 = as->v6_usable; as->v4_usable = as_check_usable(as, AF_INET); as->v6_usable = as_check_usable(as, AF_INET6); + if ((old_v4 || old_v6) && !as->v4_usable && !as->v6_usable) + DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] no usable sockets; waiting for network changes"); int need_reconnect = 0; if ((!old_v4 && as->v4_usable) || (!old_v6 && as->v6_usable)) @@ -354,11 +356,9 @@ static struct ETCP_SOCKET* create_iface_udp_socket(struct AUTO_SOCKET* as, uint3 struct ETCP_SOCKET* es = etcp_socket_add(inst, &server); if (!es) { int error = socket_get_error(); - if (attempt == 0 && saved_port) - DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] UDP reuse port failed if=%s fam=%d port=%u — trying random", ifname, family, port); - else - DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] UDP socket create failed if=%s fam=%d port=%u att=%d err=%d", - ifname, family, port, attempt + 1, socket_get_error()); + DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] UDP socket create failed iface=%s ifidx=%u family=%s port=%u attempt=%d reused=%d err=%d", + ifname, ifindex, family == AF_INET6 ? "IPv6" : "IPv4", port, attempt + 1, + attempt == 0 && saved_port != 0, error); if (error != AS_ADDR_IN_USE) return NULL; continue; } @@ -367,11 +367,11 @@ static struct ETCP_SOCKET* create_iface_udp_socket(struct AUTO_SOCKET* as, uint3 { const struct sockaddr_storage* ia = es->interface_addr.ss_family ? &es->interface_addr : &es->local_addr; if (ia->ss_family == AF_INET && ((struct sockaddr_in*)ia)->sin_addr.s_addr == INADDR_ANY) { - DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] UDP socket %s has INADDR_ANY — removing", es->name); + DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] UDP socket %s has INADDR_ANY — removing", es->name); ncd_remove_socket_links(inst, es); etcp_socket_remove(es); return NULL; } if (ia->ss_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)ia)->sin6_addr)) { - DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] UDP socket %s has :: — removing", es->name); + DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] UDP socket %s has :: — removing", es->name); ncd_remove_socket_links(inst, es); etcp_socket_remove(es); return NULL; } } @@ -439,24 +439,22 @@ static struct stcp_server* create_iface_tcp_socket(struct AUTO_SOCKET* as, uint3 } struct ETCP_SOCKET* ts = tcp_socket_add(inst, &server); - if (!ts) { DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] TCP tcp_socket_add failed if=%s fam=%d port=%u", ifname, family, port); continue; } + if (!ts) { DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] TCP tcp_socket_add failed if=%s fam=%d port=%u", ifname, family, port); return NULL; } { const struct sockaddr_storage* ia = (ts->interface_addr.ss_family) ? &ts->interface_addr : &ts->local_addr; if ((ia->ss_family == AF_INET && ((const struct sockaddr_in*)ia)->sin_addr.s_addr == INADDR_ANY) || (ia->ss_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&((const struct sockaddr_in6*)ia)->sin6_addr))) { - DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] TCP socket %s has null addr — removing", server.name); - tcp_socket_remove(ts); continue; + DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] TCP socket %s has null addr — removing", server.name); + tcp_socket_remove(ts); return NULL; } } struct stcp_server* tsrv = stcp_server_listen(&scfg, port, tcp_server_on_link, ts); if (!tsrv) { int error = socket_get_error(); - if (attempt == 0 && saved_port) - DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] TCP reuse port failed if=%s fam=%d port=%u — trying random", ifname, family, port); - else - DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] TCP socket create failed if=%s fam=%d port=%u att=%d err=%d", - ifname, family, port, attempt + 1, socket_get_error()); + DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] TCP socket create failed iface=%s ifidx=%u family=%s port=%u attempt=%d reused=%d err=%d", + ifname, ifindex, family == AF_INET6 ? "IPv6" : "IPv4", port, attempt + 1, + attempt == 0 && saved_port != 0, error); tcp_socket_remove(ts); if (error != AS_ADDR_IN_USE) return NULL; continue; @@ -1046,7 +1044,7 @@ static void reconcile_prune_ifa(struct AUTO_SOCKET* as, struct auto_sock_iface* * и оповещает GUI если были изменения. */ static int reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname) { if (!ifindex) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] invalid interface index: %s", ifname); return -1; } - int has_v4 = 0, has_v6 = 0, changed = 0, result = 0; + int has_v4 = 0, has_v6 = 0, changed = 0; uint8_t v4_type = CFG_SERVER_TYPE_NAT, v6_type = CFG_SERVER_TYPE_NAT; if (scan_and_classify_iface(ifindex, ifname, &has_v4, &has_v6, &v4_type, &v6_type) < 0) return -1; if (as->instance->config->global.auto_socket_skip_no_default_route) { @@ -1102,9 +1100,8 @@ static int reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* else *slot = create_iface_udp_socket(as, ifindex, ifname, family, type); if (*slot) { if (tcp) *tcp_addr = (*slot)->interface_addr; changed = 1; } else { - DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] incomplete endpoint iface=%s family=%d protocol=%s", - ifname, family, tcp ? "TCP" : "UDP"); - result = -1; + DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] skipping unavailable socket iface=%s ifidx=%u family=%s protocol=%s; retry on next scan", + ifname, ifindex, v6 ? "IPv6" : "IPv4", tcp ? "TCP" : "UDP"); } } } @@ -1112,7 +1109,7 @@ static int reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifa->v4_type = v4_type; ifa->v6_type = v6_type; reconcile_prune_ifa(as, ifa, ifindex, ifname); if (changed) auto_socket_post_sockets_changed(as); - return result; + return 0; } /* ═══════════ Platform monitoring ═══════════ */ @@ -1359,35 +1356,32 @@ int auto_socket_init(struct UTUN_INSTANCE* inst) { as->protocols = (uint8_t)inst->config->global.auto_socket_protocols; inst->auto_socket_state = as; - /* Android-режим: источник сокетов — [server] секции конфига (генерирует Kotlin). - * Системный мониторинг и таблица портов в БД не нужны. */ + /* Android получает адреса из конфига; системный режим — от монитора интерфейсов. */ if (as->mode == CFG_AUTO_SOCKETS_ANDROID) { if (auto_socket_reconcile_config(as) < 0) { auto_socket_destroy(inst); return -1; } - DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] initialized (android mode)"); - return 0; - } - - if (inst->topo_sqlite_db) { - int rc = sqlite3_exec(inst->topo_sqlite_db, - "CREATE TABLE IF NOT EXISTS auto_socket_ports (" - " if_name TEXT NOT NULL," - " family INTEGER NOT NULL," - " protocol INTEGER NOT NULL," - " port INTEGER NOT NULL," - " updated_at INTEGER DEFAULT (unixepoch())," - " UNIQUE(if_name, family, protocol)" - ");", NULL, NULL, NULL); - if (rc != SQLITE_OK) { - DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] cannot create port table rc=%d: %s", rc, sqlite3_errmsg(inst->topo_sqlite_db)); - auto_socket_destroy(inst); return -1; + } else { + if (inst->topo_sqlite_db) { + int rc = sqlite3_exec(inst->topo_sqlite_db, + "CREATE TABLE IF NOT EXISTS auto_socket_ports (" + " if_name TEXT NOT NULL," + " family INTEGER NOT NULL," + " protocol INTEGER NOT NULL," + " port INTEGER NOT NULL," + " updated_at INTEGER DEFAULT (unixepoch())," + " UNIQUE(if_name, family, protocol)" + ");", NULL, NULL, NULL); + if (rc != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] cannot create port table rc=%d: %s", rc, sqlite3_errmsg(inst->topo_sqlite_db)); + auto_socket_destroy(inst); return -1; + } } + if (auto_socket_init_monitor(as) < 0) { auto_socket_destroy(inst); return -1; } + if (auto_socket_scan_all(as) < 0) { auto_socket_destroy(inst); return -1; } } - - if (auto_socket_init_monitor(as) < 0) { auto_socket_destroy(inst); return -1; } - - if (auto_socket_scan_all(as) < 0) { auto_socket_destroy(inst); return -1; } - - DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] initialized protocols=%s", + int v4 = as_check_usable(as, AF_INET), v6 = as_check_usable(as, AF_INET6); + if (!v4 && !v6) DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] initialized without usable sockets; waiting for network/config changes"); + DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] initialized mode=%s usable_v4=%d usable_v6=%d protocols=%s", + as->mode == CFG_AUTO_SOCKETS_ANDROID ? "android" : "system", v4, v6, as->protocols == CFG_AUTO_SOCKET_BOTH ? "tcp+udp" : as->protocols == CFG_AUTO_SOCKET_TCP ? "tcp" : "udp"); return 0; } @@ -1477,7 +1471,6 @@ static void remove_config_socket(struct AUTO_SOCKET* as, struct ETCP_SOCKET* soc static int auto_socket_reconcile_config(struct AUTO_SOCKET* as) { struct UTUN_INSTANCE* inst = as->instance; if (!inst || !inst->config) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] missing config for reconcile"); return -1; } - int result = 0; /* Тип сокета определяем по фактическому адресу (не доверяем type из конфига): * публичный адрес → PUBLIC, приватный/link-local → NAT. */ @@ -1527,12 +1520,13 @@ static int auto_socket_reconcile_config(struct AUTO_SOCKET* as) { s->name, s->transport ? "tcp" : "udp", sockaddr_storage_to_str(&s->ip).str); ncd_add_socket_links(inst, created); } else { - DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] configured endpoint creation failed name=%s", s->name); result = -1; + DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] skipping unavailable configured socket name=%s ifidx=%u protocol=%s addr=%s; retry on next reload", + s->name, s->netif_index, s->transport ? "TCP" : "UDP", sockaddr_storage_to_str(&s->ip).str); } } auto_socket_post_sockets_changed(as); - return result; + return 0; } void auto_socket_reload_config(struct UTUN_INSTANCE* inst, struct utun_config* new_cfg) { diff --git a/src/transport_layer/auto_socket.h b/src/transport_layer/auto_socket.h index 23eb1f45..896a04e2 100644 --- a/src/transport_layer/auto_socket.h +++ b/src/transport_layer/auto_socket.h @@ -2,6 +2,8 @@ * При auto_sockets=yes init сканирует интерфейсы и включает мониторинг Linux/BSD/Windows; * auto_socket_protocols выбирает TCP, UDP или TCP+UDP (по умолчанию); адреса и сокеты обновляются автоматически. * Порты сохраняются в SQLite для повторного использования. + * Недоступные сокеты пропускаются с предупреждением; рабочие сохраняются даже при частичном отказе. + * Отсутствующие сокеты повторно создаются при сканировании сети, в Android — при обновлении конфига. * Destroy отключает мониторинг и закрывает созданные системные endpoint; сохранённые порты остаются в БД. * В режиме android сокеты приходят из конфигурации frontend и принадлежат ядру, а не списку системных интерфейсов. * Основной API — uasync-поток. Из другого потока вызывать только on_network_change; @@ -30,7 +32,9 @@ struct AUTO_SOCKET; /* Инициализирует модуль: создаёт таблицу портов в БД, запускает платформенный * мониторинг интерфейсов, сканирует все текущие UP-интерфейсы и создаёт сокеты. * instance/ua обязательны. Если нет конфига или auto_sockets=no — ничего не делает, возвращает 0. - * Успех — 0; ошибка — -1 с откатом собственных endpoint/монитора. Повторный активный init — ошибка. + * Успех — 0, в том числе если отдельные или все сокеты недоступны; мониторинг/состояние сохраняются. + * Ошибка модуля (БД, мониторинг, сканирование, внутреннее состояние) — -1 с откатом собственных endpoint/монитора. + * Повторный активный init — ошибка. * Системное полное сканирование ограничено 64 UP-интерфейсами; превышение — ошибка. */ int auto_socket_init(struct UTUN_INSTANCE* inst); @@ -49,7 +53,7 @@ void auto_socket_on_network_change(struct UTUN_INSTANCE* inst); * конфига. new_cfg — уже распарсенный конфиг; его список servers переносится в * inst->config, остальное освобождается, в том числе при отказе режима/аргументов. * new_cfg должен быть новым отдельным объектом; переданный текущий inst->config отклоняется без free. - * Обновление не атомарно: при частичном отказе новый список сохраняется, отсутствующие endpoint логируются. + * Обновление не атомарно: недоступные сокеты пропускаются с предупреждением, новый список и рабочие сокеты сохраняются. * Неизменные endpoint, включая port=0, сохраняются; смена параметров bind пересоздаёт endpoint. * Только uasync-поток, вне callbacks сверки. */ void auto_socket_reload_config(struct UTUN_INSTANCE* inst, struct utun_config* new_cfg); diff --git a/src/transport_layer/stcp_link.c b/src/transport_layer/stcp_link.c index 35c486a2..36b7d537 100644 --- a/src/transport_layer/stcp_link.c +++ b/src/transport_layer/stcp_link.c @@ -188,7 +188,7 @@ struct stcp_server *stcp_server_listen(struct stcp_link_config *cfg, uint16_t po ss->tcp_sock = tcp_sock; ss->srv = stcp_server_create(cfg->inst->ua, port, &cfg->inst->my_keys, cfg->inst->my_ed25519_pubkey, cfg->inst, server_accept_cb, ss, NULL, NULL, cfg->listen_family); - if (!ss->srv) { u_free(ss); return NULL; } + if (!ss->srv) { int error = socket_get_error(); u_free(ss); socket_set_error(error); return NULL; } uint16_t bound_port = stcp_server_get_port(ss->srv); if (!bound_port) { stcp_server_destroy(ss->srv); u_free(ss); return NULL; } if (tcp_sock) {