Hi Willy,

We use HAProxy as a Forward Proxy (I know this is not the intended
application for HAProxy) to access outside world from within the DC, and
this requires setting a source port range for return traffic to reach the
correct
box from which a connection was established. On our production boxes, we
see around 500 "no free ports" errors per day, but this could increase to
about 120K errors during big sale events. The reason for this is due to
connect getting a EADDRNOTAVAIL error, since an earlier closed socket
may be in last-ack state, as it may take some time for the remote server to
send the final ack.

The attached patch reduces the number of errors by attempting more ports,
if they are available.

Please review, and let me know if this sounds reasonable to implement.

Thanks,
- Krishna
From 2946ac0b9ba5567284d5364445ad1f9102365e38 Mon Sep 17 00:00:00 2001
From: Krishna Kumar <krishna.ku@kkumar>
Date: Thu, 9 Mar 2017 11:24:06 +0530
Subject: [PATCH] [MEDIUM] Improve "no free ports" error.

 When source IP and source port range are specified, sometimes HAProxy fails
 to connect to a server and prints "no free ports" message. This happens
 when HAProxy recently closed the socket with the same port#, but was not
 completely closed in the kernel. To fix this, attempt a few more connects
 with different port numbers based on the available ports in the port_range.

Following are some log lines with this patch, and when running out of ports:

Early Connect() failed for backend bk-noports: no free ports.
Connect(port=50116) failed for backend bk-noports, socket not fully closed, RETRYING... (max_attempts = 2)
Connect(port=50227) failed for backend bk-noports, socket not fully closed, RETRYING... (max_attempts = 1)
Connect(port=50116) failed for backend bk-noports: no free ports.

When running #parallel wgets == #source-port-range, the following messages
were printed, but none of the connects failed (though it could fail if the
the ports were completely exhausted, e.g. max_attempts = 0):

Connect(port=50241) failed for backend bk-noports, socket not fully closed, RETRYING... (max_attempts = 2)
Connect(port=50226) failed for backend bk-noports, socket not fully closed, RETRYING... (max_attempts = 2)
---
 include/proto/port_range.h |  18 ++
 include/proto/proto_tcp.h  |  19 ++
 src/proto_tcp.c            | 496 +++++++++++++++++++++++++++------------------
 3 files changed, 338 insertions(+), 195 deletions(-)

diff --git a/include/proto/port_range.h b/include/proto/port_range.h
index 8c63fac..7a64caa 100644
--- a/include/proto/port_range.h
+++ b/include/proto/port_range.h
@@ -55,6 +55,24 @@ static inline void port_range_release_port(struct port_range *range, int port)
 		range->put = 0;
 }
 
+/*
+ * Return the maximum number of ports that can be used to attempt a connect().
+ * This is to handle the following problem:
+ *	- haproxy closes a port (kernel does TCP close on socket).
+ *	- haproxy allocates the same port.
+ *	- haproxy attempts to connect, but fails as the kernel has not
+ *	  finished the close.
+ *
+ * To handle this, we attempt to connect() atmost 'range->avail' times, as
+ * this guarantees a different free port#'s each time. Beyond 'avail', we
+ * recycle the same ports which is likely to fail again, and hence is not
+ * useful. The caller must ensure that range is not NULL.
+ */
+static inline int port_range_avail(struct port_range *range)
+{
+	return range->avail;
+}
+
 /* return a new initialized port range of N ports. The ports are not
  * filled in, it's up to the caller to do it.
  */
diff --git a/include/proto/proto_tcp.h b/include/proto/proto_tcp.h
index 13d7a78..05c2b0e 100644
--- a/include/proto/proto_tcp.h
+++ b/include/proto/proto_tcp.h
@@ -40,6 +40,25 @@ int tcp_drain(int fd);
 /* Export some samples. */
 int smp_fetch_src(const struct arg *args, struct sample *smp, const char *kw, void *private);
 
