|
|
|
|
@ -74,6 +74,7 @@
|
|
|
|
|
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) |
|
|
|
|
#include <net/route.h> |
|
|
|
|
#include <net/if_dl.h> |
|
|
|
|
#include <sys/sysctl.h> |
|
|
|
|
#include <fcntl.h> |
|
|
|
|
#elif defined(_WIN32) |
|
|
|
|
#include <iphlpapi.h> |
|
|
|
|
@ -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; |
|
|
|
|
|