]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: connection: add more connection error codes to cover common errno
authorWilly Tarreau <w@1wt.eu>
Tue, 5 Nov 2024 16:49:15 +0000 (17:49 +0100)
committerWilly Tarreau <w@1wt.eu>
Tue, 5 Nov 2024 17:57:43 +0000 (18:57 +0100)
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.

doc/configuration.txt
include/haproxy/connection-t.h
include/haproxy/connection.h
src/connection.c

index 56bd10281ebfb2bd4c995aaef5bf923370583e8c..3056ba6b5b7b67032d5be3cbef78014203ce3480 100644 (file)
@@ -22727,6 +22727,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
index ad454a320ea962b14169a147980f305c1ff070a0..42f529e151ab4b31fb0ecbf0d041d39be038923d 100644 (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() */
index dc187eb918e1148a75331c304859cf5b2237e875..b5271ae4b6ab8b34cb733cdd3534a957d228fe46 100644 (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)
 {
index 59baec65b67c2efce79665d65e5cbfd7a1d4ac03..b8289ae3fff93189b91907765d89fe3e2b1cd1b8 100644 (file)
@@ -744,10 +744,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