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.
This commit is contained in:
evgeny committed 2026-08-02 11:13:47 +03:00
1 parent f67bc23cd3
commit fdbb5b7bc0
3 files changed
+104 -3

No files matched your search

+95
View File
@@ -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;