]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: sock: implement sock_accept_conn() to accept a connection
authorWilly Tarreau <w@1wt.eu>
Thu, 15 Oct 2020 07:21:31 +0000 (09:21 +0200)
committerWilly Tarreau <w@1wt.eu>
Thu, 15 Oct 2020 19:47:56 +0000 (21:47 +0200)
The socket-specific accept() code in listener_accept() has nothing to
do there. Let's move it to sock.c where it can be significantly cleaned
up. It will now directly return an accepted connection and provide a
status code instead of letting listener_accept() deal with various errno
values. Note that this doesn't support the sockpair specific code.

The function is now responsible for dealing with its own receiver's
polling state and calling fd_cant_recv() when facing EAGAIN.

One tiny change from the previous implementation is that the connection's
sockaddr is now allocated before trying accept(), which saves a memcpy()
of the resulting address for each accept at the expense of a cheap
pool_alloc/pool_free on the final accept returning EAGAIN. This still
apparently slightly improves accept performance in microbencharks.

include/haproxy/sock.h
src/proto_tcp.c
src/proto_uxst.c
src/sock.c

index 7a9125ae3d7601e5c7b088423e869a037c77905f..193d2e451b2a2942b32ea3df77bb0450396098cf 100644 (file)
@@ -41,6 +41,7 @@ int sock_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir);
 int sock_get_old_sockets(const char *unixsocket);
 int sock_find_compatible_fd(const struct receiver *rx);
 int sock_accepting_conn(const struct receiver *rx);
+struct connection *sock_accept_conn(struct listener *l, int *status);
 
 #endif /* _HAPROXY_SOCK_H */
 
