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.
This commit is contained in:
Willy Tarreau 2014-01-24 16:08:19 +01:00
parent 45b34e8abc
commit 9ce7013429

View file

@ -279,6 +279,8 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
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 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
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 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
*/
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 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
(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 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
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 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
"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 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
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 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
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 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
if (conn_xprt_init(conn) < 0) {
conn_force_close(conn);
conn->flags |= CO_FL_ERROR;
return SN_ERR_RESOURCE;
}