+
+/*
+ * The maximum number of attempts to try to bind to a free source port. This
+ * is required in case some other process has bound to the same IP/port#.
+ */
+#define MAX_BIND_ATTEMPTS		10
+
+/*
+ * The maximum number of attempts to try to connect to a server. This is
+ * required when haproxy configuration file contains directive to bind to a
+ * source IP and port range. In this case, haproxy selects a port that we
+ * think is free, bind to it (which works even if the socket was not fully
+ * closed due to SO_REUSEADDR), but fail in connect() as the socket tuple
+ * may not be fully closed in the kernel, e.g., it may be in LAST-ACK state.
+ * These retries are to try avoiding getting an EADDRNOTAVAIL error during a
+ * socket connect.
+ */
+#define MAX_CONNECT_ATTEMPTS		3
+
 #endif /* _PROTO_PROTO_TCP_H */
 
 /*
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 4741651..a09fd66 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -244,6 +244,139 @@ static int create_server_socket(struct connection *conn)
 }
 
 /*
+ * This internal function should not be called directly. If source IP is not
+ * specified, or if source port range is not specified, it returns success
+ * and this implies that the caller will directly call connect instead of
+ * binding to a local IP/source-port. Otherwise it binds the socket to IP
+ * (and optional port from the source port range, if specified for this
+ * server), and returns the return value of the bind() system call.
+ */
+static int __do_bind(struct connection *conn, struct conn_src *src,
+		     int fd, struct proxy *be)
+{
+	int ret, flags = 0;
+
+	if (!src)
+		return SF_ERR_NONE;
+
+	if (is_inet_addr(&conn->addr.from)) {
+		switch (src->opts & CO_SRC_TPROXY_MASK) {
+		case CO_SRC_TPROXY_CLI:
+			conn->flags |= CO_FL_PRIVATE;
+			/* fall through */
+		case CO_SRC_TPROXY_ADDR:
+			flags = 3;
+			break;
+		case CO_SRC_TPROXY_CIP:
+		case CO_SRC_TPROXY_DYN:
+			conn->flags |= CO_FL_PRIVATE;
+			flags = 1;
+			break;
+		}
+	}
+
+#ifdef SO_BINDTODEVICE
+	/* Note: this might fail if not CAP_NET_RAW. Also there is no need to
+	 * do this more than once if we are trying to re-connect().
+	 */
+	if (src->iface_name)
+		setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, src->iface_name, src->iface_len + 1);
+#endif
+
+	if (src->sport_range) {
+		int attempts = MAX_BIND_ATTEMPTS; /* should be more than enough to find a spare port */
+		struct sockaddr_storage sa;
+
+		ret = 1;
+		memcpy(&sa, &src->source_addr, sizeof(sa));
+
+		do {
+			/* note: in case of retry, we may have to release a
+			 * previously allocated port, hence this loop's
+			 * construct.
+			 */
+			port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
+			fdinfo[fd].port_range = NULL;
+
+			if (!attempts)
+				break;
+			attempts--;
+
+			fdinfo[fd].local_port = port_range_alloc_port(src->sport_range);
+			if (!fdinfo[fd].local_port) {
+				conn->err_code = CO_ER_PORT_RANGE;
+				break;
+			}
+
+			fdinfo[fd].port_range = src->sport_range;
+			set_host_port(&sa, fdinfo[fd].local_port);
+
+			ret = tcp_bind_socket(fd, flags, &sa, &conn->addr.from);
+
+			if (ret != 0)
+				conn->err_code = CO_ER_CANT_BIND;
+		} while (ret != 0); /* binding NOK */
+	}
+	else {
+#ifdef IP_BIND_ADDRESS_NO_PORT
+		static int bind_address_no_port = 1;
+		setsockopt(fd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, (const void *) &bind_address_no_port, sizeof(int));
+#endif
+		ret = tcp_bind_socket(fd, flags, &src->source_addr, &conn->addr.from);
+		if (ret != 0)
+			conn->err_code = CO_ER_CANT_BIND;
+	}
+
+	if (unlikely(ret != 0)) {
+		port_range_release_port(fdinfo[fd].port_range,
+					fdinfo[fd].local_port);
+		fdinfo[fd].port_range = NULL;
+		close(fd);
+
+		if (ret == 1) {
+			Alert("Port: %d. Cannot bind to source address before connect() for backend %s. Aborting.\n",
+			      fdinfo[fd].local_port, be->id);
+			send_log(be, LOG_EMERG,
+				 "Port: %d. Cannot bind to source address before connect() for backend %s.\n",
+				 fdinfo[fd].local_port, be->id);
+		} else {
+			Alert("Cannot bind to tproxy source address before connect() for backend %s. Aborting.\n",
+			      be->id);
+			send_log(be, LOG_EMERG,
+				 "Cannot bind to tproxy source address before connect() for backend %s.\n",
+				 be->id);
+		}
+		conn->flags |= CO_FL_ERROR;
+		return SF_ERR_RESOURCE;
+	}
+
+	return SF_ERR_NONE;
+}
+
+/*
+ * Return the maximum number of times that we can attempt to connect to a
+ * server. When source IP/port is not defined, this is always 1. Else,
+ * we determine the maximum 'avail' ports, and return a lower limit of that
+ * value.
+ */
+static inline int get_max_conn_attempts(struct conn_src *src)
+{
+	/*
+	 * When source ip/port is defined, the maximum number of times that
+	 * we attempt connect() to a server is the minimum of available-ports
+	 * and MAX_CONNECT_ATTEMPTS. Note this can return zero which helps
+	 * in early failure.
+	 */
+	if (src && src->sport_range) {
+		return MIN(port_range_avail(src->sport_range),
+			   MAX_CONNECT_ATTEMPTS);
+	}
+
+	/* Source IP/port is not defined, try connect() once which will work */
+	return 1;
+}
+
+/*
  * This function initiates a TCP connection establishment to the target assigned
  * to connection <conn> using (si->{target,addr.to}). A source address may be
  * pointed to by conn->addr.from in case of transparent proxying. Normal source
@@ -279,6 +412,8 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
 	struct server *srv;
 	struct proxy *be;
 	struct conn_src *src;
+	int retry = 0;		/* flag indicating we should retry connect() */
+	int max_attempts;
 
 	conn->flags = CO_FL_WAIT_L4_CONN; /* connection in progress */
 
