diff --git a/lib/platform_compat.c b/lib/platform_compat.c index 796345c9..d016a715 100644 --- a/lib/platform_compat.c +++ b/lib/platform_compat.c @@ -24,9 +24,17 @@ #include #include #include + #include + #include #ifndef IFA_F_TEMPORARY #define IFA_F_TEMPORARY 0x01 #endif + #ifndef IFA_F_MANAGETEMPADDR + #define IFA_F_MANAGETEMPADDR 0x100 + #endif + #ifndef IFA_F_STABLE_PRIVACY + #define IFA_F_STABLE_PRIVACY 0x800 + #endif #endif /* @@ -106,6 +114,87 @@ int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* ad return found ? 0 : -1; } +/* get_interface_ipv6_addr_nl — netlink-based IPv6 address lookup with proper IFA_F_* flags + * + * getifaddrs() ifa_flags contains interface flags (IFF_UP etc.), NOT address flags + * (IFA_F_TEMPORARY). To properly distinguish temporary vs permanent IPv6 addresses, + * we must use netlink RTM_GETADDR which exposes IFA_FLAGS attribute with real flags: + * - Temporary: IFA_F_TEMPORARY=1, IFA_F_MANAGETEMPADDR=0 + * - Stable/mngtmp: IFA_F_TEMPORARY=1, IFA_F_MANAGETEMPADDR=1 + * - Legacy static: IFA_F_TEMPORARY=0, IFA_F_MANAGETEMPADDR=0 + * + * prefer_stable=1: prefer permanent/stable (IFA_F_MANAGETEMPADDR or !IFA_F_TEMPORARY) + * prefer_stable=0: prefer temporary (IFA_F_TEMPORARY && !IFA_F_MANAGETEMPADDR) + * Fallback: first non-link-local address */ +int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t* addr_out) { + if (!addr_out || !netif_index) return -1; + memset(addr_out, 0, 16); + + int nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (nl_sock < 0) return -1; + + struct { + struct nlmsghdr nlh; + struct ifaddrmsg ifa; + char attrbuf[64]; + } req; + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.nlh.nlmsg_type = RTM_GETADDR; + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.nlh.nlmsg_seq = 1; + req.ifa.ifa_family = AF_INET6; + req.ifa.ifa_index = netif_index; + + if (send(nl_sock, &req, req.nlh.nlmsg_len, 0) < 0) { close(nl_sock); return -1; } + + uint8_t fallback[16], stable_addr[16], temp_addr[16]; + int has_fallback = 0, has_stable = 0, has_temp = 0; + + char buf[8192]; + int done = 0; + while (!done) { + ssize_t len = recv(nl_sock, buf, sizeof(buf), 0); + if (len < 0) break; + struct nlmsghdr* nlh = (struct nlmsghdr*)buf; + for (; NLMSG_OK(nlh, (size_t)len); nlh = NLMSG_NEXT(nlh, len)) { + if (nlh->nlmsg_type == NLMSG_DONE) { done = 1; break; } + if (nlh->nlmsg_type != RTM_NEWADDR) continue; + + struct ifaddrmsg* ifa = (struct ifaddrmsg*)NLMSG_DATA(nlh); + if (ifa->ifa_family != AF_INET6) continue; + if (ifa->ifa_index != netif_index) continue; + + uint8_t* addr = NULL; + uint32_t ifa_flags = 0; + struct rtattr* rta = IFA_RTA(ifa); + int rta_len = IFA_PAYLOAD(nlh); + for (; RTA_OK(rta, rta_len); rta = RTA_NEXT(rta, rta_len)) { + if (rta->rta_type == IFA_ADDRESS) addr = RTA_DATA(rta); + if (rta->rta_type == IFA_FLAGS) ifa_flags = *(uint32_t*)RTA_DATA(rta); + } + if (!addr) continue; + if ((addr[0] & 0xfe) == 0xfe && addr[1] == 0x80) continue; // skip link-local + + int is_temp = (ifa_flags & IFA_F_TEMPORARY) != 0; + int is_mngtmp = (ifa_flags & IFA_F_MANAGETEMPADDR) != 0; + + if (!has_fallback) { memcpy(fallback, addr, 16); has_fallback = 1; } + + if (!has_stable && (!is_temp || is_mngtmp)) { memcpy(stable_addr, addr, 16); has_stable = 1; } + if (!has_temp && is_temp && !is_mngtmp) { memcpy(temp_addr, addr, 16); has_temp = 1; } + } + } + close(nl_sock); + + if (prefer_stable && has_stable) { memcpy(addr_out, stable_addr, 16); return 0; } + if (!prefer_stable && has_temp) { memcpy(addr_out, temp_addr, 16); return 0; } + if (has_stable) { memcpy(addr_out, stable_addr, 16); return 0; } + if (has_temp) { memcpy(addr_out, temp_addr, 16); return 0; } + if (has_fallback) { memcpy(addr_out, fallback, 16); return 0; } + return -1; +} + uint32_t get_default_route_netif_index(int family) { if (family != AF_INET && family != AF_INET6) return 0; @@ -191,6 +280,12 @@ int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* ad return -1; } +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); + return -1; +} + uint32_t get_default_route_netif_index(int family) { (void)family; return 0; diff --git a/lib/platform_compat.h b/lib/platform_compat.h index b964a726..f8387f55 100644 --- a/lib/platform_compat.h +++ b/lib/platform_compat.h @@ -189,6 +189,12 @@ uint32_t get_interface_ip_by_index(uint32_t netif_index); // Returns 0 on success (fills addr_out with 16 bytes), -1 on error or not found int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out); +// Get IPv6 address using netlink (Linux-only, accurate IFA_F_* flags) +// prefer_stable=1: prefer permanent/stable (IFA_F_MANAGETEMPADDR) over temporary +// prefer_stable=0: prefer temporary over stable +// Returns 0 on success (fills addr_out with 16 bytes), -1 on error +int get_interface_ipv6_addr_nl(uint32_t netif_index, int prefer_stable, uint8_t* addr_out); + // Determine the interface index for the default route (outgoing traffic) // Creates a temp UDP socket, connects to a well-known address to force // kernel routing, then matches the chosen source IP to getifaddrs() diff --git a/src/transport_layer/etcp_connections.c b/src/transport_layer/etcp_connections.c index 004f20ed..09753c3a 100644 --- a/src/transport_layer/etcp_connections.c +++ b/src/transport_layer/etcp_connections.c @@ -519,7 +519,7 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S } int temp = (server->ipv6_mode == CFG_IPV6_MODE_TEMPORARY) ? 1 : 0; uint8_t v6addr[16]; - if (get_interface_ipv6_by_index(netif_index, temp, v6addr) != 0) { + if (get_interface_ipv6_addr_nl(netif_index, !temp, v6addr) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Failed to get %s IPv6 from netif_index=%u for socket %s", temp ? "temporary" : "permanent", netif_index, name); u_free(e_sock); @@ -649,8 +649,8 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S uint16_t v6if = (netif_index > 0) ? netif_index : get_default_route_netif_index(AF_INET6); if (v6if > 0) { uint8_t v6addr[16]; - int got = get_interface_ipv6_by_index(v6if, 0, v6addr); - if (got != 0) got = get_interface_ipv6_by_index(v6if, 1, v6addr); + int got = get_interface_ipv6_addr_nl(v6if, 1, v6addr); + if (got != 0) got = get_interface_ipv6_addr_nl(v6if, 0, v6addr); if (got == 0) { struct sockaddr_in6* if6 = (struct sockaddr_in6*)&e_sock->interface_addr; if6->sin6_family = AF_INET6;