SDL_net: Unified sockaddr->SDLNet_Address code, implemented SDLNet_GetLocalAddresses.

From c3f91ff92765483c8e88dd2a039e2bbd377c7a9e Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sun, 24 Sep 2023 02:57:26 -0400
Subject: [PATCH] Unified sockaddr->SDLNet_Address code, implemented
 SDLNet_GetLocalAddresses.

Added an example app for SDLNet_GetLocalAddresses.

This commit is not tested on, or even compiled for, Windows yet.
---
 CMakeLists.txt             |   3 +
 SDL_net.c                  | 232 ++++++++++++++++++++++++++-----------
 examples/get-local-addrs.c |  40 +++++++
 3 files changed, 210 insertions(+), 65 deletions(-)
 create mode 100644 examples/get-local-addrs.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1ae12bd..56f4da0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,3 +32,6 @@ target_link_libraries(simple-http-get PRIVATE SDL3_net::SDL3_net SDL3::SDL3)
 
 add_executable(resolve-hostnames examples/resolve-hostnames.c)
 target_link_libraries(resolve-hostnames PRIVATE SDL3_net::SDL3_net SDL3::SDL3)
+
+add_executable(get-local-addrs examples/get-local-addrs.c)
+target_link_libraries(get-local-addrs PRIVATE SDL3_net::SDL3_net SDL3::SDL3)
diff --git a/SDL_net.c b/SDL_net.c
index eda3038..a5dd21e 100644
--- a/SDL_net.c
+++ b/SDL_net.c
@@ -5,6 +5,7 @@
 #define _WIN32_WINNT 0x0600  /* we need APIs that didn't arrive until Windows Vista. */
 #include <winsock2.h>
 #include <ws2tcpip.h>
+#include <iphlpapi.h>
 typedef SOCKET Socket;
 typedef int SockLen;
 typedef SOCKADDR_STORAGE AddressStorage;
@@ -24,8 +25,6 @@ static int read(SOCKET s, char *buf, size_t count) {
     }
     return (int)count_received;
 }
-
-#define EAI_SYSTEM 0
 #define poll WSAPoll
 #else
 #include <sys/types.h>
@@ -38,6 +37,7 @@ static int read(SOCKET s, char *buf, size_t count) {
 #include <poll.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <ifaddrs.h>
 #define INVALID_SOCKET -1
 #define SOCKET_ERROR -1
 typedef int Socket;
@@ -126,7 +126,7 @@ static char *CreateSocketErrorString(int rc)
         FORMAT_MESSAGE_FROM_SYSTEM |
         FORMAT_MESSAGE_IGNORE_INSERTS,
         NULL,
-        err,
+        rc,
         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
         msgbuf,
         SDL_arraysize(msgbuf),
@@ -288,6 +288,48 @@ static void DestroyAddress(SDLNet_Address *addr)
     }
 }
 
