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.
diff --git a/include/types/connection.h b/include/types/connection.h
index 5ce5e0c..9d1b51a 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -389,6 +389,14 @@
#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 fa45db9..54fb438 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -24,6 +24,8 @@
#include <sys/stat.h>
#include <sys/types.h>
+#include <net/if.h>
+
#include <common/cfgparse.h>
#include <common/compat.h>
#include <common/config.h>
@@ -1013,6 +1015,184 @@
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 = "<CLI>", /* used for logging */
@@ -1027,6 +1207,7 @@
{ { "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 27ff0fa..d68267e 100644
--- a/src/proto_uxst.c
+++ b/src/proto_uxst.c
@@ -150,6 +150,54 @@
********************************/
+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 <sockname>.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 @@
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