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:
Andy Green 2018-03-29 13:32:33 +08:00
parent e052edb14f
commit 8f19a3fa9a
10 changed files with 192 additions and 54 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -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)) {

View file

@ -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,

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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 *

View file

@ -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);
}
/*