index 950daec818f417be4a40a06d066bf552c09d3298..cf1166415cb6f2ca94f84b15dbcfa38fa58dd54f 100644 (file)
@@ -67,6 +67,7 @@ static struct protocol proto_tcpv4 = {
        .unbind = default_unbind_listener,
        .suspend = default_suspend_listener,
        .resume  = default_resume_listener,
+       .accept_conn = sock_accept_conn,
        .rx_enable = sock_enable,
        .rx_disable = sock_disable,
        .rx_unbind = sock_unbind,
@@ -96,6 +97,7 @@ static struct protocol proto_tcpv6 = {
        .unbind = default_unbind_listener,
        .suspend = default_suspend_listener,
        .resume  = default_resume_listener,
+       .accept_conn = sock_accept_conn,
        .rx_enable = sock_enable,
        .rx_disable = sock_disable,
        .rx_unbind = sock_unbind,
index be8525b86031d081c0d44f2deb6306597465efa0..eb1cf9daa62b94d78267d9205d966d662ee46aff 100644 (file)
@@ -61,6 +61,7 @@ static struct protocol proto_unix = {
        .disable = uxst_disable_listener,
        .unbind = default_unbind_listener,
        .suspend = default_suspend_listener,
+       .accept_conn = sock_accept_conn,
        .rx_enable = sock_enable,
        .rx_disable = sock_disable,
        .rx_unbind = sock_unbind,
index 8f890ea3497b700c3496445932d9391cdc2f0280..f70bb744a2bd69dee5158cd87f078b60497dfae2 100644 (file)
@@ -10,6 +10,7 @@
  *
  */
 
+#define _GNU_SOURCE
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -27,6 +28,7 @@
 #include <haproxy/api.h>
 #include <haproxy/connection.h>
 #include <haproxy/listener-t.h>
+#include <haproxy/log.h>
 #include <haproxy/namespace.h>
 #include <haproxy/sock.h>
 #include <haproxy/sock_inet.h>
 /* the list of remaining sockets transferred from an older process */
 struct xfer_sock_list *xfer_sock_list = NULL;
 
+
+/* Accept an incoming connection from listener <l>, and return it, as well as
+ * a CO_AC_* status code into <status> if not null. Null is returned on error.
+ * <l> must be a valid listener with a valid frontend.
+ */
+struct connection *sock_accept_conn(struct listener *l, int *status)
+{
+#ifdef USE_ACCEPT4
+       static int accept4_broken;
+#endif
+       struct proxy *p = l->bind_conf->frontend;
+       struct connection *conn;
+       socklen_t laddr;
+       int ret;
+       int cfd;
+
+       conn = conn_new(&l->obj_type);
+       if (!conn)
+               goto fail_conn;
+
+       if (!sockaddr_alloc(&conn->src, NULL, 0))
+               goto fail_addr;
+
+       /* accept() will mark all accepted FDs O_NONBLOCK and the ones accepted
+        * in the master process as FD_CLOEXEC. It's not done for workers
+        * because 1) workers are not supposed to execute anything so there's
+        * no reason for uselessly slowing down everything, and 2) that would
+        * prevent us from implementing fd passing in the future.
+        */
+#ifdef USE_ACCEPT4
+       laddr = sizeof(*conn->src);
+
+       /* only call accept4() if it's known to be safe, otherwise fallback to
+        * the legacy accept() + fcntl().
+        */
+       if (unlikely(accept4_broken) ||
+           (((cfd = accept4(l->rx.fd, (struct sockaddr *)conn->src, &laddr,
+                            SOCK_NONBLOCK | (master ? SOCK_CLOEXEC : 0))) == -1) &&
+            (errno == ENOSYS || errno == EINVAL || errno == EBADF) &&
+            (accept4_broken = 1)))
+#endif
+       {
+               laddr = sizeof(*conn->src);
+               if ((cfd = accept(l->rx.fd, (struct sockaddr *)conn->src, &laddr)) != -1) {
+                       fcntl(cfd, F_SETFL, O_NONBLOCK);
+                       if (master)
+                               fcntl(cfd, F_SETFD, FD_CLOEXEC);
+               }
+       }
+
+       if (likely(cfd != -1)) {
+               /* Perfect, the connection was accepted */
+               conn->handle.fd = cfd;
+               conn->flags |= CO_FL_ADDR_FROM_SET;
+               ret = CO_AC_DONE;
+               goto done;
+       }
+
+       /* error conditions below */
+       conn_free(conn);
+       conn = NULL;
+
+       switch (errno) {
+       case EAGAIN:
+               ret = CO_AC_DONE; /* nothing more to accept */
+               if (fdtab[l->rx.fd].ev & (FD_POLL_HUP|FD_POLL_ERR)) {
+                       /* the listening socket might have been disabled in a shared
+                        * process and we're a collateral victim. We'll just pause for
+                        * a while in case it comes back. In the mean time, we need to
+                        * clear this sticky flag.
+                        */
+                       _HA_ATOMIC_AND(&fdtab[l->rx.fd].ev, ~(FD_POLL_HUP|FD_POLL_ERR));
+                       ret = CO_AC_PAUSE;
+               }
+               fd_cant_recv(l->rx.fd);
+               break;
+
+       case EINVAL:
+               /* might be trying to accept on a shut fd (eg: soft stop) */
+               ret = CO_AC_PAUSE;
+               break;
+
+       case EINTR:
+       case ECONNABORTED:
+               ret = CO_AC_RETRY;
+               break;
+
+       case ENFILE:
+               if (p)
+                       send_log(p, LOG_EMERG,
+                                "Proxy %s reached system FD limit (maxsock=%d). Please check system tunables.\n",
+                                p->id, global.maxsock);
+               ret = CO_AC_PAUSE;
+               break;
+
+       case EMFILE:
+               if (p)
+                       send_log(p, LOG_EMERG,
+                                "Proxy %s reached process FD limit (maxsock=%d). Please check 'ulimit-n' and restart.\n",
+                                p->id, global.maxsock);
+               ret = CO_AC_PAUSE;
+               break;
+
+       case ENOBUFS:
+       case ENOMEM:
+               if (p)
+                       send_log(p, LOG_EMERG,
+                                "Proxy %s reached system memory limit (maxsock=%d). Please check system tunables.\n",
+                                p->id, global.maxsock);
+               ret = CO_AC_PAUSE;
+               break;
+
+       default:
+               /* unexpected result, let's give up and let other tasks run */
+               ret = CO_AC_YIELD;
+       }
+ done:
+       if (status)
+               *status = ret;
+       return conn;
+
+ fail_addr:
+       conn_free(conn);
+       conn = NULL;
+ fail_conn:
+       ret = CO_AC_PAUSE;
+       goto done;
+}
+
 /* Create a socket to connect to the server in conn->dst (which MUST be valid),
  * using the configured namespace if needed, or the one passed by the proxy
  * protocol if required to do so. It ultimately calls socket() or socketat()