]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: cli: Add a command to send listening sockets.
authorOlivier Houchard <ohouchard@haproxy.com>
Wed, 5 Apr 2017 20:24:59 +0000 (22:24 +0200)
committerWilly Tarreau <w@1wt.eu>
Thu, 13 Apr 2017 17:15:17 +0000 (19:15 +0200)
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
src/cli.c
src/proto_uxst.c

index 5ce5e0cc52ac14ae43bb7689c0c3341d482e449b..9d1b51af22010e102d379ed1cbe205327429e5ee 100644 (file)
@@ -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 */
 
 /*
index fa45db918f1b09252c77e3dfbebf9a561a8f0f8d..54fb43892e21294ded2c2c7695b4e3a5cdd2e3b4 100644 (file)
--- 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 @@ 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 = "<CLI>", /* 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 },
        {{},}
 }};
 
index 27ff0fa407866b4c61398715d5c41e3f4ec3be6e..d68267e836bce832bbfb0ee2cc7ae3beff497547 100644 (file)
@@ -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 <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 @@ 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