From cdd8ad702c66edde4e7dc2acd954bcadc4943396 Mon Sep 17 00:00:00 2001
From: Andy Green <andy@warmcat.com>
Date: Thu, 22 Nov 2018 17:58:30 +0800
Subject: [PATCH] bind: treat EADDRINUSE as fatal

The retry stuff for bind failures is actually aimed at the scenarios the interface
either doesn't exist yet, or is not configured enough (having an IP) to be bindable yet.

This patch treats EADDRINUSE as fatal at vhost init.
---
 include/libwebsockets/lws-network-helper.h |  8 ++++---
 lib/core/libwebsockets.c                   | 28 +++++++++++++++++-----
 lib/plat/esp32/private.h                   |  1 +
 lib/plat/optee/private.h                   |  1 +
 lib/plat/unix/private.h                    |  1 +
 lib/plat/windows/private.h                 |  1 +
 lib/roles/http/server/server.c             | 24 +++++++++++++++----
 7 files changed, 50 insertions(+), 14 deletions(-)

diff --git a/include/libwebsockets/lws-network-helper.h b/include/libwebsockets/lws-network-helper.h
index a85ca821..94ee8d9a 100644
--- a/include/libwebsockets/lws-network-helper.h
+++ b/include/libwebsockets/lws-network-helper.h
@@ -71,10 +71,12 @@ lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name,
 LWS_VISIBLE LWS_EXTERN const char *
 lws_get_peer_simple(struct lws *wsi, char *name, int namelen);
 
+#define LWS_ITOSA_USABLE	0
+#define LWS_ITOSA_NOT_EXIST	-1
+#define LWS_ITOSA_NOT_USABLE	-2
+#define LWS_ITOSA_BUSY		-3 /* only returned by lws_socket_bind() on
+					EADDRINUSE */
 
-#define LWS_ITOSA_NOT_EXIST -1
-#define LWS_ITOSA_NOT_USABLE -2
-#define LWS_ITOSA_USABLE 0
 #if !defined(LWS_WITH_ESP32)
 /**
  * lws_interface_to_sa() - Convert interface name or IP to sockaddr struct
diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c
index 28323fd5..1da3a44a 100644
--- a/lib/core/libwebsockets.c
+++ b/lib/core/libwebsockets.c
@@ -2603,6 +2603,14 @@ lws_set_extension_option(struct lws *wsi, const char *ext_name,
 }
 #endif
 
+/* note: this returns a random port, or one of these <= 0 return codes:
+ *
+ * LWS_ITOSA_USABLE:     the interface is usable, returned if so and sockfd invalid
+ * LWS_ITOSA_NOT_EXIST:  the requested iface does not even exist
+ * LWS_ITOSA_NOT_USABLE: the requested iface exists but is not usable (eg, no IP)
+ * LWS_ITOSA_BUSY:       the port at the requested iface + port is already in use
+ */
+
 LWS_EXTERN int
 lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
 		const char *iface)
@@ -2633,11 +2641,11 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
 		bzero((char *) &serv_unix, sizeof(serv_unix));
 		serv_unix.sun_family = AF_UNIX;
 		if (!iface)
-			return -1;
+			return LWS_ITOSA_NOT_EXIST;
 		if (sizeof(serv_unix.sun_path) <= strlen(iface)) {
 			lwsl_err("\"%s\" too long for UNIX domain socket\n",
 			         iface);
-			return -1;
+			return LWS_ITOSA_NOT_EXIST;
 		}
 		strcpy(serv_unix.sun_path, iface);
 		if (serv_unix.sun_path[0] == '@')
@@ -2678,8 +2686,8 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
 		bzero((char *) &serv_addr4, sizeof(serv_addr4));
 		serv_addr4.sin_addr.s_addr = INADDR_ANY;
 		serv_addr4.sin_family = AF_INET;
-#if !defined(LWS_WITH_ESP32)
 
