From a62af559ecde441419041d44f4b59e8f56d35c08 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joan=20Lled=C3=B3?= <jlledom@member.fsf.org>
Date: Mon, 1 Apr 2024 18:28:28 +0200
Subject: [PATCH] Remove restriction on number of sockets

  New option LWIP_SOCKET_OPEN_COUNT: Number of sockets is not
  limited by MEMP_NUM_NETCONN. When enabled, sockets are allocated
  in the heap and the amount of sockets is only limited by the heap size.

* src/api/sockets.c:
  * Implement dynamic array logic
    * Remove `translate_socket()`, not needed anymore.
    * Use `sockets_slots_count` as the socket list capacity instead
      of `NUM_SOCKETS`
    * `alloc_socket_slots_locked()`: new method to increase sockets capacity
    * `init_socket()`: New method implementing the common socket init logic
    * `alloc_socket()`: When `LWIP_SOCKET_OPEN_COUNT = 1`, it allocates new
      slots for more sockets.
* src/include/lwip/opt.h:
  * Declare `LWIP_SOCKET_OPEN_COUNT`
    * Set to `0` by default.
* contrib/ports/unix/posixlib/lwipopts.h:
  * Set `LWIP_SOCKET_OPEN_COUNT` to `1` for the POSIX port.
---
 contrib/ports/unix/posixlib/lwipopts.h |  1 +
 src/api/sockets.c                      | 94 ++++++++++++++++++++++----
 src/include/lwip/opt.h                 |  9 +++
 3 files changed, 89 insertions(+), 15 deletions(-)

diff --git a/contrib/ports/unix/posixlib/lwipopts.h b/contrib/ports/unix/posixlib/lwipopts.h
index 1b38aa3a..c9ce395d 100644
--- a/contrib/ports/unix/posixlib/lwipopts.h
+++ b/contrib/ports/unix/posixlib/lwipopts.h
@@ -33,6 +33,7 @@
 /* Sockets API config */
 #define LWIP_COMPAT_SOCKETS       0
 #define LWIP_SOCKET_OFFSET        1
+#define LWIP_SOCKET_OPEN_COUNT    1
 #define LWIP_POLL                 1
 
 /* User posix socket headers */
diff --git a/src/api/sockets.c b/src/api/sockets.c
index b97bdd7e..0638f893 100644
--- a/src/api/sockets.c
+++ b/src/api/sockets.c
@@ -301,7 +301,12 @@ static void lwip_socket_drop_registered_mld6_memberships(int s);
 #endif /* LWIP_IPV6_MLD */
 
 /** The global array of available sockets */
+static int sockets_slots_count = NUM_SOCKETS;
+#if LWIP_SOCKET_OPEN_COUNT
+static struct lwip_sock *sockets = NULL;
+#else
 static struct lwip_sock sockets[NUM_SOCKETS];
+#endif /* LWIP_SOCKET_OPEN_COUNT */
 
 #if LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL
 #if LWIP_TCPIP_CORE_LOCKING
