Browse Source

socket_monitor: cross-platform support (Linux netlink, FreeBSD AF_ROUTE, Windows IP Helper)

- platform_compat: get_interface_ipv6_addr_nl stub uses get_interface_ipv6_by_index fallback
  - socket_monitor: extract common update_if_addr/fire_one, platform dispatch
  - FreeBSD: PF_ROUTE socket with RTM_IFINFO/NEWADDR/ADD event monitoring
  - Windows: NotifyUnicastIpAddressChange + NotifyRouteChange2 via uasync_post
topo_upd
Evgeny 2 months ago
parent
commit
26f4da1b7c
  1. 7
      lib/platform_compat.c
  2. 233
      src/transport_layer/socket_monitor.c

7
lib/platform_compat.c

@ -199,8 +199,11 @@ int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t*
} }
#else #else
int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t* addr_out) { 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) return -1;
if (addr_out) memset(addr_out, 0, 16); 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; return -1;
} }
#endif #endif

233
src/transport_layer/socket_monitor.c

@ -13,26 +13,39 @@
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#ifndef _WIN32 #ifdef __linux__
#include <linux/netlink.h> #include <linux/netlink.h>
#include <linux/rtnetlink.h> #include <linux/rtnetlink.h>
#include <net/if.h> #include <net/if.h>
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
#include <net/route.h>
#include <net/if.h>
#include <net/if_dl.h>
#elif defined(_WIN32)
#include <iphlpapi.h>
#include <netioapi.h>
#include <ws2ipdef.h>
#endif #endif
struct SOCKET_MONITOR { struct SOCKET_MONITOR {
struct UTUN_INSTANCE* instance; struct UTUN_INSTANCE* instance;
socket_t nl_sock;
void* uasync_handle; void* uasync_handle;
int seq;
uint32_t addr_changes; uint32_t addr_changes;
uint32_t link_changes; uint32_t link_changes;
uint32_t route_changes; uint32_t route_changes;
uint32_t socket_updates; 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) { static void socket_monitor_update_if_addr(struct ETCP_SOCKET* es) {
if (es->local_addr.ss_family == AF_INET) { if (es->local_addr.ss_family == AF_INET) {
if (es->netif_index > 0) { 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) { static void socket_monitor_fire_one(struct SOCKET_MONITOR* sm, struct ETCP_SOCKET* es) {
if (es->netif_index != ifindex) return;
struct sockaddr_storage old_if_addr = es->interface_addr; struct sockaddr_storage old_if_addr = es->interface_addr;
socket_monitor_update_if_addr(es); socket_monitor_update_if_addr(es);
if (memcmp(&old_if_addr, &es->interface_addr, sizeof(old_if_addr)) != 0) { 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) { static void socket_monitor_handle_addr(struct SOCKET_MONITOR* sm, struct ifaddrmsg* ifa, int msg_type) {
sm->addr_changes++; sm->addr_changes++;
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "addr event: type=%s ifindex=%u family=%d", 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 #endif
/* ===== Public API ===== */
int socket_monitor_init(struct UTUN_INSTANCE* instance) { int socket_monitor_init(struct UTUN_INSTANCE* instance) {
if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "instance is NULL"); return -1; } 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; } if (instance->socket_monitor) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socket monitor already initialized"); return -1; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "initializing socket monitor"); DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "initializing socket monitor");
#ifdef _WIN32 #ifdef __linux__
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor not available on Windows");
return 0;
#else
struct SOCKET_MONITOR* sm = u_calloc(1, sizeof(struct SOCKET_MONITOR)); struct SOCKET_MONITOR* sm = u_calloc(1, sizeof(struct SOCKET_MONITOR));
if (!sm) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "alloc failed"); return -1; } if (!sm) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "alloc failed"); return -1; }
sm->instance = instance; sm->instance = instance;
@ -240,15 +369,68 @@ int socket_monitor_init(struct UTUN_INSTANCE* instance) {
instance->socket_monitor = sm; instance->socket_monitor = sm;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor initialized: nl_sock=%d", sm->nl_sock); DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socket monitor initialized: nl_sock=%d", sm->nl_sock);
return 0; 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 #endif
} }
void socket_monitor_destroy(struct UTUN_INSTANCE* instance) { void socket_monitor_destroy(struct UTUN_INSTANCE* instance) {
if (!instance) return; if (!instance) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "destroying socket monitor"); DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "destroying socket monitor");
#ifdef _WIN32
return; #ifdef __linux__
#else
struct SOCKET_MONITOR* sm = (struct SOCKET_MONITOR*)instance->socket_monitor; struct SOCKET_MONITOR* sm = (struct SOCKET_MONITOR*)instance->socket_monitor;
if (!sm) return; if (!sm) return;
instance->socket_monitor = NULL; 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", 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); sm->nl_sock, sm->addr_changes, sm->link_changes, sm->route_changes, sm->socket_updates);
u_free(sm); 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 #endif
} }

Loading…
Cancel
Save