/* * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im> * * All rights reserved. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3.0 only, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * version 3.0 for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3.0 along with this program. If not, see * <https://www.gnu.org/licenses/>. */ #include "config.h" #ifdef HAVE_SYS_IOCTL_H # include <sys/ioctl.h> #endif #ifdef OF_HAVE_SYS_SOCKET_H # include <sys/socket.h> #endif #ifdef HAVE_SYS_SOCKIO_H # include <sys/sockio.h> #endif #ifdef HAVE_NET_IF_H # include <net/if.h> #endif #ifdef HAVE_NET_IF_ARP_H # include <net/if_arp.h> #endif #ifdef HAVE_NET_IF_DL_H # include <net/if_dl.h> #endif #ifdef HAVE_NET_IF_TYPES_H # include <net/if_types.h> #endif #import "OFSystemInfo.h" #import "OFSystemInfo+NetworkInterfaces.h" #import "OFArray.h" #import "OFData.h" #import "OFDictionary.h" #ifdef OF_HAVE_FILES #import "OFFile.h" #endif #import "OFLocale.h" #import "OFNumber.h" #import "OFSocket.h" #import "OFSocket+Private.h" #import "OFString.h" #import "OFInvalidFormatException.h" #import "OFOpenItemFailedException.h" @implementation OFSystemInfo (NetworkInterfaces) static bool queryNetworkInterfaceIndices(OFMutableDictionary *ret) { #ifdef HAVE_IF_NAMEINDEX OFStringEncoding encoding = [OFLocale encoding]; struct if_nameindex *nameindex = if_nameindex(); if (nameindex == NULL) return false; @try { for (size_t i = 0; nameindex[i].if_index != 0; i++) { OFString *name = [OFString stringWithCString: nameindex[i].if_name encoding: encoding]; OFNumber *idx = [OFNumber numberWithUnsignedInt: nameindex[i].if_index]; OFMutableDictionary *interface = [ret objectForKey: name]; if (interface == nil) { interface = [OFMutableDictionary dictionary]; [ret setObject: interface forKey: name]; } [interface setObject: idx forKey: OFNetworkInterfaceIndex]; } } @finally { if_freenameindex(nameindex); } return true; #else return false; #endif } #if defined(HAVE_IOCTL) && defined(HAVE_NET_IF_H) static bool queryNetworkInterfaceAddresses(OFMutableDictionary *ret, OFNetworkInterfaceKey key, OFSocketAddressFamily addressFamily, int family, size_t sockaddrSize) { OFStringEncoding encoding = [OFLocale encoding]; int sock = socket(family, SOCK_DGRAM, 0); OFMutableDictionary *interface; OFEnumerator *enumerator; if (sock < 0) return false; # if defined(HAVE_STRUCT_LIFCONF) && defined(SIOCGLIFCONF) struct lifconf lifc; struct lifreq *lifrs; if ((lifrs = malloc(128 * sizeof(struct lifreq))) == NULL) { closesocket(sock); return false; } @try { char *buffer; memset(&lifc, 0, sizeof(lifc)); lifc.lifc_buf = (void *)lifrs; lifc.lifc_len = 128 * sizeof(struct lifreq); if (ioctl(sock, SIOCGLIFCONF, &lifc) < 0) return false; for (buffer = lifc.lifc_buf; buffer < (char *)lifc.lifc_buf + lifc.lifc_len; buffer += sizeof(struct lifreq)) { struct lifreq *current = (struct lifreq *)(void *)buffer; OFString *name; OFMutableData *addresses; OFSocketAddress address; if (current->lifr_addr.ss_family != family) continue; name = [OFString stringWithCString: current->lifr_name encoding: encoding]; if ((interface = [ret objectForKey: name]) == nil) { interface = [OFMutableDictionary dictionary]; [ret setObject: interface forKey: name]; } addresses = [interface objectForKey: key]; if (addresses == nil) { addresses = [OFMutableData dataWithItemSize: sizeof(OFSocketAddress)]; [interface setObject: addresses forKey: key]; } memset(&address, 0, sizeof(address)); address.family = addressFamily; memcpy(&address.sockaddr.in, ¤t->lifr_addr, sockaddrSize); # if defined(OF_HAVE_IPV6) && defined(HAVE_IF_NAMETOINDEX) if (address.sockaddr.in6.sin6_family == AF_INET6 && address.sockaddr.in6.sin6_addr.s6_addr[0] == 0xFE && (address.sockaddr.in6.sin6_addr.s6_addr[1] & 0xC0) == 0x80) address.sockaddr.in6.sin6_scope_id = if_nametoindex( [name cStringWithEncoding: encoding]); # endif [addresses addItem: &address]; } } @finally { free(lifrs); closesocket(sock); } # else struct ifconf ifc; struct ifreq *ifrs; if (sock < 0) return false; if ((ifrs = malloc(128 * sizeof(struct ifreq))) == NULL) { closesocket(sock); return false; } @try { char *buffer; memset(&ifc, 0, sizeof(ifc)); ifc.ifc_buf = (void *)ifrs; ifc.ifc_len = 128 * sizeof(struct ifreq); if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) return false; buffer = ifc.ifc_buf; while (buffer < (char *)ifc.ifc_buf + ifc.ifc_len) { struct ifreq *current = (struct ifreq *)(void *)buffer; OFString *name; OFMutableData *addresses; OFSocketAddress address; if (current->ifr_addr.sa_family != family) goto next; name = [OFString stringWithCString: current->ifr_name encoding: encoding]; if ((interface = [ret objectForKey: name]) == nil) { interface = [OFMutableDictionary dictionary]; [ret setObject: interface forKey: name]; } addresses = [interface objectForKey: key]; if (addresses == nil) { addresses = [OFMutableData dataWithItemSize: sizeof(OFSocketAddress)]; [interface setObject: addresses forKey: key]; } memset(&address, 0, sizeof(address)); address.family = addressFamily; memcpy(&address.sockaddr.in, ¤t->ifr_addr, sockaddrSize); # if defined(OF_HAVE_IPV6) && defined(HAVE_IF_NAMETOINDEX) if (address.sockaddr.in6.sin6_family == AF_INET6 && address.sockaddr.in6.sin6_addr.s6_addr[0] == 0xFE && (address.sockaddr.in6.sin6_addr.s6_addr[1] & 0xC0) == 0x80) { # if defined(__KAME__) # define addr6 address.sockaddr.in6.sin6_addr.s6_addr address.sockaddr.in6.sin6_scope_id = (addr6[2] << 8) | addr6[3]; addr6[2] = addr6[3] = 0; # undef addr6 # elif defined(HAVE_IF_NAMETOINDEX) address.sockaddr.in6.sin6_scope_id = if_nametoindex( [name cStringWithEncoding: encoding]); # endif } # endif [addresses addItem: &address]; next: # ifdef HAVE_STRUCT_SOCKADDR_SA_LEN if (current->ifr_addr.sa_len > sizeof(struct sockaddr)) buffer += sizeof(struct ifreq) - sizeof(struct sockaddr) + current->ifr_addr.sa_len; else # endif buffer += sizeof(struct ifreq); } } @finally { free(ifrs); closesocket(sock); } # endif enumerator = [ret objectEnumerator]; while ((interface = [enumerator nextObject]) != nil) [[interface objectForKey: key] makeImmutable]; return true; } #endif static bool queryNetworkInterfaceIPv4Addresses(OFMutableDictionary *ret) { #if defined(HAVE_IOCTL) && defined(HAVE_NET_IF_H) return queryNetworkInterfaceAddresses(ret, OFNetworkInterfaceIPv4Addresses, OFSocketAddressFamilyIPv4, AF_INET, sizeof(struct sockaddr_in)); #else return false; #endif } #ifdef OF_HAVE_IPV6 static bool queryNetworkInterfaceIPv6Addresses(OFMutableDictionary *ret) { # if defined(OF_LINUX) && defined(OF_HAVE_FILES) # ifdef HAVE_IF_NAMETOINDEX OFStringEncoding encoding = [OFLocale encoding]; # endif OFFile *file; OFString *line; OFMutableDictionary *interface; OFEnumerator *enumerator; @try { file = [OFFile fileWithPath: @"/proc/net/if_inet6" mode: @"r"]; } @catch (OFOpenItemFailedException *e) { return false; } while ((line = [file readLine]) != nil) { OFArray *components = [line componentsSeparatedByString: @" " options: OFStringSkipEmptyComponents]; OFString *addressString, *name; OFSocketAddress address; OFMutableData *addresses; if (components.count < 6) continue; addressString = [components objectAtIndex: 0]; name = [components objectAtIndex: 5]; if (addressString.length != 32) continue; if ((interface = [ret objectForKey: name]) == nil) { interface = [OFMutableDictionary dictionary]; [ret setObject: interface forKey: name]; } memset(&address, 0, sizeof(address)); address.family = OFSocketAddressFamilyIPv6; address.sockaddr.in6.sin6_family = AF_INET6; for (size_t i = 0; i < 32; i += 2) { unsigned long long byte; @try { byte = [[addressString substringWithRange: OFMakeRange(i, 2)] unsignedLongLongValueWithBase: 16]; } @catch (OFInvalidFormatException *e) { goto next_line; } if (byte > 0xFF) goto next_line; address.sockaddr.in6.sin6_addr.s6_addr[i / 2] = (unsigned char)byte; } # ifdef HAVE_IF_NAMETOINDEX if (address.sockaddr.in6.sin6_addr.s6_addr[0] == 0xFE && (address.sockaddr.in6.sin6_addr.s6_addr[1] & 0xC0) == 0x80) address.sockaddr.in6.sin6_scope_id = if_nametoindex( [name cStringWithEncoding: encoding]); # endif if ((addresses = [interface objectForKey: OFNetworkInterfaceIPv6Addresses]) == nil) { addresses = [OFMutableData dataWithItemSize: sizeof(OFSocketAddress)]; [interface setObject: addresses forKey: OFNetworkInterfaceIPv6Addresses]; } [addresses addItem: &address]; next_line: continue; } enumerator = [ret objectEnumerator]; while ((interface = [enumerator nextObject]) != nil) [[interface objectForKey: OFNetworkInterfaceIPv6Addresses] makeImmutable]; return false; # elif defined(HAVE_IOCTL) && defined(HAVE_NET_IF_H) return queryNetworkInterfaceAddresses(ret, OFNetworkInterfaceIPv6Addresses, OFSocketAddressFamilyIPv6, AF_INET6, sizeof(struct sockaddr_in6)); # else return false; # endif } #endif #ifdef OF_HAVE_IPX static bool queryNetworkInterfaceIPXAddresses(OFMutableDictionary *ret) { # if defined(OF_LINUX) && defined(OF_HAVE_FILES) OFFile *file; OFString *line; OFMutableDictionary *interface; OFEnumerator *enumerator; @try { file = [OFFile fileWithPath: @"/proc/net/ipx/interface" mode: @"r"]; } @catch (OFOpenItemFailedException *e) { return false; } /* First line is "Network Node_Address Primary Device Frame_Type" */ if (![[file readLine] hasPrefix: @"Network "]) return false; while ((line = [file readLine]) != nil) { OFArray *components = [line componentsSeparatedByString: @" " options: OFStringSkipEmptyComponents]; OFString *name; unsigned long long network, nodeLong; unsigned char node[IPX_NODE_LEN]; OFSocketAddress address; OFMutableData *addresses; if (components.count < 5) continue; name = [components objectAtIndex: 3]; if ((interface = [ret objectForKey: name]) == nil) { interface = [OFMutableDictionary dictionary]; [ret setObject: interface forKey: name]; } @try { network = [[components objectAtIndex: 0] unsignedLongLongValueWithBase: 16]; nodeLong = [[components objectAtIndex: 1] unsignedLongLongValueWithBase: 16]; } @catch (OFInvalidFormatException *e) { continue; } if (network > 0xFFFFFFFF || nodeLong > 0xFFFFFFFFFFFF) continue; node[0] = (nodeLong >> 40) & 0xFF; node[1] = (nodeLong >> 32) & 0xFF; node[2] = (nodeLong >> 24) & 0xFF; node[3] = (nodeLong >> 16) & 0xFF; node[4] = (nodeLong >> 8) & 0xFF; node[5] = nodeLong & 0xFF; address = OFSocketAddressMakeIPX((uint32_t)network, node, 0); if ((addresses = [interface objectForKey: OFNetworkInterfaceIPXAddresses]) == nil) { addresses = [OFMutableData dataWithItemSize: sizeof(OFSocketAddress)]; [interface setObject: addresses forKey: OFNetworkInterfaceIPXAddresses]; } [addresses addItem: &address]; } enumerator = [ret objectEnumerator]; while ((interface = [enumerator nextObject]) != nil) [[interface objectForKey: OFNetworkInterfaceIPXAddresses] makeImmutable]; return false; # elif defined(HAVE_IOCTL) && defined(HAVE_NET_IF_H) return queryNetworkInterfaceAddresses(ret, OFNetworkInterfaceIPXAddresses, OFSocketAddressFamilyIPX, AF_IPX, sizeof(struct sockaddr_ipx)); # else return false; # endif } #endif #ifdef OF_HAVE_APPLETALK static bool queryNetworkInterfaceAppleTalkAddresses(OFMutableDictionary *ret) { # if defined(OF_LINUX) && defined(OF_HAVE_FILES) OFFile *file; OFString *line; OFMutableDictionary *interface; OFEnumerator *enumerator; @try { file = [OFFile fileWithPath: @"/proc/net/atalk/interface" mode: @"r"]; } @catch (OFOpenItemFailedException *e) { return false; } /* First line is "Interface Address Networks Status" */ if (![[file readLine] hasPrefix: @"Interface "]) return false; while ((line = [file readLine]) != nil) { OFArray *components = [line componentsSeparatedByString: @" " options: OFStringSkipEmptyComponents]; OFString *addressString, *name; unsigned long long network, node; OFSocketAddress address; OFMutableData *addresses; if (components.count < 4) continue; name = [components objectAtIndex: 0]; addressString = [components objectAtIndex: 1]; if (addressString.length != 7 || [addressString characterAtIndex: 4] != ':') continue; if ((interface = [ret objectForKey: name]) == nil) { interface = [OFMutableDictionary dictionary]; [ret setObject: interface forKey: name]; } @try { network = [[addressString substringWithRange: OFMakeRange(0, 4)] unsignedLongLongValueWithBase: 16]; node = [[addressString substringWithRange: OFMakeRange(5, 2)] unsignedLongLongValueWithBase: 16]; } @catch (OFInvalidFormatException *e) { continue; } if (network > 0xFFFF || node > 0xFF) continue; address = OFSocketAddressMakeAppleTalk( (uint16_t)network, (uint8_t)node, 0); if ((addresses = [interface objectForKey: OFNetworkInterfaceAppleTalkAddresses]) == nil) { addresses = [OFMutableData dataWithItemSize: sizeof(OFSocketAddress)]; [interface setObject: addresses forKey: OFNetworkInterfaceAppleTalkAddresses]; } [addresses addItem: &address]; } enumerator = [ret objectEnumerator]; while ((interface = [enumerator nextObject]) != nil) [[interface objectForKey: OFNetworkInterfaceAppleTalkAddresses] makeImmutable]; return false; # elif defined(HAVE_IOCTL) && defined(HAVE_NET_IF_H) return queryNetworkInterfaceAddresses(ret, OFNetworkInterfaceAppleTalkAddresses, OFSocketAddressFamilyAppleTalk, AF_APPLETALK, sizeof(struct sockaddr_at)); # else return false; # endif } #endif static bool queryNetworkInterfaceHardwareAddress(OFMutableDictionary *ret) { #if defined(HAVE_IOCTL) && defined(HAVE_NET_IF_H) && defined(SIOCGLIFHWADDR) OFStringEncoding encoding = [OFLocale encoding]; int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) return false; for (OFString *name in ret) { size_t nameLength = [name cStringLengthWithEncoding: encoding]; struct lifreq lifr; struct sockaddr_dl *sdl; OFData *hardwareAddress; if (nameLength > IFNAMSIZ) continue; memset(&lifr, 0, sizeof(lifr)); memcpy(&lifr.lifr_name, [name cStringWithEncoding: encoding], nameLength); if (ioctl(sock, SIOCGLIFHWADDR, &lifr) < 0) continue; if (lifr.lifr_addr.ss_family != AF_LINK) continue; sdl = (struct sockaddr_dl *)(void *)&lifr.lifr_addr; hardwareAddress = [OFData dataWithItems: LLADDR(sdl) count: sdl->sdl_alen]; [[ret objectForKey: name] setObject: hardwareAddress forKey: OFNetworkInterfaceHardwareAddress]; } return true; #elif defined(HAVE_IOCTL) && defined(HAVE_NET_IF_H) && \ defined(SIOCGIFHWADDR) && defined(HAVE_STRUCT_IFREQ_IFR_HWADDR) OFStringEncoding encoding = [OFLocale encoding]; int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) return false; for (OFString *name in ret) { size_t nameLength = [name cStringLengthWithEncoding: encoding]; struct ifreq ifr; OFData *hardwareAddress; if (nameLength > IFNAMSIZ) continue; memset(&ifr, 0, sizeof(ifr)); memcpy(&ifr.ifr_name, [name cStringWithEncoding: encoding], nameLength); if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) continue; if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) continue; hardwareAddress = [OFData dataWithItems: ifr.ifr_hwaddr.sa_data count: 6]; [[ret objectForKey: name] setObject: hardwareAddress forKey: OFNetworkInterfaceHardwareAddress]; } return true; #elif defined(HAVE_IOCTL) && defined(HAVE_NET_IF_H) && \ defined(HAVE_STRUCT_SOCKADDR_DL) OFStringEncoding encoding = [OFLocale encoding]; int sock = socket(AF_INET, SOCK_DGRAM, 0); struct ifconf ifc; struct ifreq *ifrs; if (sock < 0) return false; ifrs = malloc(128 * sizeof(struct ifreq)); if (ifrs == NULL) { closesocket(sock); return false; } @try { char *buffer; memset(&ifc, 0, sizeof(ifc)); ifc.ifc_buf = (void *)ifrs; ifc.ifc_len = 128 * sizeof(struct ifreq); if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) return false; buffer = ifc.ifc_buf; while (buffer < (char *)ifc.ifc_buf + ifc.ifc_len) { struct ifreq *current = (struct ifreq *)(void *)buffer; struct sockaddr_dl *sdl; OFString *name; OFMutableDictionary *interface; OFData *hardwareAddress; if (current->ifr_addr.sa_family != AF_LINK) goto next; sdl = (struct sockaddr_dl *)(void *)¤t->ifr_addr; if (sdl->sdl_type != IFT_ETHER) goto next; name = [OFString stringWithCString: current->ifr_name encoding: encoding]; if ((interface = [ret objectForKey: name]) == nil) { interface = [OFMutableDictionary dictionary]; [ret setObject: interface forKey: name]; } hardwareAddress = [OFData dataWithItems: LLADDR(sdl) count: sdl->sdl_alen]; [interface setObject: hardwareAddress forKey: OFNetworkInterfaceHardwareAddress]; next: # ifdef _SIZEOF_ADDR_IFREQ buffer += _SIZEOF_ADDR_IFREQ(*current); # else buffer += sizeof(struct ifreq); # endif } } @finally { free(ifrs); closesocket(sock); } return true; #else return false; #endif } + (OFDictionary OF_GENERIC(OFString *, OFNetworkInterface) *)networkInterfaces { void *pool = objc_autoreleasePoolPush(); OFMutableDictionary *ret = [OFMutableDictionary dictionary]; bool success = false; OFEnumerator *enumerator; OFMutableDictionary *interface; success |= queryNetworkInterfaceIndices(ret); success |= queryNetworkInterfaceIPv4Addresses(ret); #ifdef OF_HAVE_IPV6 success |= queryNetworkInterfaceIPv6Addresses(ret); #endif #ifdef OF_HAVE_IPX success |= queryNetworkInterfaceIPXAddresses(ret); #endif #ifdef OF_HAVE_APPLETALK success |= queryNetworkInterfaceAppleTalkAddresses(ret); #endif success |= queryNetworkInterfaceHardwareAddress(ret); if (!success) { objc_autoreleasePoolPop(pool); return nil; } enumerator = [ret objectEnumerator]; while ((interface = [enumerator nextObject]) != nil) [interface makeImmutable]; [ret makeImmutable]; [ret retain]; objc_autoreleasePoolPop(pool); return [ret autorelease]; } @end