master ohmu/fastfetch-openbsd / src / detection / localip / localip_linux.c
ohmu  ·  2026-10-15
   1#include "localip.h"
   2#include "common/io.h"
   3#include "common/netif.h"
   4#include "common/strutil.h"
   5#include "common/debug.h"
   6
   7#include <ifaddrs.h>
   8#include <arpa/inet.h>
   9#include <sys/ioctl.h>
  10#include <inttypes.h>
  11
  12#ifdef __linux__
  13    #include <linux/ethtool.h>
  14    #include <linux/sockios.h>
  15    #include <linux/if.h>
  16    #include <linux/if_addr.h>
  17#endif
  18
  19#if __has_include(<netinet6/in6_var.h>)
  20    #include <netinet6/in6_var.h>
  21#endif
  22
  23#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__NetBSD__) || defined(__HAIKU__)
  24    #include <net/if_media.h>
  25    #include <net/if_dl.h>
  26#elif !defined(__GNU__)
  27    #include <netpacket/packet.h>
  28#endif
  29#if defined(__sun) || defined(__HAIKU__)
  30    #include <sys/sockio.h>
  31#endif
  32#if defined(__sun)
  33    #include <kstat.h>
  34
  35static inline void kstatFreeWrap(kstat_ctl_t** pkc) {
  36    assert(pkc);
  37    if (*pkc) {
  38        kstat_close(*pkc);
  39    }
  40}
  41#endif
  42
  43#define FF_LOCALIP_NIFLAG(name) { IFF_##name, #name }
  44
  45static const FFLocalIpNIFlag niFlagOptions[] = {
  46    FF_LOCALIP_NIFLAG(UP),
  47    FF_LOCALIP_NIFLAG(BROADCAST),
  48#ifdef IFF_DEBUG
  49    FF_LOCALIP_NIFLAG(DEBUG),
  50#endif
  51    FF_LOCALIP_NIFLAG(LOOPBACK),
  52    FF_LOCALIP_NIFLAG(POINTOPOINT),
  53#ifdef IFF_RUNNING
  54    FF_LOCALIP_NIFLAG(RUNNING),
  55#endif
  56    FF_LOCALIP_NIFLAG(NOARP),
  57    FF_LOCALIP_NIFLAG(PROMISC),
  58    FF_LOCALIP_NIFLAG(ALLMULTI),
  59#ifdef IFF_INTELLIGENT
  60    FF_LOCALIP_NIFLAG(INTELLIGENT),
  61#endif
  62    FF_LOCALIP_NIFLAG(MULTICAST),
  63#ifdef IFF_NOTRAILERS
  64    FF_LOCALIP_NIFLAG(NOTRAILERS),
  65#endif
  66#if defined(__linux__) || defined(__GNU__)
  67    FF_LOCALIP_NIFLAG(MASTER),
  68    FF_LOCALIP_NIFLAG(SLAVE),
  69    FF_LOCALIP_NIFLAG(PORTSEL),
  70    FF_LOCALIP_NIFLAG(AUTOMEDIA),
  71    FF_LOCALIP_NIFLAG(DYNAMIC),
  72#endif
  73#ifdef __linux__
  74    FF_LOCALIP_NIFLAG(LOWER_UP),
  75    FF_LOCALIP_NIFLAG(DORMANT),
  76    FF_LOCALIP_NIFLAG(ECHO),
  77#endif
  78#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__)
  79    FF_LOCALIP_NIFLAG(OACTIVE),
  80    FF_LOCALIP_NIFLAG(SIMPLEX),
  81    FF_LOCALIP_NIFLAG(LINK0),
  82    FF_LOCALIP_NIFLAG(LINK1),
  83    FF_LOCALIP_NIFLAG(LINK2),
  84#endif
  85#ifdef IFF_ALTPHYS
  86    FF_LOCALIP_NIFLAG(ALTPHYS),
  87#endif
  88#ifdef IFF_CANTCONFIG
  89    FF_LOCALIP_NIFLAG(CANTCONFIG),
  90#endif
  91#ifdef __HAIKU__
  92    FF_LOCALIP_NIFLAG(AUTOUP),
  93    FF_LOCALIP_NIFLAG(SIMPLEX),
  94    FF_LOCALIP_NIFLAG(LINK),
  95    FF_LOCALIP_NIFLAG(AUTO_CONFIGURED),
  96    FF_LOCALIP_NIFLAG(CONFIGURING),
  97#endif
  98#ifdef __sun
  99    FF_LOCALIP_NIFLAG(MULTI_BCAST),
 100    FF_LOCALIP_NIFLAG(UNNUMBERED),
 101    FF_LOCALIP_NIFLAG(DHCPRUNNING),
 102    FF_LOCALIP_NIFLAG(PRIVATE),
 103#endif
 104    // sentinel
 105    {},
 106};
 107
 108static FFLocalIpIpv6Type getIpv6Type(struct ifaddrs* ifa) {
 109    struct sockaddr_in6* addr = (struct sockaddr_in6*) ifa->ifa_addr;
 110
 111    FF_DEBUG("Checking IPv6 type for interface %s", ifa->ifa_name);
 112
 113    FFLocalIpIpv6Type result = FF_LOCALIP_IPV6_TYPE_NONE;
 114    if (IN6_IS_ADDR_GLOBAL(&addr->sin6_addr)) {
 115        result = FF_LOCALIP_IPV6_TYPE_GUA_BIT;
 116        FF_DEBUG("Interface %s has Global Unicast Address", ifa->ifa_name);
 117    } else if (IN6_IS_ADDR_UNIQUE_LOCAL(&addr->sin6_addr)) {
 118        result = FF_LOCALIP_IPV6_TYPE_ULA_BIT;
 119        FF_DEBUG("Interface %s has Unique Local Address", ifa->ifa_name);
 120    } else if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) {
 121        result = FF_LOCALIP_IPV6_TYPE_LLA_BIT;
 122        FF_DEBUG("Interface %s has Link-Local Address", ifa->ifa_name);
 123    } else {
 124        FF_DEBUG("Interface %s has unknown IPv6 address type", ifa->ifa_name);
 125        return FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT;
 126    }
 127
 128#ifdef SIOCGIFAFLAG_IN6
 129    static int sockfd = 0;
 130    if (sockfd == 0) {
 131        sockfd = socket(AF_INET6, SOCK_DGRAM
 132    #ifdef SOCK_CLOEXEC
 133                | SOCK_CLOEXEC
 134    #endif
 135            ,
 136            0);
 137    #ifndef SOCK_CLOEXEC
 138        if (sockfd > 0) {
 139            fcntl(sockfd, F_SETFD, FD_CLOEXEC);
 140        }
 141    #endif
 142    }
 143    if (sockfd < 0) {
 144        return result;
 145    }
 146
 147    struct in6_ifreq ifr6 = {};
 148    ffStrCopy(ifr6.ifr_name, ifa->ifa_name, IFNAMSIZ);
 149    ifr6.ifr_addr = *addr;
 150
 151    if (ioctl(sockfd, SIOCGIFAFLAG_IN6, &ifr6) != 0) {
 152        return result;
 153    }
 154
 155    #ifdef IN6_IFF_PREFER_SOURCE
 156    if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_PREFER_SOURCE) {
 157        result |= FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT;
 158    }
 159    #endif
 160    if (ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_DEPRECATED | IN6_IFF_TEMPORARY | IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED
 161    #ifdef IN6_IFF_OPTIMISTIC
 162                                        | IN6_IFF_OPTIMISTIC
 163    #endif
 164                                        ))
 165        result |= FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT;
 166    return result;
 167#elif __linux__
 168    static FFlist addresses = {};
 169    static bool initialized = false;
 170    if (!initialized) {
 171        initialized = true;
 172        ffListInit(&addresses);
 173        FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
 174        if (!ffReadFileBuffer("/proc/net/if_inet6", &buffer)) {
 175            return result;
 176        }
 177
 178        char* line = nullptr;
 179        size_t len = 0;
 180        while (ffStrbufGetline(&line, &len, &buffer)) {
 181            struct in6_addr* entry = FF_LIST_ADD(struct in6_addr, addresses);
 182            uint8_t flags;
 183            if (sscanf(line, "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 " %*s %*s %*s %" SCNx8 " %*s", &entry->s6_addr[0], &entry->s6_addr[1], &entry->s6_addr[2], &entry->s6_addr[3], &entry->s6_addr[4], &entry->s6_addr[5], &entry->s6_addr[6], &entry->s6_addr[7], &entry->s6_addr[8], &entry->s6_addr[9], &entry->s6_addr[10], &entry->s6_addr[11], &entry->s6_addr[12], &entry->s6_addr[13], &entry->s6_addr[14], &entry->s6_addr[15], &flags) != 17 ||
 184                (!IN6_IS_ADDR_GLOBAL(entry) && !IN6_IS_ADDR_UNIQUE_LOCAL(entry)) ||
 185                (flags & (IFA_F_DEPRECATED | IFA_F_TEMPORARY | IFA_F_TENTATIVE | IFA_F_DADFAILED | IFA_F_OPTIMISTIC))) {
 186                --addresses.length;
 187            }
 188        }
 189    }
 190    if (addresses.capacity == 0) {
 191        return result;
 192    }
 193
 194    FF_LIST_FOR_EACH (struct in6_addr, entry, addresses) {
 195        if (memcmp(&addr->sin6_addr, entry, sizeof(struct in6_addr)) == 0) {
 196            return result;
 197        }
 198    }
 199    result |= FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT;
 200    return result;
 201#elif __sun
 202    if (ifa->ifa_flags & IFF_PREFERRED) {
 203        result |= FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT;
 204    }
 205    if (ifa->ifa_flags & (IFF_DEPRECATED | IFF_TEMPORARY | IFF_DUPLICATE)) {
 206        result |= FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT;
 207    }
 208    return result;
 209#else
 210    return result;
 211#endif
 212}
 213
 214typedef struct {
 215    struct ifaddrs* mac;
 216    FFlist /*<struct ifaddrs*>*/ ipv4;
 217    FFlist /*<struct ifaddrs*>*/ ipv6;
 218} FFAdapter;
 219
 220static void appendIpv4(const FFLocalIpOptions* options, FFstrbuf* buffer, const struct ifaddrs* ifa) {
 221    struct sockaddr_in* ipv4 = (struct sockaddr_in*) ifa->ifa_addr;
 222
 223    char addressBuffer[INET_ADDRSTRLEN + 16];
 224    inet_ntop(AF_INET, &ipv4->sin_addr, addressBuffer, INET_ADDRSTRLEN);
 225
 226    FF_DEBUG("Adding IPv4 address %s for interface %s", addressBuffer, ifa->ifa_name);
 227
 228    if (options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT) {
 229        struct sockaddr_in* netmask = (struct sockaddr_in*) ifa->ifa_netmask;
 230        int cidr = __builtin_popcount(netmask->sin_addr.s_addr);
 231        if (cidr != 0) {
 232            size_t len = strlen(addressBuffer);
 233            snprintf(addressBuffer + len, 16, "/%d", cidr);
 234        }
 235    }
 236
 237    if (buffer->length) {
 238        ffStrbufAppendC(buffer, ',');
 239    }
 240    ffStrbufAppendS(buffer, addressBuffer);
 241}
 242
 243static void appendIpv6(const FFLocalIpOptions* options, FFstrbuf* buffer, const struct ifaddrs* ifa) {
 244    struct sockaddr_in6* ipv6 = (struct sockaddr_in6*) ifa->ifa_addr;
 245
 246    char addressBuffer[INET6_ADDRSTRLEN + 16];
 247    inet_ntop(AF_INET6, &ipv6->sin6_addr, addressBuffer, INET6_ADDRSTRLEN);
 248
 249    FF_DEBUG("Adding IPv6 address %s for interface %s", addressBuffer, ifa->ifa_name);
 250
 251    if (options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT) {
 252        struct sockaddr_in6* netmask = (struct sockaddr_in6*) ifa->ifa_netmask;
 253        int cidr = 0;
 254        static_assert(sizeof(netmask->sin6_addr) % sizeof(uint64_t) == 0, "");
 255        for (uint32_t i = 0; i < sizeof(netmask->sin6_addr) / sizeof(uint64_t); ++i) {
 256            cidr += __builtin_popcountll(((uint64_t*) &netmask->sin6_addr)[i]);
 257        }
 258        if (cidr != 0) {
 259            size_t len = strlen(addressBuffer);
 260            snprintf(addressBuffer + len, 16, "/%d", cidr);
 261        }
 262    }
 263
 264    if (buffer->length) {
 265        ffStrbufAppendC(buffer, ',');
 266    }
 267    ffStrbufAppendS(buffer, addressBuffer);
 268}
 269
 270const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) {
 271    FF_DEBUG("Starting local IP detection with showType=0x%x, namePrefix='%s'",
 272        options->showType,
 273        options->namePrefix.chars);
 274
 275    struct ifaddrs* ifAddrStruct = nullptr;
 276    if (getifaddrs(&ifAddrStruct) < 0) {
 277        FF_DEBUG("getifaddrs() failed: %s", strerror(errno));
 278        return "getifaddrs(&ifAddrStruct) failed";
 279    }
 280
 281    FF_DEBUG("Successfully retrieved interface addresses");
 282
 283    FF_LIST_AUTO_DESTROY adapters = ffListCreate();
 284
 285    for (struct ifaddrs* ifa = ifAddrStruct; ifa; ifa = ifa->ifa_next) {
 286        if (!ifa->ifa_addr) {
 287            FF_DEBUG("Skipping interface %s (no address)", ifa->ifa_name);
 288            continue;
 289        }
 290
 291#ifdef IFF_RUNNING
 292        if (!(ifa->ifa_flags & IFF_RUNNING)) {
 293            FF_DEBUG("Skipping interface %s (not running)", ifa->ifa_name);
 294            continue;
 295        }
 296#endif
 297
 298        if ((ifa->ifa_flags & IFF_LOOPBACK) && !(options->showType & FF_LOCALIP_TYPE_LOOP_BIT)) {
 299            FF_DEBUG("Skipping loopback interface %s", ifa->ifa_name);
 300            continue;
 301        }
 302
 303        if (options->namePrefix.length && strncmp(ifa->ifa_name, options->namePrefix.chars, options->namePrefix.length) != 0) {
 304            FF_DEBUG("Skipping interface %s (doesn't match prefix '%s')",
 305                ifa->ifa_name,
 306                options->namePrefix.chars);
 307            continue;
 308        }
 309
 310        if (!(options->showType & FF_LOCALIP_TYPE_MAC_BIT) &&
 311            ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6) {
 312            FF_DEBUG("Skipping interface %s (unsupported address family %d)",
 313                ifa->ifa_name,
 314                ifa->ifa_addr->sa_family);
 315            continue;
 316        }
 317
 318        if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) {
 319            // If the interface is not the default route for either IPv4 or IPv6, skip it
 320            if (!((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffStrEquals(ffNetifGetDefaultRouteV4()->ifName, ifa->ifa_name)) &&
 321                !((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffStrEquals(ffNetifGetDefaultRouteV6()->ifName, ifa->ifa_name))) {
 322                FF_DEBUG("Skipping interface %s (not default route interface)", ifa->ifa_name);
 323                continue;
 324            }
 325        }
 326
 327        FF_DEBUG("Processing interface %s (family=%d, flags=0x%lx)",
 328            ifa->ifa_name,
 329            ifa->ifa_addr->sa_family,
 330            (unsigned long) ifa->ifa_flags);
 331
 332        FFAdapter* adapter = nullptr;
 333        FF_LIST_FOR_EACH (FFAdapter, x, adapters) {
 334            if (ffStrEquals(x->mac->ifa_name, ifa->ifa_name)) {
 335                adapter = x;
 336                break;
 337            }
 338        }
 339        if (!adapter) {
 340            adapter = FF_LIST_ADD(FFAdapter, adapters);
 341            *adapter = (FFAdapter) {
 342                .mac = ifa,
 343                .ipv4 = ffListCreate(),
 344                .ipv6 = ffListCreate(),
 345            };
 346            FF_DEBUG("Created new adapter entry for interface %s", ifa->ifa_name);
 347        }
 348
 349        switch (ifa->ifa_addr->sa_family) {
 350            case AF_INET:
 351                if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT) {
 352                    *FF_LIST_ADD(struct ifaddrs*, adapter->ipv4) = ifa;
 353                    FF_DEBUG("Added IPv4 entry for interface %s", ifa->ifa_name);
 354                }
 355                break;
 356            case AF_INET6:
 357                if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT) {
 358                    *FF_LIST_ADD(struct ifaddrs*, adapter->ipv6) = ifa;
 359                    FF_DEBUG("Added IPv6 entry for interface %s", ifa->ifa_name);
 360                }
 361                break;
 362#if __FreeBSD__ || __OpenBSD__ || __APPLE__ || __NetBSD__ || __HAIKU__
 363            case AF_LINK:
 364                adapter->mac = ifa;
 365                FF_DEBUG("Updated MAC entry for interface %s", ifa->ifa_name);
 366                break;
 367#elif !__sun && !__GNU__
 368            case AF_PACKET:
 369                adapter->mac = ifa;
 370                FF_DEBUG("Updated MAC entry for interface %s", ifa->ifa_name);
 371                break;
 372#endif
 373        }
 374    }
 375
 376    FF_DEBUG("Found %u network adapters", adapters.length);
 377
 378    FF_LIST_FOR_EACH (FFAdapter, adapter, adapters) {
 379        FF_DEBUG("Processing adapter %s (IPv4 entries: %u, IPv6 entries: %u)",
 380            adapter->mac->ifa_name,
 381            adapter->ipv4.length,
 382            adapter->ipv6.length);
 383
 384        if (adapter->ipv4.length == 0 && adapter->ipv6.length == 0 &&
 385            !(options->showType & FF_LOCALIP_TYPE_MAC_BIT)) {
 386            FF_DEBUG("Skipping interface %s (no IP addresses)", adapter->mac->ifa_name);
 387            continue;
 388        }
 389
 390        FFLocalIpResult* item = FF_LIST_ADD(FFLocalIpResult, *results);
 391        ffStrbufInitS(&item->name, adapter->mac->ifa_name);
 392        ffStrbufInit(&item->ipv4);
 393        ffStrbufInit(&item->ipv6);
 394        ffStrbufInit(&item->mac);
 395        ffStrbufInit(&item->flags);
 396        item->defaultRoute = FF_LOCALIP_TYPE_NONE;
 397        item->mtu = -1;
 398        item->speed = -1;
 399
 400        if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) {
 401            ffLocalIpFillNIFlags(&item->flags, adapter->mac->ifa_flags, niFlagOptions);
 402            FF_DEBUG("Added flags for interface %s: %s", adapter->mac->ifa_name, item->flags.chars);
 403        }
 404
 405        if ((options->showType & FF_LOCALIP_TYPE_IPV4_BIT)) {
 406            const FFNetifDefaultRouteResult* defaultRouteV4 = ffNetifGetDefaultRouteV4();
 407            bool isDefaultRouteIf = ffStrEquals(defaultRouteV4->ifName, adapter->mac->ifa_name);
 408
 409            if (isDefaultRouteIf) {
 410                item->defaultRoute |= FF_LOCALIP_TYPE_IPV4_BIT;
 411                FF_DEBUG("Interface %s is IPv4 default route", adapter->mac->ifa_name);
 412            }
 413
 414            if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) {
 415                if (!isDefaultRouteIf) {
 416                    FF_DEBUG("Skipping IPv4 for interface %s (not default route)", adapter->mac->ifa_name);
 417                    goto v6;
 418                }
 419            }
 420
 421            if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT)) {
 422                struct ifaddrs* ifa = nullptr;
 423                if (isDefaultRouteIf && defaultRouteV4->preferredSourceAddrV4 != 0) {
 424                    FF_LIST_FOR_EACH (struct ifaddrs*, pifa, adapter->ipv4) {
 425                        struct sockaddr_in* ipv4 = (struct sockaddr_in*) (*pifa)->ifa_addr;
 426                        if (ipv4->sin_addr.s_addr == defaultRouteV4->preferredSourceAddrV4) {
 427                            ifa = *pifa;
 428                            FF_DEBUG("Found preferred IPv4 source address for interface %s", adapter->mac->ifa_name);
 429                            break;
 430                        }
 431                    }
 432                }
 433                if (ifa) {
 434                    appendIpv4(options, &item->ipv4, ifa);
 435                } else if (adapter->ipv4.length > 0) {
 436                    appendIpv4(options, &item->ipv4, *FF_LIST_FIRST(struct ifaddrs*, adapter->ipv4));
 437                    FF_DEBUG("Using first IPv4 address for interface %s", adapter->mac->ifa_name);
 438                }
 439            } else {
 440                FF_DEBUG("Adding all IPv4 addresses for interface %s", adapter->mac->ifa_name);
 441                FF_LIST_FOR_EACH (struct ifaddrs*, pifa, adapter->ipv4) {
 442                    appendIpv4(options, &item->ipv4, *pifa);
 443                }
 444            }
 445        }
 446    v6:
 447        if ((options->showType & FF_LOCALIP_TYPE_IPV6_BIT)) {
 448            const FFNetifDefaultRouteResult* defaultRouteV6 = ffNetifGetDefaultRouteV6();
 449            bool isDefaultRouteIf = ffStrEquals(defaultRouteV6->ifName, adapter->mac->ifa_name);
 450
 451            if (isDefaultRouteIf) {
 452                item->defaultRoute |= FF_LOCALIP_TYPE_IPV6_BIT;
 453                FF_DEBUG("Interface %s is IPv6 default route", adapter->mac->ifa_name);
 454            }
 455
 456            if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) {
 457                if (!isDefaultRouteIf) {
 458                    FF_DEBUG("Skipping IPv6 for interface %s (not default route)", adapter->mac->ifa_name);
 459                    goto mac;
 460                }
 461            }
 462
 463            if (options->ipv6Type == FF_LOCALIP_IPV6_TYPE_AUTO) {
 464                if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT)) {
 465                    struct ifaddrs* selected = nullptr;
 466                    struct ifaddrs* secondary = nullptr;
 467
 468                    FF_LIST_FOR_EACH (struct ifaddrs*, pifa, adapter->ipv6) {
 469                        FFLocalIpIpv6Type type = getIpv6Type(*pifa);
 470                        if (type & FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT) {
 471                            selected = *pifa;
 472                            FF_DEBUG("Found preferred IPv6 address for interface %s", adapter->mac->ifa_name);
 473                            break;
 474                        } else if ((type & FF_LOCALIP_IPV6_TYPE_GUA_BIT) && !(type & FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT) && !selected) {
 475                            selected = *pifa;
 476                            FF_DEBUG("Found GUA IPv6 address for interface %s", adapter->mac->ifa_name);
 477                        } else if ((type & FF_LOCALIP_IPV6_TYPE_ULA_BIT) && !(type & FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT) && !secondary) {
 478                            secondary = *pifa;
 479                            FF_DEBUG("Found ULA IPv6 address for interface %s", adapter->mac->ifa_name);
 480                        }
 481                    }
 482                    if (!selected) {
 483                        selected = secondary;
 484                    }
 485
 486                    if (selected) {
 487                        appendIpv6(options, &item->ipv6, selected);
 488                    } else if (adapter->ipv6.length > 0) {
 489                        appendIpv6(options, &item->ipv6, *FF_LIST_FIRST(struct ifaddrs*, adapter->ipv6));
 490                        FF_DEBUG("Using first IPv6 address for interface %s", adapter->mac->ifa_name);
 491                    }
 492                } else {
 493                    FF_DEBUG("Adding all IPv6 addresses for interface %s", adapter->mac->ifa_name);
 494                    FF_LIST_FOR_EACH (struct ifaddrs*, pifa, adapter->ipv6) {
 495                        appendIpv6(options, &item->ipv6, *pifa);
 496                    }
 497                }
 498            } else {
 499                FF_LIST_FOR_EACH (struct ifaddrs*, pifa, adapter->ipv6) {
 500                    FFLocalIpIpv6Type type = getIpv6Type(*pifa);
 501                    if (type & options->ipv6Type) {
 502                        if ((options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT) || !(type & FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT)) {
 503                            appendIpv6(options, &item->ipv6, *pifa);
 504                            if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT)) {
 505                                break;
 506                            }
 507                        }
 508                    }
 509                }
 510            }
 511        }
 512    mac:
 513#if !defined(__sun) && !defined(__GNU__)
 514        if (options->showType & FF_LOCALIP_TYPE_MAC_BIT) {
 515            if (adapter->mac->ifa_addr) {
 516    #if __FreeBSD__ || __OpenBSD__ || __APPLE__ || __NetBSD__ || __HAIKU__
 517                uint8_t* ptr = (uint8_t*) LLADDR((struct sockaddr_dl*) adapter->mac->ifa_addr);
 518    #else
 519                uint8_t* ptr = ((struct sockaddr_ll*) adapter->mac->ifa_addr)->sll_addr;
 520    #endif
 521                ffStrbufSetF(&item->mac, "%02x:%02x:%02x:%02x:%02x:%02x", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]);
 522                FF_DEBUG("Added MAC address %s for interface %s", item->mac.chars, adapter->mac->ifa_name);
 523            } else {
 524                FF_DEBUG("No MAC address available for interface %s", adapter->mac->ifa_name);
 525            }
 526        }
 527#else
 528        (void) adapter;
 529#endif
 530    }
 531
 532#if defined(__APPLE__)
 533    void ffLocalIpFixRedactedMacs(FFlist* results);
 534    ffLocalIpFixRedactedMacs(results);
 535#endif
 536
 537    FF_LIST_FOR_EACH (FFAdapter, adapter, adapters) {
 538        ffListDestroy(&adapter->ipv4);
 539        ffListDestroy(&adapter->ipv6);
 540    }
 541
 542    if (ifAddrStruct) {
 543        freeifaddrs(ifAddrStruct);
 544        ifAddrStruct = nullptr;
 545        FF_DEBUG("Cleaned up interface address structures");
 546    }
 547
 548    if ((options->showType & FF_LOCALIP_TYPE_MTU_BIT) || (options->showType & FF_LOCALIP_TYPE_SPEED_BIT)
 549#ifdef __sun
 550        || (options->showType & FF_LOCALIP_TYPE_MAC_BIT)
 551#endif
 552    ) {
 553        FF_DEBUG("Retrieving additional interface properties (MTU/Speed/MAC)");
 554        FF_AUTO_CLOSE_FD int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
 555        if (sockfd > 0) {
 556            FF_LIST_FOR_EACH (FFLocalIpResult, iface, *results) {
 557                struct ifreq ifr = {};
 558                ffStrCopy(ifr.ifr_name, iface->name.chars, IFNAMSIZ);
 559
 560                if (options->showType & FF_LOCALIP_TYPE_MTU_BIT) {
 561                    if (ioctl(sockfd, SIOCGIFMTU, &ifr) == 0) {
 562                        iface->mtu = (int32_t) ifr.ifr_mtu;
 563                        FF_DEBUG("Interface %s MTU: %d", iface->name.chars, iface->mtu);
 564                    } else {
 565                        FF_DEBUG("Failed to get MTU for interface %s", iface->name.chars);
 566                    }
 567                }
 568
 569                if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) {
 570#ifdef __linux__
 571                    struct ethtool_cmd edata = { .cmd = ETHTOOL_GSET };
 572                    ifr.ifr_data = (void*) &edata;
 573                    if (ioctl(sockfd, SIOCETHTOOL, &ifr) == 0) {
 574                        iface->speed = (edata.speed_hi << 16) | edata.speed;
 575                        FF_DEBUG("Interface %s speed: %d Mbps", iface->name.chars, iface->speed);
 576                    } else {
 577                        // ethtool_cmd_speed is not available on Android
 578                        FF_DEBUG("Failed to get speed for interface %s via ethtool", iface->name.chars);
 579                    }
 580#elif __FreeBSD__ || __APPLE__ || __OpenBSD__ || __NetBSD__
 581                    struct ifmediareq ifmr = {};
 582                    ffStrCopy(ifmr.ifm_name, iface->name.chars, IFNAMSIZ);
 583                    if (ioctl(sockfd, SIOCGIFMEDIA, &ifmr) == 0 && (IFM_TYPE(ifmr.ifm_active) & IFM_ETHER)) {
 584                        FF_DEBUG("Interface %s media type: 0x%x", iface->name.chars, (unsigned) IFM_SUBTYPE(ifmr.ifm_active));
 585                        switch (IFM_SUBTYPE(ifmr.ifm_active)) {
 586    #ifdef IFM_HPNA_1
 587                            case IFM_HPNA_1:
 588    #endif
 589                                iface->speed = 1;
 590                                break;
 591    #ifdef IFM_1000_CX
 592                            case IFM_1000_CX:
 593    #endif
 594    #ifdef IFM_1000_CX_SGMII
 595                            case IFM_1000_CX_SGMII:
 596    #endif
 597    #ifdef IFM_1000_KX
 598                            case IFM_1000_KX:
 599    #endif
 600    #ifdef IFM_1000_LX
 601                            case IFM_1000_LX:
 602    #endif
 603    #ifdef IFM_1000_SGMII
 604                            case IFM_1000_SGMII:
 605    #endif
 606    #ifdef IFM_1000_SX
 607                            case IFM_1000_SX:
 608    #endif
 609    #ifdef IFM_1000_T
 610                            case IFM_1000_T:
 611    #endif
 612                                iface->speed = 1000;
 613                                break;
 614    #ifdef IFM_100G_AUI2
 615                            case IFM_100G_AUI2:
 616    #endif
 617    #ifdef IFM_100G_AUI2_AC
 618                            case IFM_100G_AUI2_AC:
 619    #endif
 620    #ifdef IFM_100G_AUI4
 621                            case IFM_100G_AUI4:
 622    #endif
 623    #ifdef IFM_100G_AUI4_AC
 624                            case IFM_100G_AUI4_AC:
 625    #endif
 626    #ifdef IFM_100G_CAUI2
 627                            case IFM_100G_CAUI2:
 628    #endif
 629    #ifdef IFM_100G_CAUI2_AC
 630                            case IFM_100G_CAUI2_AC:
 631    #endif
 632    #ifdef IFM_100G_CAUI4
 633                            case IFM_100G_CAUI4:
 634    #endif
 635    #ifdef IFM_100G_CAUI4_AC
 636                            case IFM_100G_CAUI4_AC:
 637    #endif
 638    #ifdef IFM_100G_CP2
 639                            case IFM_100G_CP2:
 640    #endif
 641    #ifdef IFM_100G_CR4
 642                            case IFM_100G_CR4:
 643    #endif
 644    #ifdef IFM_100G_CR_PAM4
 645                            case IFM_100G_CR_PAM4:
 646    #endif
 647    #ifdef IFM_100G_DR
 648                            case IFM_100G_DR:
 649    #endif
 650    #ifdef IFM_100G_KR2_PAM4
 651                            case IFM_100G_KR2_PAM4:
 652    #endif
 653    #ifdef IFM_100G_KR4
 654                            case IFM_100G_KR4:
 655    #endif
 656    #ifdef IFM_100G_KR_PAM4
 657                            case IFM_100G_KR_PAM4:
 658    #endif
 659    #ifdef IFM_100G_LR4
 660                            case IFM_100G_LR4:
 661    #endif
 662    #ifdef IFM_100G_SR2
 663                            case IFM_100G_SR2:
 664    #endif
 665    #ifdef IFM_100G_SR4
 666                            case IFM_100G_SR4:
 667    #endif
 668                                iface->speed = 100000;
 669                                break;
 670    #ifdef IFM_100_FX
 671                            case IFM_100_FX:
 672    #endif
 673    #ifdef IFM_100_SGMII
 674                            case IFM_100_SGMII:
 675    #endif
 676    #ifdef IFM_100_T
 677                            case IFM_100_T:
 678    #endif
 679    #ifdef IFM_100_T2
 680                            case IFM_100_T2:
 681    #endif
 682    #ifdef IFM_100_T4
 683                            case IFM_100_T4:
 684    #endif
 685    #ifdef IFM_100_TX
 686                            case IFM_100_TX:
 687    #endif
 688    #ifdef IFM_100_VG
 689                            case IFM_100_VG:
 690    #endif
 691                                iface->speed = 100;
 692                                break;
 693    #ifdef IFM_10G_AOC
 694                            case IFM_10G_AOC:
 695    #endif
 696    #ifdef IFM_10G_CR1
 697                            case IFM_10G_CR1:
 698    #endif
 699    #ifdef IFM_10G_CX4
 700                            case IFM_10G_CX4:
 701    #endif
 702    #ifdef IFM_10G_ER
 703                            case IFM_10G_ER:
 704    #endif
 705    #ifdef IFM_10G_KR
 706                            case IFM_10G_KR:
 707    #endif
 708    #ifdef IFM_10G_KX4
 709                            case IFM_10G_KX4:
 710    #endif
 711    #ifdef IFM_10G_LR
 712                            case IFM_10G_LR:
 713    #endif
 714    #ifdef IFM_10G_LRM
 715                            case IFM_10G_LRM:
 716    #endif
 717    #ifdef IFM_10G_SFI
 718                            case IFM_10G_SFI:
 719    #endif
 720    #ifdef IFM_10G_SR
 721                            case IFM_10G_SR:
 722    #endif
 723    #ifdef IFM_10G_T
 724                            case IFM_10G_T:
 725    #endif
 726    #ifdef IFM_10G_TWINAX
 727                            case IFM_10G_TWINAX:
 728    #endif
 729    #ifdef IFM_10G_TWINAX_LONG
 730                            case IFM_10G_TWINAX_LONG:
 731    #endif
 732                                iface->speed = 10000;
 733                                break;
 734    #ifdef IFM_10_2
 735                            case IFM_10_2:
 736    #endif
 737    #ifdef IFM_10_5
 738                            case IFM_10_5:
 739    #endif
 740    #ifdef IFM_10_FL
 741                            case IFM_10_FL:
 742    #endif
 743    #ifdef IFM_10_STP
 744                            case IFM_10_STP:
 745    #endif
 746    #ifdef IFM_10_T
 747                            case IFM_10_T:
 748    #endif
 749                                iface->speed = 10;
 750                                break;
 751    #ifdef IFM_200G_AUI4
 752                            case IFM_200G_AUI4:
 753    #endif
 754    #ifdef IFM_200G_AUI4_AC
 755                            case IFM_200G_AUI4_AC:
 756    #endif
 757    #ifdef IFM_200G_AUI8
 758                            case IFM_200G_AUI8:
 759    #endif
 760    #ifdef IFM_200G_AUI8_AC
 761                            case IFM_200G_AUI8_AC:
 762    #endif
 763    #ifdef IFM_200G_CR4_PAM4
 764                            case IFM_200G_CR4_PAM4:
 765    #endif
 766    #ifdef IFM_200G_DR4
 767                            case IFM_200G_DR4:
 768    #endif
 769    #ifdef IFM_200G_FR4
 770                            case IFM_200G_FR4:
 771    #endif
 772    #ifdef IFM_200G_KR4_PAM4
 773                            case IFM_200G_KR4_PAM4:
 774    #endif
 775    #ifdef IFM_200G_LR4
 776                            case IFM_200G_LR4:
 777    #endif
 778    #ifdef IFM_200G_SR4
 779                            case IFM_200G_SR4:
 780    #endif
 781                                iface->speed = 200000;
 782                                break;
 783    #ifdef IFM_20G_KR2
 784                            case IFM_20G_KR2:
 785    #endif
 786                                iface->speed = 20000;
 787                                break;
 788    #ifdef IFM_2500_KX
 789                            case IFM_2500_KX:
 790    #endif
 791    #ifdef IFM_2500_SX
 792                            case IFM_2500_SX:
 793    #endif
 794    #ifdef IFM_2500_T
 795                            case IFM_2500_T:
 796    #endif
 797    #ifdef IFM_2500_X
 798                            case IFM_2500_X:
 799    #endif
 800                                iface->speed = 2500;
 801                                break;
 802    #ifdef IFM_25G_ACC
 803                            case IFM_25G_ACC:
 804    #endif
 805    #ifdef IFM_25G_AOC
 806                            case IFM_25G_AOC:
 807    #endif
 808    #ifdef IFM_25G_AUI
 809                            case IFM_25G_AUI:
 810    #endif
 811    #ifdef IFM_25G_CR
 812                            case IFM_25G_CR:
 813    #endif
 814    #ifdef IFM_25G_CR1
 815                            case IFM_25G_CR1:
 816    #endif
 817    #ifdef IFM_25G_CR_S
 818                            case IFM_25G_CR_S:
 819    #endif
 820    #ifdef IFM_25G_KR
 821                            case IFM_25G_KR:
 822    #endif
 823    #ifdef IFM_25G_KR1
 824                            case IFM_25G_KR1:
 825    #endif
 826    #ifdef IFM_25G_KR_S
 827                            case IFM_25G_KR_S:
 828    #endif
 829    #ifdef IFM_25G_LR
 830                            case IFM_25G_LR:
 831    #endif
 832    #ifdef IFM_25G_PCIE
 833                            case IFM_25G_PCIE:
 834    #endif
 835    #ifdef IFM_25G_SR
 836                            case IFM_25G_SR:
 837    #endif
 838    #ifdef IFM_25G_T
 839                            case IFM_25G_T:
 840    #endif
 841                                iface->speed = 25000;
 842                                break;
 843    #ifdef IFM_400G_AUI8
 844                            case IFM_400G_AUI8:
 845    #endif
 846    #ifdef IFM_400G_AUI8_AC
 847                            case IFM_400G_AUI8_AC:
 848    #endif
 849    #ifdef IFM_400G_DR4
 850                            case IFM_400G_DR4:
 851    #endif
 852    #ifdef IFM_400G_FR8
 853                            case IFM_400G_FR8:
 854    #endif
 855    #ifdef IFM_400G_LR8
 856                            case IFM_400G_LR8:
 857    #endif
 858                                iface->speed = 400000;
 859                                break;
 860    #ifdef IFM_40G_CR4
 861                            case IFM_40G_CR4:
 862    #endif
 863    #ifdef IFM_40G_ER4
 864                            case IFM_40G_ER4:
 865    #endif
 866    #ifdef IFM_40G_KR4
 867                            case IFM_40G_KR4:
 868    #endif
 869    #ifdef IFM_40G_LR4
 870                            case IFM_40G_LR4:
 871    #endif
 872    #ifdef IFM_40G_SR4
 873                            case IFM_40G_SR4:
 874    #endif
 875    #ifdef IFM_40G_XLAUI
 876                            case IFM_40G_XLAUI:
 877    #endif
 878    #ifdef IFM_40G_XLAUI_AC
 879                            case IFM_40G_XLAUI_AC:
 880    #endif
 881    #ifdef IFM_40G_XLPPI
 882                            case IFM_40G_XLPPI:
 883    #endif
 884    #ifdef IFM_40G_LM4
 885                            case IFM_40G_LM4:
 886    #endif
 887                                iface->speed = 40000;
 888                                break;
 889    #ifdef IFM_5000_KR
 890                            case IFM_5000_KR:
 891    #endif
 892    #ifdef IFM_5000_KR1
 893                            case IFM_5000_KR1:
 894    #endif
 895    #ifdef IFM_5000_KR_S
 896                            case IFM_5000_KR_S:
 897    #endif
 898    #ifdef IFM_5000_T
 899                            case IFM_5000_T:
 900    #endif
 901                                iface->speed = 5000;
 902                                break;
 903    #ifdef IFM_50G_AUI1
 904                            case IFM_50G_AUI1:
 905    #endif
 906    #ifdef IFM_50G_AUI1_AC
 907                            case IFM_50G_AUI1_AC:
 908    #endif
 909    #ifdef IFM_50G_AUI2
 910                            case IFM_50G_AUI2:
 911    #endif
 912    #ifdef IFM_50G_AUI2_AC
 913                            case IFM_50G_AUI2_AC:
 914    #endif
 915    #ifdef IFM_50G_CP
 916                            case IFM_50G_CP:
 917    #endif
 918    #ifdef IFM_50G_CR2
 919                            case IFM_50G_CR2:
 920    #endif
 921    #ifdef IFM_50G_FR
 922                            case IFM_50G_FR:
 923    #endif
 924    #ifdef IFM_50G_KR2
 925                            case IFM_50G_KR2:
 926    #endif
 927    #ifdef IFM_50G_KR_PAM4
 928                            case IFM_50G_KR_PAM4:
 929    #endif
 930    #ifdef IFM_50G_LAUI2
 931                            case IFM_50G_LAUI2:
 932    #endif
 933    #ifdef IFM_50G_LAUI2_AC
 934                            case IFM_50G_LAUI2_AC:
 935    #endif
 936    #ifdef IFM_50G_LR
 937                            case IFM_50G_LR:
 938    #endif
 939    #ifdef IFM_50G_LR2
 940                            case IFM_50G_LR2:
 941    #endif
 942    #ifdef IFM_50G_PCIE
 943                            case IFM_50G_PCIE:
 944    #endif
 945    #ifdef IFM_50G_SR
 946                            case IFM_50G_SR:
 947    #endif
 948    #ifdef IFM_50G_SR2
 949                            case IFM_50G_SR2:
 950    #endif
 951    #ifdef IFM_50G_KR4
 952                            case IFM_50G_KR4:
 953    #endif
 954                                iface->speed = 50000;
 955                                break;
 956    #ifdef IFM_56G_R4
 957                            case IFM_56G_R4:
 958    #endif
 959                                iface->speed = 56000;
 960                                break;
 961                            default:
 962                                iface->speed = -1;
 963                                FF_DEBUG("Unknown media subtype for interface %s", iface->name.chars);
 964                                break;
 965                        }
 966                        if (iface->speed > 0) {
 967                            FF_DEBUG("Interface %s speed: %d Mbps", iface->name.chars, iface->speed);
 968                        }
 969                    } else {
 970                        FF_DEBUG("Failed to get media info for interface %s", iface->name.chars);
 971                    }
 972#endif
 973                }
 974
 975#if __sun || __GNU__
 976                if ((options->showType & FF_LOCALIP_TYPE_MAC_BIT) && ioctl(sockfd, SIOCGIFHWADDR, &ifr) == 0) {
 977                    const uint8_t* ptr = (uint8_t*) ifr.ifr_addr.sa_data; // NOT ifr_enaddr
 978                    ffStrbufSetF(&iface->mac, "%02x:%02x:%02x:%02x:%02x:%02x", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]);
 979                    FF_DEBUG("Added MAC address %s for interface %s (Solaris/GNU)", iface->mac.chars, iface->name.chars);
 980                }
 981#endif
 982#if __sun
 983                if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) {
 984                    [[gnu::cleanup(kstatFreeWrap)]] kstat_ctl_t* kc = kstat_open();
 985                    for (kstat_t* ks = kc->kc_chain; ks; ks = ks->ks_next) {
 986                        if (!ffStrEquals(ks->ks_class, "net") || !ffStrEquals(ks->ks_module, "link")) {
 987                            continue;
 988                        }
 989                        if (ffStrbufEqualS(&iface->name, ks->ks_name)) {
 990                            if (kstat_read(kc, ks, nullptr) >= 0) {
 991                                kstat_named_t* ifspeed = (kstat_named_t*) kstat_data_lookup(ks, "ifspeed");
 992                                if (ifspeed) {
 993                                    iface->speed = (int32_t) (ifspeed->value.ui64 / 1000 / 1000);
 994                                    FF_DEBUG("Interface %s speed: %d Mbps (kstat)", iface->name.chars, iface->speed);
 995                                }
 996                            }
 997                            break;
 998                        }
 999                    }
1000                }
1001#endif
1002            }
1003        } else {
1004            FF_DEBUG("Failed to create socket for interface property retrieval");
1005        }
1006    }
1007
1008    FF_DEBUG("Local IP detection completed, found %u interfaces", results->length);
1009    return nullptr;
1010}