SDL_net: Allow binding a TCP server to a specific interface (#74)

From bcde4b6fa4492beda3b590466872fbdeb33f3973 Mon Sep 17 00:00:00 2001
From: Kevin Lu <[EMAIL REDACTED]>
Date: Thu, 9 Mar 2023 06:12:13 +1300
Subject: [PATCH] Allow binding a TCP server to a specific interface (#74)

---
 SDL_net.h   | 41 +++++++++++++++++++++++++++++++++++++++++
 SDLnetTCP.c | 50 ++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 83 insertions(+), 8 deletions(-)

diff --git a/SDL_net.h b/SDL_net.h
index ab6fcb6..983e290 100644
--- a/SDL_net.h
+++ b/SDL_net.h
@@ -207,6 +207,45 @@ extern DECLSPEC int SDLCALL SDLNet_GetLocalAddresses(IPaddress *addresses, int m
 
 typedef struct _TCPsocket *TCPsocket;
 
+/**
+ * Open a server TCP network socket.
+ *
+ * If `ip->host` is INADDR_NONE or INADDR_ANY, the socket is bound to
+ * all interfaces, otherwise it is bound to the specified interface.
+ * The address passed in should already be swapped to network byte order
+ * (addresses returned from SDLNet_ResolveHost() are already in the
+ * correct form).
+ *
+ * \param ip The address to host a server on.
+ * \returns the newly created socket, or NULL if there was an error.
+ *
+ * \since This function is available since SDL_net 2.4.0.
+ *
+ * \sa SDLNet_TCP_Close
+ * \sa SDLNet_TCP_OpenClient
+ * \sa SDLNet_TCP_Open
+ */
+extern DECLSPEC TCPsocket SDLCALL SDLNet_TCP_OpenServer(IPaddress *ip);
+
+/**
+ * Open a client TCP network socket.
+ *
+ * Attempt a TCP connection to the remote host and port.
+ * The address passed in should already be swapped to network byte order
+ * (addresses returned from SDLNet_ResolveHost() are already in the
+ * correct form).
+ *
+ * \param ip The address to open a connection to.
+ * \returns the newly created socket, or NULL if there was an error.
+ *
+ * \since This function is available since SDL_net 2.4.0.
+ *
+ * \sa SDLNet_TCP_Close
+ * \sa SDLNet_TCP_OpenServer
+ * \sa SDLNet_TCP_Open
+ */
+extern DECLSPEC TCPsocket SDLCALL SDLNet_TCP_OpenClient(IPaddress *ip);
+
 /**
  * Open a TCP network socket.
  *
@@ -222,6 +261,8 @@ typedef struct _TCPsocket *TCPsocket;
  * \since This function is available since SDL_net 2.0.0.
  *
  * \sa SDLNet_TCP_Close
+ * \sa SDLNet_TCP_OpenServer
+ * \sa SDLNet_TCP_OpenClient
  */
 extern DECLSPEC TCPsocket SDLCALL SDLNet_TCP_Open(IPaddress *ip);
 
diff --git a/SDLnetTCP.c b/SDLnetTCP.c
index 3e4802c..1907ccb 100644
--- a/SDLnetTCP.c
+++ b/SDLnetTCP.c
@@ -36,12 +36,13 @@ struct _TCPsocket {
     int sflag;
 };
 
-/* Open a TCP network socket
-   If 'remote' is NULL, this creates a local server socket on the given port,
-   otherwise a TCP connection to the remote host and port is attempted.
+/* Open a TCP network socket.
+   If `openAsClient` is nonzero, this creates a local server socket
+   on the given port, otherwise a TCP connection to the remote host
+   and port is attempted.
    The newly created socket is returned, or NULL if there was an error.
 */
-TCPsocket SDLNet_TCP_Open(IPaddress *ip)
+static TCPsocket SDLNet_TCP_OpenInternal(IPaddress *ip, int openAsClient)
 {
     TCPsocket sock;
     struct sockaddr_in sock_addr;
@@ -59,9 +60,8 @@ TCPsocket SDLNet_TCP_Open(IPaddress *ip)
         SDLNet_SetError("Couldn't create socket");
         goto error_return;
     }
-
-    /* Connect to remote, or bind locally, as appropriate */
-    if ( (ip->host != INADDR_NONE) && (ip->host != INADDR_ANY) ) {
+    
+    if (openAsClient) {
 
     /* #########  Connecting to remote */
         SDL_memset(&sock_addr, 0, sizeof(sock_addr));
@@ -81,7 +81,8 @@ TCPsocket SDLNet_TCP_Open(IPaddress *ip)
     /* ##########  Binding locally */
         SDL_memset(&sock_addr, 0, sizeof(sock_addr));
         sock_addr.sin_family = AF_INET;
-        sock_addr.sin_addr.s_addr = INADDR_ANY;
+        sock_addr.sin_addr.s_addr = (ip->host == INADDR_NONE) ?
+                                    INADDR_ANY : ip->host;
         sock_addr.sin_port = ip->port;
 
 /*
@@ -156,6 +157,39 @@ TCPsocket SDLNet_TCP_Open(IPaddress *ip)
     return(NULL);
 }
 
+/* Open a server TCP network socket.
+   If `ip->host` is INADDR_NONE or INADDR_ANY, the socket is bound to
+   all interfaces, otherwise it is bound to the specified interface.
+   The newly created socket is returned, or NULL if there was an error.
+*/
+TCPsocket SDLNet_TCP_OpenServer(IPaddress *ip)
+{
+    return SDLNet_TCP_OpenInternal(ip, 0);
+}
+
+/* Open a client TCP network socket.
+   Attempt a TCP connection to the remote host and port.
+   The newly created socket is returned, or NULL if there was an error.
+*/
+TCPsocket SDLNet_TCP_OpenClient(IPaddress *ip)
+{
+    return SDLNet_TCP_OpenInternal(ip, 1);
+}
+
+/* Open a client or server TCP network socket.
+   If `ip->host` is INADDR_NONE or INADDR_ANY, this creates a local server
+   socket on the given port, otherwise a TCP connection to the remote host
+   and port is attempted.
+   The newly created socket is returned, or NULL if there was an error.
+*/
+TCPsocket SDLNet_TCP_Open(IPaddress *ip)
+{
+    return SDLNet_TCP_OpenInternal(
+        ip,
+        (ip->host != INADDR_NONE) && (ip->host != INADDR_ANY)
+    );
+}
+
 /* Accept an incoming connection on the given server socket.
    The newly created socket is returned, or NULL if there was an error.
 */