MINOR: connection: add more connection error codes to cover common errno

While we get reports of connection setup errors in fc_err/bc_err, we
don't have the equivalent for the recv/send/splice syscalls. Let's
add provisions for new codes that cover the common errno values that
recv/send/splice can return, i.e. ECONNREFUSED, ENOMEM, EBADF, EFAULT,
EINVAL, ENOTCONN, ENOTSOCK, ENOBUFS, EPIPE. We also add a special case
for when the poller reported the error itself. It's worth noting that
EBADF/EFAULT/EINVAL will generally indicate serious bugs in the code
and should not be reported.

The only thing is that it's quite hard to forcefully (and reliably)
trigger these errors in automated tests as the timing is critical.
Using iptables to manually reset established connections in the
middle of large transfers at least permits to see some ECONNRESET
and/or EPIPE, but the other ones are harder to trigger.

(cherry picked from commit 00c383ff65c6378327382d2c055f66efb098498d)
Signed-off-by: Willy Tarreau <w@1wt.eu>
This commit is contained in:
Willy Tarreau 2024-11-05 17:49:15 +01:00
parent aa35557e76
commit 6200536920
4 changed files with 79 additions and 0 deletions

View File

@ -22140,6 +22140,18 @@ fc_err_str : string
| 42 | "SOCKS4 Proxy handshake aborted by server" |
| 43 | "SSL fatal error" |
| 44 | "Reverse connect failure" |
| 45 | "Poller reported POLLERR" |
| 46 | "ECONNREFUSED returned by OS" |
| 47 | "ECONNRESET returned by OS" |
| 48 | "ENETUNREACH returned by OS" |
| 49 | "ENOMEM returned by OS" |
| 50 | "EBADF returned by OS" |
| 51 | "EFAULT returned by OS" |
| 52 | "EINVAL returned by OS" |
| 53 | "ENCONN returned by OS" |
| 54 | "ENSOCK returned by OS" |
| 55 | "ENOBUFS returned by OS" |
| 56 | "EPIPE returned by OS" |
+----+---------------------------------------------------------------------------+
fc_fackets : integer

View File

@ -241,6 +241,19 @@ enum {
CO_ER_SSL_FATAL, /* SSL fatal error during a SSL_read or SSL_write */
CO_ER_REVERSE, /* Error during reverse connect */
CO_ER_POLLERR, /* we only noticed POLLERR */
CO_ER_EREFUSED, /* ECONNREFUSED returned to recv/send */
CO_ER_ERESET, /* ECONNRESET returned to recv/send */
CO_ER_EUNREACH, /* ENETUNREACH returned to recv/send */
CO_ER_ENOMEM, /* ENOMEM returned to recv/send */
CO_ER_EBADF, /* EBADF returned to recv/send (serious bug) */
CO_ER_EFAULT, /* EFAULT returned to recv/send (serious bug) */
CO_ER_EINVAL, /* EINVAL returned to recv/send (serious bug) */
CO_ER_ENCONN, /* ENCONN returned to recv/send */
CO_ER_ENSOCK, /* ENSOCK returned to recv/send */
CO_ER_ENOBUFS, /* ENOBUFS returned to send */
CO_ER_EPIPE, /* EPIPE returned to send */
};
/* error return codes for accept_conn() */

View File

@ -90,6 +90,7 @@ void conn_init(struct connection *conn, void *target);
struct connection *conn_new(void *target);
void conn_free(struct connection *conn);
void conn_release(struct connection *conn);
void conn_set_errno(struct connection *conn, int err);
struct conn_hash_node *conn_alloc_hash_node(struct connection *conn);
struct sockaddr_storage *sockaddr_alloc(struct sockaddr_storage **sap, const struct sockaddr_storage *orig, socklen_t len);
void sockaddr_free(struct sockaddr_storage **sap);
@ -107,6 +108,15 @@ void register_mux_proto(struct mux_proto_list *list);
extern struct idle_conns idle_conns[MAX_THREADS];
/* set conn->err_code to any CO_ER_* code if it was not set yet, otherwise
* does nothing.
*/
static inline void conn_set_errcode(struct connection *conn, int err_code)
{
if (!conn->err_code)
conn->err_code = err_code;
}
/* returns true if the transport layer is ready */
static inline int conn_xprt_ready(const struct connection *conn)
{

View File

@ -758,10 +758,54 @@ const char *conn_err_code_str(struct connection *c)
case CO_ER_SSL_FATAL: return "SSL fatal error";
case CO_ER_REVERSE: return "Reverse connect failure";
case CO_ER_POLLERR: return "Poller reported POLLERR";
case CO_ER_EREFUSED: return "ECONNREFUSED returned by OS";
case CO_ER_ERESET: return "ECONNRESET returned by OS";
case CO_ER_EUNREACH: return "ENETUNREACH returned by OS";
case CO_ER_ENOMEM: return "ENOMEM returned by OS";
case CO_ER_EBADF: return "EBADF returned by OS";
case CO_ER_EFAULT: return "EFAULT returned by OS";
case CO_ER_EINVAL: return "EINVAL returned by OS";
case CO_ER_ENCONN: return "ENCONN returned by OS";
case CO_ER_ENSOCK: return "ENSOCK returned by OS";
case CO_ER_ENOBUFS: return "ENOBUFS returned by OS";
case CO_ER_EPIPE: return "EPIPE returned by OS";
}
return NULL;
}
/* Try to set conn->err_code to a meaningful value based on the errno value
* passed in <err>. Values of errno are meant to be set on return from recv/
* send mostly, so not all of them are handled. Any existing err_code is
* preserved.
*/
void conn_set_errno(struct connection *conn, int err)
{
uchar code = 0;
if (conn->err_code)
return;
switch (err) {
case ECONNREFUSED: code = CO_ER_EREFUSED; break;
case ECONNRESET: code = CO_ER_ERESET; break;
case EHOSTUNREACH: code = CO_ER_EUNREACH; break;
case ENETUNREACH: code = CO_ER_EUNREACH; break;
case ENOMEM: code = CO_ER_ENOMEM; break;
case EBADF: code = CO_ER_EBADF; break;
case EFAULT: code = CO_ER_EFAULT; break;
case EINVAL: code = CO_ER_EINVAL; break;
case ENOTCONN: code = CO_ER_ENCONN; break;
case ENOTSOCK: code = CO_ER_ENSOCK; break;
case ENOBUFS: code = CO_ER_ENOBUFS; break;
case EPIPE: code = CO_ER_EPIPE; break;
default: code = CO_ER_SOCK_ERR; break;
}
conn->err_code = code;
}
/* Send a message over an established connection. It makes use of send() and
* returns the same return code and errno. If the socket layer is not ready yet
* then -1 is returned and ENOTSOCK is set into errno. If the fd is not marked