diff --git a/src/proto_sockpair.c b/src/proto_sockpair.c index 357a5fe17..8be1093c3 100644 --- a/src/proto_sockpair.c +++ b/src/proto_sockpair.c @@ -34,6 +34,7 @@ #include <haproxy/global.h> #include <haproxy/list.h> #include <haproxy/listener.h> +#include <haproxy/log.h> #include <haproxy/protocol.h> #include <haproxy/proto_sockpair.h> #include <haproxy/sock.h> @@ -48,6 +49,7 @@ static void sockpair_enable_listener(struct listener *listener); static void sockpair_disable_listener(struct listener *listener); static int sockpair_connect_server(struct connection *conn, int flags); static int sockpair_accepting_conn(const struct receiver *rx); +struct connection *sockpair_accept_conn(struct listener *l, int *status); struct proto_fam proto_fam_sockpair = { .name = "sockpair", @@ -74,6 +76,7 @@ static struct protocol proto_sockpair = { .enable = sockpair_enable_listener, .disable = sockpair_disable_listener, .unbind = default_unbind_listener, + .accept_conn = sockpair_accept_conn, .rx_unbind = sock_unbind, .rx_enable = sock_enable, .rx_disable = sock_disable, @@ -471,6 +474,104 @@ static int sockpair_accepting_conn(const struct receiver *rx) return 1; } +/* 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 *sockpair_accept_conn(struct listener *l, int *status) +{ + struct proxy *p = l->bind_conf->frontend; + struct connection *conn = NULL; + int ret; + int cfd; + + if ((cfd = recv_fd_uxst(l->rx.fd)) != -1) + fcntl(cfd, F_SETFL, O_NONBLOCK); + + if (likely(cfd != -1)) { + /* Perfect, the connection was accepted */ + conn = conn_new(&l->obj_type); + if (!conn) + goto fail_conn; + + if (!sockaddr_alloc(&conn->src, NULL, 0)) + goto fail_addr; + + /* just like with UNIX sockets, only the family is filled */ + conn->src->ss_family = AF_UNIX; + conn->handle.fd = cfd; + conn->flags |= CO_FL_ADDR_FROM_SET; + ret = CO_AC_DONE; + goto done; + } + + 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; +} + /* * Local variables: * c-indent-level: 8 diff --git a/src/sock.c b/src/sock.c index f70bb744a..990db23a5 100644 --- a/src/sock.c +++ b/src/sock.c @@ -48,16 +48,13 @@ struct connection *sock_accept_conn(struct listener *l, int *status) static int accept4_broken; #endif struct proxy *p = l->bind_conf->frontend; - struct connection *conn; + struct connection *conn = NULL; + struct sockaddr_storage *addr = NULL; 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)) + if (!sockaddr_alloc(&addr, NULL, 0)) goto fail_addr; /* accept() will mark all accepted FDs O_NONBLOCK and the ones accepted @@ -73,14 +70,14 @@ struct connection *sock_accept_conn(struct listener *l, int *status) * the legacy accept() + fcntl(). */ if (unlikely(accept4_broken) || - (((cfd = accept4(l->rx.fd, (struct sockaddr *)conn->src, &laddr, + (((cfd = accept4(l->rx.fd, (struct sockaddr*)addr, &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) { + if ((cfd = accept(l->rx.fd, (struct sockaddr*)addr, &laddr)) != -1) { fcntl(cfd, F_SETFL, O_NONBLOCK); if (master) fcntl(cfd, F_SETFD, FD_CLOEXEC); @@ -89,6 +86,11 @@ struct connection *sock_accept_conn(struct listener *l, int *status) if (likely(cfd != -1)) { /* Perfect, the connection was accepted */ + conn = conn_new(&l->obj_type); + if (!conn) + goto fail_conn; + + conn->src = addr; conn->handle.fd = cfd; conn->flags |= CO_FL_ADDR_FROM_SET; ret = CO_AC_DONE; @@ -96,8 +98,7 @@ struct connection *sock_accept_conn(struct listener *l, int *status) } /* error conditions below */ - conn_free(conn); - conn = NULL; + sockaddr_free(&addr); switch (errno) { case EAGAIN: @@ -158,10 +159,9 @@ struct connection *sock_accept_conn(struct listener *l, int *status) *status = ret; return conn; - fail_addr: - conn_free(conn); - conn = NULL; fail_conn: + sockaddr_free(&addr); + fail_addr: ret = CO_AC_PAUSE; goto done; }