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.
This commit is contained in:
Olivier Houchard 2017-04-05 22:24:59 +02:00 committed by Willy Tarreau
parent 42ef75fb84
commit f886e3478d
3 changed files with 239 additions and 0 deletions

View 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 */
/*

181
src/cli.c
View File

@ -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 },
{{},}
}};

View 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