MEDIUM: tcp: report connection error at the connection level

Now when a connection error happens, it is reported in the connection
so that upper layers know exactly what happened. This is particularly
useful with health checks and resources exhaustion.
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index d01be31..ab8f343 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -279,6 +279,8 @@
 	struct proxy *be;
 	struct conn_src *src;
 
+	conn->flags = CO_FL_WAIT_L4_CONN; /* connection in progress */
+
 	switch (obj_type(conn->target)) {
 	case OBJ_TYPE_PROXY:
 		be = objt_proxy(conn->target);
@@ -289,25 +291,39 @@
 		be = srv->proxy;
 		break;
 	default:
+		conn->flags |= CO_FL_ERROR;
 		return SN_ERR_INTERNAL;
 	}
 
 	if ((fd = conn->t.sock.fd = socket(conn->addr.to.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
 		qfprintf(stderr, "Cannot get a server socket.\n");
 
-		if (errno == ENFILE)
+		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)
+		}
+		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)
+		}
+		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 SN_ERR_RESOURCE;
 	}
 
@@ -317,6 +333,8 @@
 		 */
 		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 SN_ERR_PRXCOND; /* it is a configuration limit */
 	}
 
@@ -324,6 +342,8 @@
 	    (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 SN_ERR_INTERNAL;
 	}
 
@@ -382,17 +402,23 @@
 				attempts--;
 
 				fdinfo[fd].local_port = port_range_alloc_port(src->sport_range);
-				if (!fdinfo[fd].local_port)
+				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 {
 			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)) {
@@ -413,6 +439,7 @@
 					 "Cannot bind to tproxy source address before connect() for backend %s.\n",
 					 be->id);
 			}
+			conn->flags |= CO_FL_ERROR;
 			return SN_ERR_RESOURCE;
 		}
 	}
@@ -440,22 +467,29 @@
 
 		if (errno == EAGAIN || errno == EADDRINUSE || errno == EADDRNOTAVAIL) {
 			char *msg;
-			if (errno == EAGAIN || errno == EADDRNOTAVAIL)
+			if (errno == EAGAIN || errno == EADDRNOTAVAIL) {
 				msg = "no free ports";
-			else
+				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 SN_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 SN_ERR_SRVTO;
 		} else {
 			// (errno == ECONNREFUSED || errno == ENETUNREACH || errno == EACCES || errno == EPERM)
@@ -463,11 +497,12 @@
 			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 SN_ERR_SRVCL;
 		}
 	}
 
-	conn->flags  = CO_FL_WAIT_L4_CONN; /* connection in progress */
 	conn->flags |= CO_FL_ADDR_TO_SET;
 
 	/* Prepare to send a few handshakes related to the on-wire protocol. */
@@ -480,6 +515,7 @@
 
 	if (conn_xprt_init(conn) < 0) {
 		conn_force_close(conn);
+		conn->flags |= CO_FL_ERROR;
 		return SN_ERR_RESOURCE;
 	}