Add IPv6 support to getifaddrs() on Bsd

Tested on x86_64 MacOs OpenBsd FreeBsd and NetBsd.

There is extra ioctl calls on the ipv6 bsd path
because SIOCGIFCONF doesn't returns the addr flag
or the netmask.

Luckily all bsd systems share the same values for
SIOCGIFAFLAG_IN6 and SIOCGIFNETMASK_IN6.
This commit is contained in:
Hugues Morisset 2025-08-13 11:48:24 +02:00
parent f466d757ec
commit abab1dec63
No known key found for this signature in database
2 changed files with 76 additions and 25 deletions

View file

@ -43,6 +43,7 @@
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/serialize.h"
#include "libc/sock/in.h"
#include "libc/sock/internal.h"
#include "libc/sock/struct/ifconf.h"
#include "libc/sock/struct/ifreq.h"
@ -533,8 +534,7 @@ static int ioctl_siocgifconf_sysv(int fd, struct ifconf *ifc) {
for (p = b, e = p + MIN(bufMax, READ32LE(ifcBsd)); p + 16 + 16 <= e;
p += IsBsd() ? 16 + MAX(16, p[16] & 255) : 40) {
fam = p[IsBsd() ? 17 : 16] & 255;
if (fam != AF_INET)
continue;
if (fam == AF_INET) {
ip = READ32BE(p + 20);
bzero(req, sizeof(*req));
memcpy(req->ifr_name, p, 16);
@ -542,6 +542,26 @@ static int ioctl_siocgifconf_sysv(int fd, struct ifconf *ifc) {
req->ifr_addr.sa_family = fam;
((struct sockaddr_in *)&req->ifr_addr)->sin_addr.s_addr = htonl(ip);
++req;
} else if (fam == AF_INET6) {
// Only BSD systems returns AF_INET6 addresses with SIOCGIFCONF
// BSD don't return flags or prefix length, need to get them later
bzero(req, sizeof(*req));
memcpy(req->ifr_name, p, 16);
void *addr6 = p + 24;
if (IN6_IS_ADDR_LINKLOCAL(addr6)) {
// link-local bsd special https://stackoverflow.com/q/5888359/2838914
req->ifr6_ifindex = ntohs(*((uint16_t *)(p + 26)));
*((uint16_t *)(p + 26)) = 0x0;
req->ifr6_scope = 0x20; // link
} else if (IN6_IS_ADDR_SITELOCAL(addr6)) {
req->ifr6_scope = 0x40; // site
} else if (IN6_IS_ADDR_LOOPBACK(addr6)) {
req->ifr6_scope = 0x10; // host
}
memcpy(&req->ifr6_addr, addr6, 16);
req->ifr_addr.sa_family = fam;
++req;
}
}
ifc->ifc_len = (char *)req - ifc->ifc_buf; /* Adjust len */
}

View file

@ -20,6 +20,7 @@
#include "libc/calls/calls.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/newbie.h"
#include "libc/limits.h"
#include "libc/mem/mem.h"
#include "libc/sock/sock.h"
@ -34,6 +35,9 @@
#include "libc/sysv/consts/sio.h"
#include "libc/sysv/consts/sock.h"
#define SIOCGIFAFLAG_IN6 3240126793 // bsd
#define SIOCGIFNETMASK_IN6 3240126757 // bsd
struct IfAddr {
struct ifaddrs ifaddrs;
char name[IFNAMSIZ];
@ -142,27 +146,27 @@ static int getifaddrs_linux_ip6(struct ifconf *conf) {
* @see tool/viz/getifaddrs.c for example code
*/
int getifaddrs(struct ifaddrs **out_ifpp) {
// printf("%d\n", sizeof(struct ifreq));
int rc = -1;
int fd;
int rc = 0;
int fd, fd6 = -1;
if ((fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0)) != -1) {
char *data;
size_t size;
if ((data = malloc((size = 16384)))) {
struct ifconf conf;
struct ifconf conf, confl6;
conf.ifc_buf = data;
conf.ifc_len = size;
if (!ioctl(fd, SIOCGIFCONF, &conf)) {
if (IsLinux()) {
struct ifconf confl6;
confl6.ifc_buf = data + conf.ifc_len;
confl6.ifc_len = size - conf.ifc_len;
if ((rc = getifaddrs_linux_ip6(&confl6)))
if (IsLinux()) {
rc = getifaddrs_linux_ip6(&confl6);
}
if (rc)
return rc;
conf.ifc_len += confl6.ifc_len;
}
struct ifaddrs *res = 0;
rc = -1;
for (struct ifreq *ifr = (struct ifreq *)data;
(char *)ifr < data + conf.ifc_len; ++ifr) {
uint16_t family = ifr->ifr_addr.sa_family;
@ -207,9 +211,10 @@ int getifaddrs(struct ifaddrs **out_ifpp) {
addr6->ifaddrs.ifa_broadaddr = (struct sockaddr *)&addr6->bstaddr;
addr6->ifaddrs.ifa_data = (void *)&addr6->info;
memcpy(&addr6->name, &ifr->ifr_name, IFNAMSIZ);
addr6->info.addr_flags = ifr->ifr6_flags;
addr6->info.addr_scope = ifr->ifr6_scope;
addr6->info.addr_flags = ifr->ifr6_flags;
memcpy(&addr6->name, &ifr->ifr_name, IFNAMSIZ);
addr6->addr.sin6_family = AF_INET6;
addr6->addr.sin6_port = 0;
@ -222,10 +227,33 @@ int getifaddrs(struct ifaddrs **out_ifpp) {
addr6->netmask.sin6_port = 0;
addr6->netmask.sin6_flowinfo = 0;
addr6->addr.sin6_scope_id = ifr->ifr6_ifindex;
memcpy(&addr6->netmask.sin6_addr, &ifr->ifr6_addr,
if (IsBsd()) { // on bsd we miss prefixlen and addr flags
if (fd6 == -1) {
fd6 = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
}
uint8_t in6req[288]; // BSD struct in6_ifreq
bzero(&in6req, sizeof(in6req));
memcpy(&in6req, &ifr->ifr_name, IFNAMSIZ);
in6req[16] = 28; // sin6_len sizeof(struct sockaddr_in6_bsd)
in6req[17] = AF_INET6; // sin6_family
memcpy(&in6req[24], &addr6->addr.sin6_addr,
sizeof(struct in6_addr)); // sin6_addr
if (!ioctl(fd6, SIOCGIFAFLAG_IN6, &in6req)) {
addr6->info.addr_flags =
*(int *)(&in6req[16]); // ifru_flags6
}
in6req[16] = 28; // sin6_len
in6req[17] = AF_INET6; // sin6_family
if (!ioctl(fd6, SIOCGIFNETMASK_IN6, &in6req)) {
memcpy(&(addr6->netmask.sin6_addr), &in6req[24],
sizeof(struct in6_addr));
*((uint128_t *)&(addr6->netmask.sin6_addr)) &=
(UINT128_MAX >> ifr->ifr6_prefixlen);
}
} else {
int prefixlen = ifr->ifr6_prefixlen;
*((uint128_t *)&(addr6->netmask.sin6_addr)) = htobe128(
prefixlen == 0 ? 0 : (UINT128_MAX << (128 - prefixlen)));
}
if (!ioctl(fd, SIOCGIFFLAGS, ifr)) {
addr6->ifaddrs.ifa_flags = ifr->ifr_flags;
@ -243,6 +271,9 @@ int getifaddrs(struct ifaddrs **out_ifpp) {
free(data);
}
close(fd);
if (fd6 != -1) {
close(fd6);
}
}
return rc;
}