@@ -296,63 +431,6 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
 		return SF_ERR_INTERNAL;
 	}
 
-	fd = conn->t.sock.fd = create_server_socket(conn);
-
-	if (fd == -1) {
-		qfprintf(stderr, "Cannot get a server socket.\n");
-
-		if (errno == ENFILE) {
-			conn->err_code = CO_ER_SYS_FDLIM;
-			send_log(be, LOG_EMERG,
-				 "Proxy %s reached system FD limit at %d. Please check system tunables.\n",
-				 be->id, maxfd);
-		}
-		else if (errno == EMFILE) {
-			conn->err_code = CO_ER_PROC_FDLIM;
-			send_log(be, LOG_EMERG,
-				 "Proxy %s reached process FD limit at %d. Please check 'ulimit-n' and restart.\n",
-				 be->id, maxfd);
-		}
-		else if (errno == ENOBUFS || errno == ENOMEM) {
-			conn->err_code = CO_ER_SYS_MEMLIM;
-			send_log(be, LOG_EMERG,
-				 "Proxy %s reached system memory limit at %d sockets. Please check system tunables.\n",
-				 be->id, maxfd);
-		}
-		else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
-			conn->err_code = CO_ER_NOPROTO;
-		}
-		else
-			conn->err_code = CO_ER_SOCK_ERR;
-
-		/* this is a resource error */
-		conn->flags |= CO_FL_ERROR;
-		return SF_ERR_RESOURCE;
-	}
-
-	if (fd >= global.maxsock) {
-		/* do not log anything there, it's a normal condition when this option
-		 * is used to serialize connections to a server !
-		 */
-		Alert("socket(): not enough free sockets. Raise -n argument. Giving up.\n");
-		close(fd);
-		conn->err_code = CO_ER_CONF_FDLIM;
-		conn->flags |= CO_FL_ERROR;
-		return SF_ERR_PRXCOND; /* it is a configuration limit */
-	}
-
-	if ((fcntl(fd, F_SETFL, O_NONBLOCK)==-1) ||
-	    (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) == -1)) {
-		qfprintf(stderr,"Cannot set client socket to non blocking mode.\n");
-		close(fd);
-		conn->err_code = CO_ER_SOCK_ERR;
-		conn->flags |= CO_FL_ERROR;
-		return SF_ERR_INTERNAL;
-	}
-
-	if (be->options & PR_O_TCP_SRV_KA)
-		setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one));
-
 	/* allow specific binding :
 	 * - server-specific at first
 	 * - proxy-specific next
@@ -364,166 +442,194 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
 	else
 		src = NULL;
 
-	if (src) {
-		int ret, flags = 0;
-
-		if (is_inet_addr(&conn->addr.from)) {
-			switch (src->opts & CO_SRC_TPROXY_MASK) {
-			case CO_SRC_TPROXY_CLI:
-				conn->flags |= CO_FL_PRIVATE;
-				/* fall through */
-			case CO_SRC_TPROXY_ADDR:
-				flags = 3;
-				break;
-			case CO_SRC_TPROXY_CIP:
-			case CO_SRC_TPROXY_DYN:
-				conn->flags |= CO_FL_PRIVATE;
-				flags = 1;
-				break;
-			}
-		}
-
-#ifdef SO_BINDTODEVICE
-		/* Note: this might fail if not CAP_NET_RAW */
-		if (src->iface_name)
-			setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, src->iface_name, src->iface_len + 1);
-#endif
-
-		if (src->sport_range) {
-			int attempts = 10; /* should be more than enough to find a spare port */
-			struct sockaddr_storage sa;
+	/* Find the maximum number of connect() attempt's. */
+	max_attempts = get_max_conn_attempts(src);
+	if (!max_attempts) {
+		/* No more free ports - this is a resource error */
+		conn->err_code = CO_ER_FREE_PORTS;
+		conn->flags |= CO_FL_ERROR;
+		send_log(be, LOG_ERR, "Early Connect() failed for backend %s: no free ports.\n", be->id);
 
-			ret = 1;
-			memcpy(&sa, &src->source_addr, sizeof(sa));
+		return SF_ERR_RESOURCE;
+	}
 
-			do {
-				/* note: in case of retry, we may have to release a previously
-				 * allocated port, hence this loop's construct.
-				 */
-				port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
-				fdinfo[fd].port_range = NULL;
+	/*
+	 * If we have source ip/port specified and connect() returns
+	 * EADDRNOTAVAIL, we will retry with a new port if it is available.
+	 * This error can happen if the same socket tuple in the kernel was
+	 * not completely closed, e.g. it is in LAST-ACK state. This code
+	 * tries atmost max_attempts to initiate a connection. For any other
+	 * error, we will break out of the loop. For some more context, see
+	 * comments in include/proto/proto_tcp.h @MAX_CONNECT_ATTEMPTS.
+	 */
+	do {
+		int ret;
 
-				if (!attempts)
-					break;
-				attempts--;
+		fd = conn->t.sock.fd = create_server_socket(conn);
 
-				fdinfo[fd].local_port = port_range_alloc_port(src->sport_range);
-				if (!fdinfo[fd].local_port) {
-					conn->err_code = CO_ER_PORT_RANGE;
-					break;
-				}
+		if (fd == -1) {
+			qfprintf(stderr, "Cannot get a server socket.\n");
 
-				fdinfo[fd].port_range = src->sport_range;
-				set_host_port(&sa, fdinfo[fd].local_port);
+			if (errno == ENFILE) {
+				conn->err_code = CO_ER_SYS_FDLIM;
+				send_log(be, LOG_EMERG,
+					 "Proxy %s reached system FD limit at %d. Please check system tunables.\n",
+					 be->id, maxfd);
+			}
+			else if (errno == EMFILE) {
+				conn->err_code = CO_ER_PROC_FDLIM;
+				send_log(be, LOG_EMERG,
+					 "Proxy %s reached process FD limit at %d. Please check 'ulimit-n' and restart.\n",
+					 be->id, maxfd);
+			}
+			else if (errno == ENOBUFS || errno == ENOMEM) {
+				conn->err_code = CO_ER_SYS_MEMLIM;
+				send_log(be, LOG_EMERG,
+					 "Proxy %s reached system memory limit at %d sockets. Please check system tunables.\n",
+					 be->id, maxfd);
+			}
+			else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
+				conn->err_code = CO_ER_NOPROTO;
+			}
+			else
+				conn->err_code = CO_ER_SOCK_ERR;
 
-				ret = tcp_bind_socket(fd, flags, &sa, &conn->addr.from);
-				if (ret != 0)
-					conn->err_code = CO_ER_CANT_BIND;
-			} while (ret != 0); /* binding NOK */
-		}
-		else {
-#ifdef IP_BIND_ADDRESS_NO_PORT
-			static int bind_address_no_port = 1;
-			setsockopt(fd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, (const void *) &bind_address_no_port, sizeof(int));
-#endif
-			ret = tcp_bind_socket(fd, flags, &src->source_addr, &conn->addr.from);
-			if (ret != 0)
-				conn->err_code = CO_ER_CANT_BIND;
+			/* this is a resource error */
+			conn->flags |= CO_FL_ERROR;
+			return SF_ERR_RESOURCE;
 		}
 
-		if (unlikely(ret != 0)) {
-			port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
-			fdinfo[fd].port_range = NULL;
+		if (fd >= global.maxsock) {
+			/* do not log anything there, it's a normal condition when this option
+			 * is used to serialize connections to a server !
+			 */
+			Alert("socket(): not enough free sockets. Raise -n argument. Giving up.\n");
 			close(fd);
+			conn->err_code = CO_ER_CONF_FDLIM;
+			conn->flags |= CO_FL_ERROR;
+			return SF_ERR_PRXCOND; /* it is a configuration limit */
+		}
 
-			if (ret == 1) {
-				Alert("Cannot bind to source address before connect() for backend %s. Aborting.\n",
-				      be->id);
-				send_log(be, LOG_EMERG,
-					 "Cannot bind to source address before connect() for backend %s.\n",
-					 be->id);
-			} else {
-				Alert("Cannot bind to tproxy source address before connect() for backend %s. Aborting.\n",
-				      be->id);
-				send_log(be, LOG_EMERG,
-					 "Cannot bind to tproxy source address before connect() for backend %s.\n",
-					 be->id);
-			}
+		if ((fcntl(fd, F_SETFL, O_NONBLOCK)==-1) ||
+		    (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) == -1)) {
+			qfprintf(stderr,"Cannot set client socket to non blocking mode.\n");
+			close(fd);
+			conn->err_code = CO_ER_SOCK_ERR;
 			conn->flags |= CO_FL_ERROR;
-			return SF_ERR_RESOURCE;
+			return SF_ERR_INTERNAL;
 		}
-	}
+
+		if (be->options & PR_O_TCP_SRV_KA)
+			setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one));
+
+		/* Bind a name to the socket if required */
+		ret = __do_bind(conn, src, fd, be);
+		if (ret != SF_ERR_NONE)
+			return ret;
 
 #if defined(TCP_QUICKACK)
