diff --git a/doc/tasks.md b/doc/tasks.md index d5bd91d5..8a507b73 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -3,6 +3,17 @@ Сюда пишется список задач с короткой аннотацией. Если задача большая и имеет ТЗ - то ТЗ оформляется отдельным файлом, а сюда помещается аннотация и ссылка на ТЗ. ## Текущая задача +[+] **Q8/Android не подключается к локальному чатгуи (LAN)** — сделано. Причина: тип сокета + в Android-конфиге был захардкожен `type=public`, из-за чего `sock_match` ставил + `has_priv=0` и не создавал линки к приватным (LAN) адресам пиров (`create_links … → 0 links`). + Исправлено: (1) `ip_is_public()` вынесен в `lib/platform_compat` (единый классификатор + IPv4 с корректным ntohl); (2) в `auto_socket_reconcile_config` (Android-режим) тип сокета + выставляется по адресу (public→PUBLIC, приватный/link-local→NAT), конфиг `type` игнорируется; + (3) `sock_collect_views` больше не смотрит на `type` из конфига: `has_priv` — по классификации + локального адреса, `has_inet=1` (исходящие считаем доступными), `is_eim` — по NAT-детекции + (hairpin). `is_local_subnet` упрощён до `!ip_is_public()`. Тест `test_sock_match` обновлён + (+ `ip_is_public` кейсы). `check.sh`: 86 passed / 1 failed / 1 skipped. + [ ] **DM: прямой p2p чат с любым пользователем группы** — отдельная DM-подсистема. ТЗ и архитектура: `/doc/dm_arch.md`. Статус: реализовано (dm_core/dm_mailbox/dm_crypto), тесты `test_dm` (крипто) и `test_dm_e2e` (интеграция). Остались задачи ниже. @@ -175,6 +186,10 @@ `router_no_route`), не влияет на результат — стоит разобрать отдельно. ## Открытые флаки +[ ] **test_chat_join_e2e** — стабильно падает (2/3 сценария): «J not signed by A» + `get_sign_rc=-1` (signed_by-верификация invite). Не связан с socket-классификацией + (обнаружен при прогоне после фикса Q8/LAN). Скорее всего регресс join-протокола. + [ ] **test_etcp_reconnect** — флаки под параллельной нагрузкой `make check -j4`: phase 4 (reconnect после server restart) таймаутит (`sent=480 recv=0`), при одиночном запуске стабильно `TEST PASSED`. Наш код (chat_join/chat_sync) его не трогает. diff --git a/lib/platform_compat.c b/lib/platform_compat.c index 4045b7f5..08d6ac7a 100644 --- a/lib/platform_compat.c +++ b/lib/platform_compat.c @@ -39,6 +39,25 @@ #endif #endif +/* + * Классифицирует IPv4-адрес (network byte order): + * 1 = глобальный/публичный (маршрутизируемый в интернете), + * 0 = приватный, CGNAT, loopback, link-local, multicast или зарезервированный. + */ +int ip_is_public(uint32_t addr_be) { + uint32_t a = ntohl(addr_be); + if ((a & 0xFF000000u) == 0x0A000000u) return 0; /* 10.0.0.0/8 */ + if ((a & 0xFFF00000u) == 0xAC100000u) return 0; /* 172.16.0.0/12 */ + if ((a & 0xFFFF0000u) == 0xC0A80000u) return 0; /* 192.168.0.0/16 */ + if ((a & 0xFF000000u) == 0x7F000000u) return 0; /* 127.0.0.0/8 */ + if ((a & 0xFFFF0000u) == 0xA9FE0000u) return 0; /* 169.254.0.0/16 */ + if ((a & 0xFF000000u) == 0x00000000u) return 0; /* 0.0.0.0/8 */ + if ((a & 0xF0000000u) == 0xE0000000u) return 0; /* 224.0.0.0/4 */ + if ((a & 0xF0000000u) == 0xF0000000u) return 0; /* 240.0.0.0/4 */ + if ((a & 0xFFC00000u) == 0x64400000u) return 0; /* 100.64.0.0/10 (CGNAT) */ + return 1; +} + /* * Generate cryptographically secure random bytes * Returns 0 on success, -1 on error diff --git a/lib/platform_compat.h b/lib/platform_compat.h index f8387f55..1a555272 100644 --- a/lib/platform_compat.h +++ b/lib/platform_compat.h @@ -201,6 +201,11 @@ int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t* // family: AF_INET or AF_INET6, returns ifindex (0 on failure) uint32_t get_default_route_netif_index(int family); +// Классифицирует IPv4-адрес (network byte order): +// 1 = глобальный/публичный (маршрутизируемый в интернете), +// 0 = приватный, CGNAT, loopback, link-local, multicast или зарезервированный. +int ip_is_public(uint32_t addr_be); + // Cross-platform mkdir #ifdef _WIN32 diff --git a/src/routing_layer/route_lib.c b/src/routing_layer/route_lib.c index c035ddc9..90b3898b 100644 --- a/src/routing_layer/route_lib.c +++ b/src/routing_layer/route_lib.c @@ -267,22 +267,5 @@ int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_leng } int is_local_subnet(uint32_t ip) { - uint8_t* bip=(uint8_t*)&ip; - uint8_t first_octet = bip[0]; - uint8_t second_octet = bip[1]; - - DEBUG_TRACE(DEBUG_CATEGORY_BGP, "check ip %d.%d : ", first_octet, second_octet); - - if (first_octet == 0) return 1; - if (first_octet == 10) return 1; - if (first_octet == 127) return 1; - if (first_octet == 169 && second_octet == 254) return 1; - if (first_octet >= 224 && first_octet <= 239) return 1; - if (first_octet == 172 && second_octet >= 16 && second_octet <= 31) return 1; - if (first_octet == 100 && second_octet >= 64 && second_octet <= 127) return 1; /* 100.64.0.0/10 (CGNAT) */ - if (first_octet == 192 && second_octet == 168) return 1; - if (ip==0xffffffff) return 1; - DEBUG_TRACE(DEBUG_CATEGORY_BGP, "result: global ip %d.%d : ", first_octet, second_octet); - - return 0; + return !ip_is_public(ip); } diff --git a/src/routing_layer/sock_match.c b/src/routing_layer/sock_match.c index 8ac1582b..a6b99e8e 100644 --- a/src/routing_layer/sock_match.c +++ b/src/routing_layer/sock_match.c @@ -8,27 +8,11 @@ #include "etcp_connections.h" #include "config_parser.h" #include "topo_node.h" -#include "ntp_time.h" /* Нормализация NAT-типа: сырые (NAT_TYPE_*) или проверенные (NAT_VERIFIED_*) значения. */ static int nat_is_eim(uint8_t n) { return n == NAT_TYPE_EIM || n == NAT_VERIFIED_EIM; } static int nat_is_direct(uint8_t n) { return n == NAT_TYPE_DIRECT || n == NAT_VERIFIED_DIRECT; } -uint8_t sock_role_of(uint8_t type, uint8_t nat_type, int ntp_reachable) { - uint8_t role; - if (type == CFG_SERVER_TYPE_LOCAL) - role = SOCK_ROLE_LOCAL; /* без интернета (из конфига) */ - else if (type == CFG_SERVER_TYPE_PUBLIC || type == CFG_SERVER_TYPE_UNKNOWN) - role = SOCK_ROLE_DIRECT; - else if (type == CFG_SERVER_TYPE_NAT) - role = (nat_is_eim(nat_type) || nat_is_direct(nat_type)) ? SOCK_ROLE_DIRECT : SOCK_ROLE_NAT; - else - role = SOCK_ROLE_NAT; /* PRIVATE — скрытый, только исходящие */ - if (!ntp_reachable && role != SOCK_ROLE_LOCAL) - role = SOCK_ROLE_LOCAL; /* NTP недоступен — интернета нет */ - return role; -} - uint8_t sock_target_class(uint8_t a_type, uint8_t tcfg, uint8_t tnat) { if (tcfg == CFG_SERVER_TYPE_PUBLIC || tcfg == CFG_SERVER_TYPE_UNKNOWN) return SOCK_TC_PUB; if (tcfg == CFG_SERVER_TYPE_PRIVATE || tcfg == CFG_SERVER_TYPE_LOCAL) return SOCK_TC_PRIV; @@ -42,7 +26,7 @@ int sock_match(const struct sock_view* v, uint8_t a_type, uint8_t tcfg, uint8_t uint8_t tc = sock_target_class(a_type, tcfg, tnat); if (tc == SOCK_TC_UNREACH) return 0; /* strict/unknown NAT-адрес — напрямую не пробьём */ if (tc == SOCK_TC_PUB) { - if (v->role == SOCK_ROLE_LOCAL) return 0; /* у нашего сокета нет интернета */ + if (!v->has_inet) return 0; /* у нашего сокета нет интернета */ if (a_type == TOPO_ADDR_NAT && v->is_eim && nat_is_eim(tnat) && v->public_ip4 && v->public_ip4 == target_ip4) return 0; /* оба EIM за одним NAT — hairpin */ return 1; @@ -52,27 +36,37 @@ int sock_match(const struct sock_view* v, uint8_t a_type, uint8_t tcfg, uint8_t int sock_collect_views(struct UTUN_INSTANCE* inst, struct sock_view* out, int max) { if (!inst || !out || max <= 0) return 0; - int ntp_reachable = inst->ntp.reachable; int n = 0; struct ETCP_SOCKET* s = inst->etcp_sockets; while (s && n < max) { if (s->local_addr.ss_family != AF_INET) { s = s->next; continue; } struct sock_view* v = &out[n++]; v->sock = s; - v->role = sock_role_of(s->type, s->nat_type, ntp_reachable); - v->has_inet = (uint8_t)(v->role != SOCK_ROLE_LOCAL); - v->has_priv = (uint8_t)(s->type != CFG_SERVER_TYPE_PUBLIC && s->type != CFG_SERVER_TYPE_UNKNOWN); - v->is_eim = (uint8_t)(s->type == CFG_SERVER_TYPE_NAT && nat_is_eim(s->nat_type)); - v->family = AF_INET; - v->is_tcp = s->is_tcp; + v->family = AF_INET; + v->is_tcp = s->is_tcp; v->local_ip4 = 0; v->public_ip4 = 0; + + /* Локальный адрес (interface, иначе local). */ { const struct sockaddr_storage* ia = s->interface_addr.ss_family ? &s->interface_addr : &s->local_addr; if (ia && ia->ss_family == AF_INET) v->local_ip4 = ((const struct sockaddr_in*)ia)->sin_addr.s_addr; } + + /* Публичный адрес (NAT): nat_addr, иначе локальный если он публичный. */ { const struct sockaddr_in* na = (const struct sockaddr_in*)&s->nat_addr; if (s->nat_addr.ss_family == AF_INET && na->sin_addr.s_addr != 0) - v->public_ip4 = na->sin_addr.s_addr; - else if (s->type == CFG_SERVER_TYPE_PUBLIC || s->type == CFG_SERVER_TYPE_UNKNOWN || nat_is_direct(s->nat_type)) - v->public_ip4 = v->local_ip4; } + v->public_ip4 = na->sin_addr.s_addr; } + + /* Классификация по адресу, а не по type из конфига: + * приватный локальный адрес → можем ходить в LAN (has_priv); + * исходящие считаем доступными (интернет + локалка). */ + int local_public = v->local_ip4 && ip_is_public(v->local_ip4); + v->has_priv = (uint8_t)!local_public; + v->has_inet = 1; + /* NAT-результат (главный критерий если есть) — для hairpin-фильтра. */ + v->is_eim = (uint8_t)nat_is_eim(s->nat_type); + + if (!v->public_ip4 && local_public) v->public_ip4 = v->local_ip4; + v->role = local_public ? SOCK_ROLE_DIRECT : (v->is_eim ? SOCK_ROLE_DIRECT : SOCK_ROLE_NAT); + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "sock_view[%d] sock=%s type=%d nat=%d role=%d inet=%d priv=%d eim=%d tcp=%d local=%08x public=%08x", n - 1, s->name, s->type, s->nat_type, v->role, v->has_inet, v->has_priv, v->is_eim, v->is_tcp, v->local_ip4, v->public_ip4); diff --git a/src/routing_layer/sock_match.h b/src/routing_layer/sock_match.h index 3d7a527d..e134710e 100644 --- a/src/routing_layer/sock_match.h +++ b/src/routing_layer/sock_match.h @@ -65,22 +65,25 @@ struct ETCP_SOCKET; * Плоское представление нашего сокета для сопоставления. * "ip сокета" — local_ip4 (interface), "публичный" — public_ip4 (NAT/прямой), * "тип" — role + флаги has_inet/has_priv/is_eim. + * + * Флаги вычисляются из КЛАССИФИКАЦИИ АДРЕСА и результата NAT-детекции, + * а не из type сокета в конфиге: + * has_priv — локальный IPv4 приватный/не-маршрутизируемый (можем ходить в LAN); + * has_inet — исходящие считаем доступными в интернет (всегда 1); + * is_eim — результат NAT-детекции EIM (для hairpin-фильтра). */ struct sock_view { struct ETCP_SOCKET* sock; - uint8_t role; /* SOCK_ROLE_* */ - uint8_t has_inet; /* 1 — есть интернет (role != LOCAL) */ - uint8_t has_priv; /* 1 — есть приватный интерфейс (type != PUBLIC/UNKNOWN) */ + uint8_t role; /* SOCK_ROLE_* (производное, для логов) */ + uint8_t has_inet; /* 1 — интернет доступен */ + uint8_t has_priv; /* 1 — есть приватный (LAN) интерфейс */ uint8_t is_eim; /* 1 — NAT + EIM (для hairpin) */ uint8_t family; /* AF_INET / AF_INET6 */ uint8_t is_tcp; uint32_t local_ip4; /* interface_addr, network order (0 если нет) */ - uint32_t public_ip4; /* nat_addr / interface если direct, network order (0 если нет) */ + uint32_t public_ip4; /* nat_addr / interface если public, network order (0 если нет) */ }; -/* Роль нашего сокета по конфиг-типу, NAT-типу и доступности интернета. */ -uint8_t sock_role_of(uint8_t type, uint8_t nat_type, int ntp_reachable); - /* Класс адреса пира по типу адреса (TOPO_ADDR_*), конфиг-типу и NAT-типу сокета. */ uint8_t sock_target_class(uint8_t a_type, uint8_t tcfg, uint8_t tnat); diff --git a/src/transport_layer/auto_socket.c b/src/transport_layer/auto_socket.c index ff5cc5ad..6a32bcf6 100644 --- a/src/transport_layer/auto_socket.c +++ b/src/transport_layer/auto_socket.c @@ -87,23 +87,6 @@ #define AS_PROTO_UDP 0 #define AS_PROTO_TCP 1 -/* Классифицирует IPv4-адрес: 1 = глобальный/публичный (можно использовать как PUBLIC-сокет), - * 0 = приватный, CGNAT, loopback, link-local, multicast или зарезервированный. - * Определяет тип создаваемого сокета по адресам на интерфейсе. */ -static int is_ipv4_public(uint32_t addr_be) { - uint32_t a = ntohl(addr_be); - if ((a & 0xFF000000u) == 0x0A000000u) return 0; /* 10.0.0.0/8 */ - if ((a & 0xFFF00000u) == 0xAC100000u) return 0; /* 172.16.0.0/12 */ - if ((a & 0xFFFF0000u) == 0xC0A80000u) return 0; /* 192.168.0.0/16 */ - if ((a & 0xFF000000u) == 0x7F000000u) return 0; /* 127.0.0.0/8 */ - if ((a & 0xFFFF0000u) == 0xA9FE0000u) return 0; /* 169.254.0.0/16 */ - if ((a & 0xFF000000u) == 0x00000000u) return 0; /* 0.0.0.0/8 */ - if ((a & 0xF0000000u) == 0xE0000000u) return 0; /* 224.0.0.0/4 */ - if ((a & 0xF0000000u) == 0xF0000000u) return 0; /* 240.0.0.0/4 */ - if ((a & 0xFFC00000u) == 0x64400000u) return 0; /* 100.64.0.0/10 (CGNAT) */ - return 1; -} - /* Классифицирует IPv6-адрес для определения типа сокета: * AS_V6_LL = link-local (fe80::) — создаём NAT-сокет * AS_V6_LOC = ULA (fc00::/fd00::) — локальный, NAT-сокет @@ -648,7 +631,7 @@ static int scan_and_classify_iface(uint32_t ifindex, const char* ifname, struct sockaddr_in* sin = (struct sockaddr_in*)ifa->ifa_addr; if (sin->sin_addr.s_addr == INADDR_ANY) continue; *out_has_v4 = 1; - if (is_ipv4_public(sin->sin_addr.s_addr)) + if (ip_is_public(sin->sin_addr.s_addr)) *out_v4_type = CFG_SERVER_TYPE_PUBLIC; } else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ifa->ifa_addr; @@ -754,7 +737,7 @@ static int scan_and_classify_iface(uint32_t ifindex, const char* ifname, const struct sockaddr_in* sin = (const struct sockaddr_in*)u->Address.lpSockaddr; if (sin->sin_addr.s_addr == INADDR_ANY) continue; *out_has_v4 = 1; - if (is_ipv4_public(sin->sin_addr.s_addr)) *out_v4_type = CFG_SERVER_TYPE_PUBLIC; + if (ip_is_public(sin->sin_addr.s_addr)) *out_v4_type = CFG_SERVER_TYPE_PUBLIC; } else if (u->Address.lpSockaddr->sa_family == AF_INET6) { const struct sockaddr_in6* sin6 = (const struct sockaddr_in6*)u->Address.lpSockaddr; int cls = v6_classify(sin6->sin6_addr.s6_addr); @@ -1437,6 +1420,22 @@ static void auto_socket_reconcile_config(struct AUTO_SOCKET* as) { struct UTUN_INSTANCE* inst = as->instance; if (!inst || !inst->config) return; + /* Тип сокета определяем по фактическому адресу (не доверяем type из конфига): + * публичный адрес → PUBLIC, приватный/link-local → NAT. */ + for (struct CFG_SERVER* s = inst->config->servers; s; s = s->next) { + uint8_t by_addr = CFG_SERVER_TYPE_NAT; + if (s->ip.ss_family == AF_INET) { + if (ip_is_public(((const struct sockaddr_in*)&s->ip)->sin_addr.s_addr)) + by_addr = CFG_SERVER_TYPE_PUBLIC; + } else if (s->ip.ss_family == AF_INET6) { + if (v6_classify(((const struct sockaddr_in6*)&s->ip)->sin6_addr.s6_addr) == AS_V6_DIR) + by_addr = CFG_SERVER_TYPE_PUBLIC; + } + if (s->type != by_addr) + DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] server %s type %d → %d (by address)", s->name, s->type, by_addr); + s->type = by_addr; + } + /* Проход 1: удалить сокеты, которых нет в новом списке, либо которые изменились. */ struct ETCP_SOCKET* sock = inst->etcp_sockets; while (sock) { diff --git a/tests/test_sock_match.c b/tests/test_sock_match.c index c3fbff47..a6eb1d67 100644 --- a/tests/test_sock_match.c +++ b/tests/test_sock_match.c @@ -11,6 +11,7 @@ #include "../src/config_parser.h" #include "../src/transport_layer/etcp_connections.h" #include "../src/routing_layer/topo_node.h" +#include "../lib/platform_compat.h" static int fails = 0; static int checks = 0; @@ -23,21 +24,21 @@ static struct sock_view mk_view(uint8_t role, uint8_t has_inet, uint8_t has_priv return v; } -static void test_role_of(void) { - /* config -> role, при ntp_reachable=1 */ - CHECK(sock_role_of(CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_DIRECT, "public -> DIRECT"); - CHECK(sock_role_of(CFG_SERVER_TYPE_UNKNOWN, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_DIRECT, "unknown -> DIRECT"); - CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, 1) == SOCK_ROLE_DIRECT, "nat+EIM -> DIRECT"); - CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_DIRECT,1) == SOCK_ROLE_DIRECT, "nat+DIRECT -> DIRECT"); - CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT,1) == SOCK_ROLE_NAT, "nat+STRICT -> NAT"); - CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_NAT, "nat+unknown -> NAT"); - CHECK(sock_role_of(CFG_SERVER_TYPE_PRIVATE, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_NAT, "private -> NAT"); - CHECK(sock_role_of(CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_LOCAL, "local -> LOCAL"); - /* NTP offline: все кроме LOCAL демотируются */ - CHECK(sock_role_of(CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, 0) == SOCK_ROLE_LOCAL, "offline public -> LOCAL"); - CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, 0) == SOCK_ROLE_LOCAL, "offline nat+EIM -> LOCAL"); - CHECK(sock_role_of(CFG_SERVER_TYPE_PRIVATE, NAT_TYPE_UNKNOWN, 0) == SOCK_ROLE_LOCAL, "offline private -> LOCAL"); - CHECK(sock_role_of(CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 0) == SOCK_ROLE_LOCAL, "offline local -> LOCAL"); +/* Классификация IPv4-адреса (network byte order): публичный/маршрутизируемый или нет. + * Адрес задаём «логически» (старший байт = первый октет) и переводим в s_addr через htonl. */ +static void test_ip_is_public(void) { + CHECK(ip_is_public(htonl(0x08080808u)) == 1, "8.8.8.8 public"); + CHECK(ip_is_public(htonl(0x01020304u)) == 1, "1.2.3.4 public"); + CHECK(ip_is_public(htonl(0xC0A80101u)) == 0, "192.168.1.1 private"); + CHECK(ip_is_public(htonl(0x0A000001u)) == 0, "10.0.0.1 private"); + CHECK(ip_is_public(htonl(0xAC100001u)) == 0, "172.16.0.1 private"); + CHECK(ip_is_public(htonl(0xAC1F0001u)) == 0, "172.31.0.1 private"); + CHECK(ip_is_public(htonl(0x64400001u)) == 0, "100.64.0.1 CGNAT"); + CHECK(ip_is_public(htonl(0x7F000001u)) == 0, "127.0.0.1 loopback"); + CHECK(ip_is_public(htonl(0xA9FE0101u)) == 0, "169.254.1.1 link-local"); + CHECK(ip_is_public(htonl(0x00000000u)) == 0, "0.0.0.0"); + CHECK(ip_is_public(htonl(0xE0000001u)) == 0, "224.0.0.1 multicast"); + CHECK(ip_is_public(htonl(0xFFFFFFFFu)) == 0, "255.255.255.255 reserved"); } static void test_target_class(void) { @@ -78,7 +79,7 @@ static void test_match(void) { } int main(void) { - test_role_of(); + test_ip_is_public(); test_target_class(); test_match(); if (fails) { printf("RESULT: %d/%d failed\n", fails, checks); return 1; }