From f886e3478def3afe645b086243a919246a4102a4 Mon Sep 17 00:00:00 2001 From: Olivier Houchard Date: Wed, 5 Apr 2017 22:24:59 +0200 Subject: [PATCH] MINOR: cli: Add a command to send listening sockets. Add a new command that will send all the listening sockets, via the stats socket, and their properties. This is a first step to workaround the linux problem when reloading haproxy. --- include/types/connection.h | 8 ++ src/cli.c | 181 +++++++++++++++++++++++++++++++++++++ src/proto_uxst.c | 50 ++++++++++ 3 files changed, 239 insertions(+) diff --git a/include/types/connection.h b/include/types/connection.h index 5ce5e0cc5..9d1b51af2 100644 --- a/include/types/connection.h +++ b/include/types/connection.h @@ -389,6 +389,14 @@ struct tlv_ssl { #define PP2_CLIENT_CERT_CONN 0x02 #define PP2_CLIENT_CERT_SESS 0x04 + +/* + * Linux seems to be able to send 253 fds per sendmsg(), not sure + * about the other OSes. + */ +/* Max number of file descriptors we send in one sendmsg() */ +#define MAX_SEND_FD 253 + #endif /* _TYPES_CONNECTION_H */ /* diff --git a/src/cli.c b/src/cli.c index fa45db918..54fb43892 100644 --- a/src/cli.c +++ b/src/cli.c @@ -24,6 +24,8 @@ #include #include +#include + #include #include #include @@ -1013,6 +1015,184 @@ static int bind_parse_level(char **args, int cur_arg, struct proxy *px, struct b return 0; } +/* Send all the bound sockets, always returns 1 */ +static int _getsocks(char **args, struct appctx *appctx, void *private) +{ + char *cmsgbuf = NULL; + unsigned char *tmpbuf = NULL; + struct cmsghdr *cmsg; + struct stream_interface *si = appctx->owner; + struct connection *remote = objt_conn(si_opposite(si)->end); + struct msghdr msghdr; + struct iovec iov; + int *tmpfd; + int tot_fd_nb = 0; + struct proxy *px; + int i = 0; + int fd = remote->t.sock.fd; + int curoff = 0; + int old_fcntl; + int ret; + + /* Temporary set the FD in blocking mode, that will make our life easier */ + old_fcntl = fcntl(fd, F_GETFL); + if (old_fcntl < 0) { + Warning("Couldn't get the flags for the unix socket\n"); + goto out; + } + cmsgbuf = malloc(CMSG_SPACE(sizeof(int) * MAX_SEND_FD)); + if (!cmsgbuf) { + Warning("Failed to allocate memory to send sockets\n"); + goto out; + } + if (fcntl(fd, F_SETFL, old_fcntl &~ O_NONBLOCK) == -1) { + Warning("Cannot make the unix socket blocking\n"); + goto out; + } + iov.iov_base = &tot_fd_nb; + iov.iov_len = sizeof(tot_fd_nb); + if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) + goto out; + memset(&msghdr, 0, sizeof(msghdr)); + /* + * First, calculates the total number of FD, so that we can let + * the caller know how much he should expects. + */ + px = proxy; + while (px) { + struct listener *l; + + list_for_each_entry(l, &px->conf.listeners, by_fe) { + /* Only transfer IPv4/IPv6 sockets */ + if (l->proto->sock_family == AF_INET || + l->proto->sock_family == AF_INET6 || + l->proto->sock_family == AF_UNIX) + tot_fd_nb++; + } + px = px->next; + } + if (tot_fd_nb == 0) + goto out; + + /* First send the total number of file descriptors, so that the + * receiving end knows what to expect. + */ + msghdr.msg_iov = &iov; + msghdr.msg_iovlen = 1; + ret = sendmsg(fd, &msghdr, 0); + if (ret != sizeof(tot_fd_nb)) { + Warning("Failed to send the number of sockets to send\n"); + goto out; + } + + /* Now send the fds */ + msghdr.msg_control = cmsgbuf; + msghdr.msg_controllen = CMSG_SPACE(sizeof(int) * MAX_SEND_FD); + cmsg = CMSG_FIRSTHDR(&msghdr); + cmsg->cmsg_len = CMSG_LEN(MAX_SEND_FD * sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + tmpfd = (int *)CMSG_DATA(cmsg); + + px = proxy; + /* For each socket, e message is sent, containing the following : + * Size of the namespace name (or 0 if none), as an unsigned char. + * The namespace name, if any + * Size of the interface name (or 0 if none), as an unsigned char + * The interface name, if any + * Listener options, as an int. + */ + /* We will send sockets MAX_SEND_FD per MAX_SEND_FD, allocate a + * buffer big enough to store the socket informations. + */ + tmpbuf = malloc(MAX_SEND_FD * (1 + NAME_MAX + 1 + IFNAMSIZ + sizeof(int))); + if (tmpbuf == NULL) { + Warning("Failed to allocate memory to transfer socket informations\n"); + goto out; + } + iov.iov_base = tmpbuf; + while (px) { + struct listener *l; + + list_for_each_entry(l, &px->conf.listeners, by_fe) { + int ret; + /* Only transfer IPv4/IPv6 sockets */ + if (l->state >= LI_LISTEN && + (l->proto->sock_family == AF_INET || + l->proto->sock_family == AF_INET6 || + l->proto->sock_family == AF_UNIX)) { + memcpy(&tmpfd[i % MAX_SEND_FD], &l->fd, sizeof(l->fd)); + if (!l->netns) + tmpbuf[curoff++] = 0; +#ifdef CONFIG_HAP_NS + else { + char *name = l->netns->node.key; + unsigned char len = l->netns->name_len; + tmpbuf[curoff++] = len; + memcpy(tmpbuf + curoff, name, len); + curoff += len; + } +#endif + if (l->interface) { + unsigned char len = strlen(l->interface); + tmpbuf[curoff++] = len; + memcpy(tmpbuf + curoff, l->interface, len); + curoff += len; + } else + tmpbuf[curoff++] = 0; + memcpy(tmpbuf + curoff, &l->options, + sizeof(l->options)); + curoff += sizeof(l->options); + + + i++; + } else + continue; + if ((!(i % MAX_SEND_FD))) { + iov.iov_len = curoff; + if (sendmsg(fd, &msghdr, 0) != curoff) { + Warning("Failed to transfer sockets\n"); + printf("errno %d\n", errno); + goto out; + } + /* Wait for an ack */ + do { + ret = recv(fd, &tot_fd_nb, + sizeof(tot_fd_nb), 0); + } while (ret == -1 && errno == EINTR); + if (ret <= 0) { + Warning("Unexpected error while transferring sockets\n"); + goto out; + } + curoff = 0; + } + + } + px = px->next; + } + if (i % MAX_SEND_FD) { + iov.iov_len = curoff; + cmsg->cmsg_len = CMSG_LEN((i % MAX_SEND_FD) * sizeof(int)); + msghdr.msg_controllen = CMSG_SPACE(sizeof(int) * (i % MAX_SEND_FD)); + if (sendmsg(fd, &msghdr, 0) != curoff) { + Warning("Failed to transfer sockets\n"); + goto out; + } + } + +out: + if (old_fcntl >= 0 && fcntl(fd, F_SETFL, old_fcntl) == -1) { + Warning("Cannot make the unix socket non-blocking\n"); + goto out; + } + appctx->st0 = CLI_ST_END; + free(cmsgbuf); + free(tmpbuf); + return 1; +} + + + static struct applet cli_applet = { .obj_type = OBJ_TYPE_APPLET, .name = "", /* used for logging */ @@ -1027,6 +1207,7 @@ static struct cli_kw_list cli_kws = {{ },{ { { "set", "timeout", NULL }, "set timeout : change a timeout setting", cli_parse_set_timeout, NULL, NULL }, { { "show", "env", NULL }, "show env [var] : dump environment variables known to the process", cli_parse_show_env, cli_io_handler_show_env, NULL }, { { "show", "cli", "sockets", NULL }, "show cli sockets : dump list of cli sockets", cli_parse_default, cli_io_handler_show_cli_sock, NULL }, + { { "_getsocks", NULL }, NULL, _getsocks, NULL }, {{},} }}; diff --git a/src/proto_uxst.c b/src/proto_uxst.c index 27ff0fa40..d68267e83 100644 --- a/src/proto_uxst.c +++ b/src/proto_uxst.c @@ -150,6 +150,54 @@ static void destroy_uxst_socket(const char *path) ********************************/ +static int uxst_find_compatible_fd(struct listener *l) +{ + struct xfer_sock_list *xfer_sock = xfer_sock_list; + int ret = -1; + + while (xfer_sock) { + struct sockaddr_un *un1 = (void *)&l->addr; + struct sockaddr_un *un2 = (void *)&xfer_sock->addr; + + /* + * The bound socket's path as returned by getsockaddr + * will be the temporary name .XXXXX.tmp, + * so we can't just compare the two names + */ + if (xfer_sock->addr.ss_family == AF_UNIX && + strncmp(un1->sun_path, un2->sun_path, + strlen(un1->sun_path)) == 0) { + char *after_sockname = un2->sun_path + + strlen(un1->sun_path); + /* Make a reasonnable effort to check that + * it is indeed a haproxy-generated temporary + * name, it's not perfect, but probably good enough. + */ + if (after_sockname[0] == '.') { + after_sockname++; + while (after_sockname[0] >= '0' && + after_sockname[0] <= '9') + after_sockname++; + if (!strcmp(after_sockname, ".tmp")) + break; + } + } + xfer_sock = xfer_sock->next; + } + if (xfer_sock != NULL) { + ret = xfer_sock->fd; + if (xfer_sock == xfer_sock_list) + xfer_sock_list = xfer_sock->next; + if (xfer_sock->prev) + xfer_sock->prev->next = xfer_sock->next; + if (xfer_sock->next) + xfer_sock->next->prev = xfer_sock->next->prev; + free(xfer_sock); + } + return ret; + +} + /* This function creates a UNIX socket associated to the listener. It changes * the state from ASSIGNED to LISTEN. The socket is NOT enabled for polling. * The return value is composed from ERR_NONE, ERR_RETRYABLE and ERR_FATAL. It @@ -179,6 +227,8 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle if (listener->state != LI_ASSIGNED) return ERR_NONE; /* already bound */ + if (listener->fd == -1) + listener->fd = uxst_find_compatible_fd(listener); path = ((struct sockaddr_un *)&listener->addr)->sun_path; /* if the listener already has an fd assigned, then we were offered the