+static SDLNet_Address *CreateSDLNetAddrFromSockAddr(struct sockaddr *saddr, SockLen saddrlen)
+{
+    // !!! FIXME: this all seems inefficient in the name of keeping addresses generic.
+    char hostbuf[128];
+    int gairc = getnameinfo(saddr, saddrlen, hostbuf, sizeof (hostbuf), NULL, 0, NI_NUMERICHOST);
+    if (gairc != 0) {
+        SetGetAddrInfoError("Failed to determine address", gairc);
+        return NULL;
+    }
+
+    SDLNet_Address *addr = (SDLNet_Address *) SDL_calloc(1, sizeof (SDLNet_Address));
+    if (!addr) {
+        SDL_OutOfMemory();
+        return NULL;
+    }
+    SDL_AtomicSet(&addr->status, 1);
+
+    struct addrinfo hints;
+    SDL_zero(hints);
+    hints.ai_family = saddr->sa_family;
+    hints.ai_socktype = SOCK_DGRAM;
+    hints.ai_protocol = 0;
+    hints.ai_flags = AI_NUMERICHOST;
+
+    gairc = getaddrinfo(hostbuf, NULL, &hints, &addr->ainfo);
+    if (gairc != 0) {
+        SDL_free(addr);
+        SetGetAddrInfoError("Failed to determine address", gairc);
+        return NULL;
+    }
+
+    addr->human_readable = SDL_strdup(hostbuf);
+    if (!addr->human_readable) {
+        freeaddrinfo(addr->ainfo);
+        SDL_free(addr);
+        SDL_OutOfMemory();
+        return NULL;
+    }
+
+    return SDLNet_RefAddress(addr);
+}
+
 int SDLNet_Init(void)
 {
     char *origerrstr = NULL;
@@ -528,8 +570,118 @@ void SDLNet_SimulateAddressResolutionLoss(int percent_loss)
 
 SDLNet_Address **SDLNet_GetLocalAddresses(int *num_addresses)
 {
-    // !!! FIXME: write me!
-    return NULL;
+    int count = 0;
+    SDLNet_Address **retval = NULL;
+
+    *num_addresses = 0;
+
+#ifdef __WINDOWS__
+    // !!! FIXME: maybe LoadLibrary(iphlpapi) on the first call, since most
+    // !!! FIXME: things won't ever use this.
+
+    // MSDN docs say start with a 15K buffer, which usually works on the first
+    //  try, instead of trying to query for size, allocate, and then retry,
+    //  since this tends to be more expensive.
+    ULONG buflen = 15 * 1024;
+    IP_ADAPTER_ADDRESSES *addrs = NULL;
+    ULONG rc;
+
+    do {
+        SDL_free(addrs);
+        addrs = (IP_ADAPTER_ADDRESSES *) SDL_malloc(buflen);
+        if (!addrs) {
+            SDL_OutOfMemory();
+            return NULL;
+        }
+
+        const ULONG flags = GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME;
+        rc = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, addrs, &buflen);
+    } while (rc == ERROR_BUFFER_OVERFLOW);
+
+    if (rc != NO_ERROR) {
+        SetSocketError("GetAdaptersAddresses failed", rc);
+        SDL_free(addrs);
+        return NULL;
+    }
+
+    for (IP_ADAPTER_ADDRESSES *i = addrs; i != NULL; i = i->Next) {
+        for (IP_ADAPTER_UNICAST_ADDRESS *j = i->FirstUnicastAddress; j != NULL; j = j->Next) {
+            count++;
+        }
+    }
+
+    retval = (SDLNet_Address **) SDL_calloc(count + 1, sizeof (SDLNet_Address *));
+    if (!retval) {
+        SDL_OutOfMemory();
+        SDL_free(addrs);
+        return NULL;
+    }
+
+    count = 0;
+    for (IP_ADAPTER_ADDRESSES *i = addrs; i != NULL; i = i->Next) {
+        for (IP_ADAPTER_UNICAST_ADDRESS *j = i->FirstUnicastAddress; j != NULL; j = j->Next) {
+            SDLNet_Address *addr = CreateSDLNetAddrFromSockAddr(j->Address.lpSockaddr, j->Address.iSockaddrLength);
+            if (addr) {
+                retval[count++] = addr;
+            }
+        }
+    }
+
+    SDL_free(addrs);
+
+#else
+    struct ifaddrs *ifaddr;
+    if (getifaddrs(&ifaddr) == -1) {
+        SDL_SetError("getifaddrs failed: %s", strerror(errno));
+        return NULL;  // oh well.
+    }
+
+    for (struct ifaddrs *i = ifaddr; i != NULL; i = i->ifa_next) {
+        if (i->ifa_name != NULL) {
+            count++;
+        }
+    }
+
+    retval = (SDLNet_Address **) SDL_calloc(count + 1, sizeof (SDLNet_Address *));
+    if (!retval) {
+        if (ifaddr) {
+            freeifaddrs(ifaddr);
+        }
+        SDL_OutOfMemory();
+        return NULL;
+    }
+
+    count = 0;
+    for (struct ifaddrs *i = ifaddr; i != NULL; i = i->ifa_next) {
+        if (i->ifa_name != NULL) {
+            SDLNet_Address *addr = NULL;
+            // !!! FIXME: getifaddrs doesn't return the sockaddr length, so we have to go with known protocols.  :/
+            if (i->ifa_addr->sa_family == AF_INET) {
+                addr = CreateSDLNetAddrFromSockAddr(i->ifa_addr, sizeof (struct sockaddr_in));
+            } else if (i->ifa_addr->sa_family == AF_INET6) {
+                addr = CreateSDLNetAddrFromSockAddr(i->ifa_addr, sizeof (struct sockaddr_in6));
+            }
+
+            if (addr) {
+                retval[count++] = addr;
+            }
+        }
+    }
+
+    if (ifaddr) {
+        freeifaddrs(ifaddr);
+    }
+#endif
+
+    *num_addresses = count;
+
+    // try to shrink allocation.
+    void *ptr = SDL_realloc(retval, (count + 1) * sizeof (SDLNet_Address *));
+    if (ptr) {
+        retval = (SDLNet_Address **) ptr;
+    }
+
+    return retval;
 }
 
 void SDLNet_FreeLocalAddresses(SDLNet_Address **addresses)
