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}