From 5d39083a6dea9be28e1b39932a2659d0e3486796 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 17 Aug 2026 23:43:15 +0300 Subject: [PATCH] =?UTF-8?q?auto=5Fsocket:=20=D0=BF=D1=80=D0=BE=D0=BF=D1=83?= =?UTF-8?q?=D1=81=D0=BA=D0=B0=D1=82=D1=8C=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80?= =?UTF-8?q?=D1=84=D0=B5=D0=B9=D1=81=D1=8B=20=D0=B1=D0=B5=D0=B7=20default-?= =?UTF-8?q?=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83=D1=82=D0=B0=20(=D0=BE?= =?UTF-8?q?=D0=BF=D1=86=D0=B8=D1=8F=20auto=5Fsocket=5Fskip=5Fno=5Fdefault?= =?UTF-8?q?=5Froute,=20default=3Don)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config_parser.c | 6 ++ src/config_parser.h | 1 + src/transport_layer/auto_socket.c | 160 +++++++++++++++++++++++++++++- 3 files changed, 166 insertions(+), 1 deletion(-) diff --git a/src/config_parser.c b/src/config_parser.c index 64cd2a45..7e69d4c3 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -410,6 +410,10 @@ static int parse_global(const char *key, const char *value, struct global_config global->auto_sockets = strcasecmp(value, "yes") == 0 || strcmp(value, "1") == 0; return 0; } + if (strcmp(key, "auto_socket_skip_no_default_route") == 0) { + global->auto_socket_skip_no_default_route = strcasecmp(value, "yes") == 0 || strcmp(value, "1") == 0; + return 0; + } if (strcmp(key, "debug_level") == 0) { return assign_string(global->debug_level, sizeof(global->debug_level), value); } @@ -864,6 +868,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename) cfg->global.keepalive_adaptive = 1; // Default: adaptive enabled cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX) cfg->global.auto_sockets = 0; // Default: disabled, use manual [server] sockets + cfg->global.auto_socket_skip_no_default_route = 1; // Default: в авто-режиме пропускать интерфейсы без default-маршрута cfg->global.tcp_recv_buf = 0; // 0 = OS default cfg->global.firewall_rules = NULL; cfg->global.firewall_rule_count = 0; @@ -1161,6 +1166,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha cfg->global.keepalive_adaptive = 1; cfg->global.bbr_max_cwnd = 1048576; cfg->global.auto_sockets = 0; + cfg->global.auto_socket_skip_no_default_route = 1; cfg->global.tcp_recv_buf = 0; cfg->global.firewall_rules = NULL; cfg->global.firewall_rule_count = 0; diff --git a/src/config_parser.h b/src/config_parser.h index 7d874a38..06b9c4d5 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -190,6 +190,7 @@ struct global_config { // TCP proxy server (exit node) configuration ([tcp_proxy_server] section) int tcp_proxy_server_enabled; int auto_sockets; // 1 = auto-create sockets from interfaces, skip [server] + int auto_socket_skip_no_default_route; // 1 = в авто-режиме не создавать сокеты на интерфейсах без default-маршрута int tcp_recv_buf; // SO_RCVBUF для TCP сокетов (0=default ОС) int tcp_proxy_server_fib; // FreeBSD SO_SETFIB, 0=не менять int tcp_proxy_server_so_mark; // Linux SO_MARK, 0=не менять diff --git a/src/transport_layer/auto_socket.c b/src/transport_layer/auto_socket.c index 65709dfe..c19d37d2 100644 --- a/src/transport_layer/auto_socket.c +++ b/src/transport_layer/auto_socket.c @@ -74,6 +74,7 @@ #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #include #include +#include #include #elif defined(_WIN32) #include @@ -787,6 +788,142 @@ static void auto_socket_scan_all(struct AUTO_SOCKET* as) { #endif /* _WIN32 */ +/* ═══════════ default-маршрут: проверка возможности выхода в интернет ═══════════ + * + * Фильтр для авто-режима: интерфейс без default-маршрута (шлюза) не может + * достучаться до публичных пиров и даёт только sendto ENETUNREACH (например + * VirtualBox host-only, изолированные VPN). Такие интерфейсы пропускаем. + * Возвращают 1 если default-маршрут есть, 0 если нет. При невозможности + * определить — 1 (не пропускаем, чтобы не сломать существующие сетапы). */ + +#if defined(__linux__) + +static int iface_has_default_v4(uint32_t ifindex, const char* ifname) { + (void)ifindex; + FILE* f = fopen("/proc/net/route", "r"); + if (!f) return 1; + char line[512]; + int found = 0; + if (fgets(line, sizeof(line), f)) { + while (fgets(line, sizeof(line), f)) { + char iface[IFNAMSIZ]; + unsigned long dest; + if (sscanf(line, "%15s %lx %*x %*x", iface, &dest) == 2) { + if (dest == 0 && strcmp(iface, ifname) == 0) { found = 1; break; } + } + } + } + fclose(f); + return found; +} + +static int iface_has_default_v6(uint32_t ifindex, const char* ifname) { + (void)ifindex; + FILE* f = fopen("/proc/net/ipv6_route", "r"); + if (!f) return 1; + char line[512]; + int found = 0; + while (fgets(line, sizeof(line), f)) { + char dest[33], iface[IFNAMSIZ]; + unsigned int prefixlen; + if (sscanf(line, "%32s %2x %*32s %*2x %*32s %*8x %*8x %*8x %*8x %15s", dest, &prefixlen, iface) == 3) { + if (prefixlen == 0 && strcmp(iface, ifname) == 0) { found = 1; break; } + } + } + fclose(f); + return found; +} + +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + +/* Вытаскивает sockaddr по индексу RTAX_* из rt_msghdr (вместо SA_SIZE — универсально). */ +static struct sockaddr* rta_get(struct rt_msghdr* rtm, int rtax) { + if (!(rtm->rtm_addrs & (1 << rtax))) return NULL; + char* p = (char*)(rtm + 1); + for (int i = 0; i < rtax; i++) { + if (rtm->rtm_addrs & (1 << i)) { + struct sockaddr* sa = (struct sockaddr*)p; + p += 1 + ((((size_t)sa->sa_len - 1) | (sizeof(uint32_t) - 1))); + } + } + return (struct sockaddr*)p; +} + +static int freebsd_iface_has_default(uint32_t ifindex, int family) { + int mib[6] = { CTL_NET, PF_ROUTE, 0, family, NET_RT_DUMP, 0 }; + size_t need = 0; + if (sysctl(mib, 6, NULL, &need, NULL, 0) < 0 || need == 0) return 1; + char* buf = u_malloc(need); + if (!buf) return 1; + if (sysctl(mib, 6, buf, &need, NULL, 0) < 0) { u_free(buf); return 1; } + + int found = 0; + char* p = buf; + char* end = buf + need; + while (p + sizeof(struct rt_msghdr) <= end) { + struct rt_msghdr* rtm = (struct rt_msghdr*)p; + if (rtm->rtm_msglen < sizeof(struct rt_msghdr) || rtm->rtm_msglen > (size_t)(end - p)) break; + struct sockaddr* mask = rta_get(rtm, RTAX_NETMASK); + struct sockaddr_dl* ifp = (struct sockaddr_dl*)rta_get(rtm, RTAX_IFP); + int is_default = 0; + if (mask && ifp && ifp->sdl_family == AF_LINK) { + if (family == AF_INET && mask->sa_family == AF_INET) + is_default = (((struct sockaddr_in*)mask)->sin_addr.s_addr == 0); + else if (family == AF_INET6 && mask->sa_family == AF_INET6) + is_default = IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)mask)->sin6_addr); + } + if (is_default && ifp->sdl_index == ifindex) { found = 1; break; } + p += rtm->rtm_msglen; + } + u_free(buf); + return found; +} + +static int iface_has_default_v4(uint32_t ifindex, const char* ifname) { + (void)ifname; + return freebsd_iface_has_default(ifindex, AF_INET); +} + +static int iface_has_default_v6(uint32_t ifindex, const char* ifname) { + (void)ifname; + return freebsd_iface_has_default(ifindex, AF_INET6); +} + +#elif defined(_WIN32) + +static int iface_has_default_v4(uint32_t ifindex, const char* ifname) { + (void)ifname; + MIB_IPFORWARD_TABLE2* table = NULL; + if (GetIpForwardTable2(AF_INET, &table) != NO_ERROR || !table) return 1; + int found = 0; + for (ULONG i = 0; i < table->NumEntries; i++) { + if (table->Table[i].InterfaceIndex == ifindex && + table->Table[i].DestinationPrefix.PrefixLength == 0) { found = 1; break; } + } + FreeMibTable(table); + return found; +} + +static int iface_has_default_v6(uint32_t ifindex, const char* ifname) { + (void)ifname; + MIB_IPFORWARD_TABLE2* table = NULL; + if (GetIpForwardTable2(AF_INET6, &table) != NO_ERROR || !table) return 1; + int found = 0; + for (ULONG i = 0; i < table->NumEntries; i++) { + if (table->Table[i].InterfaceIndex == ifindex && + table->Table[i].DestinationPrefix.PrefixLength == 0) { found = 1; break; } + } + FreeMibTable(table); + return found; +} + +#else + +static int iface_has_default_v4(uint32_t ifindex, const char* ifname) { (void)ifindex; (void)ifname; return 1; } +static int iface_has_default_v6(uint32_t ifindex, const char* ifname) { (void)ifindex; (void)ifname; return 1; } + +#endif + /* ═══════════ reconcile: сверка и исправление состояния сокетов ═══════════ * * reconcile — ключевой механизм модуля. При любом изменении сети сравнивает @@ -841,6 +978,19 @@ static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char uint8_t v4_need_type = CFG_SERVER_TYPE_NAT, v6_need_type = CFG_SERVER_TYPE_NAT; if (scan_and_classify_iface(ifindex, ifname, &has_v4, &has_v6, &v4_need_type, &v6_need_type) < 0) return; + /* Фильтр авто-режима: интерфейс без default-маршрута не может достать до + * публичных пиров — не создаём на нём сокеты (иначе только sendto ENETUNREACH). */ + if (as->instance->config->global.auto_socket_skip_no_default_route) { + if (has_v4 && !iface_has_default_v4(ifindex, ifname)) { + DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] %s has no default v4 route — skip v4", ifname); + has_v4 = 0; + } + if (has_v6 && !iface_has_default_v6(ifindex, ifname)) { + DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] %s has no default v6 route — skip v6", ifname); + has_v6 = 0; + } + } + struct auto_sock_iface* ifa = as->ifaces; while (ifa && ifa->netif_index != ifindex) ifa = ifa->next; @@ -1020,6 +1170,12 @@ static void handle_nl_link(struct AUTO_SOCKET* as, struct ifinfomsg* ifi, int ms } } +/* При изменении маршрута (RTM_NEWROUTE / RTM_DELROUTE): default-маршрут + * (dst_len==0) мог появиться/исчезнуть на интерфейсе — пересканируем все. */ +static void handle_nl_route(struct AUTO_SOCKET* as, struct rtmsg* rtm) { + if (rtm->rtm_dst_len == 0) auto_socket_scan_all(as); +} + /* Коллбэк uasync для netlink-сокета. Принимает все сообщения netlink, * разбирает их и направляет в handle_nl_addr / handle_nl_link. */ static void auto_socket_netlink_cb(int fd, void* arg) { @@ -1040,6 +1196,8 @@ static void auto_socket_netlink_cb(int fd, void* arg) { handle_nl_addr(as, (struct ifaddrmsg*)NLMSG_DATA(nlh), nlh->nlmsg_type); else if (nlh->nlmsg_type == RTM_NEWLINK || nlh->nlmsg_type == RTM_DELLINK) handle_nl_link(as, (struct ifinfomsg*)NLMSG_DATA(nlh), nlh->nlmsg_type); + else if (nlh->nlmsg_type == RTM_NEWROUTE || nlh->nlmsg_type == RTM_DELROUTE) + handle_nl_route(as, (struct rtmsg*)NLMSG_DATA(nlh)); if (nlh->nlmsg_type == NLMSG_DONE) break; nlh = NLMSG_NEXT(nlh, remaining); } @@ -1056,7 +1214,7 @@ static int auto_socket_init_monitor(struct AUTO_SOCKET* as) { struct sockaddr_nl nladdr; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; - nladdr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; + nladdr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE; if (bind(as->nl_sock, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) { DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] netlink bind: %s — relying on JNI/scan_all for updates", strerror(errno)); close(as->nl_sock); as->nl_sock = -1; return 0;