From f1e83d52403060d674161944e849b51f95707c9a Mon Sep 17 00:00:00 2001 From: Hugues Morisset Date: Wed, 21 May 2025 10:20:22 +0200 Subject: [PATCH] Add IPv6 support to getifaddrs() on Linux (#1415) --- libc/sock/ifaddrs.c | 196 ++++++++++++++++++++++++++++++++------- libc/sock/struct/ifreq.h | 15 +++ tool/viz/getifaddrs.c | 78 +++++++++++++++- 3 files changed, 256 insertions(+), 33 deletions(-) diff --git a/libc/sock/ifaddrs.c b/libc/sock/ifaddrs.c index 01c0d8e05..9ea609e09 100644 --- a/libc/sock/ifaddrs.c +++ b/libc/sock/ifaddrs.c @@ -18,13 +18,19 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/sock/ifaddrs.h" #include "libc/calls/calls.h" +#include "libc/calls/syscall-sysv.internal.h" +#include "libc/dce.h" +#include "libc/limits.h" #include "libc/mem/mem.h" #include "libc/sock/sock.h" #include "libc/sock/struct/ifconf.h" #include "libc/sock/struct/ifreq.h" +#include "libc/sock/struct/sockaddr6.h" +#include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/af.h" #include "libc/sysv/consts/iff.h" +#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/sio.h" #include "libc/sysv/consts/sock.h" @@ -36,6 +42,20 @@ struct IfAddr { struct sockaddr_in bstaddr; }; +struct IfAddr6Info { + int addr_scope; + int addr_flags; +}; + +struct IfAddr6 { + struct ifaddrs ifaddrs; + char name[IFNAMSIZ]; + struct sockaddr_in6 addr; + struct sockaddr_in6 netmask; + struct sockaddr_in6 bstaddr; // unused + struct IfAddr6Info info; +}; + /** * Frees network interface address list. */ @@ -48,6 +68,73 @@ void freeifaddrs(struct ifaddrs *ifp) { } } +// hex repr to network order int +static uint128_t hex2no(const char *str) { + uint128_t res = 0; + const int max_quads = sizeof(uint128_t) * 2; + int i = 0; + while ((i < max_quads) && str[i]) { + uint8_t acc = (((str[i] & 0xF) + (str[i] >> 6)) | ((str[i] >> 3) & 0x8)); + acc = acc << 4; + i += 1; + if (str[i]) { + acc = acc | (((str[i] & 0xF) + (str[i] >> 6)) | ((str[i] >> 3) & 0x8)); + i += 1; + } + res = (res >> 8) | (((uint128_t)acc) << ((sizeof(uint128_t) - 1) * 8)); + } + res = res >> ((max_quads - i) * 4); + return res; +} + +/** + * Gets network interface IPv6 address list on linux. + * + * @return 0 on success, or -1 w/ errno + */ +static int getifaddrs_linux_ip6(struct ifconf *conf) { + int fd; + int n = 0; + struct ifreq *ifreq = conf->ifc_req; + const int bufsz = 44 + IFNAMSIZ + 1; + char buf[bufsz + 1]; // one line max size + if ((fd = sys_openat(0, "/proc/net/if_inet6", O_RDONLY, 0)) == -1) { + return -1; + } + + while ((n = sys_read(fd, &buf[n], bufsz - n)) && + ((char *)ifreq < (conf->ifc_buf + conf->ifc_len))) { + // flags linux include/uapi/linux/if_addr.h:44 + // scope linux include/net/ipv6.h:L99 + + // *addr, *index, *plen, *scope, *flags, *ifname + char *s[] = {&buf[0], &buf[33], &buf[36], &buf[39], &buf[42], &buf[45]}; + int ifnamelen = 0; + while (*s[5] == ' ') { + ++s[5]; + } + while (s[5][ifnamelen] > '\n') { + ++ifnamelen; + } + buf[32] = buf[35] = buf[38] = buf[41] = buf[44] = s[5][ifnamelen] = '\0'; + bzero(ifreq, sizeof(*ifreq)); + ifreq->ifr_addr.sa_family = AF_INET6; + memcpy(&ifreq->ifr_name, s[5], ifnamelen); + *((uint128_t *)&ifreq->ifr6_addr) = hex2no(s[0]); + ifreq->ifr6_ifindex = hex2no(s[1]); + ifreq->ifr6_prefixlen = hex2no(s[2]); + ifreq->ifr6_scope = hex2no(s[3]); + ifreq->ifr6_flags = hex2no(s[4]); + ++ifreq; + int tlen = &s[5][ifnamelen] - &buf[0] + 1; + n = bufsz - tlen; + memcpy(&buf, &buf[tlen], n); + } + + conf->ifc_len = (char *)ifreq - conf->ifc_buf; + return sys_close(fd); +} + /** * Gets network interface address list. * @@ -55,6 +142,7 @@ void freeifaddrs(struct ifaddrs *ifp) { * @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; if ((fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0)) != -1) { @@ -65,42 +153,88 @@ int getifaddrs(struct ifaddrs **out_ifpp) { 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))) + return rc; + conf.ifc_len += confl6.ifc_len; + } + struct ifaddrs *res = 0; for (struct ifreq *ifr = (struct ifreq *)data; (char *)ifr < data + conf.ifc_len; ++ifr) { - if (ifr->ifr_addr.sa_family != AF_INET) { - continue; // TODO(jart): IPv6 support - } - struct IfAddr *addr; - if ((addr = calloc(1, sizeof(struct IfAddr)))) { - memcpy(addr->name, ifr->ifr_name, IFNAMSIZ); - addr->ifaddrs.ifa_name = addr->name; - memcpy(&addr->addr, &ifr->ifr_addr, sizeof(struct sockaddr_in)); - addr->ifaddrs.ifa_addr = (struct sockaddr *)&addr->addr; - addr->ifaddrs.ifa_netmask = (struct sockaddr *)&addr->netmask; - if (!ioctl(fd, SIOCGIFFLAGS, ifr)) { - addr->ifaddrs.ifa_flags = ifr->ifr_flags; + uint16_t family = ifr->ifr_addr.sa_family; + if (family == AF_INET) { + struct IfAddr *addr; + if ((addr = calloc(1, sizeof(struct IfAddr)))) { + memcpy(addr->name, ifr->ifr_name, IFNAMSIZ); + addr->ifaddrs.ifa_name = addr->name; + memcpy(&addr->addr, &ifr->ifr_addr, sizeof(struct sockaddr_in)); + addr->ifaddrs.ifa_addr = (struct sockaddr *)&addr->addr; + addr->ifaddrs.ifa_netmask = (struct sockaddr *)&addr->netmask; + if (!ioctl(fd, SIOCGIFFLAGS, ifr)) { + addr->ifaddrs.ifa_flags = ifr->ifr_flags; + } + if (!ioctl(fd, SIOCGIFNETMASK, ifr)) { + memcpy(&addr->netmask, &ifr->ifr_addr, + sizeof(struct sockaddr_in)); + } + unsigned long op; + if (addr->ifaddrs.ifa_flags & IFF_BROADCAST) { + op = SIOCGIFBRDADDR; + } else if (addr->ifaddrs.ifa_flags & IFF_POINTOPOINT) { + op = SIOCGIFDSTADDR; + } else { + op = 0; + } + if (op && !ioctl(fd, op, ifr)) { + memcpy(&addr->bstaddr, &ifr->ifr_addr, + sizeof(struct sockaddr_in)); + addr->ifaddrs.ifa_broadaddr = // is union'd w/ ifu_dstaddr + (struct sockaddr *)&addr->bstaddr; + } + addr->ifaddrs.ifa_next = res; + res = (struct ifaddrs *)addr; } - if (!ioctl(fd, SIOCGIFNETMASK, ifr)) { - memcpy(&addr->netmask, &ifr->ifr_addr, - sizeof(struct sockaddr_in)); + } else if (family == AF_INET6) { + struct IfAddr6 *addr6; + if ((addr6 = calloc(1, sizeof(struct IfAddr6)))) { + addr6->ifaddrs.ifa_name = addr6->name; + addr6->ifaddrs.ifa_addr = (struct sockaddr *)&addr6->addr; + addr6->ifaddrs.ifa_netmask = (struct sockaddr *)&addr6->netmask; + 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->addr.sin6_family = AF_INET6; + addr6->addr.sin6_port = 0; + addr6->addr.sin6_flowinfo = 0; + addr6->addr.sin6_scope_id = ifr->ifr6_ifindex; + memcpy(&addr6->addr.sin6_addr, &ifr->ifr6_addr, + sizeof(struct in6_addr)); + + addr6->netmask.sin6_family = AF_INET6; + 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, + sizeof(struct in6_addr)); + *((uint128_t *)&(addr6->netmask.sin6_addr)) &= + (UINT128_MAX >> ifr->ifr6_prefixlen); + + if (!ioctl(fd, SIOCGIFFLAGS, ifr)) { + addr6->ifaddrs.ifa_flags = ifr->ifr_flags; + } + + bzero(&addr6->bstaddr, sizeof(struct sockaddr_in6)); + addr6->ifaddrs.ifa_next = res; + res = (struct ifaddrs *)addr6; } - unsigned long op; - if (addr->ifaddrs.ifa_flags & IFF_BROADCAST) { - op = SIOCGIFBRDADDR; - } else if (addr->ifaddrs.ifa_flags & IFF_POINTOPOINT) { - op = SIOCGIFDSTADDR; - } else { - op = 0; - } - if (op && !ioctl(fd, op, ifr)) { - memcpy(&addr->bstaddr, &ifr->ifr_addr, - sizeof(struct sockaddr_in)); - addr->ifaddrs.ifa_broadaddr = // is union'd w/ ifu_dstaddr - (struct sockaddr *)&addr->bstaddr; - } - addr->ifaddrs.ifa_next = res; - res = (struct ifaddrs *)addr; } } *out_ifpp = res; diff --git a/libc/sock/struct/ifreq.h b/libc/sock/struct/ifreq.h index 0f7061f5f..1f6317cb6 100644 --- a/libc/sock/struct/ifreq.h +++ b/libc/sock/struct/ifreq.h @@ -1,6 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_SOCK_STRUCT_IFREQ_H_ #define COSMOPOLITAN_LIBC_SOCK_STRUCT_IFREQ_H_ #include "libc/sock/struct/sockaddr.h" +#include "libc/sock/struct/sockaddr6.h" COSMOPOLITAN_C_START_ #define IF_NAMESIZE 16 @@ -11,6 +12,14 @@ struct ifreq { char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */ } ifr_ifrn; union { + struct { + uint16_t sa_family; + uint16_t ifr6_ifindex; /* Interface index */ + uint16_t ifr6_flags; /* Flags */ + uint8_t ifr6_scope; /* Addr scope */ + uint8_t ifr6_prefixlen; /* Prefix length */ + struct in6_addr ifr6_addr; + } in6; struct sockaddr ifru_addr; /* SIOCGIFADDR */ struct sockaddr ifru_dstaddr; /* SIOCGIFDSTADDR */ struct sockaddr ifru_netmask; /* SIOCGIFNETMASK */ @@ -29,5 +38,11 @@ struct ifreq { #define ifr_flags ifr_ifru.ifru_flags /* flags */ #define ifr_ifindex ifr_ifru.ifru_ivalue +#define ifr6_addr ifr_ifru.in6.ifr6_addr /* IP6 Addr */ +#define ifr6_scope ifr_ifru.in6.ifr6_scope /* IP6 Addr scope */ +#define ifr6_prefixlen ifr_ifru.in6.ifr6_prefixlen /* IP6 Prefix length */ +#define ifr6_ifindex ifr_ifru.in6.ifr6_ifindex /* IP6 If index */ +#define ifr6_flags ifr_ifru.in6.ifr6_flags /* IP6 If flags */ + COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_SOCK_STRUCT_IFREQ_H_ */ diff --git a/tool/viz/getifaddrs.c b/tool/viz/getifaddrs.c index bd9b22de8..142e8005e 100644 --- a/tool/viz/getifaddrs.c +++ b/tool/viz/getifaddrs.c @@ -33,7 +33,7 @@ eth0 addr: 10.10.10.237 netmask: 255.255.255.0 - broadcast: 255.255.255.0 + broadcast: 10.10.10.255 flags: IFF_UP IFF_BROADCAST IFF_MULTICAST IFF_RUNNING lo @@ -74,13 +74,87 @@ int main(int argc, char *argv[]) { tinyprint(1, "netmask: ", buf, "\n", NULL); } if ((ifa->ifa_flags & IFF_BROADCAST) && - sockaddr2str(ifa->ifa_netmask, buf, sizeof(buf))) { + sockaddr2str(ifa->ifa_broadaddr, buf, sizeof(buf))) { tinyprint(1, "broadcast: ", buf, "\n", NULL); } else if ((ifa->ifa_flags & IFF_POINTOPOINT) && sockaddr2str(ifa->ifa_dstaddr, buf, sizeof(buf))) { tinyprint(1, "dstaddr: ", buf, "\n", NULL); } + if (ifa->ifa_addr->sa_family == AF_INET6) { + int scope = ((int *)ifa->ifa_data)[0]; + int aflags = ((int *)ifa->ifa_data)[1]; + // #define IPV6_ADDR_LOOPBACK 0x0010U + // #define IPV6_ADDR_LINKLOCAL 0x0020U + // #define IPV6_ADDR_SITELOCAL 0x0040U + + // #define IFA_F_TEMPORARY 0x01 + // #define IFA_F_NODAD 0x02 + // #define IFA_F_OPTIMISTIC 0x04 + // #define IFA_F_DADFAILED 0x08 + // #define IFA_F_HOMEADDRESS 0x10 + // #define IFA_F_DEPRECATED 0x20 + // #define IFA_F_TENTATIVE 0x40 + // #define IFA_F_PERMANENT 0x80 + // #define IFA_F_MANAGETEMPADDR 0x100 + // #define IFA_F_NOPREFIXROUTE 0x200 + // #define IFA_F_MCAUTOJOIN 0x400 + // #define IFA_F_STABLE_PRIVACY 0x800 + tinyprint(1, "scope:", NULL); + if (scope == 0x10) { + tinyprint(1, " loopback", NULL); + } + if (scope == 0x20) { + tinyprint(1, " linklocal", NULL); + } + if (scope == 0x40) { + tinyprint(1, " sitelocal", NULL); + } + if (scope == 0x00) { + tinyprint(1, " global", NULL); + } + tinyprint(1, "\n", NULL); + + tinyprint(1, "addr flags:", NULL); + if (aflags & 0x01) { + tinyprint(1, " temporary", NULL); + } + if (aflags & 0x02) { + tinyprint(1, " nodad", NULL); + } + if (aflags & 0x04) { + tinyprint(1, " optimistic", NULL); + } + if (aflags & 0x08) { + tinyprint(1, " dadfailed", NULL); + } + if (aflags & 0x10) { + tinyprint(1, " homeaddress", NULL); + } + if (aflags & 0x20) { + tinyprint(1, " deprecated", NULL); + } + if (aflags & 0x40) { + tinyprint(1, " tentative", NULL); + } + if (aflags & 0x80) { + tinyprint(1, " permanent", NULL); + } + if (aflags & 0x100) { + tinyprint(1, " managetempaddr", NULL); + } + if (aflags & 0x200) { + tinyprint(1, " noprefixroute", NULL); + } + if (aflags & 0x400) { + tinyprint(1, " mcautojoin", NULL); + } + if (aflags & 0x800) { + tinyprint(1, " stable_privacy", NULL); + } + tinyprint(1, "\n", NULL); + } + tinyprint(1, "flags:", NULL); if (ifa->ifa_flags & IFF_UP) { tinyprint(1, " IFF_UP", NULL);