mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-01-31 10:31:46 +00:00
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:
parent
42ef75fb84
commit
f886e3478d
@ -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
181
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 },
|
||||
{{},}
|
||||
}};
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user