@@ -793,49 +945,22 @@ int SDLNet_AcceptClient(SDLNet_Server *server, SDLNet_StreamSocket **client_stre
         return SDL_SetError("Failed to make incoming socket non-blocking");
     }
 
-    // !!! FIXME: this all seems inefficient in the name of keeping addresses generic.
-    char hostbuf[128];
     char portbuf[16];
-    const int rc = getnameinfo((struct sockaddr *) &from, fromlen, hostbuf, sizeof (hostbuf), portbuf, sizeof (portbuf), NI_NUMERICHOST | NI_NUMERICSERV);
-    if (rc != 0) {
-        CloseSocketHandle(handle);
-        return SetGetAddrInfoError("Failed to determine incoming connection's address", rc);
-    }
-
-    SDLNet_Address *fromaddr = (SDLNet_Address *) SDL_calloc(1, sizeof (SDLNet_Address));
-    if (!fromaddr) {
-        CloseSocketHandle(handle);
-        return SDL_OutOfMemory();
-    }
-    SDL_AtomicSet(&fromaddr->status, 1);
-
-    struct addrinfo hints;
-    SDL_zero(hints);
-    hints.ai_family = ((struct sockaddr *) &from)->sa_family;
-    hints.ai_socktype = SOCK_DGRAM;
-    hints.ai_protocol = 0;
-    hints.ai_flags = AI_NUMERICHOST;
-
-    const int gairc = getaddrinfo(hostbuf, NULL, &hints, &fromaddr->ainfo);
+    const int gairc = getnameinfo((struct sockaddr *) &from, fromlen, NULL, 0, portbuf, sizeof (portbuf), NI_NUMERICSERV);
     if (gairc != 0) {
-        SDL_free(fromaddr);
         CloseSocketHandle(handle);
-        return SetGetAddrInfoError("Failed to determine incoming connection's address: %s", rc);
+        return SetGetAddrInfoError("Failed to determine port number", gairc);
     }
 
-    fromaddr->human_readable = SDL_strdup(hostbuf);
-    if (!fromaddr->human_readable) {
-        freeaddrinfo(fromaddr->ainfo);
-        SDL_free(fromaddr);
+    SDLNet_Address *fromaddr = CreateSDLNetAddrFromSockAddr((struct sockaddr *) &from, fromlen);
+    if (!fromaddr) {
         CloseSocketHandle(handle);
-        return SDL_OutOfMemory();
+        return -1;  // error string was already set.
     }
 
     SDLNet_StreamSocket *sock = (SDLNet_StreamSocket *) SDL_calloc(1, sizeof (SDLNet_StreamSocket));
     if (!sock) {
-        SDL_free(fromaddr->human_readable);
-        freeaddrinfo(fromaddr->ainfo);
-        SDL_free(fromaddr);
+        SDLNet_UnrefAddress(fromaddr);
         CloseSocketHandle(handle);
         return SDL_OutOfMemory();
     }
@@ -1261,7 +1386,6 @@ int SDLNet_ReceiveDatagram(SDLNet_DatagramSocket *sock, SDLNet_Datagram **dgram)
         return 0;
     }
 
