Browse Source

ipv6: use netlink RTM_GETADDR to properly detect permanent vs temporary IPv6 addresses

getifaddrs() ifa_flags contains interface flags (IFF_UP etc.), NOT address
flags (IFA_F_TEMPORARY). Since IFA_F_TEMPORARY == IFA_F_SECONDARY == 0x01,
and 0x01 in interface flags is IFF_UP, ALL addresses appeared as 'temporary',
making the permanent>temporary priority non-functional.

New netlink-based get_interface_ipv6_addr_nl() reads real IFA_FLAGS from
RTM_GETADDR response, correctly identifying:
- 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

Verified: wlo1 permanent (3a7a:eff:fef5:9637) now correctly selected
for prefer_stable=1, temporary (1fb5:b7c:7538:dcf4) for prefer_stable=0.
topo_upd
evgeny 2 months ago
parent
commit
fdbb5b7bc0
  1. 95
      lib/platform_compat.c
  2. 6
      lib/platform_compat.h
  3. 6
      src/transport_layer/etcp_connections.c

95
lib/platform_compat.c

@ -24,9 +24,17 @@
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#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;

6
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()

6
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;

Loading…
Cancel
Save