network interface: defer bindings to absent network interfaces
Previously down network interfaces without an IPv4 address are removed from the posix api that lists network interfaces. That means if you bound a vhost listen socket to a particular interface, it will fail at startup time. This patch adds these vhosts to a list, starts the vhost without a listen socket, and checks to see if the vhost's network interface has appeared while the rest of lws is running. If it appears, the listen socket is opened on the network interface and the vhost becomes reachable.
This commit is contained in:
parent
e052edb14f
commit
8f19a3fa9a
10 changed files with 192 additions and 54 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
Loading…
Add table
Reference in a new issue