-	/* disabling tcp quick ack now allows the first request to leave the
-	 * machine with the first ACK. We only do this if there are pending
-	 * data in the buffer.
-	 */
-	if (delack == 2 || ((delack || data || conn->send_proxy_ofs) && (be->options2 & PR_O2_SMARTCON)))
-                setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &zero, sizeof(zero));
+		/* disabling tcp quick ack now allows the first request
+		 * to leave the machine with the first ACK. We only do
+		 * this if there are pending data in the buffer.
+		 */
+		if ((delack == 2 || ((delack || data ||
+		     conn->send_proxy_ofs) &&
+		     (be->options2 & PR_O2_SMARTCON))))
+			setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK,
+				   &zero, sizeof(zero));
 #endif
 
 #ifdef TCP_USER_TIMEOUT
-	/* there is not much more we can do here when it fails, it's still minor */
-	if (srv && srv->tcp_ut)
-		setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &srv->tcp_ut, sizeof(srv->tcp_ut));
+		/*
+		 * there is not much more we can do here when it fails,
+		 * it's still minor
+		 */
+		if (srv && srv->tcp_ut)
+			setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT,
+				   &srv->tcp_ut, sizeof(srv->tcp_ut));
 #endif
-	if (global.tune.server_sndbuf)
-                setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &global.tune.server_sndbuf, sizeof(global.tune.server_sndbuf));
-
-	if (global.tune.server_rcvbuf)
-                setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &global.tune.server_rcvbuf, sizeof(global.tune.server_rcvbuf));
-
-	if (connect(fd, (struct sockaddr *)&conn->addr.to, get_addr_len(&conn->addr.to)) == -1) {
-		if (errno == EINPROGRESS || errno == EALREADY) {
-			/* common case, let's wait for connect status */
-			conn->flags |= CO_FL_WAIT_L4_CONN;
-		}
-		else if (errno == EISCONN) {
-			/* should normally not happen but if so, indicates that it's OK */
-			conn->flags &= ~CO_FL_WAIT_L4_CONN;
-		}
-		else if (errno == EAGAIN || errno == EADDRINUSE || errno == EADDRNOTAVAIL) {
-			char *msg;
-			if (errno == EAGAIN || errno == EADDRNOTAVAIL) {
-				msg = "no free ports";
-				conn->err_code = CO_ER_FREE_PORTS;
+		if (global.tune.server_sndbuf)
+			setsockopt(fd, SOL_SOCKET, SO_SNDBUF,
+				   &global.tune.server_sndbuf,
+				   sizeof(global.tune.server_sndbuf));
+
+		if (global.tune.server_rcvbuf)
+			setsockopt(fd, SOL_SOCKET, SO_RCVBUF,
+				   &global.tune.server_rcvbuf,
+				   sizeof(global.tune.server_rcvbuf));
+
+		retry = 0;
+		ret = connect(fd, (struct sockaddr *)&conn->addr.to,
+			    get_addr_len(&conn->addr.to));
+
+		if (ret == -1) {
+			if (errno == EINPROGRESS || errno == EALREADY) {
+				/* common case, let's wait for connect status */
+				conn->flags |= CO_FL_WAIT_L4_CONN;
 			}
-			else {
-				msg = "local address already in use";
-				conn->err_code = CO_ER_ADDR_INUSE;
+			else if (errno == EISCONN) {
+				/* should normally not happen but if so, indicates that it's OK */
+				conn->flags &= ~CO_FL_WAIT_L4_CONN;
 			}
+			else if (errno == EAGAIN || errno == EADDRINUSE || errno == EADDRNOTAVAIL) {
+				char *msg;
+
+				if (errno == EAGAIN || errno == EADDRNOTAVAIL) {
+					if (errno == EADDRNOTAVAIL &&
+					    --max_attempts > 0) {
+						/*
+						 * Try a new port since this
+						 * tuple may not be fully closed
+						 * in the kernel, e.g. the
+						 * socket is in last-ack state.
+						 */
+						send_log(be, LOG_ERR, "Connect(port=%d) failed for backend %s, socket not fully closed, RETRYING... (max_attempts = %d)\n", fdinfo[fd].local_port, be->id, max_attempts);
+						port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
+						fdinfo[fd].port_range = NULL;
+						close(fd);
+						retry = 1;
+						continue;
+					}
+
+					msg = "no free ports";
+					conn->err_code = CO_ER_FREE_PORTS;
+				}
+				else {
+					msg = "local address already in use";
+					conn->err_code = CO_ER_ADDR_INUSE;
+				}
 
-			qfprintf(stderr,"Connect() failed for backend %s: %s.\n", be->id, msg);
-			port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
-			fdinfo[fd].port_range = NULL;
-			close(fd);
-			send_log(be, LOG_ERR, "Connect() failed for backend %s: %s.\n", be->id, msg);
-			conn->flags |= CO_FL_ERROR;
-			return SF_ERR_RESOURCE;
-		} else if (errno == ETIMEDOUT) {
-			//qfprintf(stderr,"Connect(): ETIMEDOUT");
-			port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
-			fdinfo[fd].port_range = NULL;
-			close(fd);
-			conn->err_code = CO_ER_SOCK_ERR;
-			conn->flags |= CO_FL_ERROR;
-			return SF_ERR_SRVTO;
-		} else {
-			// (errno == ECONNREFUSED || errno == ENETUNREACH || errno == EACCES || errno == EPERM)
-			//qfprintf(stderr,"Connect(): %d", errno);
-			port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
-			fdinfo[fd].port_range = NULL;
-			close(fd);
-			conn->err_code = CO_ER_SOCK_ERR;
-			conn->flags |= CO_FL_ERROR;
-			return SF_ERR_SRVCL;
+				qfprintf(stderr, "Connect(port=%d) failed for backend %s: %s.\n", fdinfo[fd].local_port, be->id, msg);
+				port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
+				fdinfo[fd].port_range = NULL;
+				close(fd);
+				send_log(be, LOG_ERR, "Connect(port=%d) failed for backend %s: %s.\n", fdinfo[fd].local_port, be->id, msg);
+				conn->flags |= CO_FL_ERROR;
+				return SF_ERR_RESOURCE;
+			} else if (errno == ETIMEDOUT) {
+				//qfprintf(stderr,"Connect(): ETIMEDOUT");
+				port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
+				fdinfo[fd].port_range = NULL;
+				close(fd);
+				conn->err_code = CO_ER_SOCK_ERR;
+				conn->flags |= CO_FL_ERROR;
+				return SF_ERR_SRVTO;
+			} else {
+				// (errno == ECONNREFUSED || errno == ENETUNREACH || errno == EACCES || errno == EPERM)
+				//qfprintf(stderr,"Connect(): %d", errno);
+				port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
+				fdinfo[fd].port_range = NULL;
+				close(fd);
+				conn->err_code = CO_ER_SOCK_ERR;
+				conn->flags |= CO_FL_ERROR;
+				return SF_ERR_SRVCL;
+			}
 		}
-	}
-	else {
-		/* connect() == 0, this is great! */
-		conn->flags &= ~CO_FL_WAIT_L4_CONN;
-	}
+		else {
+			/* connect() == 0, this is great! */
+			conn->flags &= ~CO_FL_WAIT_L4_CONN;
+		}
+	} while (retry);
 
 	conn->flags |= CO_FL_ADDR_TO_SET;
 
-- 
2.1.4

Reply via email to