+#if !defined(LWS_WITH_ESP32)
 		if (iface) {
 		    m = interface_to_sa(vhost, iface,
 				    (struct sockaddr_in *)v, n);
@@ -2700,20 +2708,28 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
 
 	/* just checking for the interface extant */
 	if (sockfd == LWS_SOCK_INVALID)
-		return 0;
+		return LWS_ITOSA_USABLE;
 
 	n = bind(sockfd, v, n);
 #ifdef LWS_WITH_UNIX_SOCK
 	if (n < 0 && LWS_UNIX_SOCK_ENABLED(vhost)) {
 		lwsl_err("ERROR on binding fd %d to \"%s\" (%d %d)\n",
 			 sockfd, iface, n, LWS_ERRNO);
-		return -1;
+		return LWS_ITOSA_NOT_EXIST;
 	} else
 #endif
 	if (n < 0) {
 		lwsl_err("ERROR on binding fd %d to port %d (%d %d)\n",
 				sockfd, port, n, LWS_ERRNO);
-		return -1;
+
+		/* if something already listening, tell caller to fail permanently */
+
+		if (LWS_ERRNO == LWS_EADDRINUSE)
+			return LWS_ITOSA_BUSY;
+
+		/* otherwise ask caller to retry later */
+
+		return LWS_ITOSA_NOT_EXIST;
 	}
 
 #if defined(LWS_WITH_UNIX_SOCK)
diff --git a/lib/plat/esp32/private.h b/lib/plat/esp32/private.h
index 74de11c7..29c0c0c6 100644
--- a/lib/plat/esp32/private.h
+++ b/lib/plat/esp32/private.h
@@ -60,6 +60,7 @@
  #define LWS_EISCONN EISCONN
  #define LWS_ENOTCONN ENOTCONN
  #define LWS_EWOULDBLOCK EWOULDBLOCK
+ #define LWS_EADDRINUSE EADDRINUSE
 
  #define lws_set_blocking_send(wsi)
 
diff --git a/lib/plat/optee/private.h b/lib/plat/optee/private.h
index 85137c44..68da4c54 100644
--- a/lib/plat/optee/private.h
+++ b/lib/plat/optee/private.h
@@ -57,6 +57,7 @@
  #define LWS_EISCONN EISCONN
  #define LWS_ENOTCONN ENOTCONN
  #define LWS_EWOULDBLOCK EWOULDBLOCK
+ #define LWS_EADDRINUSE EADDRINUSE
 
  #define lws_set_blocking_send(wsi)
 
diff --git a/lib/plat/unix/private.h b/lib/plat/unix/private.h
index fcd05356..8583ee7b 100644
--- a/lib/plat/unix/private.h
+++ b/lib/plat/unix/private.h
@@ -127,6 +127,7 @@
 #define LWS_EISCONN EISCONN
 #define LWS_ENOTCONN ENOTCONN
 #define LWS_EWOULDBLOCK EWOULDBLOCK
+#define LWS_EADDRINUSE EADDRINUSE
 #define lws_set_blocking_send(wsi)
 #define LWS_SOCK_INVALID (-1)
 
diff --git a/lib/plat/windows/private.h b/lib/plat/windows/private.h
index 591a6836..980028ce 100644
--- a/lib/plat/windows/private.h
+++ b/lib/plat/windows/private.h
@@ -41,6 +41,7 @@
  #define LWS_EISCONN WSAEISCONN
  #define LWS_ENOTCONN WSAENOTCONN
  #define LWS_EWOULDBLOCK WSAEWOULDBLOCK
+ #define LWS_EADDRINUSE WSAEADDRINUSE
  #define MSG_NOSIGNAL 0
  #define SHUT_RDWR SD_BOTH
  #define SOL_TCP IPPROTO_TCP
diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c
index cd1181fa..a2b9075b 100644
--- a/lib/roles/http/server/server.c
+++ b/lib/roles/http/server/server.c
@@ -83,15 +83,22 @@ _lws_vhost_init_server(const struct lws_context_creation_info *info,
 		 * let's check before we do anything else about the disposition
 		 * of the interface he wants to bind to...
 		 */
-		is = lws_socket_bind(vhost, LWS_SOCK_INVALID, vhost->listen_port, vhost->iface);
+		is = lws_socket_bind(vhost, LWS_SOCK_INVALID, vhost->listen_port,
+				vhost->iface);
 		lwsl_debug("initial if check says %d\n", is);
+
+		if (is == LWS_ITOSA_BUSY)
+			/* treat as fatal */
+			return -1;
+
 deal:
 
 		lws_start_foreach_llp(struct lws_vhost **, pv,
 				      vhost->context->no_listener_vhost_list) {
 			if (is >= LWS_ITOSA_USABLE && *pv == vhost) {
 				/* on the list and shouldn't be: remove it */
-				lwsl_debug("deferred iface: removing vh %s\n", (*pv)->name);
+				lwsl_debug("deferred iface: removing vh %s\n",
+						(*pv)->name);
 				*pv = vhost->no_listener_vhost_list;
 				vhost->no_listener_vhost_list = NULL;
 				goto done_list;
@@ -107,7 +114,8 @@ deal:
 			/* ... but needs to be: so add it */
 
 			lwsl_debug("deferred iface: adding vh %s\n", vhost->name);
-			vhost->no_listener_vhost_list = vhost->context->no_listener_vhost_list;
+			vhost->no_listener_vhost_list =
+					vhost->context->no_listener_vhost_list;
 			vhost->context->no_listener_vhost_list = vhost;
 		}
 
@@ -226,6 +234,13 @@ done_list:
 		lws_plat_set_socket_options(vhost, sockfd, 0);
 
 		is = lws_socket_bind(vhost, sockfd, vhost->listen_port, vhost->iface);
+		if (is == LWS_ITOSA_BUSY) {
+			/* treat as fatal */
+			compatible_close(sockfd);
+
+			return -1;
+		}
+
 		/*
 		 * There is a race where the network device may come up and then
 		 * go away and fail here.  So correctly handle unexpected failure
@@ -250,8 +265,7 @@ done_list:
 			wsi->unix_skt = 1;
 			vhost->listen_port = is;
 
-			lwsl_debug("%s: lws_socket_bind says %d\n", __func__,
-					is);
+			lwsl_debug("%s: lws_socket_bind says %d\n", __func__, is);
 		}
 
 		wsi->context = vhost->context;
-- 
GitLab