diff --git a/lib/platform_compat.c b/lib/platform_compat.c index 3c1dc8f8..6e14af8d 100644 --- a/lib/platform_compat.c +++ b/lib/platform_compat.c @@ -199,8 +199,11 @@ int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t* } #else int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t* addr_out) { - (void)netif_index; (void)prefer_stable; - if (addr_out) memset(addr_out, 0, 16); + if (!addr_out) return -1; + memset(addr_out, 0, 16); + int temporary = prefer_stable ? 0 : 1; + if (get_interface_ipv6_by_index(netif_index, temporary, addr_out) == 0) return 0; + if (get_interface_ipv6_by_index(netif_index, !temporary, addr_out) == 0) return 0; return -1; } #endif diff --git a/src/transport_layer/socket_monitor.c b/src/transport_layer/socket_monitor.c index 17dfa6f8..34d8c9a2 100644 --- a/src/transport_layer/socket_monitor.c +++ b/src/transport_layer/socket_monitor.c @@ -13,26 +13,39 @@ #include #include -#ifndef _WIN32 +#ifdef __linux__ #include #include #include +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#include +#include +#include +#elif defined(_WIN32) +#include +#include +#include #endif struct SOCKET_MONITOR { struct UTUN_INSTANCE* instance; - socket_t nl_sock; void* uasync_handle; - int seq; uint32_t addr_changes; uint32_t link_changes; uint32_t route_changes; uint32_t socket_updates; +#ifdef __linux__ + socket_t nl_sock; +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + socket_t route_sock; +#elif defined(_WIN32) + HANDLE addr_notify_handle; + HANDLE route_notify_handle; +#endif }; -static void socket_monitor_read_cb(int fd, void* arg); +/* ===== Common functions (all platforms) ===== */ -#ifndef _WIN32 static void socket_monitor_update_if_addr(struct ETCP_SOCKET* es) { if (es->local_addr.ss_family == AF_INET) { if (es->netif_index > 0) { @@ -103,8 +116,7 @@ static void socket_monitor_update_if_addr(struct ETCP_SOCKET* es) { } } -static void socket_monitor_check_and_fire(struct SOCKET_MONITOR* sm, struct ETCP_SOCKET* es, uint32_t ifindex) { - if (es->netif_index != ifindex) return; +static void socket_monitor_fire_one(struct SOCKET_MONITOR* sm, struct ETCP_SOCKET* es) { struct sockaddr_storage old_if_addr = es->interface_addr; socket_monitor_update_if_addr(es); if (memcmp(&old_if_addr, &es->interface_addr, sizeof(old_if_addr)) != 0) { @@ -115,6 +127,14 @@ static void socket_monitor_check_and_fire(struct SOCKET_MONITOR* sm, struct ETCP } } +static void socket_monitor_check_and_fire(struct SOCKET_MONITOR* sm, struct ETCP_SOCKET* es, uint32_t ifindex) { + if (es->netif_index != ifindex) return; + socket_monitor_fire_one(sm, es); +} + +/* ===== Linux: netlink ===== */ +#ifdef __linux__ + static void socket_monitor_handle_addr(struct SOCKET_MONITOR* sm, struct ifaddrmsg* ifa, int msg_type) { sm->addr_changes++; DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "addr event: type=%s ifindex=%u family=%d", @@ -196,17 +216,126 @@ static void socket_monitor_read_cb(int fd, void* arg) { } } } + +/* ===== FreeBSD: AF_ROUTE routing socket ===== */ +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + +static void socket_monitor_bsd_read_cb(int fd, void* arg) { + struct SOCKET_MONITOR* sm = (struct SOCKET_MONITOR*)arg; + char buf[8192]; + for (;;) { + ssize_t n = read(fd, buf, sizeof(buf)); + if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) break; + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "route socket read fd=%d failed: %s", fd, strerror(errno)); + break; + } + if (n < (ssize_t)sizeof(struct rt_msghdr)) break; + + struct rt_msghdr* rtm = (struct rt_msghdr*)buf; + if (rtm->rtm_version != RTM_VERSION) continue; + + switch (rtm->rtm_type) { + case RTM_IFINFO: + case RTM_IFANNOUNCE: + sm->link_changes++; + DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "link event: type=%d", rtm->rtm_type); + { + struct ETCP_SOCKET* es = sm->instance->etcp_sockets; + while (es) { + etcp_socket_cbk_fire(es, ETCP_SOCKET_EVENT_STATUS_CHANGED); + socket_monitor_fire_one(sm, es); + es = es->next; + } + } + break; + case RTM_NEWADDR: + case RTM_DELADDR: + sm->addr_changes++; + DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "addr event: type=%s", rtm->rtm_type == RTM_NEWADDR ? "NEW" : "DEL"); + { + struct ETCP_SOCKET* es = sm->instance->etcp_sockets; + while (es) { socket_monitor_fire_one(sm, es); es = es->next; } + } + break; + case RTM_ADD: + case RTM_DELETE: + sm->route_changes++; + DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "route event: type=%s", rtm->rtm_type == RTM_ADD ? "ADD" : "DELETE"); + { + struct ETCP_SOCKET* es = sm->instance->etcp_sockets; + while (es) { + if (es->netif_index != 0 || es->local_addr.ss_family == 0) { es = es->next; continue; } + int is_any = 0; + if (es->local_addr.ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)&es->local_addr; + is_any = (sin->sin_addr.s_addr == 0); + } else if (es->local_addr.ss_family == AF_INET6) { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&es->local_addr; + is_any = (memcmp(&sin6->sin6_addr, &in6addr_any, 16) == 0); + } + if (is_any) socket_monitor_fire_one(sm, es); + es = es->next; + } + } + break; + } + } +} + +/* ===== Windows: IP Helper callbacks ===== */ +#elif defined(_WIN32) + +static void process_win_addr_notify(void* arg) { + struct SOCKET_MONITOR* sm = (struct SOCKET_MONITOR*)arg; + struct ETCP_SOCKET* es = sm->instance->etcp_sockets; + while (es) { socket_monitor_fire_one(sm, es); es = es->next; } +} + +static void process_win_route_notify(void* arg) { + struct SOCKET_MONITOR* sm = (struct SOCKET_MONITOR*)arg; + struct ETCP_SOCKET* es = sm->instance->etcp_sockets; + while (es) { + if (es->netif_index != 0 || es->local_addr.ss_family == 0) { es = es->next; continue; } + int is_any = 0; + if (es->local_addr.ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)&es->local_addr; + is_any = (sin->sin_addr.s_addr == 0); + } else if (es->local_addr.ss_family == AF_INET6) { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&es->local_addr; + is_any = (memcmp(&sin6->sin6_addr, &in6addr_any, 16) == 0); + } + if (is_any) socket_monitor_fire_one(sm, es); + es = es->next; + } +} + +static void WINAPI addr_change_cb(PVOID ctx, PMIB_UNICASTIPADDRESS_ROW row, MIB_NOTIFICATION_TYPE type) { + (void)row; (void)type; + struct SOCKET_MONITOR* sm = (struct SOCKET_MONITOR*)ctx; + sm->addr_changes++; + DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "addr event: type=%d", (int)type); + uasync_post(sm->instance->ua, process_win_addr_notify, sm); +} + +static void WINAPI route_change_cb(PVOID ctx, PMIB_IPFORWARD_ROW2 row, MIB_NOTIFICATION_TYPE type) { + (void)row; (void)type; + struct SOCKET_MONITOR* sm = (struct SOCKET_MONITOR*)ctx; + sm->route_changes++; + DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "route event: type=%d", (int)type); + uasync_post(sm->instance->ua, process_win_route_notify, sm); +} + #endif +/* ===== Public API ===== */ + int socket_monitor_init(struct UTUN_INSTANCE* instance) { if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "instance is NULL"); return -1; } if (instance->socket_monitor) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socket monitor already initialized"); return -1; } DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "initializing socket monitor"); -#ifdef _WIN32 - DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor not available on Windows"); - return 0; -#else +#ifdef __linux__ struct SOCKET_MONITOR* sm = u_calloc(1, sizeof(struct SOCKET_MONITOR)); if (!sm) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "alloc failed"); return -1; } sm->instance = instance; @@ -240,15 +369,68 @@ int socket_monitor_init(struct UTUN_INSTANCE* instance) { instance->socket_monitor = sm; DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor initialized: nl_sock=%d", sm->nl_sock); return 0; + +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + struct SOCKET_MONITOR* sm = u_calloc(1, sizeof(struct SOCKET_MONITOR)); + if (!sm) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "alloc failed"); return -1; } + sm->instance = instance; + sm->route_sock = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); + if (sm->route_sock < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "failed to create route socket: %s", strerror(errno)); + u_free(sm); + return -1; + } + if (fcntl(sm->route_sock, F_SETFL, O_NONBLOCK) < 0) { + DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "failed to set route socket non-blocking: %s", strerror(errno)); + } + sm->uasync_handle = uasync_add_socket(instance->ua, sm->route_sock, socket_monitor_bsd_read_cb, NULL, NULL, sm); + if (!sm->uasync_handle) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "failed to register route socket fd=%d with uasync", sm->route_sock); + close(sm->route_sock); + u_free(sm); + return -1; + } + instance->socket_monitor = sm; + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor initialized: route_sock=%d", sm->route_sock); + return 0; + +#elif defined(_WIN32) + struct SOCKET_MONITOR* sm = u_calloc(1, sizeof(struct SOCKET_MONITOR)); + if (!sm) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "alloc failed"); return -1; } + sm->instance = instance; + + DWORD ret = NotifyUnicastIpAddressChange(AF_UNSPEC, addr_change_cb, sm, FALSE, &sm->addr_notify_handle); + if (ret != NO_ERROR) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "NotifyUnicastIpAddressChange failed: %lu", (unsigned long)ret); + u_free(sm); + return -1; + } + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "NotifyUnicastIpAddressChange registered: handle=%p", sm->addr_notify_handle); + + ret = NotifyRouteChange2(AF_UNSPEC, route_change_cb, sm, FALSE, &sm->route_notify_handle); + if (ret != NO_ERROR) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "NotifyRouteChange2 failed: %lu", (unsigned long)ret); + CancelMibChangeNotify2(sm->addr_notify_handle); + u_free(sm); + return -1; + } + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "NotifyRouteChange2 registered: handle=%p", sm->route_notify_handle); + + instance->socket_monitor = sm; + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor initialized via IP Helper"); + return 0; + +#else + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor not available on this platform"); + return 0; #endif } void socket_monitor_destroy(struct UTUN_INSTANCE* instance) { if (!instance) return; DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "destroying socket monitor"); -#ifdef _WIN32 - return; -#else + +#ifdef __linux__ struct SOCKET_MONITOR* sm = (struct SOCKET_MONITOR*)instance->socket_monitor; if (!sm) return; instance->socket_monitor = NULL; @@ -257,5 +439,28 @@ void socket_monitor_destroy(struct UTUN_INSTANCE* instance) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor destroyed: nl_sock=%d addr_changes=%u link_changes=%u route_changes=%u socket_updates=%u", sm->nl_sock, sm->addr_changes, sm->link_changes, sm->route_changes, sm->socket_updates); u_free(sm); + +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + struct SOCKET_MONITOR* sm = (struct SOCKET_MONITOR*)instance->socket_monitor; + if (!sm) return; + instance->socket_monitor = NULL; + uasync_remove_socket(instance->ua, sm->uasync_handle); + close(sm->route_sock); + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor destroyed: route_sock=%d addr_changes=%u link_changes=%u route_changes=%u socket_updates=%u", + sm->route_sock, sm->addr_changes, sm->link_changes, sm->route_changes, sm->socket_updates); + u_free(sm); + +#elif defined(_WIN32) + struct SOCKET_MONITOR* sm = (struct SOCKET_MONITOR*)instance->socket_monitor; + if (!sm) return; + instance->socket_monitor = NULL; + CancelMibChangeNotify2(sm->addr_notify_handle); + CancelMibChangeNotify2(sm->route_notify_handle); + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor destroyed: addr_changes=%u link_changes=%u route_changes=%u socket_updates=%u", + sm->addr_changes, sm->link_changes, sm->route_changes, sm->socket_updates); + u_free(sm); + +#else + (void)instance; #endif }