mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
roles: netlink
This creates a role for RFC3549 Netlink monitoring. If the OS supports it (currently, linux) then each pt creates a wsi with the netlink role and dumps the current routing table at pt init. It then maintains a cache of the routing table in each pt. Upon routing table changes an SMD message is issued as an event, and Captive Portal Detection is triggered. All of the pt's current connections are reassessed for routability under the changed routing table, those that no longer have a valid route or gateway are closed.
This commit is contained in:
parent
915f888f3e
commit
643a001ed8
21 changed files with 1132 additions and 99 deletions
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
51
READMEs/README.routing.md
Normal file
51
READMEs/README.routing.md
Normal file
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -33,12 +33,39 @@
|
|||
#include <lwip/sockets.h>
|
||||
#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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
288
lib/core-net/route.c
Normal file
288
lib/core-net/route.c
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* 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 <private-lib-core.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -75,4 +75,3 @@ lws_plat_pipe_close(struct lws *wsi)
|
|||
|
||||
pt->dummy_pipe_fds[0] = pt->dummy_pipe_fds[1] = -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
469
lib/roles/netlink/ops-netlink.c
Normal file
469
lib/roles/netlink/ops-netlink.c
Normal file
|
@ -0,0 +1,469 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* 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 <private-lib-core.h>
|
||||
|
||||
#include <asm/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
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,
|
||||
};
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
Loading…
Add table
Reference in a new issue