diff --git a/include/haproxy/sock.h b/include/haproxy/sock.h index e405bb30c..c106ffde4 100644 --- a/include/haproxy/sock.h +++ b/include/haproxy/sock.h @@ -35,6 +35,7 @@ extern struct xfer_sock_list *xfer_sock_list; int sock_create_server_socket(struct connection *conn); int sock_get_src(int fd, struct sockaddr *sa, socklen_t salen, int dir); int sock_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir); +int sock_get_old_sockets(const char *unixsocket); int sock_find_compatible_fd(const struct listener *l); #endif /* _HAPROXY_SOCK_H */ diff --git a/src/haproxy.c b/src/haproxy.c index 7eb5c0e9a..8f41cf579 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -1124,244 +1123,6 @@ next_dir_entry: free(err); } -/* Retrieves old sockets from worker process running the CLI at address - * . Fills xfer_sock_list with what is found. Returns 0 on - * success, -1 on failure. - */ -static int get_old_sockets(const char *unixsocket) -{ - char *cmsgbuf = NULL, *tmpbuf = NULL; - int *tmpfd = NULL; - struct sockaddr_un addr; - struct cmsghdr *cmsg; - struct msghdr msghdr; - struct iovec iov; - struct xfer_sock_list *xfer_sock = NULL; - struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; - int sock = -1; - int ret = -1; - int ret2 = -1; - int fd_nb; - int got_fd = 0; - int i = 0; - size_t maxoff = 0, curoff = 0; - - memset(&msghdr, 0, sizeof(msghdr)); - cmsgbuf = malloc(CMSG_SPACE(sizeof(int)) * MAX_SEND_FD); - if (!cmsgbuf) { - ha_warning("Failed to allocate memory to send sockets\n"); - goto out; - } - sock = socket(PF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { - ha_warning("Failed to connect to the old process socket '%s'\n", - unixsocket); - goto out; - } - strncpy(addr.sun_path, unixsocket, sizeof(addr.sun_path) - 1); - addr.sun_path[sizeof(addr.sun_path) - 1] = 0; - addr.sun_family = PF_UNIX; - ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr)); - if (ret < 0) { - ha_warning("Failed to connect to the old process socket '%s'\n", - unixsocket); - goto out; - } - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv)); - iov.iov_base = &fd_nb; - iov.iov_len = sizeof(fd_nb); - msghdr.msg_iov = &iov; - msghdr.msg_iovlen = 1; - send(sock, "_getsocks\n", strlen("_getsocks\n"), 0); - /* First, get the number of file descriptors to be received */ - if (recvmsg(sock, &msghdr, MSG_WAITALL) != sizeof(fd_nb)) { - ha_warning("Failed to get the number of sockets to be transferred !\n"); - goto out; - } - if (fd_nb == 0) { - ret2 = 0; - goto out; - } - tmpbuf = malloc(fd_nb * (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int))); - if (tmpbuf == NULL) { - ha_warning("Failed to allocate memory while receiving sockets\n"); - goto out; - } - tmpfd = malloc(fd_nb * sizeof(int)); - if (tmpfd == NULL) { - ha_warning("Failed to allocate memory while receiving sockets\n"); - goto out; - } - msghdr.msg_control = cmsgbuf; - msghdr.msg_controllen = CMSG_SPACE(sizeof(int)) * MAX_SEND_FD; - iov.iov_len = MAX_SEND_FD * (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int)); - do { - int ret3; - - iov.iov_base = tmpbuf + curoff; - ret = recvmsg(sock, &msghdr, 0); - if (ret == -1 && errno == EINTR) - continue; - if (ret <= 0) - break; - /* Send an ack to let the sender know we got the sockets - * and it can send some more - */ - do { - ret3 = send(sock, &got_fd, sizeof(got_fd), 0); - } while (ret3 == -1 && errno == EINTR); - for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg != NULL; - cmsg = CMSG_NXTHDR(&msghdr, cmsg)) { - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS) { - size_t totlen = cmsg->cmsg_len - - CMSG_LEN(0); - if (totlen / sizeof(int) + got_fd > fd_nb) { - ha_warning("Got to many sockets !\n"); - goto out; - } - /* - * Be paranoid and use memcpy() to avoid any - * potential alignment issue. - */ - memcpy(&tmpfd[got_fd], CMSG_DATA(cmsg), totlen); - got_fd += totlen / sizeof(int); - } - } - curoff += ret; - } while (got_fd < fd_nb); - - if (got_fd != fd_nb) { - ha_warning("We didn't get the expected number of sockets (expecting %d got %d)\n", - fd_nb, got_fd); - goto out; - } - maxoff = curoff; - curoff = 0; - for (i = 0; i < got_fd; i++) { - int fd = tmpfd[i]; - socklen_t socklen; - int len; - - xfer_sock = calloc(1, sizeof(*xfer_sock)); - if (!xfer_sock) { - ha_warning("Failed to allocate memory in get_old_sockets() !\n"); - break; - } - xfer_sock->fd = -1; - - socklen = sizeof(xfer_sock->addr); - if (getsockname(fd, (struct sockaddr *)&xfer_sock->addr, &socklen) != 0) { - ha_warning("Failed to get socket address\n"); - free(xfer_sock); - xfer_sock = NULL; - continue; - } - if (curoff >= maxoff) { - ha_warning("Inconsistency while transferring sockets\n"); - goto out; - } - len = tmpbuf[curoff++]; - if (len > 0) { - /* We have a namespace */ - if (curoff + len > maxoff) { - ha_warning("Inconsistency while transferring sockets\n"); - goto out; - } - xfer_sock->namespace = malloc(len + 1); - if (!xfer_sock->namespace) { - ha_warning("Failed to allocate memory while transferring sockets\n"); - goto out; - } - memcpy(xfer_sock->namespace, &tmpbuf[curoff], len); - xfer_sock->namespace[len] = 0; - xfer_sock->ns_namelen = len; - curoff += len; - } - if (curoff >= maxoff) { - ha_warning("Inconsistency while transferring sockets\n"); - goto out; - } - len = tmpbuf[curoff++]; - if (len > 0) { - /* We have an interface */ - if (curoff + len > maxoff) { - ha_warning("Inconsistency while transferring sockets\n"); - goto out; - } - xfer_sock->iface = malloc(len + 1); - if (!xfer_sock->iface) { - ha_warning("Failed to allocate memory while transferring sockets\n"); - goto out; - } - memcpy(xfer_sock->iface, &tmpbuf[curoff], len); - xfer_sock->iface[len] = 0; - xfer_sock->if_namelen = len; - curoff += len; - } - if (curoff + sizeof(int) > maxoff) { - ha_warning("Inconsistency while transferring sockets\n"); - goto out; - } - - /* we used to have 32 bits of listener options here but we don't - * use them anymore. - */ - curoff += sizeof(int); - - /* determine the foreign status directly from the socket itself */ - if (sock_inet_is_foreign(fd, xfer_sock->addr.ss_family)) - xfer_sock->options |= LI_O_FOREIGN; - - /* keep only the v6only flag depending on what's currently - * active on the socket, and always drop the v4v6 one. - */ -#if defined(IPV6_V6ONLY) - { - int val = 0; - socklen_t len = sizeof(val); - - if (xfer_sock->addr.ss_family == AF_INET6 && - getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, &len) == 0 && - val > 0) - xfer_sock->options |= LI_O_V6ONLY; - } -#endif - - xfer_sock->fd = fd; - if (xfer_sock_list) - xfer_sock_list->prev = xfer_sock; - xfer_sock->next = xfer_sock_list; - xfer_sock->prev = NULL; - xfer_sock_list = xfer_sock; - xfer_sock = NULL; - } - - ret2 = 0; -out: - /* If we failed midway make sure to close the remaining - * file descriptors - */ - if (tmpfd != NULL && i < got_fd) { - for (; i < got_fd; i++) { - close(tmpfd[i]); - } - } - free(tmpbuf); - free(tmpfd); - free(cmsgbuf); - if (sock != -1) - close(sock); - if (xfer_sock) { - free(xfer_sock->namespace); - free(xfer_sock->iface); - if (xfer_sock->fd != -1) - close(xfer_sock->fd); - free(xfer_sock); - } - return (ret2); -} - /* * copy and cleanup the current argv * Remove the -sf /-st / -x parameters @@ -3281,7 +3042,7 @@ int main(int argc, char **argv) if (old_unixsocket) { if (strcmp("/dev/null", old_unixsocket) != 0) { - if (get_old_sockets(old_unixsocket) != 0) { + if (sock_get_old_sockets(old_unixsocket) != 0) { ha_alert("Failed to get the sockets from the old process!\n"); if (!(global.mode & MODE_MWORKER)) exit(1); diff --git a/src/sock.c b/src/sock.c index 5bb6e13d5..b8a729ba6 100644 --- a/src/sock.c +++ b/src/sock.c @@ -22,6 +22,8 @@ #include #include +#include + #include #include #include @@ -81,6 +83,266 @@ int sock_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir) return getsockname(fd, sa, &salen); } +/* Try to retrieve exported sockets from worker at CLI . These + * ones will be placed into the xfer_sock_list for later use by function + * sock_find_compatible_fd(). Returns 0 on success, -1 on failure. + */ +int sock_get_old_sockets(const char *unixsocket) +{ + char *cmsgbuf = NULL, *tmpbuf = NULL; + int *tmpfd = NULL; + struct sockaddr_un addr; + struct cmsghdr *cmsg; + struct msghdr msghdr; + struct iovec iov; + struct xfer_sock_list *xfer_sock = NULL; + struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; + int sock = -1; + int ret = -1; + int ret2 = -1; + int fd_nb; + int got_fd = 0; + int cur_fd = 0; + size_t maxoff = 0, curoff = 0; + + memset(&msghdr, 0, sizeof(msghdr)); + cmsgbuf = malloc(CMSG_SPACE(sizeof(int)) * MAX_SEND_FD); + if (!cmsgbuf) { + ha_warning("Failed to allocate memory to send sockets\n"); + goto out; + } + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + ha_warning("Failed to connect to the old process socket '%s'\n", unixsocket); + goto out; + } + + strncpy(addr.sun_path, unixsocket, sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + addr.sun_family = PF_UNIX; + + ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + ha_warning("Failed to connect to the old process socket '%s'\n", unixsocket); + goto out; + } + + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv)); + iov.iov_base = &fd_nb; + iov.iov_len = sizeof(fd_nb); + msghdr.msg_iov = &iov; + msghdr.msg_iovlen = 1; + + if (send(sock, "_getsocks\n", strlen("_getsocks\n"), 0) != strlen("_getsocks\n")) { + ha_warning("Failed to get the number of sockets to be transferred !\n"); + goto out; + } + + /* First, get the number of file descriptors to be received */ + if (recvmsg(sock, &msghdr, MSG_WAITALL) != sizeof(fd_nb)) { + ha_warning("Failed to get the number of sockets to be transferred !\n"); + goto out; + } + + if (fd_nb == 0) { + ret2 = 0; + goto out; + } + + tmpbuf = malloc(fd_nb * (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int))); + if (tmpbuf == NULL) { + ha_warning("Failed to allocate memory while receiving sockets\n"); + goto out; + } + + tmpfd = malloc(fd_nb * sizeof(int)); + if (tmpfd == NULL) { + ha_warning("Failed to allocate memory while receiving sockets\n"); + goto out; + } + + msghdr.msg_control = cmsgbuf; + msghdr.msg_controllen = CMSG_SPACE(sizeof(int)) * MAX_SEND_FD; + iov.iov_len = MAX_SEND_FD * (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int)); + + do { + int ret3; + + iov.iov_base = tmpbuf + curoff; + + ret = recvmsg(sock, &msghdr, 0); + + if (ret == -1 && errno == EINTR) + continue; + + if (ret <= 0) + break; + + /* Send an ack to let the sender know we got the sockets + * and it can send some more + */ + do { + ret3 = send(sock, &got_fd, sizeof(got_fd), 0); + } while (ret3 == -1 && errno == EINTR); + + for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg != NULL; cmsg = CMSG_NXTHDR(&msghdr, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + size_t totlen = cmsg->cmsg_len - CMSG_LEN(0); + + if (totlen / sizeof(int) + got_fd > fd_nb) { + ha_warning("Got to many sockets !\n"); + goto out; + } + + /* + * Be paranoid and use memcpy() to avoid any + * potential alignment issue. + */ + memcpy(&tmpfd[got_fd], CMSG_DATA(cmsg), totlen); + got_fd += totlen / sizeof(int); + } + } + curoff += ret; + } while (got_fd < fd_nb); + + if (got_fd != fd_nb) { + ha_warning("We didn't get the expected number of sockets (expecting %d got %d)\n", + fd_nb, got_fd); + goto out; + } + + maxoff = curoff; + curoff = 0; + + for (cur_fd = 0; cur_fd < got_fd; cur_fd++) { + int fd = tmpfd[cur_fd]; + socklen_t socklen; + int val; + int len; + + xfer_sock = calloc(1, sizeof(*xfer_sock)); + if (!xfer_sock) { + ha_warning("Failed to allocate memory in get_old_sockets() !\n"); + break; + } + xfer_sock->fd = -1; + + socklen = sizeof(xfer_sock->addr); + if (getsockname(fd, (struct sockaddr *)&xfer_sock->addr, &socklen) != 0) { + ha_warning("Failed to get socket address\n"); + free(xfer_sock); + xfer_sock = NULL; + continue; + } + + if (curoff >= maxoff) { + ha_warning("Inconsistency while transferring sockets\n"); + goto out; + } + + len = tmpbuf[curoff++]; + if (len > 0) { + /* We have a namespace */ + if (curoff + len > maxoff) { + ha_warning("Inconsistency while transferring sockets\n"); + goto out; + } + xfer_sock->namespace = malloc(len + 1); + if (!xfer_sock->namespace) { + ha_warning("Failed to allocate memory while transferring sockets\n"); + goto out; + } + memcpy(xfer_sock->namespace, &tmpbuf[curoff], len); + xfer_sock->namespace[len] = 0; + xfer_sock->ns_namelen = len; + curoff += len; + } + + if (curoff >= maxoff) { + ha_warning("Inconsistency while transferring sockets\n"); + goto out; + } + + len = tmpbuf[curoff++]; + if (len > 0) { + /* We have an interface */ + if (curoff + len > maxoff) { + ha_warning("Inconsistency while transferring sockets\n"); + goto out; + } + xfer_sock->iface = malloc(len + 1); + if (!xfer_sock->iface) { + ha_warning("Failed to allocate memory while transferring sockets\n"); + goto out; + } + memcpy(xfer_sock->iface, &tmpbuf[curoff], len); + xfer_sock->iface[len] = 0; + xfer_sock->if_namelen = len; + curoff += len; + } + + if (curoff + sizeof(int) > maxoff) { + ha_warning("Inconsistency while transferring sockets\n"); + goto out; + } + + /* we used to have 32 bits of listener options here but we don't + * use them anymore. + */ + curoff += sizeof(int); + + /* determine the foreign status directly from the socket itself */ + if (sock_inet_is_foreign(fd, xfer_sock->addr.ss_family)) + xfer_sock->options |= LI_O_FOREIGN; + +#if defined(IPV6_V6ONLY) + /* keep only the v6only flag depending on what's currently + * active on the socket, and always drop the v4v6 one. + */ + socklen = sizeof(val); + if (xfer_sock->addr.ss_family == AF_INET6 && + getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, &socklen) == 0 && val > 0) + xfer_sock->options |= LI_O_V6ONLY; +#endif + + xfer_sock->fd = fd; + if (xfer_sock_list) + xfer_sock_list->prev = xfer_sock; + xfer_sock->next = xfer_sock_list; + xfer_sock->prev = NULL; + xfer_sock_list = xfer_sock; + xfer_sock = NULL; + } + + ret2 = 0; +out: + /* If we failed midway make sure to close the remaining + * file descriptors + */ + if (tmpfd != NULL && cur_fd < got_fd) { + for (; cur_fd < got_fd; cur_fd++) { + close(tmpfd[cur_fd]); + } + } + + free(tmpbuf); + free(tmpfd); + free(cmsgbuf); + + if (sock != -1) + close(sock); + + if (xfer_sock) { + free(xfer_sock->namespace); + free(xfer_sock->iface); + if (xfer_sock->fd != -1) + close(xfer_sock->fd); + free(xfer_sock); + } + return (ret2); +} + /* When binding the listeners, check if a socket has been sent to us by the * previous process that we could reuse, instead of creating a new one. Note * that some address family-specific options are checked on the listener and