diff --git a/include/haproxy/sock.h b/include/haproxy/sock.h index 7a9125ae3..193d2e451 100644 --- a/include/haproxy/sock.h +++ b/include/haproxy/sock.h @@ -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 */ diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 950daec81..cf1166415 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -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, diff --git a/src/proto_uxst.c b/src/proto_uxst.c index be8525b86..eb1cf9daa 100644 --- a/src/proto_uxst.c +++ b/src/proto_uxst.c @@ -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, diff --git a/src/sock.c b/src/sock.c index 8f890ea34..f70bb744a 100644 --- a/src/sock.c +++ b/src/sock.c @@ -10,6 +10,7 @@ * */ +#define _GNU_SOURCE #include #include #include @@ -27,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +37,135 @@ /* the list of remaining sockets transferred from an older process */ struct xfer_sock_list *xfer_sock_list = NULL; + +/* Accept an incoming connection from listener , and return it, as well as + * a CO_AC_* status code into if not null. Null is returned on error. + * 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()