-    // !!! FIXME: this all seems inefficient in the name of keeping addresses generic.
     char hostbuf[128];
     char portbuf[16];
     const int rc = getnameinfo((struct sockaddr *) &from, fromlen, hostbuf, sizeof (hostbuf), portbuf, sizeof (portbuf), NI_NUMERICHOST | NI_NUMERICSERV);
@@ -1296,45 +1420,23 @@ int SDLNet_ReceiveDatagram(SDLNet_DatagramSocket *sock, SDLNet_Datagram **dgram)
 
     const SDL_bool create_fromaddr = (!fromaddr) ? SDL_TRUE : SDL_FALSE;
     if (create_fromaddr) {
-        fromaddr = (SDLNet_Address *) SDL_calloc(1, sizeof (SDLNet_Address));
+        fromaddr = CreateSDLNetAddrFromSockAddr((struct sockaddr *) &from, fromlen);
         if (!fromaddr) {
-            return SDL_OutOfMemory();
-        }
-        SDL_AtomicSet(&fromaddr->status, 1);
-
-        struct addrinfo hints;
-        SDL_zero(hints);
-        hints.ai_family = ((struct sockaddr *) &from)->sa_family;
-        hints.ai_socktype = SOCK_DGRAM;
-        hints.ai_protocol = 0;
-        hints.ai_flags = AI_NUMERICHOST;
-
-        const int gairc = getaddrinfo(hostbuf, NULL, &hints, &fromaddr->ainfo);
-        if (gairc != 0) {
-            SDL_free(fromaddr);
-            return SetGetAddrInfoError("Failed to determine incoming packet's address", gairc);
-        }
-
-        fromaddr->human_readable = SDL_strdup(hostbuf);
-        if (!fromaddr->human_readable) {
-            freeaddrinfo(fromaddr->ainfo);
-            SDL_free(fromaddr);
-            return SDL_OutOfMemory();
+            return -1;  // already set the error string.
         }
     }
 
     SDLNet_Datagram *dg = SDL_malloc(sizeof (SDLNet_Datagram) + br);
     if (!dg) {
         if (create_fromaddr) {
-            freeaddrinfo(fromaddr->ainfo);
-            SDL_free(fromaddr);
+            SDLNet_UnrefAddress(fromaddr);
         }
         return SDL_OutOfMemory();
     }
 
     dg->buf = (Uint8 *) (dg+1);
     SDL_memcpy(dg->buf, sock->recv_buffer, br);
-    dg->addr = SDLNet_RefAddress(fromaddr);
+    dg->addr = create_fromaddr ? fromaddr : SDLNet_RefAddress(fromaddr);
     dg->port = (Uint16) SDL_atoi(portbuf);
     dg->buflen = br;
 
diff --git a/examples/get-local-addrs.c b/examples/get-local-addrs.c
new file mode 100644
index 0000000..d53ee13
--- /dev/null
+++ b/examples/get-local-addrs.c
@@ -0,0 +1,40 @@
+/*
+ * This is just for demonstration purposes! This doesn't
+ * do anything as complicated as, say, the `ifconfig` utility.
+ *
+ * All this to say: don't use this for anything serious!
+ */
+
+#include <stdio.h>
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_main.h>
+#include "SDL_net.h"
+
+int main(int argc, char **argv)
+{
+    SDLNet_Address **addrs = NULL;
+    int num_addrs = 0;
+    int i;
+
+    if (SDLNet_Init() < 0) {
+        SDL_Log("SDLNet_Init() failed: %s", SDL_GetError());
+        return 1;
+    }
+
+    addrs = SDLNet_GetLocalAddresses(&num_addrs);
+    if (addrs == NULL) {
+        SDL_Log("Failed to determine local addresses: %s", SDL_GetError());
+        SDLNet_Quit();
+        return 1;
+    }
+
+    SDL_Log("We saw %d local addresses:", num_addrs);
+    for (i = 0; i < num_addrs; i++) {
+        SDL_Log("  - %s", SDLNet_GetAddressString(addrs[i]));
+    }
+
+    SDLNet_FreeLocalAddresses(addrs);
+    SDLNet_Quit();
+
+    return 0;
+}