diff --git a/READMEs/README.lwsws.md b/READMEs/README.lwsws.md index 6e5ed9b0..636004d9 100644 --- a/READMEs/README.lwsws.md +++ b/READMEs/README.lwsws.md @@ -223,7 +223,7 @@ See also "rawonly" below. - `keeplive-timeout` (in secs) defaults to 60 for lwsws, it may be set as a vhost option - - `interface` lets you specify which network interface to listen on, if not given listens on all + - `interface` lets you specify which network interface to listen on, if not given listens on all. If the network interface is not usable (eg, ethernet cable out) it will be logged at startup with such vhost not listening, and lws will poll for it and bind a listen socket to the interface if and when it becomes available. - "`unix-socket`": "1" causes the unix socket specified in the interface option to be used instead of an INET socket diff --git a/lib/context.c b/lib/context.c index eb3109eb..3f452162 100644 --- a/lib/context.c +++ b/lib/context.c @@ -871,7 +871,7 @@ lws_create_vhost(struct lws_context *context, lwsl_err("%s: lws_context_init_client_ssl failed\n", __func__); goto bail1; } - if (lws_context_init_server(info, vh)) { + if (lws_context_init_server(info, vh) < 0) { lwsl_err("init server failed\n"); goto bail1; } diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index f7b99065..0a3d4469 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -2534,7 +2534,7 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, #ifndef LWS_PLAT_OPTEE socklen_t len = sizeof(struct sockaddr_storage); #endif - int n; + int n, m; struct sockaddr_storage sin; struct sockaddr *v; @@ -2563,10 +2563,17 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, n = sizeof(struct sockaddr_in6); bzero((char *) &serv_addr6, sizeof(serv_addr6)); if (iface) { - if (interface_to_sa(vhost, iface, - (struct sockaddr_in *)v, n) < 0) { - lwsl_err("Unable to find if %s\n", iface); - return -1; + m = interface_to_sa(vhost, iface, + (struct sockaddr_in *)v, n); + if (m == LWS_ITOSA_NOT_USABLE) { + lwsl_info("%s: netif %s: Not usable\n", + __func__, iface); + return m; + } + if (m == LWS_ITOSA_NOT_EXIST) { + lwsl_info("%s: netif %s: Does not exist\n", + __func__, iface); + return m; } serv_addr6.sin6_scope_id = lws_get_addr_scope(iface); } @@ -2583,16 +2590,28 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, serv_addr4.sin_family = AF_INET; #if !defined(LWS_WITH_ESP32) - if (iface && - interface_to_sa(vhost, iface, - (struct sockaddr_in *)v, n) < 0) { - lwsl_err("Unable to find interface %s\n", iface); - return -1; + if (iface) { + m = interface_to_sa(vhost, iface, + (struct sockaddr_in *)v, n); + if (m == LWS_ITOSA_NOT_USABLE) { + lwsl_info("%s: netif %s: Not usable\n", + __func__, iface); + return m; + } + if (m == LWS_ITOSA_NOT_EXIST) { + lwsl_info("%s: netif %s: Does not exist\n", + __func__, iface); + return m; + } } #endif serv_addr4.sin_port = htons(port); } /* ipv4 */ + /* just checking for the interface extant */ + if (sockfd == LWS_SOCK_INVALID) + return 0; + n = bind(sockfd, v, n); #ifdef LWS_WITH_UNIX_SOCK if (n < 0 && LWS_UNIX_SOCK_ENABLED(vhost)) { diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index a488a157..119eee7b 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -5353,16 +5353,30 @@ lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name, LWS_VISIBLE LWS_EXTERN const char * lws_get_peer_simple(struct lws *wsi, char *name, int namelen); #if !defined(LWS_WITH_ESP32) + +#define LWS_ITOSA_NOT_EXIST -1 +#define LWS_ITOSA_NOT_USABLE -2 +#define LWS_ITOSA_USABLE 0 + /** * lws_interface_to_sa() - Convert interface name or IP to sockaddr struct * - * \param ipv6: Allow IPV6 addresses + * \param ipv6: Allow IPV6 addresses * \param ifname: Interface name or IP - * \param addr: struct sockaddr_in * to be written + * \param addr: struct sockaddr_in * to be written * \param addrlen: Length of addr * * This converts a textual network interface name to a sockaddr usable by - * other network functions + * other network functions. + * + * If the network interface doesn't exist, it will return LWS_ITOSA_NOT_EXIST. + * + * If the network interface is not usable, eg ethernet cable is removed, it + * may logically exist but not have any IP address. As such it will return + * LWS_ITOSA_NOT_USABLE. + * + * If the network interface exists and is usable, it will return + * LWS_ITOSA_USABLE. */ LWS_VISIBLE LWS_EXTERN int lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, diff --git a/lib/plat/lws-plat-esp32.c b/lib/plat/lws-plat-esp32.c index 7310bd0c..c0828605 100644 --- a/lib/plat/lws-plat-esp32.c +++ b/lib/plat/lws-plat-esp32.c @@ -387,7 +387,7 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, size_t addrlen) { #if 0 - int rc = -1; + int rc = LWS_ITOSA_NOT_EXIST; struct ifaddrs *ifr; struct ifaddrs *ifc; @@ -433,26 +433,26 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, default: continue; } - rc = 0; + rc = LWS_ITOSA_USABLE; } freeifaddrs(ifr); - if (rc == -1) { + if (rc == LWS_ITOSA_NOT_EXIST) { /* check if bind to IP address */ #ifdef LWS_WITH_IPV6 if (inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1) - rc = 0; + rc = LWS_ITOSA_USABLE; else #endif if (inet_pton(AF_INET, ifname, &addr->sin_addr) == 1) - rc = 0; + rc = LWS_ITOSA_USABLE; } return rc; #endif - return -1; + return LWS_ITOSA_NOT_EXIST; } LWS_VISIBLE void diff --git a/lib/plat/lws-plat-unix.c b/lib/plat/lws-plat-unix.c index 4927b3da..d24bd86f 100644 --- a/lib/plat/lws-plat-unix.c +++ b/lib/plat/lws-plat-unix.c @@ -640,7 +640,7 @@ LWS_VISIBLE int lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, size_t addrlen) { - int rc = -1; + int rc = LWS_ITOSA_NOT_EXIST; struct ifaddrs *ifr; struct ifaddrs *ifc; @@ -653,12 +653,19 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, if (!ifc->ifa_addr) continue; - lwsl_info(" interface %s vs %s\n", ifc->ifa_name, ifname); + lwsl_debug(" interface %s vs %s (fam %d) ipv6 %d\n", ifc->ifa_name, ifname, ifc->ifa_addr->sa_family, ipv6); if (strcmp(ifc->ifa_name, ifname)) continue; switch (ifc->ifa_addr->sa_family) { +#if defined(AF_PACKET) + case AF_PACKET: + /* interface exists but is not usable */ + rc = LWS_ITOSA_NOT_USABLE; + continue; +#endif + case AF_INET: #ifdef LWS_WITH_IPV6 if (ipv6) { @@ -686,20 +693,20 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, default: continue; } - rc = 0; + rc = LWS_ITOSA_USABLE; } freeifaddrs(ifr); - if (rc == -1) { + if (rc) { /* check if bind to IP address */ #ifdef LWS_WITH_IPV6 if (inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1) - rc = 0; + rc = LWS_ITOSA_USABLE; else #endif if (inet_pton(AF_INET, ifname, &addr->sin_addr) == 1) - rc = 0; + rc = LWS_ITOSA_USABLE; } return rc; diff --git a/lib/plat/lws-plat-win.c b/lib/plat/lws-plat-win.c index 1181184f..437c4047 100644 --- a/lib/plat/lws-plat-win.c +++ b/lib/plat/lws-plat-win.c @@ -425,7 +425,7 @@ lws_interface_to_sa(int ipv6, if (ipv6) { if (lws_plat_inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1) { - return 0; + return LWS_ITOSA_USABLE; } } #endif @@ -439,11 +439,11 @@ lws_interface_to_sa(int ipv6, } if (address == INADDR_NONE) - return -1; + return LWS_ITOSA_NOT_EXIST; addr->sin_addr.s_addr = (unsigned long)(lws_intptr_t)address; - return 0; + return LWS_ITOSA_USABLE; } LWS_VISIBLE void diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index a0bbda48..196724c8 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -971,6 +971,7 @@ struct lws_vhost { const struct lws_protocol_vhost_options *pvo; const struct lws_protocol_vhost_options *headers; struct lws **same_vh_protocol_list; + struct lws_vhost *no_listener_vhost_list; #if !defined(LWS_NO_CLIENT) struct lws_dll_lws dll_active_client_conns; #endif @@ -1092,6 +1093,7 @@ struct lws_context { struct lws **lws_lookup; /* fd to wsi */ #endif struct lws_vhost *vhost_list; + struct lws_vhost *no_listener_vhost_list; struct lws_vhost *vhost_pending_destruction_list; struct lws_plugin *plugin_list; struct lws_deferred_free *deferred_free_list; diff --git a/lib/server/server.c b/lib/server/server.c index 7a3063ae..4a6bc0ad 100644 --- a/lib/server/server.c +++ b/lib/server/server.c @@ -28,6 +28,12 @@ const char * const method_names[] = { #endif }; +/* + * return 0: all done + * 1: nonfatal error + * <0: fatal error + */ + int lws_context_init_server(struct lws_context_creation_info *info, struct lws_vhost *vhost) @@ -41,24 +47,30 @@ lws_context_init_server(struct lws_context_creation_info *info, lws_sockfd_type sockfd; struct lws_vhost *vh; struct lws *wsi; - int m = 0; + int m = 0, is; (void)method_names; (void)opt; + + if (info) { + vhost->iface = info->iface; + vhost->listen_port = info->port; + } + /* set up our external listening socket we serve on */ - if (info->port == CONTEXT_PORT_NO_LISTEN || - info->port == CONTEXT_PORT_NO_LISTEN_SERVER) + if (vhost->listen_port == CONTEXT_PORT_NO_LISTEN || + vhost->listen_port == CONTEXT_PORT_NO_LISTEN_SERVER) return 0; vh = vhost->context->vhost_list; while (vh) { - if (vh->listen_port == info->port) { - if ((!info->iface && !vh->iface) || - (info->iface && vh->iface && - !strcmp(info->iface, vh->iface))) { - vhost->listen_port = info->port; - vhost->iface = info->iface; + if (vh->listen_port == vhost->listen_port) { + if (((!vhost->iface && !vh->iface) || + (vhost->iface && vh->iface && + !strcmp(vhost->iface, vh->iface))) && + vh->lserv_wsi + ) { lwsl_notice(" using listen skt from vhost %s\n", vh->name); return 0; @@ -67,6 +79,60 @@ lws_context_init_server(struct lws_context_creation_info *info, vh = vh->vhost_next; } + if (vhost->iface) { + /* + * let's check before we do anything else about the disposition + * of the interface he wants to bind to... + */ + is = lws_socket_bind(vhost, LWS_SOCK_INVALID, vhost->listen_port, vhost->iface); + lwsl_debug("initial if check says %d\n", is); +deal: + lws_context_lock(vhost->context); + lws_start_foreach_llp(struct lws_vhost **, pv, + vhost->context->no_listener_vhost_list) { + if (is >= LWS_ITOSA_USABLE && *pv == vhost) { + /* on the list and shouldn't be: remove it */ + lwsl_debug("deferred iface: removing vh %s\n", (*pv)->name); + *pv = vhost->no_listener_vhost_list; + vhost->no_listener_vhost_list = NULL; + goto done_list; + } + if (is < LWS_ITOSA_USABLE && *pv == vhost) + goto done_list; + } lws_end_foreach_llp(pv, no_listener_vhost_list); + + /* not on the list... */ + + if (is < LWS_ITOSA_USABLE) { + + /* ... but needs to be: so add it */ + + lwsl_debug("deferred iface: adding vh %s\n", vhost->name); + vhost->no_listener_vhost_list = vhost->context->no_listener_vhost_list; + vhost->context->no_listener_vhost_list = vhost; + } + +done_list: + lws_context_unlock(vhost->context); + + switch (is) { + default: + break; + case LWS_ITOSA_NOT_EXIST: + /* can't add it */ + if (info) /* first time */ + lwsl_err("VH %s: iface %s port %d DOESN'T EXIST\n", + vhost->name, vhost->iface, vhost->listen_port); + return 1; + case LWS_ITOSA_NOT_USABLE: + /* can't add it */ + if (info) /* first time */ + lwsl_err("VH %s: iface %s port %d NOT USABLE\n", + vhost->name, vhost->iface, vhost->listen_port); + return 1; + } + } + #if LWS_POSIX (void)n; #if defined(__linux__) @@ -106,7 +172,7 @@ lws_context_init_server(struct lws_context_creation_info *info, (const void *)&opt, sizeof(opt)) < 0) { lwsl_err("reuseaddr failed\n"); compatible_close(sockfd); - return 1; + return -1; } } else #endif @@ -118,7 +184,7 @@ lws_context_init_server(struct lws_context_creation_info *info, (const void *)&opt, sizeof(opt)) < 0) { lwsl_err("reuseaddr failed\n"); compatible_close(sockfd); - return 1; + return -1; } #if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY) @@ -129,7 +195,7 @@ lws_context_init_server(struct lws_context_creation_info *info, if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (const void*)&value, sizeof(value)) < 0) { compatible_close(sockfd); - return 1; + return -1; } } #endif @@ -147,20 +213,28 @@ lws_context_init_server(struct lws_context_creation_info *info, if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&opt, sizeof(opt)) < 0) { compatible_close(sockfd); - return 1; + return -1; } #endif #endif lws_plat_set_socket_options(vhost, sockfd); #if LWS_POSIX - n = lws_socket_bind(vhost, sockfd, info->port, info->iface); - if (n < 0) - goto bail; - info->port = n; + is = lws_socket_bind(vhost, sockfd, vhost->listen_port, vhost->iface); + /* + * 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); + goto deal; + } + vhost->listen_port = is; + + lwsl_debug("%s: lws_socket_bind says %d\n", __func__, is); #endif - vhost->listen_port = info->port; - vhost->iface = info->iface; wsi = lws_zalloc(sizeof(struct lws), "listen wsi"); if (wsi == NULL) { @@ -180,8 +254,10 @@ lws_context_init_server(struct lws_context_creation_info *info, lws_uv_initvhost(vhost, wsi); #endif - if (__insert_wsi_socket_into_fds(vhost->context, wsi)) + if (__insert_wsi_socket_into_fds(vhost->context, wsi)) { + lwsl_notice("inserting wsi socket into fds failed\n"); goto bail; + } vhost->context->count_wsi_allocated++; vhost->lserv_wsi = wsi; @@ -197,13 +273,13 @@ lws_context_init_server(struct lws_context_creation_info *info, } } /* for each thread able to independently listen */ #endif - if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) { + if (!lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) { #ifdef LWS_WITH_UNIX_SOCK if (LWS_UNIX_SOCK_ENABLED(vhost)) - lwsl_info(" Listening on \"%s\"\n", info->iface); + lwsl_info(" Listening on \"%s\"\n", vhost->iface); else #endif - lwsl_info(" Listening on port %d\n", info->port); + lwsl_info(" Listening on port %d\n", vhost->listen_port); } return 0; @@ -211,7 +287,7 @@ lws_context_init_server(struct lws_context_creation_info *info, bail: compatible_close(sockfd); - return 1; + return -1; } struct lws_vhost * diff --git a/lib/service.c b/lib/service.c index c6358fe6..63d6da73 100644 --- a/lib/service.c +++ b/lib/service.c @@ -1308,6 +1308,26 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, } lws_end_foreach_ll(v, vhost_next); if (wsi) lws_free(wsi); + + /* + * Phase 5: check for unconfigured vhosts due to required + * interface missing before + */ + + lws_context_lock(context); + lws_start_foreach_llp(struct lws_vhost **, pv, + context->no_listener_vhost_list) { + struct lws_vhost *v = *pv; + lwsl_debug("deferred iface: checking if on vh %s\n", (*pv)->name); + if (lws_context_init_server(NULL, *pv) == 0) { + /* became happy */ + lwsl_notice("vh %s: became connected\n", v->name); + *pv = v->no_listener_vhost_list; + v->no_listener_vhost_list = NULL; + break; + } + } lws_end_foreach_llp(pv, no_listener_vhost_list); + lws_context_unlock(context); } /*