cosmopolitan/libc/calls/ioctl_siocgifconf-nt.c
2022-08-13 13:11:56 -07:00

414 lines
14 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/errno.h"
#include "libc/intrin/cmpxchg.h"
#include "libc/intrin/weaken.h"
#include "libc/mem/mem.h"
#include "libc/nt/errors.h"
#include "libc/nt/iphlpapi.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/ipadapteraddresses.h"
#include "libc/nt/winsock.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/internal.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/ifconf.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/errfuns.h"
/* Maximum number of unicast addresses handled for each interface */
#define MAX_UNICAST_ADDR 32
#define MAX_NAME_CLASH ((int)('z' - 'a')) /* Allow a..z */
struct HostAdapterInfoNode {
struct HostAdapterInfoNode *next;
char name[IFNAMSIZ]; /* Obtained from FriendlyName */
struct sockaddr unicast;
struct sockaddr netmask;
struct sockaddr broadcast;
short flags;
} * __hostInfo;
/* Frees all the nodes of the _hostInfo */
static void freeHostInfo(void) {
struct HostAdapterInfoNode *next, *node = __hostInfo;
if (weaken(free)) {
while (node) {
next = node->next;
weaken(free)(node);
node = next;
}
}
__hostInfo = NULL;
}
/* Given a short adapter name, look into __hostInfo to see if there is
* an adapter with the same name. Returns the pointer to the HostAdapterInfoNode
* if found, or NULL if not found
*/
static struct HostAdapterInfoNode *findAdapterByName(const char *name) {
struct HostAdapterInfoNode *node = __hostInfo;
while (node) {
if (!strncmp(name, node->name, IFNAMSIZ)) {
return node;
}
node = node->next;
}
return NULL;
}
/* Creates a new HostAdapterInfoNode object, initializes it from
* the given adapter, unicast address and address prefixes
* and insert it in the __hostInfo.
* Increments the pointers to the unicast addresses and
* the address prefixes
* Returns NULL if an error occurred or the newly created
* HostAdapterInfoNode object (last in the list)
*/
struct HostAdapterInfoNode *appendHostInfo(
struct HostAdapterInfoNode *parentInfoNode,
const char *baseName, /* Max length = IFNAMSIZ-1 */
const struct NtIpAdapterAddresses
*aa, /* Top level adapter object being processed */
struct NtIpAdapterUnicastAddress *
*ptrUA, /* Ptr to ptr to unicast address list node */
struct NtIpAdapterPrefix *
*ptrAP, /* Ptr to ptr to Adapter prefix list node */
int count) { /* count is used to create a unique name in case of alias */
struct HostAdapterInfoNode *temp;
struct HostAdapterInfoNode *node;
uint32_t ip, netmask, broadcast;
struct sockaddr_in *a;
int attemptNum;
if (!weaken(calloc) || !(node = weaken(calloc)(1, sizeof(*node)))) {
errno = ENOMEM;
return NULL;
}
memcpy(node->name, baseName, IFNAMSIZ);
/* Are there more than a single unicast address ? */
if (count > 0 || ((*ptrUA)->Next != NULL)) {
/* Yes, compose it using <baseName>:<count> */
size_t nameLen = strlen(node->name);
if (nameLen + 2 > IFNAMSIZ - 2) {
/* Appending the ":x" will exceed the size, need to chop the end */
nameLen -= 2;
}
node->name[nameLen - 2] = ':';
node->name[nameLen - 1] = '0' + count;
node->name[nameLen] = '\0';
}
/* Is there a name clash with other interfaces? */
for (attemptNum = 0; attemptNum < MAX_NAME_CLASH; ++attemptNum) {
temp = findAdapterByName(node->name);
if (!temp) {
break;
} else {
/* Yes, this name has been already used, append an extra
* character to resolve conflict. Note since the max length
* of the string now is IFNAMSIZ-2, we have just enough space for this.
* E.g. 'Ethernet_1' -> 'Ethernet_1a'
*/
size_t pos = strlen(node->name);
node->name[pos] = 'a' + attemptNum;
node->name[pos + 1] = '\0';
/* Try again */
}
}
if (attemptNum == MAX_NAME_CLASH) {
/* Cannot resolve the conflict */
if (weaken(free)) {
weaken(free)(node);
}
errno = EEXIST;
return NULL;
}
/* Finally we got a unique short and friendly name */
node->unicast = *((*ptrUA)->Address.lpSockaddr);
if (*ptrUA == aa->FirstUnicastAddress) {
short flags;
/* This is the first unicast address of this interface
* calculate the flags for this adapter. Flags to consider:
* IFF_UP
* IFF_BROADCAST ** TODO: We need to validate
* IFF_LOOPBACK
* IFF_POINTOPOINT
* IFF_MULTICAST
* IFF_RUNNING ** Same as IFF_UP for now
* IFF_PROMISC ** NOT SUPPORTED, unknown how to retrieve it
*/
flags = 0;
if (aa->OperStatus == kNtIfOperStatusUp) flags |= IFF_UP | IFF_RUNNING;
if (aa->IfType == kNtIfTypePpp) flags |= IFF_POINTOPOINT;
if (!(aa->Flags & kNtIpAdapterNoMulticast)) flags |= IFF_MULTICAST;
if (aa->IfType == kNtIfTypeSoftwareLoopback) flags |= IFF_LOOPBACK;
if (aa->FirstPrefix) flags |= IFF_BROADCAST;
node->flags = flags;
} else {
/* Copy from previous node */
node->flags = parentInfoNode->flags;
}
ip = ntohl(
((struct sockaddr_in *)(*ptrUA)->Address.lpSockaddr)->sin_addr.s_addr);
netmask = (uint32_t)-1 << (32 - (*ptrUA)->OnLinkPrefixLength);
broadcast = (ip & netmask) | (~netmask & -1);
a = (struct sockaddr_in *)&node->netmask;
a->sin_family = AF_INET;
a->sin_addr.s_addr = htonl(netmask);
a = (struct sockaddr_in *)&node->broadcast;
a->sin_family = AF_INET;
a->sin_addr.s_addr = htonl(broadcast);
/* Process the prefix and extract the netmask and broadcast */
/* According to the doc:
*
* On Windows Vista and later, the linked IP_ADAPTER_PREFIX
* structures pointed to by the FirstPrefix member include three
* IP adapter prefixes for each IP address assigned to the
* adapter. These include the host IP address prefix, the subnet
* IP address prefix, and the subnet broadcast IP address prefix.
* In addition, for each adapter there is a multicast address
* prefix and a broadcast address prefix.
* -Source: MSDN on IP_ADAPTER_ADDRESSES_LH
*
* For example, interface "Ethernet", with 2 unicast addresses:
*
* - 192.168.1.84
* - 192.168.5.99
*
* The Prefix list has 8 elements:
*
* #1: 192.168.1.0/24 <- Network, use the PrefixLength for netmask
* #2: 192.168.1.84/32 <- Host IP
* #3: 192.168.1.255/32 <- Subnet broadcast
*
* #4: 192.168.5.0/24 <- Network
* #5: 192.168.5.99/32 <- Host IP
* #6: 192.168.5.255/32 <- Subnet broadcast
*
* #7: 224.0.0.0/4 <- Multicast
* #8: 255.255.255.255/32 <- Broadcast
*/
if (ptrAP && *ptrAP) {
*ptrAP = (*ptrAP)->Next; /* skip net ip */
if (*ptrAP) {
*ptrAP = (*ptrAP)->Next; /* skip host ip */
if (*ptrAP) {
node->broadcast = *((*ptrAP)->Address.lpSockaddr);
}
}
}
*ptrUA = (*ptrUA)->Next;
/* Append this node to the last node (if any) */
if (parentInfoNode) {
parentInfoNode->next = node;
}
/* Success */
return node;
}
/* Returns -1 in case of failure */
static int createHostInfo(struct NtIpAdapterAddresses *firstAdapter) {
static bool once;
struct NtIpAdapterAddresses *aa;
struct NtIpAdapterUnicastAddress *ua;
struct NtIpAdapterPrefix *ap;
struct HostAdapterInfoNode *node = NULL;
char baseName[IFNAMSIZ];
char name[IFNAMSIZ];
int count, i;
/* __hostInfo must be empty */
assert(__hostInfo == NULL);
for (aa = firstAdapter; aa; aa = aa->Next) {
/* Skip all the interfaces with no address and the ones that are not AF_INET
*/
if (!aa->FirstUnicastAddress ||
aa->FirstUnicastAddress->Address.lpSockaddr->sa_family != AF_INET) {
continue;
}
/* Use max IFNAMSIZ-1 chars, leave the last char for eventual conflicts */
tprecode16to8(baseName, IFNAMSIZ - 1, aa->FriendlyName);
baseName[IFNAMSIZ - 2] = '\0';
/* Replace any space with a '_' */
for (i = 0; i < IFNAMSIZ - 2; ++i) {
if (baseName[i] == ' ') baseName[i] = '_';
if (!baseName[i]) break;
}
for (count = 0, ua = aa->FirstUnicastAddress, ap = aa->FirstPrefix;
(ua != NULL) && (count < MAX_UNICAST_ADDR); ++count) {
node = appendHostInfo(node, baseName, aa, &ua, &ap, count);
if (!node) goto err;
if (!__hostInfo) {
__hostInfo = node;
if (_cmpxchg(&once, false, true)) {
atexit(freeHostInfo);
}
}
}
/* Note: do we need to process the remaining adapter prefix?
* ap - points to broadcast addr
* ap->Next - points to interface multicast addr
* Ignoring them for now
*/
}
return 0;
err:
freeHostInfo();
return -1;
}
static int readAdapterAddresses(void) {
uint32_t size, rc;
struct NtIpAdapterAddresses *aa = NULL;
/* Calculate the required data size
* Note: alternatively you can use AF_UNSPEC to also return IPv6 interfaces
*/
rc = GetAdaptersAddresses(AF_INET,
kNtGaaFlagSkipAnycast | kNtGaaFlagSkipMulticast |
kNtGaaFlagSkipDnsServer |
kNtGaaFlagIncludePrefix,
NULL, /* Reserved */
NULL, /* Ptr */
&size);
if (rc != kNtErrorBufferOverflow) {
ebadf();
goto err;
}
if (!weaken(malloc) ||
!(aa = (struct NtIpAdapterAddresses *)weaken(malloc)(size))) {
enomem();
goto err;
}
/* Re-run GetAdaptersAddresses this time with a valid buffer */
rc = GetAdaptersAddresses(AF_INET,
kNtGaaFlagSkipAnycast | kNtGaaFlagSkipMulticast |
kNtGaaFlagSkipDnsServer |
kNtGaaFlagIncludePrefix,
// kNtGaaFlagIncludePrefix,
NULL, aa, &size);
if (rc != kNtErrorSuccess) {
errno = GetLastError();
goto err;
}
if (createHostInfo(aa) == -1) {
goto err;
}
if (weaken(free)) {
weaken(free)(aa);
}
return 0;
err:
if (weaken(free)) {
weaken(free)(aa);
}
freeHostInfo();
return -1;
}
textwindows int ioctl_siocgifconf_nt(int fd, struct ifconf *ifc) {
struct NtIpAdapterAddresses *aa;
struct HostAdapterInfoNode *node;
struct ifreq *ptr;
if (__hostInfo) {
freeHostInfo();
}
if (readAdapterAddresses() == -1) {
return -1;
}
for (ptr = ifc->ifc_req, node = __hostInfo;
(((char *)(ptr + 1) - ifc->ifc_buf) < ifc->ifc_len) && node;
ptr++, node = node->next) {
memcpy(ptr->ifr_name, node->name, IFNAMSIZ);
memcpy(&ptr->ifr_addr, &node->unicast, sizeof(struct sockaddr));
}
ifc->ifc_len = (char *)ptr - ifc->ifc_buf;
return 0;
}
/**
* Returns unicast addresses.
*/
int ioctl_siocgifaddr_nt(int fd, struct ifreq *ifr) {
struct HostAdapterInfoNode *node;
node = findAdapterByName(ifr->ifr_name);
if (!node) return ebadf();
memcpy(&ifr->ifr_addr, &node->unicast, sizeof(struct sockaddr));
return 0;
}
/* Performs the SIOCGIFFLAGS operation */
int ioctl_siocgifflags_nt(int fd, struct ifreq *ifr) {
struct HostAdapterInfoNode *node;
node = findAdapterByName(ifr->ifr_name);
if (!node) return ebadf();
ifr->ifr_flags = node->flags;
return 0;
}
/* Performs the SIOCGIFNETMASK operation */
int ioctl_siocgifnetmask_nt(int fd, struct ifreq *ifr) {
struct HostAdapterInfoNode *node;
node = findAdapterByName(ifr->ifr_name);
if (!node) return ebadf();
memcpy(&ifr->ifr_netmask, &node->netmask, sizeof(struct sockaddr));
return 0;
}
/**
* Returns broadcast address.
*/
int ioctl_siocgifbrdaddr_nt(int fd, struct ifreq *ifr) {
struct HostAdapterInfoNode *node;
node = findAdapterByName(ifr->ifr_name);
if (!node) return ebadf();
memcpy(&ifr->ifr_broadaddr, &node->broadcast, sizeof(struct sockaddr));
return 0;
}