diff --git a/.sai.json b/.sai.json index f6f515d6b..00889c7c8 100644 --- a/.sai.json +++ b/.sai.json @@ -170,6 +170,10 @@ "cmake": "-DLWS_IPV6=ON", "platforms": "windows-10/x86_64-amd/mingw64, windows-10/x86_64-amd/msvc" }, + "nonetlink": { + "cmake": "-DLWS_WITH_NETLINK=0", + "platforms": "none, linux-ubuntu-2004/x86_64-amd/gcc" + }, "nossl": { "cmake": "-DLWS_WITH_SSL=OFF", "platforms": "netbsd-iOS/aarch64/llvm" diff --git a/CMakeLists.txt b/CMakeLists.txt index c2bbc64b8..28bbdab61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -252,6 +252,13 @@ option(LWS_HTTP_HEADERS_ALL "Override header reduction optimization and include option(LWS_WITH_SUL_DEBUGGING "Enable zombie lws_sul checking on object deletion" OFF) option(LWS_WITH_PLUGINS_API "Build generic lws_plugins apis (see LWS_WITH_PLUGINS to also build protocol plugins)" OFF) +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + option(LWS_WITH_NETLINK "Monitor Netlink for Routing Table changes" ON) +else() + set(LWS_WITH_NETLINK 0) +endif() + + # # to use miniz, enable both LWS_WITH_ZLIB and LWS_WITH_MINIZ # diff --git a/READMEs/README.routing.md b/READMEs/README.routing.md new file mode 100644 index 000000000..7af8f5ef6 --- /dev/null +++ b/READMEs/README.routing.md @@ -0,0 +1,51 @@ +# Lws routing + +lws is mainly built around POSIX sockets and operates from the +information available from those. But in some cases, it needs to go +a step further and monitor and understand the device routing table. + +## Recognizing loss of routability + +On mobile devices, switching between interfaces and losing / regaining +connections quickly is a given. But POSIX sockets do not act like +that, the socket remains connected until something times it out if it +no longer has a route to its peer, and the tcp timeouts can be in the +order of minutes. + +In order to do better, lws must monitor and understand how the routing +table relates to existing connections, dynamically. + +## Linux: netlink + +For linux-based devices you can build in netlink-based route monitoring +with `-DLWS_WITH_NETLINK=1`, lws aquires a copy of the routing table +when the context / pt starts up and modifies it according to netlink +messages from then on. + +On Linux routing table events do not take much care about backing out +changes made on interface up by, eg, NetworkManager. So lws also +monitors for link / interface down to remove the related routes. + +## Actions in lws based on routing table + +Both server and client connections now store their peer sockaddr in the +wsi, and when the routing table changes, all active wsi on a pt are +checked against the routing table to confirm the peer is still +routable. + +For example if there is no net route matching the peer and no gateway, +the connection is invalidated and closed. Similarly, if we are +removing the highest priority gateway route, all connections to a peer +without a net route match are invalidated. However connections with +an unaffected matching net route like 127.0.0.0/8 are left alone. + +## Intergration to other subsystems + +If SMD is built in, on any route change a NETWORK message +`{"rt":"add|del"}` is issued. + +If SMD is built in, on any route change involving a gateway, a NETWORK +message `{"trigger":"cpdcheck", "src":"gw-change"}` is issued. If +Captive Portal Detection is built in, this will cause a new captive +portal detection sequence. + diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 69d187758..3382f74f2 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -154,6 +154,7 @@ #cmakedefine LWS_LOGS_TIMESTAMP #cmakedefine LWS_WITH_MBEDTLS #cmakedefine LWS_WITH_MINIZ +#cmakedefine LWS_WITH_NETLINK #cmakedefine LWS_WITH_NETWORK #cmakedefine LWS_WITH_NO_LOGS #cmakedefine LWS_WITH_CLIENT diff --git a/include/libwebsockets/lws-adopt.h b/include/libwebsockets/lws-adopt.h index 1a4842e77..5a4f1b65a 100644 --- a/include/libwebsockets/lws-adopt.h +++ b/include/libwebsockets/lws-adopt.h @@ -79,6 +79,13 @@ typedef union { lws_filefd_type filefd; } lws_sock_file_fd_type; +typedef union { +#if defined(LWS_WITH_IPV6) + struct sockaddr_in6 sa6; +#endif + struct sockaddr_in sa4; +} lws_sockaddr46; + #if defined(LWS_WITH_UDP) struct lws_udp { struct sockaddr sa; diff --git a/include/libwebsockets/lws-network-helper.h b/include/libwebsockets/lws-network-helper.h index 2e686e790..cac5b9b54 100644 --- a/include/libwebsockets/lws-network-helper.h +++ b/include/libwebsockets/lws-network-helper.h @@ -33,12 +33,39 @@ #include #endif -typedef union { -#if defined(LWS_WITH_IPV6) - struct sockaddr_in6 sa6; -#endif - struct sockaddr_in sa4; -} lws_sockaddr46; +typedef uint8_t lws_route_uidx_t; + +typedef struct lws_dns_score { + uint8_t precedence; + uint8_t label; +} lws_dns_score_t; + +/* + * This represents an entry in the system routing table + */ + +typedef struct lws_route { + lws_dll2_t list; + + lws_sockaddr46 dest; + lws_sockaddr46 gateway; + + struct lws_route *source; /* when used as lws_dns_sort_t */ + lws_dns_score_t score; /* when used as lws_dns_sort_t */ + + int if_idx; + int priority; + int ifa_flags; /* if source_ads */ + + lws_route_uidx_t uidx; /* unique index for this route */ + + uint8_t proto; + uint8_t dest_len; + uint8_t scope; /* if source_ads */ + uint8_t af; /* if source_ads */ + + uint8_t source_ads:1; +} lws_route_t; /** * lws_canonical_hostname() - returns this host's hostname diff --git a/lib/core-net/CMakeLists.txt b/lib/core-net/CMakeLists.txt index bf20d9aaa..f9a30c470 100644 --- a/lib/core-net/CMakeLists.txt +++ b/lib/core-net/CMakeLists.txt @@ -45,6 +45,12 @@ if (LWS_WITH_SYS_STATE) ) endif() +if (LWS_WITH_NETLINK) + list(APPEND SOURCES + core-net/route.c + ) +endif() + if (LWS_WITH_DETAILED_LATENCY) list(APPEND SOURCES core-net/detailed-latency.c) diff --git a/lib/core-net/pollfd.c b/lib/core-net/pollfd.c index 894d7772a..22dc89160 100644 --- a/lib/core-net/pollfd.c +++ b/lib/core-net/pollfd.c @@ -293,7 +293,12 @@ __insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi) #endif assert(wsi); + +#if defined(LWS_WITH_NETLINK) + assert(wsi->event_pipe || wsi->a.vhost || wsi == pt->netlink); +#else assert(wsi->event_pipe || wsi->a.vhost); +#endif assert(lws_socket_is_valid(wsi->desc.sockfd)); #if defined(LWS_WITH_EXTERNAL_POLL) diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index 9063bf37a..44e2a3f93 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -52,23 +52,6 @@ struct lws_muxable { extern "C" { #endif -/* - * All lws_tls...() functions must return this type, converting the - * native backend result and doing the extra work to determine which one - * as needed. - * - * Native TLS backend return codes are NOT ALLOWED outside the backend. - * - * Non-SSL mode also uses these types. - */ -enum lws_ssl_capable_status { - LWS_SSL_CAPABLE_ERROR = -1, /* it failed */ - LWS_SSL_CAPABLE_DONE = 0, /* it succeeded */ - LWS_SSL_CAPABLE_MORE_SERVICE_READ = -2, /* retry WANT_READ */ - LWS_SSL_CAPABLE_MORE_SERVICE_WRITE = -3, /* retry WANT_WRITE */ - LWS_SSL_CAPABLE_MORE_SERVICE = -4, /* general retry */ -}; - #define __lws_sul_insert_us(owner, sul, _us) \ (sul)->us = lws_now_usecs() + _us; \ __lws_sul_insert(owner, sul) @@ -416,6 +399,11 @@ struct lws_context_per_thread { lws_sockfd_type dummy_pipe_fds[2]; struct lws *pipe_wsi; +#if defined(LWS_WITH_NETLINK) + lws_dll2_owner_t routing_table; + struct lws *netlink; +#endif + /* --- role based members --- */ #if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS) @@ -456,6 +444,10 @@ struct lws_context_per_thread { unsigned char tid; +#if defined(LWS_WITH_NETLINK) + lws_route_uidx_t route_uidx; +#endif + unsigned char inside_service:1; unsigned char inside_lws_service:1; unsigned char event_loop_foreign:1; @@ -895,6 +887,10 @@ struct lws { #endif #if defined(LWS_WITH_STATS) && defined(LWS_WITH_TLS) char seen_rx; +#endif +#if defined(LWS_WITH_NETLINK) + lws_route_uidx_t peer_route_uidx; + /**< unique index of the route the connection is estimated to take */ #endif uint8_t immortal_substream_count; /* volatile to make sure code is aware other thread can change */ @@ -1242,6 +1238,16 @@ struct lws * lws_http_client_connect_via_info2(struct lws *wsi); +struct lws * +lws_wsi_create_with_role(struct lws_context *context, int tsi, + const struct lws_role_ops *ops); +int +lws_wsi_inject_to_loop(struct lws_context_per_thread *pt, struct lws *wsi); + +int +lws_wsi_extract_from_loop(struct lws *wsi); + + #if defined(LWS_WITH_CLIENT) int lws_http_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd); @@ -1358,6 +1364,38 @@ lws_same_vh_protocol_insert(struct lws *wsi, int n); void lws_seq_destroy_all_on_pt(struct lws_context_per_thread *pt); +void +lws_addrinfo_clean(struct lws *wsi); + +int +_lws_route_pt_close_unroutable(struct lws_context_per_thread *pt); + +void +_lws_routing_entry_dump(lws_route_t *rou); + +void +_lws_routing_table_dump(struct lws_context_per_thread *pt); + +int +_lws_route_remove(struct lws_context_per_thread *pt, lws_route_t *robj); + +void +_lws_route_table_empty(struct lws_context_per_thread *pt); + +void +_lws_route_table_ifdown(struct lws_context_per_thread *pt, int idx); + +lws_route_uidx_t +_lws_route_get_uidx(struct lws_context_per_thread *pt); + +int +_lws_route_pt_close_route_users(struct lws_context_per_thread *pt, + lws_route_uidx_t uidx); + +lws_route_t * +_lws_route_est_outgoing(struct lws_context_per_thread *pt, + const lws_sockaddr46 *dest); + int lws_broadcast(struct lws_context_per_thread *pt, int reason, void *in, size_t len); diff --git a/lib/core-net/route.c b/lib/core-net/route.c new file mode 100644 index 000000000..227a029df --- /dev/null +++ b/lib/core-net/route.c @@ -0,0 +1,288 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2020 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. + * + * We mainly focus on the routing table / gateways because those are the + * elements that decide if we can get on to the internet or not. + * + * Everything here is _ because the caller needs to hold the pt lock in order + * to access the pt routing table safely + */ + +#include + +#if defined(_DEBUG) +void +_lws_routing_entry_dump(lws_route_t *rou) +{ + char da[48], gw[48]; + + lws_sa46_write_numeric_address(&rou->dest, da, sizeof(da)); + lws_sa46_write_numeric_address(&rou->gateway, gw, sizeof(gw)); + + lwsl_info(" (%d)%s/%d, gw: (%d)%s, ifidx: %d, pri: %d, proto: %d\n", + rou->dest.sa4.sin_family, da, rou->dest_len, + rou->gateway.sa4.sin_family, gw, + rou->if_idx, rou->priority, rou->proto); +} + +void +_lws_routing_table_dump(struct lws_context_per_thread *pt) +{ + lws_start_foreach_dll(struct lws_dll2 *, d, + lws_dll2_get_head(&pt->routing_table)) { + lws_route_t *rou = lws_container_of(d, lws_route_t, list); + + _lws_routing_entry_dump(rou); + } lws_end_foreach_dll(d); +} +#endif + +/* + * We will provide a "fingerprint ordinal" as the route uidx that is unique in + * the routing table. Wsi that connect mark themselves with the uidx of the + * route they are estimated to be using. + * + * This lets us detect things like gw changes, eg when switching from wlan to + * lte there may still be a valid gateway route, but all existing tcp + * connections previously using the wlan gateway will be broken, since their + * connections are from its gateway to the peer. + * + * So when we take down a route, we take care to look for any wsi that was + * estimated to be using that route, eg, for gateway, and close those wsi. + * + * It's OK if the route uidx wraps, we explicitly confirm nobody else is using + * the uidx before assigning one to a new route. + */ + +lws_route_uidx_t +_lws_route_get_uidx(struct lws_context_per_thread *pt) +{ + while (1) { + char again = 0; + + /* Anybody in the table already uses the pt's next uidx? */ + + lws_start_foreach_dll(struct lws_dll2 *, d, + lws_dll2_get_head(&pt->routing_table)) { + lws_route_t *rou = lws_container_of(d, lws_route_t, list); + + if (rou->uidx == pt->route_uidx) { + /* if so, bump and restart the check */ + pt->route_uidx++; + again = 1; + } + } lws_end_foreach_dll(d); + + if (!again) + return pt->route_uidx++; + } +} + +int +_lws_route_remove(struct lws_context_per_thread *pt, lws_route_t *robj) +{ + lws_start_foreach_dll(struct lws_dll2 *, d, + lws_dll2_get_head(&pt->routing_table)) { + lws_route_t *rou = lws_container_of(d, lws_route_t, list); + + if (!lws_sa46_compare_ads(&robj->dest, &rou->dest) && + !lws_sa46_compare_ads(&robj->gateway, &rou->gateway) && + robj->dest_len == rou->dest_len && + robj->if_idx == rou->if_idx && + robj->priority == rou->priority) { + // lwsl_debug("%s: deleting route\n", __func__); + _lws_route_pt_close_route_users(pt, robj->uidx); + lws_dll2_remove(&rou->list); + lws_free(rou); + + return 0; + } + + } lws_end_foreach_dll(d); + + return 1; +} + +void +_lws_route_table_empty(struct lws_context_per_thread *pt) +{ + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + lws_dll2_get_head(&pt->routing_table)) { + lws_route_t *rou = lws_container_of(d, lws_route_t, list); + lws_dll2_remove(&rou->list); + lws_free(rou); + + } lws_end_foreach_dll_safe(d, d1); +} + +void +_lws_route_table_ifdown(struct lws_context_per_thread *pt, int idx) +{ + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + lws_dll2_get_head(&pt->routing_table)) { + lws_route_t *rou = lws_container_of(d, lws_route_t, list); + + if (rou->if_idx == idx) { + lws_dll2_remove(&rou->list); + lws_free(rou); + } + + } lws_end_foreach_dll_safe(d, d1); +} + +lws_route_t * +_lws_route_est_outgoing(struct lws_context_per_thread *pt, + const lws_sockaddr46 *dest) +{ + lws_route_t *best_gw = NULL; + int best_gw_priority = INT_MAX; + + if (!dest->sa4.sin_family) { + lwsl_notice("%s: dest has 0 AF\n", __func__); + /* leave it alone */ + return NULL; + } + + /* + * Given the dest address and the current routing table, select the + * route we think it would go out on + */ + + lws_start_foreach_dll(struct lws_dll2 *, d, + lws_dll2_get_head(&pt->routing_table)) { + lws_route_t *rou = lws_container_of(d, lws_route_t, list); + + // _lws_routing_entry_dump(rou); + + if (rou->dest.sa4.sin_family && + !lws_sa46_on_net(dest, &rou->dest, rou->dest_len)) { + /* + * Yes, he has a matching network route, it beats out + * any gateway route. This is like finding a route for + * 192.168.0.0/24 when dest is 192.168.0.1. + */ + + // lwsl_notice("%s: returning %p\n", __func__, rou); + + return rou; + } + + lwsl_debug("%s: dest af %d, rou gw af %d, pri %d\n", __func__, + dest->sa4.sin_family, + rou->gateway.sa4.sin_family, rou->priority); + + if (rou->gateway.sa4.sin_family && + + /* + * dest gw + * 4 4 OK + * 4 6 OK with ::ffff:x:x + * 6 4 not supported directly + * 6 6 OK + */ + + (dest->sa4.sin_family == rou->gateway.sa4.sin_family || + (dest->sa4.sin_family == AF_INET && + rou->gateway.sa4.sin_family == AF_INET6)) && + rou->priority < best_gw_priority) { + lwsl_info("%s: gw hit\n", __func__); + best_gw_priority = rou->priority; + best_gw = rou; + } + + } lws_end_foreach_dll(d); + + /* + * Either best_gw is the best gw route and we set *best_gw_priority to + * the best one's priority, or we're returning NULL as no network or + * gw route for dest. + */ + + lwsl_info("%s: returning %p\n", __func__, best_gw); + + return best_gw; +} + +int +_lws_route_check_wsi(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; + + if (!wsi->sa46_peer.sa4.sin_family || + wsi->desc.sockfd == LWS_SOCK_INVALID) + /* not a socket or not connected, leave it alone */ + return 0; /* OK */ + + return !_lws_route_est_outgoing(pt, &wsi->sa46_peer); +} + +/* + * priority_deleted_route should be -1 if no deleted route + */ + +int +_lws_route_pt_close_unroutable(struct lws_context_per_thread *pt) +{ + struct lws *wsi; + unsigned int n; + + if (!pt->context->nl_initial_done || + pt->context->mgr_system.state < LWS_SYSTATE_IFACE_COLDPLUG) + return 0; + + for (n = 0; n < pt->fds_count; n++) { + wsi = wsi_from_fd(pt->context, pt->fds[n].fd); + if (!wsi) + continue; + + if (_lws_route_check_wsi(wsi)) { + lwsl_info("%s: culling wsi %p\n", __func__, wsi); + lws_wsi_close(wsi, LWS_TO_KILL_ASYNC); + } + } + + return 0; +} + +int +_lws_route_pt_close_route_users(struct lws_context_per_thread *pt, + lws_route_uidx_t uidx) +{ + struct lws *wsi; + unsigned int n; + + for (n = 0; n < pt->fds_count; n++) { + wsi = wsi_from_fd(pt->context, pt->fds[n].fd); + if (!wsi) + continue; + + if (wsi->desc.sockfd != LWS_SOCK_INVALID && + wsi->sa46_peer.sa4.sin_family && + wsi->peer_route_uidx == uidx) { + lwsl_info("%s: culling wsi %p\n", __func__, wsi); + lws_wsi_close(wsi, LWS_TO_KILL_ASYNC); + } + } + + return 0; +} diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index 5e01967d6..ebadfffc7 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -42,6 +42,9 @@ const struct lws_role_ops *available_roles[] = { #endif #if defined(LWS_ROLE_MQTT) && defined(LWS_WITH_CLIENT) &role_ops_mqtt, +#endif +#if defined(LWS_WITH_NETLINK) + &role_ops_netlink, #endif NULL }; @@ -909,7 +912,6 @@ int lws_create_event_pipes(struct lws_context *context) { struct lws_context_per_thread *pt; - size_t s = sizeof(struct lws); struct lws *wsi; int n; @@ -929,29 +931,12 @@ lws_create_event_pipes(struct lws_context *context) if (pt->pipe_wsi) return 0; -#if defined(LWS_WITH_EVENT_LIBS) - s += context->event_loop_ops->evlib_size_wsi; -#endif - - wsi = lws_zalloc(s, "event pipe wsi"); - if (!wsi) { - lwsl_err("%s: Out of mem\n", __func__); + wsi = lws_wsi_create_with_role(context, n, &role_ops_pipe); + if (!wsi) return 1; - } -#if defined(LWS_WITH_EVENT_LIBS) - wsi->evlib_wsi = (uint8_t *)wsi + sizeof(*wsi); -#endif - wsi->a.context = context; - lws_role_transition(wsi, 0, LRS_UNCONNECTED, &role_ops_pipe); - wsi->a.protocol = NULL; - wsi->tsi = n; - wsi->a.vhost = NULL; - wsi->event_pipe = 1; - wsi->desc.sockfd = LWS_SOCK_INVALID; - context->pt[n].pipe_wsi = wsi; - context->count_wsi_allocated++; - lws_pt_lock(pt, __func__); /* -------------- pt { */ + wsi->event_pipe = 1; + pt->pipe_wsi = wsi; if (!lws_plat_pipe_create(wsi)) { /* @@ -966,21 +951,14 @@ lws_create_event_pipes(struct lws_context *context) wsi->desc.sockfd = context->pt[n].dummy_pipe_fds[0]; lwsl_debug("event pipe fd %d\n", wsi->desc.sockfd); - if (context->event_loop_ops->sock_accept) - if (context->event_loop_ops->sock_accept(wsi)) + if (lws_wsi_inject_to_loop(pt, wsi)) goto bail; - - if (__insert_wsi_socket_into_fds(context, wsi)) - goto bail; } - - lws_pt_unlock(pt); } return 0; bail: - lws_pt_unlock(pt); return 1; } @@ -988,23 +966,14 @@ bail: void lws_destroy_event_pipe(struct lws *wsi) { + int n; + lwsl_info("%s\n", __func__); - if (lws_socket_is_valid(wsi->desc.sockfd)) - __remove_wsi_socket_from_fds(wsi); - - if (!wsi->a.context->event_loop_ops->destroy_wsi && - wsi->a.context->event_loop_ops->wsi_logical_close) { - wsi->a.context->event_loop_ops->wsi_logical_close(wsi); - lws_plat_pipe_close(wsi); - return; - } - - if (wsi->a.context->event_loop_ops->destroy_wsi) - wsi->a.context->event_loop_ops->destroy_wsi(wsi); + n = lws_wsi_extract_from_loop(wsi); lws_plat_pipe_close(wsi); - wsi->a.context->count_wsi_allocated--; - lws_free(wsi); + if (!n) + lws_free(wsi); } diff --git a/lib/core-net/wsi.c b/lib/core-net/wsi.c index 2a8064edf..2194c7b48 100644 --- a/lib/core-net/wsi.c +++ b/lib/core-net/wsi.c @@ -190,6 +190,86 @@ lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len) return 0; } +struct lws * +lws_wsi_create_with_role(struct lws_context *context, int tsi, + const struct lws_role_ops *ops) +{ + size_t s = sizeof(struct lws); + struct lws *wsi; + +#if defined(LWS_WITH_EVENT_LIBS) + s += context->event_loop_ops->evlib_size_wsi; +#endif + + wsi = lws_zalloc(s, __func__); + + if (!wsi) { + lwsl_err("%s: Out of mem\n", __func__); + return NULL; + } + +#if defined(LWS_WITH_EVENT_LIBS) + wsi->evlib_wsi = (uint8_t *)wsi + sizeof(*wsi); +#endif + wsi->a.context = context; + lws_role_transition(wsi, 0, LRS_UNCONNECTED, ops); + wsi->a.protocol = NULL; + wsi->tsi = tsi; + wsi->a.vhost = NULL; + wsi->desc.sockfd = LWS_SOCK_INVALID; + + context->count_wsi_allocated++; + + return wsi; +} + +int +lws_wsi_inject_to_loop(struct lws_context_per_thread *pt, struct lws *wsi) +{ + int ret = 1; + + lws_pt_lock(pt, __func__); /* -------------- pt { */ + + if (pt->context->event_loop_ops->sock_accept) + if (pt->context->event_loop_ops->sock_accept(wsi)) + goto bail; + + if (__insert_wsi_socket_into_fds(pt->context, wsi)) + goto bail; + + ret = 0; + +bail: + lws_pt_unlock(pt); + + return ret; +} + +/* + * Take a copy of wsi->desc.sockfd before calling this, then close it + * afterwards + */ + +int +lws_wsi_extract_from_loop(struct lws *wsi) +{ + if (lws_socket_is_valid(wsi->desc.sockfd)) + __remove_wsi_socket_from_fds(wsi); + + wsi->a.context->count_wsi_allocated--; + + if (!wsi->a.context->event_loop_ops->destroy_wsi && + wsi->a.context->event_loop_ops->wsi_logical_close) { + wsi->a.context->event_loop_ops->wsi_logical_close(wsi); + return 1; /* close / destroy continues async */ + } + + if (wsi->a.context->event_loop_ops->destroy_wsi) + wsi->a.context->event_loop_ops->destroy_wsi(wsi); + + return 0; /* he is destroyed */ +} + int lws_callback_vhost_protocols_vhost(struct lws_vhost *vh, int reason, void *in, size_t len) diff --git a/lib/core/context.c b/lib/core/context.c index 24bea92ee..0cdfc7228 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -113,7 +113,8 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr, { struct lws_context *context = lws_container_of(mgr, struct lws_context, mgr_system); -#if defined(LWS_WITH_SECURE_STREAMS) && defined(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM) +#if defined(LWS_WITH_SECURE_STREAMS) && \ + defined(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM) lws_system_blob_t *ab0, *ab1; #endif int n; @@ -147,7 +148,24 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr, } #endif -#if defined(LWS_WITH_SECURE_STREAMS) && defined(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM) +#if defined(LWS_WITH_NETLINK) + /* + * If we're going to use netlink routing data for DNS, we have to + * wait to collect it asynchronously from the platform first. Netlink + * role init starts a ctx sul for 350ms (reset to 100ms each time some + * new netlink data comes) that sets nl_initial_done and tries to move + * us to OPERATIONAL + */ + + if (target == LWS_SYSTATE_IFACE_COLDPLUG && !context->nl_initial_done) { + lwsl_notice("%s: waiting for netlink coldplug\n", __func__); + + return 1; + } +#endif + +#if defined(LWS_WITH_SECURE_STREAMS) && \ + defined(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM) /* * Skip this if we are running something without the policy for it * @@ -161,7 +179,8 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr, context->pss_policies && ab0 && ab1 && !lws_system_blob_get_size(ab0) && lws_system_blob_get_size(ab1)) { - lwsl_info("%s: AUTH1 state triggering api.amazon.com auth\n", __func__); + lwsl_info("%s: AUTH1 state triggering api.amazon.com auth\n", + __func__); /* * Start trying to acquire it if it's not already in progress * returns nonzero if we determine it's not needed @@ -872,12 +891,6 @@ lws_create_context(const struct lws_context_creation_info *info) lws_seq_pt_init(&context->pt[n]); #endif - LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) { - if (ar->pt_init_destroy) - ar->pt_init_destroy(context, info, - &context->pt[n], 0); - } LWS_FOR_EVERY_AVAILABLE_ROLE_END; - #if defined(LWS_WITH_CGI) role_ops_cgi.pt_init_destroy(context, info, &context->pt[n], 0); #endif @@ -970,6 +983,14 @@ lws_create_context(const struct lws_context_creation_info *info) if (lws_create_event_pipes(context)) goto bail; + + for (n = 0; n < context->count_threads; n++) { + LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) { + if (ar->pt_init_destroy) + ar->pt_init_destroy(context, info, + &context->pt[n], 0); + } LWS_FOR_EVERY_AVAILABLE_ROLE_END; + } #endif lws_context_init_ssl_library(info); @@ -1056,6 +1077,7 @@ lws_create_context(const struct lws_context_creation_info *info) goto bail; #endif } + #endif #if defined(LWS_WITH_SYS_STATE) diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index c92443469..905dff371 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -165,6 +165,24 @@ struct lws_tx_credit { #undef X509_NAME +/* + * All lws_tls...() functions must return this type, converting the + * native backend result and doing the extra work to determine which one + * as needed. + * + * Native TLS backend return codes are NOT ALLOWED outside the backend. + * + * Non-SSL mode also uses these types. + */ +enum lws_ssl_capable_status { + LWS_SSL_CAPABLE_ERROR = -1, /* it failed */ + LWS_SSL_CAPABLE_DONE = 0, /* it succeeded */ + LWS_SSL_CAPABLE_MORE_SERVICE_READ = -2, /* retry WANT_READ */ + LWS_SSL_CAPABLE_MORE_SERVICE_WRITE = -3, /* retry WANT_WRITE */ + LWS_SSL_CAPABLE_MORE_SERVICE = -4, /* general retry */ +}; + + #if defined(LWS_WITH_TLS) #include "private-lib-tls.h" #endif @@ -291,6 +309,9 @@ struct lws_context { struct lws_context_per_thread pt[LWS_MAX_SMP]; lws_retry_bo_t default_retry; lws_sorted_usec_list_t sul_system_state; +#if defined(LWS_WITH_NETLINK) + lws_sorted_usec_list_t sul_nl_coldplug; +#endif #if defined(LWS_PLAT_FREERTOS) struct sockaddr_in frt_pipe_si; @@ -509,6 +530,9 @@ struct lws_context { unsigned int finalize_destroy_after_internal_loops_stopped:1; unsigned int max_fds_unrelated_to_ulimit:1; unsigned int policy_updated:1; +#if defined(LWS_WITH_NETLINK) + unsigned int nl_initial_done:1; +#endif short count_threads; short plugin_protocol_count; diff --git a/lib/plat/unix/unix-misc.c b/lib/plat/unix/unix-misc.c index 953c6247f..7c6679fa5 100644 --- a/lib/plat/unix/unix-misc.c +++ b/lib/plat/unix/unix-misc.c @@ -115,3 +115,25 @@ lws_plat_recommended_rsa_bits(void) { return 4096; } + +/* + * Platform-specific ntpclient server configuration + */ + +int +lws_plat_ntpclient_config(struct lws_context *context) +{ +#if defined(LWS_HAVE_GETENV) + char *ntpsrv = getenv("LWS_NTP_SERVER"); + + if (ntpsrv && strlen(ntpsrv) < 64) { + lws_system_blob_direct_set(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_NTP_SERVER, 0), + (const uint8_t *)ntpsrv, + strlen(ntpsrv)); + return 1; + } +#endif + return 0; +} + diff --git a/lib/plat/unix/unix-pipe.c b/lib/plat/unix/unix-pipe.c index a225dab7b..696f8da28 100644 --- a/lib/plat/unix/unix-pipe.c +++ b/lib/plat/unix/unix-pipe.c @@ -75,4 +75,3 @@ lws_plat_pipe_close(struct lws *wsi) pt->dummy_pipe_fds[0] = pt->dummy_pipe_fds[1] = -1; } - diff --git a/lib/plat/unix/unix-resolv.c b/lib/plat/unix/unix-resolv.c index 00a473513..f58776348 100644 --- a/lib/plat/unix/unix-resolv.c +++ b/lib/plat/unix/unix-resolv.c @@ -88,24 +88,3 @@ lws_plat_asyncdns_init(struct lws_context *context, lws_sockaddr46 *sa46) return LADNS_CONF_SERVER_UNKNOWN; } - -/* - * Platform-specific ntpclient server configuration - */ - -int -lws_plat_ntpclient_config(struct lws_context *context) -{ -#if defined(LWS_HAVE_GETENV) - char *ntpsrv = getenv("LWS_NTP_SERVER"); - - if (ntpsrv && strlen(ntpsrv) < 64) { - lws_system_blob_direct_set(lws_system_get_blob(context, - LWS_SYSBLOB_TYPE_NTP_SERVER, 0), - (const uint8_t *)ntpsrv, - strlen(ntpsrv)); - return 1; - } -#endif - return 0; -} diff --git a/lib/roles/CMakeLists.txt b/lib/roles/CMakeLists.txt index 3660f6b2c..000cd93f2 100644 --- a/lib/roles/CMakeLists.txt +++ b/lib/roles/CMakeLists.txt @@ -80,6 +80,10 @@ if (LWS_WITH_CLIENT AND (LWS_ROLE_H1 OR LWS_ROLE_H2)) roles/http/client/client-http.c) endif() +if (LWS_WITH_NETLINK) + list(APPEND SOURCES roles/netlink/ops-netlink.c) +endif() + # # Keep explicit parent scope exports at end # diff --git a/lib/roles/netlink/ops-netlink.c b/lib/roles/netlink/ops-netlink.c new file mode 100644 index 000000000..e9e4f8d04 --- /dev/null +++ b/lib/roles/netlink/ops-netlink.c @@ -0,0 +1,469 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2020 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. + * + * We mainly focus on the routing table / gateways because those are the + * elements that decide if we can get on to the internet or not. + * + * We also need to understand the source addresses of possible outgoing routes, + * and follow LINK down (ifconfig down) to clean up routes on the interface idx + * going down that are not otherwise cleaned. + */ + +#include + +#include +#include +#include +#include + +static void +lws_netlink_coldplug_done_cb(lws_sorted_usec_list_t *sul) +{ + struct lws_context *ctx = lws_container_of(sul, struct lws_context, + sul_nl_coldplug); + ctx->nl_initial_done = 1; + + /* if nothing is there to intercept anything, go all the way */ + lws_state_transition_steps(&ctx->mgr_system, LWS_SYSTATE_OPERATIONAL); +} + +static int +rops_handle_POLLIN_netlink(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + uint8_t s[512] +#if defined(_DEBUG) + , route_change = 0 +#endif +#if defined(LWS_WITH_SYS_SMD) + , gateway_change = 0 +#endif + ; + struct sockaddr_nl nladdr; + lws_route_t robj, *rou; + struct nlmsghdr *h; + struct msghdr msg; + struct iovec iov; + int n; + + if (!(pollfd->revents & LWS_POLLIN)) + return LWS_HPI_RET_HANDLED; + + if (!pt->context->nl_initial_done && pt == &pt->context->pt[0]) { + /* + * While netlink info still coming, keep moving the timer for + * calling it "done" to +100ms until after it stops coming + */ + lws_context_lock(pt->context, __func__); + lws_sul_schedule(pt->context, 0, &pt->context->sul_nl_coldplug, + lws_netlink_coldplug_done_cb, + 100 * LWS_US_PER_MS); + lws_context_unlock(pt->context); + } + + memset(&msg, 0, sizeof(msg)); + + iov.iov_base = (void *)s; + iov.iov_len = sizeof(s); + msg.msg_name = (void *)&(nladdr); + msg.msg_namelen = sizeof(nladdr); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + n = recvmsg(wsi->desc.sockfd, &msg, 0); + if (n < 0) + return LWS_HPI_RET_PLEASE_CLOSE_ME; + + h = (struct nlmsghdr *)s; + +/* + * On some platforms nlh->nlmsg_len is a uint32_t but len is expected to be + * an int. This causes the last comparison to blow with + * + * comparison of integers of different signs: '__u32' (aka 'unsigned int') and + * 'int' [-Werror,-Wsign-compare] + * + * rtnetlink messages cannot be huge, solve it by casting nmmsg_len to int + */ + +#define LWS_NLMSG_OK(nlh, len) ((len) >= (int) sizeof(struct nlmsghdr) && \ + (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ + (int)(nlh)->nlmsg_len <= (len)) + + for ( ; LWS_NLMSG_OK(h, n); h = NLMSG_NEXT(h, n)) { + struct ifaddrmsg *ifam; + struct rtattr *ra; + struct rtmsg *rm; + int ra_len; + uint8_t *p; + + /* + * We have to care about NEWLINK so we can understand when a + * network interface went down, and clear the related routes. + * + * We don't get individual DELROUTEs for these. + */ + + if (h->nlmsg_type == RTM_NEWLINK) { + struct ifinfomsg *ifi = NLMSG_DATA(h); + + /* + * Despite "New"link this is actually telling us there + * is some change on the network interface IFF_ state + */ + + if (!(ifi->ifi_flags & IFF_UP)) { + /* + * Interface is down, so scrub all routes that + * applied to it + */ + lws_pt_lock(pt, __func__); + _lws_route_table_ifdown(pt, ifi->ifi_index); + lws_pt_unlock(pt); + } + continue; + } + + memset(&robj, 0, sizeof(robj)); + robj.if_idx = -1; + robj.priority = -1; + + rm = (struct rtmsg *)NLMSG_DATA(h); + + if (h->nlmsg_type == RTM_NEWADDR || + h->nlmsg_type == RTM_DELADDR) { + ifam = (struct ifaddrmsg *)NLMSG_DATA(h); + + robj.source_ads = 1; + robj.dest_len = ifam->ifa_prefixlen; + robj.if_idx = ifam->ifa_index; + robj.scope = ifam->ifa_scope; + robj.ifa_flags = ifam->ifa_flags; + robj.dest.sa4.sin_family = ifam->ifa_family; + + /* address attributes */ + ra = (struct rtattr *)IFA_RTA(ifam); + ra_len = IFA_PAYLOAD(h); + } else { + /* route attributes */ + ra = (struct rtattr *)RTM_RTA(rm); + ra_len = RTM_PAYLOAD(h); + } + + robj.proto = rm->rtm_protocol; + + for ( ; RTA_OK(ra, ra_len); ra = RTA_NEXT(ra, ra_len)) { + switch (ra->rta_type) { + case RTA_DST: + lws_sa46_copy_address(&robj.dest, RTA_DATA(ra), + rm->rtm_family); + robj.dest_len = rm->rtm_dst_len; + break; + case RTA_GATEWAY: + lws_sa46_copy_address(&robj.gateway, + RTA_DATA(ra), + rm->rtm_family); +#if defined(LWS_WITH_SYS_SMD) + gateway_change = 1; +#endif + break; + case RTA_OIF: /* int: output interface index */ + robj.if_idx = *(char*)RTA_DATA(ra); + break; + case RTA_PRIORITY: /* int: priority of route */ + p = RTA_DATA(ra); + robj.priority = p[3] << 24 | p[2] << 16 | + p[1] << 8 | p[0]; + break; + case RTA_PREFSRC: /* protocol ads: preferred src ads */ + break; + case RTA_CACHEINFO: /* struct rta_cacheinfo */ + break; +#if defined(LWS_HAVE_RTA_PREF) + case RTA_PREF: /* char: RFC4191 v6 router preference */ + break; +#endif + case RTA_TABLE: /* int */ + break; + + default: + lwsl_info("%s: unknown attr type %d\n", + __func__, ra->rta_type); + break; + } + } + + switch (h->nlmsg_type) { + + case RTM_DELROUTE: + /* + * This will also take down wsi marked as using it + */ + lws_pt_lock(pt, __func__); + _lws_route_remove(pt, &robj); + lws_pt_unlock(pt); + goto inform; + + case RTM_NEWROUTE: + + /* + * We don't want any routing debris like /32 or broadcast + * in our routing table... we will collect source addresses + * bound to interfaces via NEWADDR + */ + + if (rm->rtm_type != RTN_UNICAST && + rm->rtm_type != RTN_LOCAL) + break; + + if (rm->rtm_flags & RTM_F_CLONED) + break; + + /* fallthru */ + + case RTM_NEWADDR: + rou = lws_malloc(sizeof(*rou), __func__); + if (!rou) { + lwsl_err("%s: oom\n", __func__); + return LWS_HPI_RET_HANDLED; + } + + *rou = robj; + + lws_pt_lock(pt, __func__); + + /* + * We lock the pt before getting the uidx, so it + * cannot race + */ + + rou->uidx = _lws_route_get_uidx(pt); + lws_dll2_add_tail(&rou->list, &pt->routing_table); + + _lws_route_pt_close_unroutable(pt); + + lws_pt_unlock(pt); + +inform: +#if defined(_DEBUG) + route_change = 1; +#endif +#if defined(LWS_WITH_SYS_SMD) + /* + * Reflect the route add / del event using SMD. + * Participants interested can refer to the pt + * routing table + */ + (void)lws_smd_msg_printf(pt->context, LWSSMDCL_NETWORK, + "{\"rt\":\"%s\"}\n", + (h->nlmsg_type == RTM_DELROUTE) ? + "del" : "add"); +#endif + + break; + + default: + // lwsl_info("%s: unknown msg type %d\n", __func__, + // h->nlmsg_type); + break; + } + } + +#if defined(LWS_WITH_SYS_SMD) + if (gateway_change) + /* + * If a route with a gw was added or deleted, retrigger captive + * portal detection if we have that + */ + (void)lws_smd_msg_printf(pt->context, LWSSMDCL_NETWORK, + "{\"trigger\": \"cpdcheck\", " + "\"src\":\"gw-change\"}"); +#endif + +#if defined(_DEBUG) + if (route_change) { + lws_pt_lock(pt, __func__); + _lws_routing_table_dump(pt); + lws_pt_unlock(pt); + } +#endif + + return LWS_HPI_RET_HANDLED; +} + +struct nl_req_s { + struct nlmsghdr hdr; + struct rtmsg gen; +}; + +int +rops_pt_init_destroy_netlink(struct lws_context *context, + const struct lws_context_creation_info *info, + struct lws_context_per_thread *pt, int destroy) +{ + struct sockaddr_nl sanl; + struct nl_req_s req; + struct msghdr msg; + struct iovec iov; + struct lws *wsi; + int n; + + if (destroy) { + /* + * pt netlink wsi closed + freed as part of pt's destroy + * wsi mass close, just need to take down the routing table + */ + _lws_route_table_empty(pt); + + return 0; + } + + if (pt->netlink) + return 0; + + lwsl_info("%s: creating netlink skt\n", __func__); + + /* + * We want a netlink socket per pt as well + */ + wsi = lws_wsi_create_with_role(context, (int)(pt - &context->pt[0]), + &role_ops_netlink); + if (!wsi) + goto bail; + + wsi->desc.sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (wsi->desc.sockfd == LWS_SOCK_INVALID) { + lwsl_err("%s: unable to open netlink\n", __func__); + goto bail1; + } + + memset(&sanl, 0, sizeof(sanl)); + sanl.nl_family = AF_NETLINK; + sanl.nl_pid = getpid(); + sanl.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_ROUTE +#if defined(LWS_WITH_IPV6) + | RTMGRP_IPV6_ROUTE +#endif + ; + + if (bind(wsi->desc.sockfd, (struct sockaddr*)&sanl, sizeof(sanl)) < 0) { + lwsl_err("%s: netlink bind failed\n", __func__); + goto bail2; + } + + pt->netlink = wsi; + if (lws_wsi_inject_to_loop(pt, wsi)) + goto bail2; + + if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { + lwsl_err("%s: pollfd in fail\n", __func__); + goto bail2; + } + + /* + * Since we're starting the PT, ask to be sent all the existing routes. + * + * This requires CAP_ADMIN, or root... we do this early before dropping + * privs + */ + + memset(&sanl, 0, sizeof(sanl)); + memset(&msg, 0, sizeof(msg)); + memset(&req, 0, sizeof(req)); + + sanl.nl_family = AF_NETLINK; + + req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.gen)); + req.hdr.nlmsg_type = RTM_GETROUTE; + req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.hdr.nlmsg_seq = 1; + req.hdr.nlmsg_pid = getpid(); + req.gen.rtm_family = AF_PACKET; + req.gen.rtm_table = RT_TABLE_DEFAULT; + + iov.iov_base = &req; + iov.iov_len = req.hdr.nlmsg_len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = &sanl; + msg.msg_namelen = sizeof(sanl); + + n = sendmsg(wsi->desc.sockfd, (struct msghdr *)&msg, 0); + if (n < 0) { + lwsl_notice("%s: rt dump req failed... permissions? errno %d\n", + __func__, LWS_ERRNO); + } + + /* + * Responses are going to come asynchronously, since we can't process + * DNS lookups properly until we collected the initial netlink responses + * let's set a timer that will let us advance from lws_system + * LWS_SYSTATE_IFACE_COLDPLUG + */ + + lwsl_debug("%s: starting netlink coldplug wait\n", __func__); + lws_sul_schedule(context, 0, &context->sul_nl_coldplug, + lws_netlink_coldplug_done_cb, 450 * LWS_US_PER_MS); + + return 0; + +bail2: + compatible_close(wsi->desc.sockfd); +bail1: + lws_free(wsi); +bail: + return 1; +} + +const struct lws_role_ops role_ops_netlink = { + /* role name */ "netlink", + /* alpn id */ NULL, + /* check_upgrades */ NULL, + /* pt_init_destroy */ rops_pt_init_destroy_netlink, + /* init_vhost */ NULL, + /* destroy_vhost */ NULL, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_netlink, + /* handle_POLLOUT */ NULL, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* adoption_bind */ NULL, + /* client_bind */ NULL, + /* issue_keepalive */ NULL, + /* adoption_cb clnt, srv */ { 0, 0 }, + /* rx_cb clnt, srv */ { 0, 0 }, + /* writeable cb clnt, srv */ { 0, 0 }, + /* close cb clnt, srv */ { 0, 0 }, + /* protocol_bind_cb c,s */ { 0, 0 }, + /* protocol_unbind_cb c,s */ { 0, 0 }, + /* file_handle */ 0, +}; diff --git a/lib/roles/private-lib-roles.h b/lib/roles/private-lib-roles.h index 78328b9ff..6dca19bf8 100644 --- a/lib/roles/private-lib-roles.h +++ b/lib/roles/private-lib-roles.h @@ -274,7 +274,8 @@ struct lws_role_ops { /* core roles */ extern const struct lws_role_ops role_ops_raw_skt, role_ops_raw_file, - role_ops_listen, role_ops_pipe; + role_ops_listen, role_ops_pipe, + role_ops_netlink; /* bring in role private declarations */ diff --git a/lib/system/smd/README.md b/lib/system/smd/README.md index 214f32a51..c61ef4527 100644 --- a/lib/system/smd/README.md +++ b/lib/system/smd/README.md @@ -151,6 +151,36 @@ click|The button activity resulted in a classification as a single-click longclick|The button activity resulted in a classification as a long-click doubleclick|The button activity resulted in a classification as a double-click +### Routing Table Change + +Class: `LWSSMDCL_NETWORK` + +If able to subscribe to OS routing table changes (eg, by rtnetlink on Linux +which is supported), lws announces there have been changes using SMD. + +If Captive Portal Detect is enabled, and routing tables changes can be seen, +then a new CPD is requested automatically and the results will be seen over SMD +when that completes. + +Schema: + +``` + { + "rt": "add|del", "add" if being added + } +``` + +When the context / pts are created, if linux then lws attempts to get the +routing table sent, which requires root. This is done before the permissions +are dropped after protocols init. + +Lws maintains a cache of the routing table in each pt. Upon changes, existing +connections are reassessed to see if their peer can still be routed to, if not +the connection is closed. + +If a gateway route changes, `{"trigger":"cpdcheck","src":"gw-change"}` is +issued on SMD as well. + ### Captive Portal Detection Class: `LWSSMDCL_NETWORK`