@@ -450,7 +455,7 @@ static struct lwip_sock *
 tryget_socket_unconn_nouse(int fd)
 {
   int s = fd - LWIP_SOCKET_OFFSET;
-  if ((s < 0) || (s >= NUM_SOCKETS)) {
+  if ((s < 0) || (s >= sockets_slots_count)) {
     LWIP_DEBUGF(SOCKETS_DEBUG, ("tryget_socket_unconn(%d): invalid\n", fd));
     return NULL;
   }
@@ -519,7 +524,7 @@ get_socket(int fd)
 {
   struct lwip_sock *sock = tryget_socket(fd);
   if (!sock) {
-    if ((fd < LWIP_SOCKET_OFFSET) || (fd >= (LWIP_SOCKET_OFFSET + NUM_SOCKETS))) {
+    if ((fd < LWIP_SOCKET_OFFSET) || (fd >= (LWIP_SOCKET_OFFSET + sockets_slots_count))) {
       LWIP_DEBUGF(SOCKETS_DEBUG, ("get_socket(%d): invalid\n", fd));
     }
     set_errno(EBADF);
@@ -528,6 +533,57 @@ get_socket(int fd)
   return sock;
 }
 
+#if LWIP_SOCKET_OPEN_COUNT
+/**
+ * Allocate MEMP_NUM_NETCONN slots for new sockets.
+ *
+ * @return 0 on success; -1 on error
+ */
+static int
+alloc_socket_slots_locked(void) {
+  int sockets_new_count;
+  struct lwip_sock *old_sockets, *new_sockets;
+
+  sockets_new_count = sockets_slots_count + MEMP_NUM_NETCONN;
+  new_sockets = (struct lwip_sock*)mem_calloc(sockets_new_count, sizeof(struct lwip_sock));
+  if(!new_sockets) {
+    /* No free memory */
+    return -1;
+  }
+  old_sockets = sockets;
+  MEMCPY(new_sockets, old_sockets, sizeof(struct lwip_sock) * sockets_slots_count);
+  sockets = new_sockets;
+  sockets_slots_count = sockets_new_count;
+  mem_free(old_sockets);
+
+  return 0;
+}
+#endif /* LWIP_SOCKET_OPEN_COUNT */
+
+/**
+ * Initialize a new allocated socket.
+ *
+ * @param idx ths socket index in the internal array
+ * @param newconn the netconn for which to initialize a socket
+ * @param accepted 1 if socket has been created by accept(),
+ *                 0 if socket has been created by socket()
+ * @return the index of the new socket; -1 on error
+ */
+static int
+init_socket(int idx, struct netconn *newconn, int accepted)
+{
+  sockets[idx].lastdata.pbuf = NULL;
+#if LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL
+  LWIP_ASSERT("sockets[i].select_waiting == 0", sockets[idx].select_waiting == 0);
+  sockets[idx].rcvevent   = 0;
+  /* TCP sendbuf is empty, but the socket is not yet writable until connected
+    * (unless it has been created by accept()). */
+  sockets[idx].sendevent  = (NETCONNTYPE_GROUP(newconn->type) == NETCONN_TCP ? (accepted != 0) : 1);
+  sockets[idx].errevent   = 0;
+#endif /* LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL */
+  return idx + LWIP_SOCKET_OFFSET;
+}
+
 /**
  * Allocate a new socket for a given netconn.
  *
@@ -541,10 +597,15 @@ alloc_socket(struct netconn *newconn, int accepted)
 {
   int i;
   SYS_ARCH_DECL_PROTECT(lev);
-  LWIP_UNUSED_ARG(accepted);
+
+#if LWIP_SOCKET_OPEN_COUNT
+  if (sockets == NULL) {
+    sockets = (struct lwip_sock*)mem_calloc(NUM_SOCKETS, sizeof(struct lwip_sock));
+  }
+#endif /* LWIP_SOCKET_OPEN_COUNT */
 
   /* allocate a new socket identifier */
-  for (i = 0; i < NUM_SOCKETS; ++i) {
+  for (i = 0; i < sockets_slots_count; ++i) {
     /* Protect socket array */
     SYS_ARCH_PROTECT(lev);
     if (!sockets[i].conn) {
@@ -560,20 +621,23 @@ alloc_socket(struct netconn *newconn, int accepted)
       /* The socket is not yet known to anyone, so no need to protect
          after having marked it as used. */
       SYS_ARCH_UNPROTECT(lev);
-      sockets[i].lastdata.pbuf = NULL;
-#if LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL
-      LWIP_ASSERT("sockets[i].select_waiting == 0", sockets[i].select_waiting == 0);
-      sockets[i].rcvevent   = 0;
-      /* TCP sendbuf is empty, but the socket is not yet writable until connected
-       * (unless it has been created by accept()). */
-      sockets[i].sendevent  = (NETCONNTYPE_GROUP(newconn->type) == NETCONN_TCP ? (accepted != 0) : 1);
-      sockets[i].errevent   = 0;
-#endif /* LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL */
-      return i + LWIP_SOCKET_OFFSET;
+      return init_socket(i, newconn, accepted);
     }
     SYS_ARCH_UNPROTECT(lev);
   }
+#if LWIP_SOCKET_OPEN_COUNT
+  SYS_ARCH_PROTECT(lev);
+  if (alloc_socket_slots_locked() < 0) {
+    SYS_ARCH_UNPROTECT(lev);
+    return -1;
+  }
+  sockets[i].conn = newconn;
+  SYS_ARCH_UNPROTECT(lev);
+
+  return init_socket(i, newconn, accepted);
+#else /* LWIP_SOCKET_OPEN_COUNT */
   return -1;
+#endif /* LWIP_SOCKET_OPEN_COUNT */
 }
 
 /** Free a socket (under lock)
@@ -696,7 +760,7 @@ lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen)
     done_socket(sock);
     return -1;
   }
-  LWIP_ASSERT("invalid socket index", (newsock >= LWIP_SOCKET_OFFSET) && (newsock < NUM_SOCKETS + LWIP_SOCKET_OFFSET));
+  LWIP_ASSERT("invalid socket index", (newsock >= LWIP_SOCKET_OFFSET) && (newsock < sockets_slots_count + LWIP_SOCKET_OFFSET));
   nsock = &sockets[newsock - LWIP_SOCKET_OFFSET];
 
   /* Note that POSIX only requires us to check addr is non-NULL. addrlen must
diff --git a/src/include/lwip/opt.h b/src/include/lwip/opt.h
index 2573285f..5fa95a39 100644
--- a/src/include/lwip/opt.h
+++ b/src/include/lwip/opt.h
@@ -2069,6 +2069,15 @@
 #define LWIP_SOCKET_EXTERNAL_HEADERS    0
 #endif
 
+/**
+ * LWIP_SOCKET_OPEN_COUNT==1: Number of sockets is not limited to MEMP_NUM_NETCONN.
+ * When enabled, sockets are allocated in the heap and the amount of sockets is
+ * only limited by the heap size. Handle with care regarding execution speed.
+ */
+#if !defined LWIP_SOCKET_OPEN_COUNT || defined __DOXYGEN__
+#define LWIP_SOCKET_OPEN_COUNT          0
+#endif
+
 /**
  * LWIP_TCP_KEEPALIVE==1: Enable TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT
  * options processing. Note that TCP_KEEPIDLE and TCP_KEEPINTVL have to be set
-- 
2.39.2

