diff --git a/CMakeLists.txt b/CMakeLists.txt index 193ac6458..07c2604e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -662,6 +662,8 @@ CHECK_C_SOURCE_COMPILES("#include \nvoid main(void) { while(1) ; } v CHECK_C_SOURCE_COMPILES("#include \nvoid main(void) { while(1) ; } void xxexit(void){}" LWS_HAVE_SYS_RESOURCE_H) CHECK_C_SOURCE_COMPILES("#include \nvoid main(void) { while(1) ; } void xxexit(void){}" LWS_HAVE_LINUX_IPV6_H) CHECK_C_SOURCE_COMPILES("#include \nvoid main(void) { while(1) ; } void xxexit(void){}" LWS_HAVE_NET_ETHERNET_H) +CHECK_C_SOURCE_COMPILES("#include \nvoid main(void) { while(1) ; } void xxexit(void){}" LWS_HAVE_SYSTEMD_H) + if (LWS_EXT_PTHREAD_INCLUDE_DIR) diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 3a1ec0d77..8a93ad75c 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -242,6 +242,7 @@ #cmakedefine LWS_WITH_SYS_METRICS #cmakedefine LWS_WITH_SYS_NTPCLIENT #cmakedefine LWS_WITH_SYS_STATE +#cmakedefine LWS_HAVE_SYSTEMD_H #cmakedefine LWS_WITHOUT_TEST_SERVER #cmakedefine LWS_WITHOUT_TESTAPPS #cmakedefine LWS_WITH_THREADPOOL diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index b48a3200f..01e43a009 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -959,6 +959,14 @@ struct lws_context_creation_info { * selected default loglevel can then be cleanly overridden using -d 1039 etc * commandline switch */ + lws_sockfd_type vh_listen_sockfd; + /**< VHOST: 0 for normal vhost listen socket fd creation, if any. + * Nonzero to force the selection of an already-existing fd for the + * vhost's listen socket, which is already prepared. This is intended + * for an external process having chosen the fd, which cannot then be + * zero. + */ + /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility * @@ -1253,6 +1261,20 @@ lws_context_default_loop_run_destroy(struct lws_context *cx); LWS_VISIBLE LWS_EXTERN int lws_cmdline_passfail(int argc, const char **argv, int actual); +/** + * lws_systemd_inherited_fd() - prepare vhost creation info for systemd exported fd if any + * + * \param index: 0+ index of exported fd + * \param info: info struct to be prepared with related info, if any + * + * Returns 0 and points info to the related fd, aligning the other information + * to the type of fd and port it is bound to, or returns nonzero if no such + * inherited fd. + */ +LWS_VISIBLE LWS_EXTERN int +lws_systemd_inherited_fd(unsigned int index, + struct lws_context_creation_info *info); + /** * lws_context_is_being_destroyed() - find out if context is being destroyed * diff --git a/lib/plat/unix/CMakeLists.txt b/lib/plat/unix/CMakeLists.txt index b27bbb67b..e3c23c995 100644 --- a/lib/plat/unix/CMakeLists.txt +++ b/lib/plat/unix/CMakeLists.txt @@ -63,6 +63,12 @@ if (LWS_WITH_NETWORK) list(APPEND SOURCES plat/unix/unix-resolv.c) endif() endif() + if (LWS_HAVE_SYSTEMD_H) + list(APPEND SOURCES + plat/unix/unix-systemd.c + ) + list(APPEND LIB_LIST_AT_END systemd) + endif() endif() if (LWS_WITH_PLUGINS_API) diff --git a/lib/plat/unix/unix-systemd.c b/lib/plat/unix/unix-systemd.c new file mode 100644 index 000000000..405ae0e09 --- /dev/null +++ b/lib/plat/unix/unix-systemd.c @@ -0,0 +1,83 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2024 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif + +#include +#include + +#include "private-lib-core.h" + + +int +lws_systemd_inherited_fd(unsigned int index, + struct lws_context_creation_info *info) +{ + unsigned int inherited = (unsigned int)sd_listen_fds(0); + + if (index >= inherited) + return -1; + + info->vh_listen_sockfd = (int)(SD_LISTEN_FDS_START + index); + + if (sd_is_socket_unix(info->vh_listen_sockfd, 0, 0, NULL, 0)) + info->options |= LWS_SERVER_OPTION_UNIX_SOCK; + + if (sd_is_socket_inet(info->vh_listen_sockfd, AF_UNSPEC, 0, 1, 0)) { + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + + if (getsockname(info->vh_listen_sockfd, + (struct sockaddr *)&addr, &addrlen)) { + lwsl_err("%s: getsockname failed for fd %d\n", + __func__, info->vh_listen_sockfd); + return -1; + } + + switch (((struct sockaddr *)&addr)->sa_family) { + case AF_INET: + info->port = ntohs(((struct sockaddr_in *)&addr)->sin_port); + lwsl_info("%s: inet socket %d\n", __func__, info->port); + break; + case AF_INET6: + info->port = ntohs(((struct sockaddr_in6 *)&addr)->sin6_port); + lwsl_info("%s: inet6 socket %d\n", __func__, info->port); + break; + } + + if (sd_is_socket_inet(info->vh_listen_sockfd, AF_INET6, 0, 1, 0)) + info->options |= LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | + LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE; + + if (sd_is_socket_inet(info->vh_listen_sockfd, AF_INET, 0, 1, 0)) { + info->options &= (uint64_t)~(LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | + LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); + info->options |= LWS_SERVER_OPTION_DISABLE_IPV6; + } + } + + return 0; +} diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index 44e7c1ef7..1a711082a 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -72,6 +72,10 @@ check_extant(struct lws_dll2 *d, void *user) if (wsi->af != a ->af) return 0; + if (a->info && a->info->vh_listen_sockfd && + wsi->desc.sockfd != a->info->vh_listen_sockfd) + return 0; + lwsl_notice(" using listen skt from vhost %s\n", wsi->a.vhost->name); return 1; @@ -107,7 +111,7 @@ deal: if (!san--) return -1; - if (a->vhost->iface) { + if (a->vhost->iface && (!a->info || !a->info->vh_listen_sockfd)) { /* * let's check before we do anything else about the disposition @@ -183,6 +187,11 @@ done_list: LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND ? -1 : 1; } + } else { + if (a->info && a->info->vh_listen_sockfd) { + a->vhost->iface = "inherited"; + a->vhost->listen_port = a->info->port; + } } (void)n; @@ -202,7 +211,10 @@ done_list: for (m = 0; m < limit; m++) { - sockfd = lws_fi(&a->vhost->fic, "listenskt") ? + if (a->info && a->info->vh_listen_sockfd) + sockfd = dup(a->info->vh_listen_sockfd); + else + sockfd = lws_fi(&a->vhost->fic, "listenskt") ? LWS_SOCK_INVALID : socket(a->af, SOCK_STREAM, 0); @@ -248,9 +260,11 @@ done_list: * There will be a separate ipv4 listen socket if that's * enabled. */ - if (a->af == AF_INET6 && + if (a->af == AF_INET6 && (!a->info || !a->info->vh_listen_sockfd) && setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (const void*)&value, sizeof(value)) < 0) { + lwsl_err("ipv6 only failed\n"); + compatible_close(sockfd); return -1; } @@ -267,6 +281,7 @@ done_list: if (n || cx->count_threads > 1) /* ... also implied by threads > 1 */ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&opt, sizeof(opt)) < 0) { + lwsl_err("reuseport failed\n"); compatible_close(sockfd); return -1; } @@ -274,28 +289,30 @@ done_list: #endif lws_plat_set_socket_options(a->vhost, sockfd, 0); - is = lws_socket_bind(a->vhost, NULL, sockfd, - a->vhost->listen_port, - a->vhost->iface, a->af); + if (!a->info || !a->info->vh_listen_sockfd) { + is = lws_socket_bind(a->vhost, NULL, sockfd, + a->vhost->listen_port, + a->vhost->iface, a->af); - if (is == LWS_ITOSA_BUSY) { - /* treat as fatal */ - compatible_close(sockfd); + if (is == LWS_ITOSA_BUSY) { + /* treat as fatal */ + compatible_close(sockfd); - return -1; - } + return -1; + } - /* - * There is a race where the network device may come up and then - * go away and fail here. So correctly handle unexpected failure - * here despite we earlier confirmed it. - */ - if (is < 0) { - lwsl_info("%s: lws_socket_bind says %d\n", __func__, is); - compatible_close(sockfd); - if (a->vhost->iface) - goto deal; - return -1; + /* + * There is a race where the network device may come up and then + * go away and fail here. So correctly handle unexpected failure + * here despite we earlier confirmed it. + */ + if (is < 0) { + lwsl_info("%s: lws_socket_bind says %d\n", __func__, is); + compatible_close(sockfd); + if (a->vhost->iface) + goto deal; + return -1; + } } /* @@ -373,13 +390,17 @@ done_list: goto bail; } - if (wsi) + if (wsi) { + if (a->info && a->info->vh_listen_sockfd) + a->vhost->listen_port = a->info->port; + __lws_lc_tag(a->vhost->context, &a->vhost->context->lcg[LWSLCG_WSI], &wsi->lc, "listen|%s|%s|%d", a->vhost->name, a->vhost->iface ? a->vhost->iface : "", (int)a->vhost->listen_port); + } } /* for each thread able to independently listen */ @@ -399,6 +420,7 @@ done_list: return 0; bail: + lwsl_err("%s: bailing\n", __func__); compatible_close(sockfd); return -1;