diff --git a/.travis.yml b/.travis.yml index df21b9068..247a136cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ env: global: - secure: "KhAdQ9ja+LBObWNQTYO7Df5J4DyOih6S+eerDMu8UPSO+CoWV2pWoQzbOfocjyOscGOwC+2PrrHDNZyGfqkCLDXg1BxynXPCFerHC1yc2IajvKpGXmAAygNIvp4KACDfGv/dkXrViqIzr/CdcNaU4vIMHSVb5xkeLi0W1dPnQOI=" matrix: - - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2=/usr/lib/x86_64-linux-gnu/dbus-1.0/include/ -DLWS_WITH_GENCRYPTO=1 -DLWS_WITH_JOSE=1" + - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2=/usr/lib/x86_64-linux-gnu/dbus-1.0/include/ -DLWS_WITH_GENCRYPTO=1 -DLWS_WITH_JOSE=1 -DLWS_WITH_SYS_ASYNC_DNS=1" - LWS_METHOD=lwsws2 CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2=/usr/lib/x86_64-linux-gnu/dbus-1.0/include/ -DLWS_WITH_LWS_DSH=1" - LWS_METHOD=default CMAKE_ARGS="-DLWS_WITH_MINIMAL_EXAMPLES=1" - LWS_METHOD=mbedtls CMAKE_ARGS="-DLWS_WITH_MBEDTLS=1 -DLWS_WITH_HTTP2=1 -DLWS_WITH_LWSWS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_JOSE=1 -DCMAKE_BUILD_TYPE=DEBUG" @@ -13,7 +13,7 @@ env: - LWS_METHOD=noext CMAKE_ARGS="-DLWS_WITHOUT_EXTENSIONS=ON -DLWS_WITH_MINIMAL_EXAMPLES=1" - LWS_METHOD=nonetwork CMAKE_ARGS="-DLWS_WITH_NETWORK=0" - LWS_METHOD=libev CMAKE_ARGS="-DLWS_WITH_LIBEV=ON" - - LWS_METHOD=noipv6 CMAKE_ARGS="-DLWS_IPV6=OFF" + - LWS_METHOD=ipv6 CMAKE_ARGS="-DLWS_IPV6=ON" - LWS_METHOD=nossl CMAKE_ARGS="-DLWS_WITH_SSL=OFF" - LWS_METHOD=nodaemon CMAKE_ARGS="-DLWS_WITHOUT_DAEMONIZE=ON" - LWS_METHOD=cgi CMAKE_ARGS="-DLWS_WITH_CGI=ON" diff --git a/CMakeLists.txt b/CMakeLists.txt index 63fa67e61..3779924ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ option(LWS_WITH_HTTP_BROTLI "Also offer brotli http stream compression (requires option(LWS_WITH_ACME "Enable support for ACME automatic cert acquisition + maintenance (letsencrypt etc)" OFF) option(LWS_WITH_HUBBUB "Enable libhubbub rewriting support" OFF) option(LWS_WITH_FTS "Full Text Search support" OFF) +option(LWS_WITH_SYS_ASYNC_DNS "Nonblocking internal IPv4 DNS resolver" OFF) # # TLS library options... all except mbedTLS are basically OpenSSL variants. # @@ -76,6 +77,7 @@ option(LWS_WITH_ESP32 "Build for ESP32" OFF) option(LWS_WITH_ESP32_HELPER "Build ESP32 helper" OFF) option(LWS_PLAT_OPTEE "Build for OPTEE" OFF) option(LWS_PLAT_FREERTOS "Build for FreeRTOS" OFF) +option(LWS_PLAT_ANDROID "Android flavour of unix platform" OFF) # # Client / Server / Test Apps build control @@ -351,7 +353,7 @@ if (LWS_WITH_MBEDTLS) include_directories(lib/tls/mbedtls/wrapper/include) endif() -include_directories(include plugins lib/core lib/core-net lib/event-libs include/abstract lib/tls lib/roles lib/event-libs/libuv lib/event-libs/poll lib/event-libs/libevent lib/event-libs/libev lib/jose/jwe lib/jose/jws lib/jose lib/misc lib/roles/http lib/roles/http/compression lib/roles/h1 lib/roles/h2 lib/roles/ws lib/roles/cgi lib/roles/dbus lib/roles/raw-proxy lib/abstract) +include_directories(include plugins lib/core lib/core-net lib/event-libs include/abstract lib/tls lib/roles lib/event-libs/libuv lib/event-libs/poll lib/event-libs/libevent lib/event-libs/libev lib/jose/jwe lib/jose/jws lib/jose lib/misc lib/roles/http lib/roles/http/compression lib/roles/h1 lib/roles/h2 lib/roles/ws lib/roles/cgi lib/roles/dbus lib/roles/raw-proxy lib/abstract lib/system/async-dns) if (LWS_PLAT_FREERTOS) include_directories(lib/plat/freertos lib/plat/freertos/esp32) @@ -1022,6 +1024,12 @@ if (LWS_WITH_NETWORK) lib/roles/pipe/ops-pipe.c ) + if (LWS_WITH_SYS_ASYNC_DNS) + list(APPEND SOURCES + lib/system/async-dns/async-dns.c + lib/system/async-dns/async-dns-parse.c) + endif() + if (LWS_WITH_LWS_DSH) list(APPEND SOURCES lib/core-net/lws-dsh.c) @@ -1334,6 +1342,7 @@ else() lib/plat/freertos/freertos-pipe.c lib/plat/freertos/freertos-service.c lib/plat/freertos/freertos-sockets.c + lib/plat/freertos/freertos-resolv.c lib/misc/romfs.c) if (LWS_WITH_ESP32_HELPER) list(APPEND SOURCES lib/plat/freertos/esp32/esp32-helpers.c) @@ -1358,6 +1367,13 @@ else() lib/plat/unix/unix-sockets.c lib/plat/unix/unix-fds.c ) + if (LWS_WITH_SYS_ASYNC_DNS) + if (LWS_PLAT_ANDROID) + list(APPEND SOURCES lib/plat/unix/android/android-resolv.c) + else() + list(APPEND SOURCES lib/plat/unix/unix-resolv.c) + endif() + endif() endif() if (LWS_WITH_PLUGINS AND LWS_WITH_LIBUV) diff --git a/LICENSE b/LICENSE index 71663aeb1..b3c9edbb3 100644 --- a/LICENSE +++ b/LICENSE @@ -6,15 +6,16 @@ them. Original liberal license retained: - - lib/misc/sha-1.c - 3-clause BSD license retained, link to original - - win32port/zlib - ZLIB license (see zlib.h) - - lib/tls/mbedtls/wrapper - Apache 2.0 (only built if linked against mbedtls) + - lib/misc/sha-1.c - 3-clause BSD license retained, link to original + - win32port/zlib - ZLIB license (see zlib.h) + - lib/tls/mbedtls/wrapper - Apache 2.0 (only built if linked against mbedtls) + - lib/misc/base64-decode.c - already MIT Relicensed to MIT: - - lib/misc/base64-decode.c - relicensed, link to original - - lib/misc/daemonize.c - relicensed from Public Domain to MIT, - link to original Public Domain version + - lib/misc/daemonize.c - relicensed from Public Domain to MIT, + link to original Public Domain version + - lib/plat/windows/windows-resolv.c - relicensed from "Beerware v42" to MIT Public Domain (CC-zero) to simplify reuse: diff --git a/READMEs/README.async-dns.md b/READMEs/README.async-dns.md new file mode 100644 index 000000000..7defcaedf --- /dev/null +++ b/READMEs/README.async-dns.md @@ -0,0 +1,76 @@ +# Asynchronous DNS + +## Introduction + +Lws now features optional asynchronous, ie, nonblocking DNS +resolution done on the event loop, enable `-DLWS_WITH_SYS_ASYNC_DNS=1` +at cmake to build it in. + +## Description + +The default libc name resolution is via libc `getaddrinfo()`, which is +blocking, possibly for quite long periods (seconds). If you are +taking care about latency, but want to create outgoing connections, +you can't tolerate this exception from the rule that everything in +lws is nonblocking. + +Lws' asynchronous DNS resolver creates a caching name resolver +that directly queries the configured nameserver itself over UDP, +from the event loop. + +It supports both ipv4 / A records and ipv6 / AAAA records (see later +for a description about how). One server supported over UDP :53, +and the nameserver is autodicovered on linux, windows, and freertos. + +Other features + + - lws-style paranoid response parsing + - random unique tid generation to increase difficulty of poisoning + - it's really integrated with the lws event loop, it does not spawn + threads or use the libc resolver, and of course no blocking at all + - platform-specific server address capturing (from /etc/resolv.conf + on linux, windows apis on windows) + - LRU caching + - piggybacking (multiple requests before the first completes go on + a list on the first request, not spawn multiple requests) + - observes TTL in cache + - TTL and timeout use `lws_sul` timers on the event loop + - ipv6 pieces only built if cmake `LWS_IPV6` enabled + +## Api + +If enabled at cmake, the async DNS implementation is used automatically +for lws client connections. It's also possible to call it directly, see +the api-test-async-dns example for how. + +The Api follows that of `getaddrinfo()` but results are not created on +the heap. Instead a single, const cached copy of the addrinfo struct +chain is reference-counted, with `lws_async_dns_freeaddrinfo()` provided +to deduct from the reference count. Cached items with a nonzero +reference count can't be destroyed from the cache, so it's safe to keep +a pointer to the results and iterate through them. + +## Dealing with IPv4 and IPv6 + +DNS is a very old standard that has some quirks... one of them is that +multiple queries are not supported in one packet, even though the protocol +suggests it is. This creates problems on ipv6 enabled systems, where +it may prefer to have AAAA results, but the server may only have A records. + +To square the circle, for ipv4 only systems (`LWS_IPV6=0`) the resolver +requests only A records. For ipv6-capable systems, it always requests +first A and then immediately afterwards AAAA records. + +To simplify the implementation, the tid b0 is used to differentiate +between A (b0 = 0) and AAAA (b0 = 1) requests and responses using the +same query body. + +The first response to come back is parsed, and a cache entry made... +it leaves a note in the query about the address of the last `struct addrinfo` +record. When the second response comes, a second allocation is made, +but not added to the logical cache... instead it's chained on to the +first cache entry and the `struct addrinfo` linked-list from the +first cache entry is extended into the second one. At the time the +second result arrives, the query is destroyed and the cached results +provided on the result callback. + diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 6dc7ee3ce..ef120343b 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -95,6 +95,7 @@ #cmakedefine LWS_WITH_ABSTRACT #cmakedefine LWS_WITH_ACCESS_LOG #cmakedefine LWS_WITH_ACME +#cmakedefine LWS_WITH_SYS_ASYNC_DNS #cmakedefine LWS_WITH_BORINGSSL #cmakedefine LWS_WITH_CGI #cmakedefine LWS_WITH_CUSTOM_HEADERS diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 38391c990..8c582a718 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -314,6 +314,7 @@ lws_pthread_mutex_unlock(pthread_mutex_t *lock) #ifndef lws_container_of #define lws_container_of(P,T,M) ((T *)((char *)(P) - offsetof(T, M))) #endif +#define LWS_ALIGN_TO(x, bou) x += ((bou) - ((x) % (bou))) % (bou) struct lws; @@ -569,6 +570,7 @@ struct lws; #include #include +#include #if defined(LWS_WITH_TLS) diff --git a/include/libwebsockets/lws-adopt.h b/include/libwebsockets/lws-adopt.h index 30c91def5..2d4fc5d60 100644 --- a/include/libwebsockets/lws-adopt.h +++ b/include/libwebsockets/lws-adopt.h @@ -173,6 +173,7 @@ lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, * lws_create_adopt_udp() - create, bind and adopt a UDP socket * * \param vhost: lws vhost + * \param ads: NULL or address to do dns lookup on * \param port: UDP port to bind to, -1 means unbound * \param flags: 0 or LWS_CAUDP_NO_BIND * \param protocol_name: Name of protocol on vhost to bind wsi to @@ -182,6 +183,7 @@ lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, * returns NULL, having cleaned up any new wsi pieces. * */ LWS_VISIBLE LWS_EXTERN struct lws * -lws_create_adopt_udp(struct lws_vhost *vhost, int port, int flags, - const char *protocol_name, struct lws *parent_wsi); +lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port, + int flags, const char *protocol_name, + struct lws *parent_wsi); ///@} diff --git a/include/libwebsockets/lws-async-dns.h b/include/libwebsockets/lws-async-dns.h new file mode 100644 index 000000000..a83f0623a --- /dev/null +++ b/include/libwebsockets/lws-async-dns.h @@ -0,0 +1,79 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2019 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. + */ + +typedef enum dns_query_type { + LWS_ADNS_RECORD_A = 0x01, + LWS_ADNS_RECORD_CNAME = 0x05, + LWS_ADNS_RECORD_MX = 0x0f, + LWS_ADNS_RECORD_AAAA = 0x1c, +} adns_query_type_t; + +typedef enum { + LADNS_RET_FAILED_WSI_CLOSED = -4, + LADNS_RET_NXDOMAIN = -3, + LADNS_RET_TIMEDOUT = -2, + LADNS_RET_FAILED = -1, + LADNS_RET_FOUND, + LADNS_RET_CONTINUING +} lws_async_dns_retcode_t; + +typedef struct lws * (*lws_async_dns_cb_t)(struct lws *wsi, const char *ads, + const struct addrinfo *result, int n, + void *opaque); + +/** + * lws_async_dns_query() - perform a dns lookup using async dns + * + * \param context: the lws_context + * \param tsi: thread service index (usually 0) + * \param name: DNS name to look up + * \param qtype: type of query (A, AAAA etc) + * \param cb: query completion callback + * \param wsi: wsi if the query is related to one + * + * Starts an asynchronous DNS lookup, on completion the \p cb callback will + * be called. + * + * The reference count on the cached object is incremented for every callback + * that was called with the cached addrinfo results. + * + * The cached object can't be evicted until the reference count reaches zero... + * use lws_async_dns_freeaddrinfo() to indicate you're finsihed with the + * results for each callback that happened with them. + */ +LWS_VISIBLE LWS_EXTERN lws_async_dns_retcode_t +lws_async_dns_query(struct lws_context *context, int tsi, const char *name, + adns_query_type_t qtype, lws_async_dns_cb_t cb, + struct lws *wsi, void *opaque); + +/** + * lws_async_dns_freeaddrinfo() - decrement refcount on cached addrinfo results + * + * \param ai: the first addrinfo returned as result in the callback + * + * Decrements the cache object's reference count. When it reaches zero, the + * cached object may be reaped subject to LRU rules. + */ +LWS_VISIBLE LWS_EXTERN void +lws_async_dns_freeaddrinfo(const struct addrinfo *ai); diff --git a/lib/core-net/adopt.c b/lib/core-net/adopt.c index e4d05d9de..d356b9045 100644 --- a/lib/core-net/adopt.c +++ b/lib/core-net/adopt.c @@ -72,6 +72,11 @@ lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi) new_wsi->pending_timeout = NO_PENDING_TIMEOUT; new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; +#if defined(LWS_WITH_DETAILED_LATENCY) + if (vhost->context->detailed_latency_cb) + new_wsi->detlat.earliest_write_req_pre_write = lws_now_usecs(); +#endif + /* initialize the instance struct */ lwsi_set_state(new_wsi, LRS_UNCONNECTED); @@ -107,34 +112,15 @@ lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi) /* if not a socket, it's a raw, non-ssl file descriptor */ -LWS_VISIBLE struct lws * -lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, - lws_sock_file_fd_type fd, const char *vh_prot_name, - struct lws *parent) +static struct lws * +lws_adopt_descriptor_vhost1(struct lws_vhost *vh, lws_adoption_type type, + const char *vh_prot_name, struct lws *parent) { struct lws_context *context = vh->context; struct lws_context_per_thread *pt; struct lws *new_wsi; int n; -#if defined(LWS_WITH_PEER_LIMITS) - struct lws_peer *peer = NULL; - - if (type & LWS_ADOPT_SOCKET) { - peer = lws_get_or_create_peer(vh, fd.sockfd); - - if (peer && context->ip_limit_wsi && - peer->count_wsi >= context->ip_limit_wsi) { - lwsl_notice("Peer reached wsi limit %d\n", - context->ip_limit_wsi); - lws_stats_bump(&context->pt[0], - LWSSTATS_C_PEER_LIMIT_WSI_DENIED, - 1); - return NULL; - } - } -#endif - /* * Notice that in SMP case, the wsi may be being created on an * entirely different pt / tsi for load balancing. In that case as @@ -145,15 +131,9 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, if (parent) n = parent->tsi; new_wsi = lws_create_new_server_wsi(vh, n); - if (!new_wsi) { - if (type & LWS_ADOPT_SOCKET) - compatible_close(fd.sockfd); + if (!new_wsi) return NULL; - } -#if defined(LWS_WITH_PEER_LIMITS) - if (peer) - lws_peer_add_wsi(context, peer, new_wsi); -#endif + pt = &context->pt[(int)new_wsi->tsi]; lws_stats_bump(pt, LWSSTATS_C_CONNECTIONS, 1); @@ -163,26 +143,6 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, parent->child_list = new_wsi; } - /* enforce that every fd is nonblocking */ - - if (type & LWS_ADOPT_SOCKET) { - if (lws_plat_set_nonblocking(fd.sockfd)) { - lwsl_err("%s: unable to set sockfd nonblocking\n", - __func__); - goto bail; - } - } -#if !defined(WIN32) - else - if (lws_plat_set_nonblocking(fd.filefd)) { - lwsl_err("%s: unable to set filefd nonblocking\n", - __func__); - goto bail; - } -#endif - - new_wsi->desc = fd; - if (vh_prot_name) { new_wsi->protocol = lws_vhost_name_to_protocol(new_wsi->vhost, vh_prot_name); @@ -197,14 +157,60 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, } } - if (!LWS_SSL_ENABLED(new_wsi->vhost) || !(type & LWS_ADOPT_SOCKET)) - type &= ~LWS_ADOPT_ALLOW_SSL; - if (lws_role_call_adoption_bind(new_wsi, type, vh_prot_name)) { - lwsl_err("Unable to find a role that can adopt descriptor type 0x%x\n", type); + lwsl_err("%s: no role for desc type 0x%x\n", __func__, type); goto bail; } + return new_wsi; + +bail: + lwsl_notice("%s: exiting on bail\n", __func__); + if (parent) + parent->child_list = new_wsi->sibling_list; + if (new_wsi->user_space) + lws_free(new_wsi->user_space); + + vh->context->count_wsi_allocated--; + + lws_vhost_unbind_wsi(new_wsi); + lws_free(new_wsi); + + return NULL; +} + +static struct lws * +lws_adopt_descriptor_vhost2(struct lws *new_wsi, lws_adoption_type type, + lws_sock_file_fd_type fd) +{ + struct lws_context_per_thread *pt = + &new_wsi->context->pt[(int)new_wsi->tsi]; + int n; + + /* enforce that every fd is nonblocking */ + + if (type & LWS_ADOPT_SOCKET) { + if (lws_plat_set_nonblocking(fd.sockfd)) { + lwsl_err("%s: unable to set sockfd %d nonblocking\n", + __func__, fd.sockfd); + goto fail; + } + } +#if !defined(WIN32) + else + if (lws_plat_set_nonblocking(fd.filefd)) { + lwsl_err("%s: unable to set filefd nonblocking\n", + __func__); + goto fail; + } +#endif + + new_wsi->desc = fd; + + if (!LWS_SSL_ENABLED(new_wsi->vhost) || + !(type & LWS_ADOPT_SOCKET)) + type &= ~LWS_ADOPT_ALLOW_SSL; + /* * A new connection was accepted. Give the user a chance to * set properties of the newly created wsi. There's no protocol @@ -217,8 +223,8 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, n = new_wsi->role_ops->adoption_cb[lwsi_role_server(new_wsi)]; #if !defined(LWS_AMAZON_RTOS) - if (context->event_loop_ops->accept) - if (context->event_loop_ops->accept(new_wsi)) + if (new_wsi->context->event_loop_ops->accept) + if (new_wsi->context->event_loop_ops->accept(new_wsi)) goto fail; #endif @@ -233,7 +239,7 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, if (!(type & LWS_ADOPT_ALLOW_SSL)) { lws_pt_lock(pt, __func__); - if (__insert_wsi_socket_into_fds(context, new_wsi)) { + if (__insert_wsi_socket_into_fds(new_wsi->context, new_wsi)) { lws_pt_unlock(pt); lwsl_err("%s: fail inserting socket\n", __func__); goto fail; @@ -259,7 +265,7 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, /* role may need to do something after all adoption completed */ lws_role_call_adoption_bind(new_wsi, type | _LWS_ADOPT_FINISH, - vh_prot_name); + new_wsi->protocol->name); #if LWS_MAX_SMP > 1 /* its actual pt can service it now */ @@ -277,22 +283,48 @@ fail: "adopt skt fail"); return NULL; +} -bail: - lwsl_notice("%s: exiting on bail\n", __func__); - if (parent) - parent->child_list = new_wsi->sibling_list; - if (new_wsi->user_space) - lws_free(new_wsi->user_space); - vh->context->count_wsi_allocated--; +/* if not a socket, it's a raw, non-ssl file descriptor */ - lws_vhost_unbind_wsi(new_wsi); - lws_free(new_wsi); +LWS_VISIBLE struct lws * +lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, + lws_sock_file_fd_type fd, const char *vh_prot_name, + struct lws *parent) +{ + struct lws *new_wsi; +#if defined(LWS_WITH_PEER_LIMITS) + struct lws_peer *peer = NULL; - compatible_close(fd.sockfd); + if (type & LWS_ADOPT_SOCKET) { + peer = lws_get_or_create_peer(vh, fd.sockfd); - return NULL; + if (peer && vh->context->ip_limit_wsi && + peer->count_wsi >= vh->context->ip_limit_wsi) { + lwsl_notice("Peer reached wsi limit %d\n", + vh->context->ip_limit_wsi); + lws_stats_bump(&vh->context->pt[0], + LWSSTATS_C_PEER_LIMIT_WSI_DENIED, + 1); + return NULL; + } + } +#endif + + new_wsi = lws_adopt_descriptor_vhost1(vh, type, vh_prot_name, parent); + if (!new_wsi) { + if (type & LWS_ADOPT_SOCKET) + compatible_close(fd.sockfd); + return NULL; + } + +#if defined(LWS_WITH_PEER_LIMITS) + if (peer) + lws_peer_add_wsi(vh->context, peer, new_wsi); +#endif + + return lws_adopt_descriptor_vhost2(new_wsi, type, fd); } LWS_VISIBLE struct lws * @@ -377,76 +409,182 @@ bail: return NULL; } -LWS_EXTERN struct lws * -lws_create_adopt_udp(struct lws_vhost *vhost, int port, int flags, - const char *protocol_name, struct lws *parent_wsi) +#if defined(LWS_WITH_CLIENT) +static struct lws * +lws_create_adopt_udp2(struct lws *wsi, const char *ads, + const struct addrinfo *r, int n, void *opaque) +{ + lws_sock_file_fd_type sock; + + assert(wsi); + + if (!wsi->dns_results) + wsi->dns_results_next = wsi->dns_results = r; + + if (n < 0 || !r) + goto bail; + + while (wsi->dns_results_next) { + + /* + * We have done the dns lookup, identify the result we want + * if any, and then complete the adoption by binding wsi to + * socket opened on it. + * + * Ignore the weak assumptions about protocol driven by port + * number and force to DGRAM / UDP since that's what this + * function is for. + */ + + sock.sockfd = socket(wsi->dns_results_next->ai_family, + SOCK_DGRAM, IPPROTO_UDP); + + if (sock.sockfd == LWS_SOCK_INVALID) + goto resume; + + if (wsi->do_bind && + bind(sock.sockfd, wsi->dns_results_next->ai_addr, +#if defined(_WIN32) + (int)wsi->dns_results_next->ai_addrlen +#else + wsi->dns_results_next->ai_addrlen +#endif + ) == -1) { + lwsl_notice("%s: bind failed\n", __func__); + goto resume; + } + + if (!wsi->do_bind) { + ((struct sockaddr_in *)wsi->dns_results_next->ai_addr)-> + sin_port = htons(wsi->c_port); + + if (connect(sock.sockfd, wsi->dns_results_next->ai_addr, + wsi->dns_results_next->ai_addrlen) == -1) { + lwsl_err("%s: conn fd %d fam %d %s:%u failed " + "(salen %d) errno %d\n", __func__, + sock.sockfd, + wsi->dns_results_next->ai_addr->sa_family, + ads ? ads : "null", wsi->c_port, + (int)wsi->dns_results_next->ai_addrlen, + LWS_ERRNO); + compatible_close(sock.sockfd); + goto resume; + } + + memcpy(&wsi->udp->sa, wsi->dns_results_next->ai_addr, + wsi->dns_results_next->ai_addrlen); + wsi->udp->salen = wsi->dns_results_next->ai_addrlen; + } + + /* complete the udp socket adoption flow */ + + lws_addrinfo_clean(wsi); + return lws_adopt_descriptor_vhost2(wsi, + LWS_ADOPT_RAW_SOCKET_UDP, sock); + +resume: + wsi->dns_results_next = wsi->dns_results_next->ai_next; + } + + lwsl_err("%s: unable to create INET socket\n", __func__); + lws_addrinfo_clean(wsi); + +bail: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "adopt udp2 fail"); + + return NULL; +} + +struct lws * +lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port, + int flags, const char *protocol_name, + struct lws *parent_wsi) { #if !defined(LWS_PLAT_OPTEE) - lws_sock_file_fd_type sock; - struct addrinfo h, *r, *rp; - struct lws *wsi = NULL; - char buf[16]; - int n; + struct lws *wsi; - memset(&h, 0, sizeof(h)); - h.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ - h.ai_socktype = SOCK_DGRAM; - h.ai_protocol = IPPROTO_UDP; - h.ai_flags = AI_PASSIVE; -#ifdef AI_ADDRCONFIG - h.ai_flags |= AI_ADDRCONFIG; -#endif + lwsl_info("%s: %s:%u\n", __func__, ads ? ads : "null", port); - lws_snprintf(buf, sizeof(buf), "%u", port); - n = getaddrinfo(NULL, buf, &h, &r); - if (n) { -#if !defined(LWS_PLAT_FREERTOS) - lwsl_info("%s: getaddrinfo error: %s\n", __func__, gai_strerror(n)); -#else - lwsl_info("%s: getaddrinfo error: %s\n", __func__, strerror(n)); -#endif + /* create the logical wsi without any valid fd */ + + wsi = lws_adopt_descriptor_vhost1(vhost, LWS_ADOPT_RAW_SOCKET_UDP, + protocol_name, parent_wsi); + if (!wsi) { + lwsl_err("%s: udp wsi creation failed\n", __func__); goto bail; } + wsi->do_bind = !!(flags & LWS_CAUDP_BIND); + wsi->c_port = port; - for (rp = r; rp; rp = rp->ai_next) { - sock.sockfd = socket(rp->ai_family, rp->ai_socktype, - rp->ai_protocol); - if (sock.sockfd != LWS_SOCK_INVALID) - break; - } - if (!rp) { - lwsl_err("%s: unable to create INET socket\n", __func__); - goto bail1; - } +#if !defined(LWS_WITH_SYS_ASYNC_DNS) + { + struct addrinfo *r, h; + char buf[16]; + int n; - if ((flags & LWS_CAUDP_BIND) && bind(sock.sockfd, rp->ai_addr, -#if defined(_WIN32) - (int)rp->ai_addrlen -#else - rp->ai_addrlen + memset(&h, 0, sizeof(h)); + h.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + h.ai_socktype = SOCK_DGRAM; + h.ai_protocol = IPPROTO_UDP; + h.ai_flags = AI_PASSIVE; +#ifdef AI_ADDRCONFIG + h.ai_flags |= AI_ADDRCONFIG; #endif - ) == -1) { - lwsl_err("%s: bind failed\n", __func__); - goto bail2; + + /* if the dns lookup is synchronous, do the whole thing now */ + lws_snprintf(buf, sizeof(buf), "%u", port); + n = getaddrinfo(ads, buf, &h, &r); + if (n) { +#if !defined(LWS_PLAT_FREERTOS) + lwsl_info("%s: getaddrinfo error: %s\n", __func__, + gai_strerror(n)); +#else + lwsl_info("%s: getaddrinfo error: %s\n", __func__, strerror(n)); +#endif + freeaddrinfo(r); + goto bail1; + } + /* complete it immediately after the blocking dns lookup + * finished... free r when connect either completed or failed */ + wsi = lws_create_adopt_udp2(wsi, ads, r, 0, NULL); + + return wsi; } +#else + if (ads) { + /* + * with async dns, use the wsi as the point about which to do + * the dns lookup and have it call the second part when it's + * done. + * + * Keep a refcount on the results and free it when we connected + * or definitively failed. + */ + if (lws_async_dns_query(vhost->context, 0, ads, + LWS_ADNS_RECORD_A, + lws_create_adopt_udp2, wsi, NULL) == + LADNS_RET_FAILED) { + lwsl_err("%s: async dns failed\n", __func__); + goto bail1; + } + } else + wsi = lws_create_adopt_udp2(wsi, ads, NULL, 0, NULL); - wsi = lws_adopt_descriptor_vhost(vhost, LWS_ADOPT_RAW_SOCKET_UDP, sock, - protocol_name, parent_wsi); - if (!wsi) - lwsl_err("%s: udp adoption failed\n", __func__); + /* dns lookup is happening asynchronously */ + + return wsi; +#endif -bail2: - if (!wsi) - compatible_close((int)sock.sockfd); bail1: - freeaddrinfo(r); - + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "udp create fail"); + wsi = NULL; bail: return wsi; #else return NULL; #endif } +#endif LWS_VISIBLE struct lws * lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd, diff --git a/lib/core-net/close.c b/lib/core-net/close.c index eb8a5e361..989ae38d5 100644 --- a/lib/core-net/close.c +++ b/lib/core-net/close.c @@ -133,6 +133,8 @@ lws_close_trans_q_leader(struct lws_dll2 *d, void *user) void lws_inform_client_conn_fail(struct lws *wsi, void *arg, size_t len) { + lws_addrinfo_clean(wsi); + if (wsi->already_did_cce) return; @@ -149,6 +151,22 @@ lws_inform_client_conn_fail(struct lws *wsi, void *arg, size_t len) } #endif +void +lws_addrinfo_clean(struct lws *wsi) +{ +#if defined(LWS_WITH_CLIENT) + if (!wsi->dns_results) + return; + +#if defined(LWS_WITH_SYS_ASYNC_DNS) + lws_async_dns_freeaddrinfo(wsi->dns_results); +#else + freeaddrinfo((struct addrinfo *)wsi->dns_results); +#endif + wsi->dns_results = NULL; +#endif +} + void __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *caller) @@ -176,6 +194,8 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, lws_free_set_NULL(wsi->cli_hostname_copy); + lws_addrinfo_clean(wsi); + /* * if we have wsi in our transaction queue, if we are closing we * must go through and close all those first @@ -317,6 +337,7 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, } if (lwsi_state(wsi) == LRS_WAITING_CONNECT || + lwsi_state(wsi) == LRS_WAITING_DNS || lwsi_state(wsi) == LRS_H1C_ISSUE_HANDSHAKE) goto just_kill_connection; @@ -347,6 +368,10 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, just_kill_connection: +#if defined(LWS_WITH_SYS_ASYNC_DNS) + lws_async_dns_cancel(wsi); +#endif + #if defined(LWS_WITH_HTTP_PROXY) if (wsi->http.buflist_post_body) lws_buflist_destroy_all_segments(&wsi->http.buflist_post_body); @@ -371,6 +396,7 @@ just_kill_connection: #if defined(LWS_WITH_CLIENT) if ((lwsi_state(wsi) == LRS_WAITING_SERVER_REPLY || + lwsi_state(wsi) == LRS_WAITING_DNS || lwsi_state(wsi) == LRS_WAITING_CONNECT) && !wsi->already_did_cce && wsi->protocol) lws_inform_client_conn_fail(wsi, diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index c1d18b0ae..0898c2f97 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -165,14 +165,6 @@ enum pmd_return { PMDR_FAILED = -1 }; -typedef union { -#ifdef LWS_WITH_IPV6 - struct sockaddr_in6 sa6; -#endif - struct sockaddr_in sa4; -} sockaddr46; - - #if defined(LWS_WITH_PEER_LIMITS) struct lws_peer { struct lws_peer *next; @@ -284,7 +276,7 @@ typedef struct lws_dsh_obj_head { typedef struct lws_dsh_obj { lws_dll2_t list; /* must be first */ - struct lws_dsh *dsh; /* invalid when on free list */ + struct lws_dsh *dsh; /* invalid when on free list */ size_t size; /* invalid when on free list */ size_t asize; } lws_dsh_obj_t; @@ -306,6 +298,34 @@ typedef struct lws_dsh { */ } lws_dsh_t; +/* + * lws_async_dns + */ + +typedef struct lws_async_dns { + lws_sockaddr46 sa46; /* nameserver */ + lws_dll2_owner_t waiting_send; + lws_dll2_owner_t waiting_resp; + lws_dll2_owner_t cached; + struct lws *wsi; + time_t time_set_server; + char dns_server_set; +} lws_async_dns_t; + +typedef enum { + LADNS_CONF_SERVER_UNKNOWN = -1, + LADNS_CONF_SERVER_SAME, + LADNS_CONF_SERVER_CHANGED +} lws_async_dns_server_check_t; + +#if defined(LWS_WITH_SYS_ASYNC_DNS) +void +lws_aysnc_dns_completed(struct lws *wsi, void *sa, size_t salen, + lws_async_dns_retcode_t ret); +#endif +void +lws_async_dns_cancel(struct lws *wsi); + /* * so we can have n connections being serviced simultaneously, * these things need to be isolated per-thread. @@ -395,6 +415,10 @@ struct lws_context_per_thread { struct lws_signal_watcher w_sigint; #endif +#if defined(LWS_WITH_DETAILED_LATENCY) + lws_usec_t ust_left_poll; +#endif + /* --- */ unsigned long count_conns; @@ -448,7 +472,6 @@ struct lws_conn_stats { * SSL SNI -> wsi -> bind after SSL negotiation */ - struct lws_vhost { #if defined(LWS_WITH_CLIENT) && defined(LWS_CLIENT_HTTP_PROXYING) char proxy_basic_auth_token[128]; @@ -545,6 +568,10 @@ struct lws_vhost { void __lws_vhost_destroy2(struct lws_vhost *vh); +/* + * struct lws + */ + struct lws { /* structs */ @@ -562,11 +589,6 @@ struct lws { struct _lws_dbus_mode_related dbus; #endif - - const struct lws_role_ops *role_ops; - lws_wsi_state_t wsistate; - lws_wsi_state_t wsistate_pre_close; - /* lifetime members */ #if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || \ @@ -577,9 +599,23 @@ struct lws { struct lws_io_watcher w_write; #endif +#if defined(LWS_WITH_DETAILED_LATENCY) + lws_detlat_t detlat; +#endif + lws_sorted_usec_list_t sul_timeout; lws_sorted_usec_list_t sul_hrtimer; - + struct lws_dll2 dll_buflist; /* guys with pending rxflow */ + struct lws_dll2 same_vh_protocol; +#if defined(LWS_WITH_SYS_ASYNC_DNS) + struct lws_dll2 adns; /* on adns list of guys to tell result */ + lws_async_dns_cb_t adns_cb; /* callback with result */ +#endif +#if defined(LWS_WITH_CLIENT) + struct lws_dll2 dll_cli_active_conns; + struct lws_dll2_owner dll2_cli_txn_queue_owner; + struct lws_dll2 dll2_cli_txn_queue; +#endif /* pointers */ struct lws_context *context; @@ -587,14 +623,10 @@ struct lws { struct lws *parent; /* points to parent, if any */ struct lws *child_list; /* points to first child */ struct lws *sibling_list; /* subsequent children at same level */ - + const struct lws_role_ops *role_ops; const struct lws_protocols *protocol; - struct lws_dll2 same_vh_protocol; - struct lws_sequencer *seq; /* associated sequencer if any */ - struct lws_dll2 dll_buflist; /* guys with pending rxflow */ - #if defined(LWS_WITH_THREADPOOL) struct lws_threadpool_task *tp_task; #endif @@ -607,9 +639,8 @@ struct lws { #if defined(LWS_WITH_CLIENT) struct client_info_stash *stash; char *cli_hostname_copy; - struct lws_dll2 dll_cli_active_conns; - struct lws_dll2_owner dll2_cli_txn_queue_owner; - struct lws_dll2 dll2_cli_txn_queue; + const struct addrinfo *dns_results; + const struct addrinfo *dns_results_next; #endif void *user_space; void *opaque_parent_data; @@ -629,11 +660,8 @@ struct lws { uint64_t accept_start_us; #endif #endif - -#ifdef LWS_LATENCY - unsigned long action_start; - unsigned long latency_start; -#endif + lws_wsi_state_t wsistate; + lws_wsi_state_t wsistate_pre_close; /* ints */ #define LWS_NO_FDS_POS (-1) @@ -676,8 +704,10 @@ struct lws { unsigned int protocol_bind_balance:1; unsigned int unix_skt:1; unsigned int close_when_buffered_out_drained:1; - unsigned int h1_ws_proxied; - unsigned int proxied_ws_parent; + unsigned int h1_ws_proxied:1; + unsigned int proxied_ws_parent:1; + unsigned int do_bind:1; + unsigned int oom4:1; unsigned int could_have_pending:1; /* detect back-to-back writes */ unsigned int outer_will_close:1; @@ -703,9 +733,7 @@ struct lws { unsigned int sock_send_blocking:1; #endif -#if defined(LWS_WITH_CLIENT) - unsigned short c_port; -#endif + uint16_t c_port; /* chars */ @@ -993,7 +1021,7 @@ LWS_EXTERN lws_usec_t __lws_seq_timeout_check(struct lws_context_per_thread *pt, lws_usec_t usnow); LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT -lws_client_connect_2(struct lws *wsi); +lws_client_connect_2_dnsreq(struct lws *wsi); LWS_VISIBLE struct lws * LWS_WARN_UNUSED_RESULT lws_client_reset(struct lws **wsi, int ssl, const char *address, int port, @@ -1077,6 +1105,9 @@ lws_plat_pipe_signal(struct lws *wsi); void lws_plat_pipe_close(struct lws *wsi); +void +lws_addrinfo_clean(struct lws *wsi); + LWS_EXTERN void lws_add_wsi_to_draining_ext_list(struct lws *wsi); LWS_EXTERN void @@ -1167,9 +1198,21 @@ lws_buflist_aware_consume(struct lws *wsi, struct lws_tokens *ebuf, int used, extern const struct lws_protocols protocol_abs_client_raw_skt, protocol_abs_client_unit_test; +void +__lws_reset_wsi(struct lws *wsi); + void lws_inform_client_conn_fail(struct lws *wsi, void *arg, size_t len); +#if defined(LWS_WITH_SYS_ASYNC_DNS) +lws_async_dns_server_check_t +lws_plat_asyncdns_init(struct lws_context *context, lws_sockaddr46 *sa); +int +lws_async_dns_init(struct lws_context *context); +void +lws_async_dns_deinit(lws_async_dns_t *dns); +#endif + #ifdef __cplusplus }; #endif diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index 18b5d1e7e..096299fcf 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -443,6 +443,9 @@ lws_create_vhost(struct lws_context *context, #if defined(LWS_CLIENT_HTTP_PROXYING) && \ defined(LWS_WITH_CLIENT) && defined(LWS_HAVE_GETENV) char *p; +#endif +#if defined(LWS_WITH_SYS_ASYNC_DNS) + extern struct lws_protocols lws_async_dns_protocol; #endif int n; @@ -558,6 +561,7 @@ lws_create_vhost(struct lws_context *context, /* * give the vhost a unified list of protocols including: * + * - internal, async_dns if enabled (first vhost only) * - internal, abstracted ones * - the ones that came from plugins * - his user protocols @@ -594,6 +598,16 @@ lws_create_vhost(struct lws_context *context, vh->count_protocols++; } #endif + /* + * 3: async dns protocol (first vhost only) + */ +#if defined(LWS_WITH_SYS_ASYNC_DNS) + if (!context->vhost_list) { + memcpy(&lwsp[m++], &lws_async_dns_protocol, + sizeof(struct lws_protocols)); + vh->count_protocols++; + } +#endif /* * 3: For compatibility, all protocols enabled on vhost if only @@ -763,6 +777,7 @@ lws_create_vhost(struct lws_context *context, goto bail1; } #endif + n = !!context->vhost_list; while (1) { if (!(*vh1)) { @@ -772,6 +787,11 @@ lws_create_vhost(struct lws_context *context, vh1 = &(*vh1)->vhost_next; }; +#if defined(LWS_WITH_SYS_ASYNC_DNS) + if (!n && lws_async_dns_init(context)) + goto bail1; +#endif + /* for the case we are adding a vhost much later, after server init */ if (context->protocol_init_done) diff --git a/lib/core/context.c b/lib/core/context.c index c0ed762c5..ec9ba4435 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -639,6 +639,10 @@ lws_context_destroy3(struct lws_context *context) #endif } +#if defined(LWS_WITH_SYS_ASYNC_DNS) + lws_async_dns_deinit(&context->async_dns); +#endif + if (context->pt[0].fds) lws_free_set_NULL(context->pt[0].fds); #endif diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index 9fe69a3ba..7c318c449 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -259,34 +259,72 @@ struct lws_deferred_free */ struct lws_context { - time_t last_ws_ping_pong_check_s; - lws_usec_t time_up; /* monotonic */ + #if defined(LWS_WITH_SERVER) + char canonical_hostname[96]; + #endif + #if defined(LWS_WITH_FILE_OPS) - const struct lws_plat_file_ops *fops; struct lws_plat_file_ops fops_platform; #endif - struct lws_context **pcontext_finalize; -#if defined(LWS_WITH_TLS) - const struct lws_tls_ops *tls_ops; +#if defined(LWS_WITH_ZIP_FOPS) + struct lws_plat_file_ops fops_zip; #endif - const char *username, *groupname; +#if defined(LWS_WITH_NETWORK) + + struct lws_context_per_thread pt[LWS_MAX_SMP]; #if defined(LWS_WITH_HTTP2) struct http2_settings set; #endif -#if defined(LWS_WITH_ZIP_FOPS) - struct lws_plat_file_ops fops_zip; -#endif -#if defined(LWS_WITH_NETWORK) - struct lws_context_per_thread pt[LWS_MAX_SMP]; + #if defined(LWS_WITH_SERVER_STATUS) struct lws_conn_stats conn_stats; #endif +#if LWS_MAX_SMP > 1 + struct lws_mutex_refcount mr; +#endif + +#if defined(LWS_WITH_LIBEV) + struct lws_context_eventlibs_libev ev; +#endif +#if defined(LWS_WITH_LIBUV) + struct lws_context_eventlibs_libuv uv; +#endif +#if defined(LWS_WITH_LIBEVENT) + struct lws_context_eventlibs_libevent event; +#endif + +#if defined(LWS_WITH_TLS) + struct lws_context_tls tls; +#endif + +#if defined(LWS_WITH_SYS_ASYNC_DNS) + lws_async_dns_t async_dns; +#endif + + /* pointers */ + struct lws_vhost *vhost_list; struct lws_vhost *no_listener_vhost_list; struct lws_vhost *vhost_pending_destruction_list; + struct lws_context **pcontext_finalize; + const char *username, *groupname; + +#if defined(LWS_WITH_SERVER) + const char *server_string; +#endif + + struct lws_event_loop_ops *event_loop_ops; + +#if defined(LWS_WITH_FILE_OPS) + const struct lws_plat_file_ops *fops; +#endif + +#if defined(LWS_WITH_TLS) + const struct lws_tls_ops *tls_ops; +#endif #if defined(LWS_WITH_PLUGINS) struct lws_plugin *plugin_list; #endif @@ -297,10 +335,7 @@ struct lws_context { struct lws **lws_lookup; #endif -#endif -#if LWS_MAX_SMP > 1 - struct lws_mutex_refcount mr; -#endif +#endif /* NETWORK */ #if defined(LWS_AMAZON_RTOS) mbedtls_entropy_context mec; @@ -329,38 +364,14 @@ struct lws_context { #endif void (*eventlib_signal_cb)(void *event_lib_handle, int signum); + time_t last_ws_ping_pong_check_s; + lws_usec_t time_up; /* monotonic */ + #if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) cap_value_t caps[4]; char count_caps; #endif -#if defined(LWS_WITH_NETWORK) -#if defined(LWS_WITH_LIBEV) - struct lws_context_eventlibs_libev ev; -#endif -#if defined(LWS_WITH_LIBUV) - struct lws_context_eventlibs_libuv uv; -#endif -#if defined(LWS_WITH_LIBEVENT) - struct lws_context_eventlibs_libevent event; -#endif - struct lws_event_loop_ops *event_loop_ops; -#endif - -#if defined(LWS_WITH_TLS) && defined(LWS_WITH_NETWORK) - struct lws_context_tls tls; -#endif - -#if defined(LWS_WITH_SERVER) - char canonical_hostname[128]; - const char *server_string; -#endif - -#ifdef LWS_LATENCY - unsigned long worst_latency; - char worst_latency_info[256]; -#endif - #if defined(LWS_PLAT_FREERTOS) unsigned long time_last_state_dump; uint32_t last_free_heap; diff --git a/lib/misc/base64-decode.c b/lib/misc/base64-decode.c index 3262685ab..4bd9e0102 100644 --- a/lib/misc/base64-decode.c +++ b/lib/misc/base64-decode.c @@ -3,7 +3,7 @@ * * http://base64.sourceforge.net/b64.c * - * with the following license: + * already with MIT license, which is retained. * * LICENCE: Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc. * @@ -33,9 +33,7 @@ * Bob Trower 08/04/01 -- Create Version 0.00.00B * * I cleaned it up quite a bit to match the (linux kernel) style of the rest - * of libwebsockets; this version is under LGPL2.1 + SLE like the rest of lws - * since he explicitly allows sublicensing, but I give the URL above so you can - * get the original with Bob's super-liberal terms directly if you prefer. + * of libwebsockets */ #include diff --git a/lib/plat/freertos/freertos-resolv.c b/lib/plat/freertos/freertos-resolv.c new file mode 100644 index 000000000..2c7601827 --- /dev/null +++ b/lib/plat/freertos/freertos-resolv.c @@ -0,0 +1,42 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2019 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. + */ + +#include "private-lib-core.h" + +lws_async_dns_server_check_t +lws_plat_asyncdns_init(struct lws_context *context, lws_sockaddr46 *sa46) +{ + uint32_t ipv4; + lws_async_dns_server_check_t s = LADNS_CONF_SERVER_CHANGED; + + FreeRTOS_GetAddressConfiguration(NULL, NULL, NULL, &ipv4); + + sa46->sa4.sin_family = AF_INET; + if (sa46->sa4.sin_addr.s_addr == ipv4) + s = LADNS_CONF_SERVER_SAME; + + sa46->sa4.sin_addr.s_addr = ipv4; + + return s; +} diff --git a/lib/plat/freertos/private-lib-plat-freertos.h b/lib/plat/freertos/private-lib-plat-freertos.h index a376d2790..84b2e1a12 100644 --- a/lib/plat/freertos/private-lib-plat-freertos.h +++ b/lib/plat/freertos/private-lib-plat-freertos.h @@ -55,6 +55,9 @@ gai_strerror(int); #if defined(LWS_AMAZON_RTOS) #include "FreeRTOS.h" +#if defined(LWS_WITH_SYS_ASYNC_DNS) + #include "FreeRTOS_IP.h" +#endif #include "timers.h" #include #else diff --git a/lib/plat/unix/android/android-resolv.c b/lib/plat/unix/android/android-resolv.c new file mode 100644 index 000000000..0f51ed0d3 --- /dev/null +++ b/lib/plat/unix/android/android-resolv.c @@ -0,0 +1,54 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2019 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. + */ + +#include "private-lib-core.h" +#include + +lws_async_dns_server_check_t +lws_plat_asyncdns_init(struct lws_context *context, lws_sockaddr46 *sa46) +{ + char d[PROP_VALUE_MAX], *p = d; + uint32_t ip32; + uint8_t i[4]; + int n; + + d[0] = '\0'; + if (__system_property_get("net.dns1", d) <= 0) + return LADNS_CONF_SERVER_UNKNOWN; + + for (n = 0; n < 4; n++) { + i[n] = atoi(d); + p = strchr(d, '.'); + if (n != 3 && !p) + return LADNS_CONF_SERVER_UNKNOWN; + } + + ip32 = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; + n = ip32 == sa->sin_addr.s_addr; + sa46->sa4.sin_family = AF_INET; + sa46->sa4.sin_addr.s_addr = ip32; + + return n ? LADNS_CONF_SERVER_SAME : LADNS_CONF_SERVER_CHANGED; +} + diff --git a/lib/plat/unix/unix-resolv.c b/lib/plat/unix/unix-resolv.c new file mode 100644 index 000000000..9765c2d45 --- /dev/null +++ b/lib/plat/unix/unix-resolv.c @@ -0,0 +1,88 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2019 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. + */ + +#include "private-lib-core.h" + +lws_async_dns_server_check_t +lws_plat_asyncdns_init(struct lws_context *context, lws_sockaddr46 *sa46) +{ + lws_async_dns_server_check_t s = LADNS_CONF_SERVER_CHANGED; + char resolv[512], ads[48]; + lws_sockaddr46 sa46t; + lws_tokenize_t ts; + int fd, n, ns = 0; + + /* grab the first chunk of /etc/resolv.conf */ + + fd = open("/etc/resolv.conf", LWS_O_RDONLY); + if (fd < 0) + return LADNS_CONF_SERVER_UNKNOWN; + + n = read(fd, resolv, sizeof(resolv) - 1); + close(fd); + if (n < 0) + return LADNS_CONF_SERVER_UNKNOWN; + + resolv[n] = '\0'; + lws_tokenize_init(&ts, resolv, LWS_TOKENIZE_F_DOT_NONTERM | + LWS_TOKENIZE_F_NO_FLOATS | + LWS_TOKENIZE_F_NO_INTEGERS | + LWS_TOKENIZE_F_MINUS_NONTERM | + LWS_TOKENIZE_F_HASH_COMMENT); + do { + ts.e = lws_tokenize(&ts); + if (ts.e != LWS_TOKZE_TOKEN) { + ns = 0; + continue; + } + + if (!ns && !strncmp("nameserver", ts.token, ts.token_len)) { + ns = 1; + continue; + } + if (!ns) + continue; + + /* we are a token just after the "nameserver" token */ + + ns = 0; + if (ts.token_len > (int)sizeof(ads) - 1) + continue; + + memcpy(ads, ts.token, ts.token_len); + ads[ts.token_len] = '\0'; + if (lws_sa46_parse_numeric_address(ads, &sa46t) < 0) + continue; + + if (!lws_sa46_compare_ads(sa46, &sa46t)) + s = LADNS_CONF_SERVER_SAME; + + *sa46 = sa46t; + + return s; + + } while (ts.e > 0); + + return LADNS_CONF_SERVER_UNKNOWN; +} diff --git a/lib/plat/windows/windows-resolv.c b/lib/plat/windows/windows-resolv.c new file mode 100644 index 000000000..1cfbcc633 --- /dev/null +++ b/lib/plat/windows/windows-resolv.c @@ -0,0 +1,73 @@ +/* + * Adapted from tadns 1.1, from http://adns.sourceforge.net/ + * Original license --> + * + * Copyright (c) 2004-2005 Sergey Lyubka + * + * "THE BEER-WARE LICENSE" (Revision 42): + * Sergey Lyubka wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. + * + * Integrated into lws, largely rewritten and relicensed (as allowed above) + * + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2019 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. + */ + +#include "private-lib-core.h" + +lws_async_dns_server_check_t +lws_plat_asyncdns_init(struct lws_context *context, lws_sockaddr46 *sa46) +{ + char subkey[512], dhcpns[512], ns[512], value[128], *key = + "SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces"; + HKEY hKey, hSub; + LONG err; + int i, n; + + if ((err = RegOpenKey(HKEY_LOCAL_MACHINE, key, &hKey)) != ERROR_SUCCESS) { + lwsl_err("%s: cannot open reg key %s: %d\n", __func__, key, err); + + return 1; + } + + for (i = 0; RegEnumKey(hKey, i, subkey, sizeof(subkey)) == ERROR_SUCCESS; i++) { + DWORD type, len = sizeof(value); + + if (RegOpenKey(hKey, subkey, &hSub) == ERROR_SUCCESS && + (RegQueryValueEx(hSub, "NameServer", 0, + &type, value, &len) == ERROR_SUCCESS || + RegQueryValueEx(hSub, "DhcpNameServer", 0, + &type, value, &len) == ERROR_SUCCESS)) { + n = lws_sa46_parse_numeric_address(value, sa46) + RegCloseKey(hSub); + RegCloseKey(hKey); + return n == 0 ? LADNS_CONF_SERVER_CHANGED : + LADNS_CONF_SERVER_UNKNOWN; + } + } + RegCloseKey(hKey); + + return LADNS_CONF_SERVER_UNKNOWN; +} + diff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c index 002d7c925..688f9bf5b 100644 --- a/lib/roles/http/client/client-handshake.c +++ b/lib/roles/http/client/client-handshake.c @@ -24,6 +24,7 @@ #include "private-lib-core.h" +#if !defined(LWS_WITH_SYS_ASYNC_DNS) static int lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) { @@ -36,7 +37,7 @@ lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) if (wsi->ipv6) { #if !defined(__ANDROID__) - hints.ai_family = AF_INET6; + hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_V4MAPPED; #endif } else @@ -48,10 +49,11 @@ lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) return getaddrinfo(ads, NULL, &hints, result); } - +#endif struct lws * -lws_client_connect_3(struct lws *wsi, struct lws *wsi_piggyback, ssize_t plen) +lws_client_connect_4_established(struct lws *wsi, struct lws *wsi_piggyback, + ssize_t plen) { #if defined(LWS_CLIENT_HTTP_PROXYING) struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; @@ -155,9 +157,9 @@ send_hs: lwsl_info("%s: wsi %p: waiting to send hdrs (par state 0x%x)\n", __func__, wsi, lwsi_state(wsi_piggyback)); } else { - lwsl_info("%s: wsi %p: %s %s client created own conn (raw %d)\n", + lwsl_info("%s: wsi %p: %s %s client created own conn (raw %d) vh %s\n", __func__, wsi, wsi->role_ops->name, - wsi->protocol->name, rawish); + wsi->protocol->name, rawish, wsi->vhost->name); /* we are making our own connection */ if (!rawish) @@ -228,38 +230,438 @@ failed: } struct lws * -lws_client_connect_2(struct lws *wsi) +lws_client_connect_3_connect(struct lws *wsi, const char *ads, + const struct addrinfo *result, int n, void *opaque) { +#if defined(LWS_WITH_UNIX_SOCK) + struct sockaddr_un sau; +#endif #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) #if defined(LWS_CLIENT_HTTP_PROXYING) struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; #endif - const char *adsin; - ssize_t plen = 0; #endif -#if defined(LWS_WITH_UNIX_SOCK) - struct sockaddr_un sau; - char unix_skt = 0; -#endif - int n, port = 0; - const char *cce = "", *iface; - const struct sockaddr *psa; - const char *meth = NULL; - struct addrinfo *result; - const char *ads; - sockaddr46 sa46; - #ifdef LWS_WITH_IPV6 char ipv6only = lws_check_opt(wsi->vhost->options, - LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | - LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); - struct sockaddr_in addr; + LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | + LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); +#endif + const struct sockaddr *psa = NULL; + const char *cce = "", *iface; + ssize_t plen = 0; + lws_sockaddr46 sa46; + char ni[48]; + int m; + +#ifdef LWS_WITH_IPV6 #if defined(__ANDROID__) ipv6only = 0; #endif #endif + /* + * async dns calls back here for everybody who cares when it gets a + * result... but if we are piggybacking, we do not want to connect + * ourselves + */ + + if (!lws_dll2_is_detached(&wsi->dll2_cli_txn_queue)) + return wsi; +#if 0 + if (!ads && !result) { + cce = "dns resolution failed"; + if (!wsi->oom4) + goto oom4; + else + goto failed; + } +#endif + + /* + * We can check using getsockopt if our connect actually completed + */ + + if (lwsi_state(wsi) == LRS_WAITING_CONNECT && + lws_socket_is_valid(wsi->desc.sockfd)) { + socklen_t sl = sizeof(int); + int e = 0; + + /* + * this resets SO_ERROR after reading it. If there's an error + * condition the connect definitively failed. + */ + + if (!getsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_ERROR, + &e, &sl)) { + if (!e) { + lwsl_info("%s: getsockopt check: conn OK\n", + __func__); + + goto conn_good; + } + + lwsl_debug("%s: getsockopt says err %d\n", __func__, e); + } + + lwsl_debug("%s: getsockopt check: conn fail: errno %d\n", + __func__, LWS_ERRNO); + goto try_next_result_fds; + } + +#if defined(LWS_WITH_UNIX_SOCK) + if (*ads == '+') { + ads++; + memset(&sau, 0, sizeof(sau)); + sau.sun_family = AF_UNIX; + strncpy(sau.sun_path, ads, sizeof(sau.sun_path)); + sau.sun_path[sizeof(sau.sun_path) - 1] = '\0'; + + lwsl_info("%s: Unix skt: %s\n", __func__, ads); + + if (sau.sun_path[0] == '@') + sau.sun_path[0] = '\0'; + + goto ads_known; + } +#endif + +#if defined(LWS_WITH_SYS_ASYNC_DNS) + if (n == LADNS_RET_FAILED) { + lwsl_notice("%s: adns failed %s\n", __func__, ads); + goto oom4; + } +#endif + + if (!wsi->dns_results) { + wsi->dns_results_next = wsi->dns_results = result; + if (result) + lwsl_debug("%s: result %p result->ai_next %p\n", + __func__, result, result->ai_next); + } + +#if defined(LWS_CLIENT_HTTP_PROXYING) && \ + (defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)) + + /* Decide what it is we need to connect to: + * + * Priority 1: connect to http proxy */ + + if (wsi->vhost->http.http_proxy_port) { + plen = lws_snprintf((char *)pt->serv_buf, 256, + "CONNECT %s:%u HTTP/1.0\x0d\x0a" + "User-agent: libwebsockets\x0d\x0a", + ads, wsi->c_port); + + if (wsi->vhost->proxy_basic_auth_token[0]) + plen += lws_snprintf((char *)pt->serv_buf + plen, 256, + "Proxy-authorization: basic %s\x0d\x0a", + wsi->vhost->proxy_basic_auth_token); + + plen += lws_snprintf((char *)pt->serv_buf + plen, 5, "\x0d\x0a"); + ads = wsi->vhost->http.http_proxy_address; + wsi->c_port = wsi->vhost->http.http_proxy_port; +#else + if (0) { +#endif + +#if defined(LWS_WITH_SOCKS5) + + /* Priority 2: Connect to SOCK5 Proxy */ + + } else if (wsi->vhost->socks_proxy_port) { + if (socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen)) { + cce = "socks msg too large"; + goto oom4; + } + + lwsl_client("Sending SOCKS Greeting\n"); + ads = wsi->vhost->socks_proxy_address; + wsi->c_port = wsi->vhost->socks_proxy_port; +#endif + } + + memset(&sa46, 0, sizeof(sa46)); + + if (n || !wsi->dns_results) { + /* lws_getaddrinfo46 failed, there is no usable result */ + lwsl_notice("%s: lws_getaddrinfo46 failed %d\n", + __func__, n); + cce = "ipv6 lws_getaddrinfo46 failed"; + goto oom4; + } + + /* + * Let's try connecting to each of the results in turn until one works + * or we run out of results + */ + +next_result: + + psa = (const struct sockaddr *)&sa46; + n = sizeof(sa46); + memset(&sa46, 0, sizeof(sa46)); + + switch (wsi->dns_results_next->ai_family) { + case AF_INET: +#if defined(LWS_WITH_IPV6) + if (ipv6only) { + sa46.sa4.sin_family = AF_INET6; + + /* map IPv4 to IPv6 */ + memset((char *)&sa46.sa6.sin6_addr, 0, + sizeof(sa46.sa6.sin6_addr)); + sa46.sa6.sin6_addr.s6_addr[10] = 0xff; + sa46.sa6.sin6_addr.s6_addr[11] = 0xff; + memcpy(&sa46.sa6.sin6_addr.s6_addr[12], + &((struct sockaddr_in *) + wsi->dns_results_next->ai_addr)->sin_addr, + sizeof(struct in_addr)); + sa46.sa6.sin6_port = htons(wsi->c_port); + ni[0] = '\0'; + lws_write_numeric_address(sa46.sa6.sin6_addr.s6_addr, + 16, ni, sizeof(ni)); + lwsl_info("%s: %s ipv4->ipv6 %s\n", __func__, ads, ni); + break; + } +#endif + sa46.sa4.sin_family = AF_INET; + sa46.sa4.sin_addr.s_addr = + ((struct sockaddr_in *)wsi->dns_results_next->ai_addr)-> + sin_addr.s_addr; + memset(&sa46.sa4.sin_zero, 0, sizeof(sa46.sa4.sin_zero)); + sa46.sa4.sin_port = htons(wsi->c_port); + n = sizeof(struct sockaddr_in); + lws_write_numeric_address((uint8_t *)&sa46.sa4.sin_addr.s_addr, + 4, ni, sizeof(ni)); + lwsl_info("%s: %s ipv4 %s\n", __func__, ads, ni); + break; + case AF_INET6: +#if defined(LWS_WITH_IPV6) + if (!wsi->ipv6) + goto try_next_result; + sa46.sa4.sin_family = AF_INET6; + memcpy(&sa46.sa6.sin6_addr, + &((struct sockaddr_in6 *)wsi->dns_results_next->ai_addr)-> + sin6_addr, sizeof(struct in6_addr)); + sa46.sa6.sin6_scope_id = ((struct sockaddr_in6 *) + wsi->dns_results_next->ai_addr)->sin6_scope_id; + sa46.sa6.sin6_flowinfo = ((struct sockaddr_in6 *) + wsi->dns_results_next->ai_addr)->sin6_flowinfo; + sa46.sa6.sin6_port = htons(wsi->c_port); + lws_write_numeric_address((uint8_t *)&sa46.sa6.sin6_addr, + 16, ni, sizeof(ni)); + lwsl_info("%s: %s ipv6 %s\n", __func__, ads, ni); +#else + goto try_next_result; /* ipv4 only can't use this */ +#endif + break; + } + +#if defined(LWS_WITH_UNIX_SOCK) +ads_known: +#endif + + /* now we decided on ipv4 or ipv6, set the port and create socket*/ + + if (!lws_socket_is_valid(wsi->desc.sockfd)) { + + if (wsi->context->event_loop_ops->check_client_connect_ok && + wsi->context->event_loop_ops->check_client_connect_ok(wsi)) { + cce = "waiting for event loop watcher to close"; + goto oom4; + } + +#if defined(LWS_WITH_UNIX_SOCK) + if (wsi->unix_skt) + wsi->desc.sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + else +#endif + wsi->desc.sockfd = socket(sa46.sa4.sin_family, + SOCK_STREAM, 0); + + if (!lws_socket_is_valid(wsi->desc.sockfd)) { + lwsl_warn("Unable to open socket\n"); + goto try_next_result; + } + + if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd, +#if defined(LWS_WITH_UNIX_SOCK) + wsi->unix_skt)) { +#else + 0)) { +#endif + lwsl_err("Failed to set wsi socket options\n"); + goto try_next_result_closesock; + } + + lwsl_debug("%s: %p: WAITING_CONNECT\n", __func__, wsi); + lwsi_set_state(wsi, LRS_WAITING_CONNECT); + +#if !defined(LWS_AMAZON_RTOS) + if (wsi->context->event_loop_ops->accept) + if (wsi->context->event_loop_ops->accept(wsi)) + goto try_next_result_closesock; +#endif + + if (__insert_wsi_socket_into_fds(wsi->context, wsi)) + goto try_next_result_closesock; + + if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) + goto try_next_result_fds; + + /* + * Past here, we can't simply free the structs as error + * handling as oom4 does. + * + * We can run the whole close flow, or unpick the fds inclusion + * and anything else we have done. + */ + wsi->oom4 = 1; + if (!wsi->protocol) + wsi->protocol = &wsi->vhost->protocols[0]; + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, + AWAITING_TIMEOUT); + + if (wsi->stash) + iface = wsi->stash->cis[CIS_IFACE]; + else + iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); + + if (iface) { + n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, + iface, wsi->ipv6); + if (n < 0) + goto try_next_result_fds; + } + } + +#if defined(LWS_WITH_UNIX_SOCK) + if (wsi->unix_skt) { + psa = (const struct sockaddr *)&sau; + n = sizeof(sau); + } else +#endif + + if (!psa) /* coverity */ + goto try_next_result_fds; + + /* + * The actual connection attempt + */ + + m = connect(wsi->desc.sockfd, (const struct sockaddr *)psa, n); + if (m == -1) { + + lwsl_debug("%s: connect says errno: %d\n", __func__, LWS_ERRNO); + + if (LWS_ERRNO != LWS_EALREADY && + LWS_ERRNO != LWS_EINPROGRESS && + LWS_ERRNO != LWS_EWOULDBLOCK +#ifdef _WIN32 + && LWS_ERRNO != WSAEINVAL +#endif + ) { +#if defined(_DEBUG) + char nads[48]; + lws_sa46_write_numeric_address(&sa46, nads, sizeof(nads)); + lwsl_info("%s: Connect failed: %s port %d\n", + __func__, nads, wsi->c_port); +#endif + goto try_next_result_fds; + } + + if (lws_plat_check_connection_error(wsi)) + goto try_next_result_fds; + + /* + * must do specifically a POLLOUT poll to hear + * about the connect completion + */ + if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) + goto try_next_result_fds; + + return wsi; + } + +conn_good: + + lwsl_debug("%s: Connection started\n", __func__); + + lws_addrinfo_clean(wsi); + + if (wsi->protocol) + wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE, + wsi->user_space, NULL, 0); + + return lws_client_connect_4_established(wsi, NULL, plen); + + +oom4: + if (lwsi_role_client(wsi) && wsi->protocol /* && lwsi_state_est(wsi) */) + lws_inform_client_conn_fail(wsi,(void *)cce, strlen(cce)); + + /* take care that we might be inserted in fds already */ + if (wsi->position_in_fds_table != LWS_NO_FDS_POS) + goto failed1; + + /* + * We can't be an active client connection any more, if we thought + * that was what we were going to be doing. It should be if we are + * failing by oom4 path, we are still called by + * lws_client_connect_via_info() and will be returning NULL to that, + * so nobody else should have had a chance to queue on us. + */ + { + struct lws_vhost *vhost = wsi->vhost; + + lws_vhost_lock(vhost); + __lws_free_wsi(wsi); + lws_vhost_unlock(vhost); + } + + return NULL; + +try_next_result_fds: + wsi->oom4 = 0; + __remove_wsi_socket_from_fds(wsi); + +try_next_result_closesock: + compatible_close(wsi->desc.sockfd); + wsi->desc.sockfd = LWS_SOCK_INVALID; + +try_next_result: + if (wsi->dns_results_next) { + wsi->dns_results_next = wsi->dns_results_next->ai_next; + if (wsi->dns_results_next) + goto next_result; + } + cce = "Unable to connect"; + +//failed: + lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce)); + +failed1: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2"); + + return NULL; +} + +struct lws * +lws_client_connect_2_dnsreq(struct lws *wsi) +{ +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + const char *adsin; +#endif + const char *cce = "", *meth = NULL, *ads; + struct addrinfo *result = NULL; +#if defined(LWS_WITH_IPV6) + struct sockaddr_in addr; + const char *iface; +#endif + int n, port = 0; + #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) if (!wsi->http.ah && !wsi->stash) { cce = "ah was NULL at cc2"; @@ -291,8 +693,8 @@ lws_client_connect_2(struct lws *wsi) lws_vhost_lock(wsi->vhost); /* ----------------------------------- { */ - lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, - lws_dll2_get_head(&wsi->vhost->dll_cli_active_conns_owner)) { + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, lws_dll2_get_head( + &wsi->vhost->dll_cli_active_conns_owner)) { struct lws *w = lws_container_of(d, struct lws, dll_cli_active_conns); @@ -351,7 +753,7 @@ lws_client_connect_2(struct lws *wsi) * to take over parsing the rx. */ lws_vhost_unlock(wsi->vhost); /* } ---------- */ - return lws_client_connect_3(wsi, w, plen); + return lws_client_connect_4_established(wsi, w, 0); } } lws_end_foreach_dll_safe(d, d1); @@ -407,19 +809,9 @@ create_new_conn: ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); #if defined(LWS_WITH_UNIX_SOCK) if (*ads == '+') { - ads++; - memset(&sau, 0, sizeof(sau)); - sau.sun_family = AF_UNIX; - strncpy(sau.sun_path, ads, sizeof(sau.sun_path)); - sau.sun_path[sizeof(sau.sun_path) - 1] = '\0'; - - lwsl_info("%s: Unix skt: %s\n", __func__, ads); - - if (sau.sun_path[0] == '@') - sau.sun_path[0] = '\0'; - - unix_skt = 1; - goto ads_known; + wsi->unix_skt = 1; + n = 0; + goto next_step; } #endif @@ -448,17 +840,6 @@ create_new_conn: * Priority 1: connect to http proxy */ if (wsi->vhost->http.http_proxy_port) { - plen = lws_snprintf((char *)pt->serv_buf, 256, - "CONNECT %s:%u HTTP/1.0\x0d\x0a" - "User-agent: libwebsockets\x0d\x0a", - ads, wsi->c_port); - - if (wsi->vhost->proxy_basic_auth_token[0]) - plen += lws_snprintf((char *)pt->serv_buf + plen, 256, - "Proxy-authorization: basic %s\x0d\x0a", - wsi->vhost->proxy_basic_auth_token); - - plen += lws_snprintf((char *)pt->serv_buf + plen, 5, "\x0d\x0a"); ads = wsi->vhost->http.http_proxy_address; port = wsi->vhost->http.http_proxy_port; #else @@ -470,11 +851,6 @@ create_new_conn: /* Priority 2: Connect to SOCK5 Proxy */ } else if (wsi->vhost->socks_proxy_port) { - if (socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen)) { - cce = "socks msg too large"; - goto oom4; - } - lwsl_client("Sending SOCKS Greeting\n"); ads = wsi->vhost->socks_proxy_address; port = wsi->vhost->socks_proxy_port; @@ -491,271 +867,31 @@ create_new_conn: * prepare the actual connection * to whatever we decided to connect to */ + lwsi_set_state(wsi, LRS_WAITING_DNS); - lwsl_info("%s: %p: address %s:%u\n", __func__, wsi, ads, port); + lwsl_warn("%s: %p: lookup %s:%u\n", __func__, wsi, ads, port); + (void)port; +#if !defined(LWS_WITH_SYS_ASYNC_DNS) n = lws_getaddrinfo46(wsi, ads, &result); - memset(&sa46, 0, sizeof(sa46)); -#ifdef LWS_WITH_IPV6 - if (wsi->ipv6) { - struct sockaddr_in6 *sa6; - - if (n || !result) { - /* lws_getaddrinfo46 failed, there is no usable result */ - lwsl_notice("%s: lws_getaddrinfo46 failed %d\n", - __func__, n); - cce = "ipv6 lws_getaddrinfo46 failed"; - goto oom4; - } - - sa6 = ((struct sockaddr_in6 *)result->ai_addr); - sa46.sa6.sin6_family = AF_INET6; - switch (result->ai_family) { - case AF_INET: - if (ipv6only) - break; - /* map IPv4 to IPv6 */ - memset((char *)&sa46.sa6.sin6_addr, 0, - sizeof(sa46.sa6.sin6_addr)); - sa46.sa6.sin6_addr.s6_addr[10] = 0xff; - sa46.sa6.sin6_addr.s6_addr[11] = 0xff; - memcpy(&sa46.sa6.sin6_addr.s6_addr[12], - &((struct sockaddr_in *)result->ai_addr)->sin_addr, - sizeof(struct in_addr)); - lwsl_notice("uplevelling AF_INET to AF_INET6\n"); - break; - - case AF_INET6: - memcpy(&sa46.sa6.sin6_addr, &sa6->sin6_addr, - sizeof(struct in6_addr)); - sa46.sa6.sin6_scope_id = sa6->sin6_scope_id; - sa46.sa6.sin6_flowinfo = sa6->sin6_flowinfo; - break; - default: - lwsl_err("Unknown address family\n"); - freeaddrinfo(result); - cce = "unknown address family"; - goto oom4; - } - } else -#endif /* use ipv6 */ - - /* use ipv4 */ - { - void *p = NULL; - - if (!n) { - struct addrinfo *res = result; - - /* pick the first AF_INET (IPv4) result */ - - while (!p && res) { - switch (res->ai_family) { - case AF_INET: - p = &((struct sockaddr_in *)res->ai_addr)->sin_addr; - break; - } - - res = res->ai_next; - } -#if defined(LWS_FALLBACK_GETHOSTBYNAME) - } else if (n == EAI_SYSTEM) { - struct hostent *host; - - lwsl_info("ipv4 getaddrinfo err, try gethostbyname\n"); - host = gethostbyname(ads); - if (host) { - p = host->h_addr; - } else { - lwsl_err("gethostbyname failed\n"); - cce = "gethostbyname (ipv4) failed"; - goto oom4; - } -#endif - } else { - lwsl_err("getaddrinfo failed: %s: %d\n", ads, n); - cce = "getaddrinfo failed"; - goto oom4; - } - - if (!p) { - if (result) - freeaddrinfo(result); - lwsl_err("Couldn't identify address\n"); - cce = "unable to lookup address"; - goto oom4; - } - - sa46.sa4.sin_family = AF_INET; - sa46.sa4.sin_addr = *((struct in_addr *)p); - memset(&sa46.sa4.sin_zero, 0, sizeof(sa46.sa4.sin_zero)); - } - - if (result) - freeaddrinfo(result); - -#if defined(LWS_WITH_UNIX_SOCK) -ads_known: -#endif - - /* now we decided on ipv4 or ipv6, set the port */ - - if (!lws_socket_is_valid(wsi->desc.sockfd)) { - - if (wsi->context->event_loop_ops->check_client_connect_ok && - wsi->context->event_loop_ops->check_client_connect_ok(wsi)) { - cce = "waiting for event loop watcher to close"; - goto oom4; - } - -#if defined(LWS_WITH_UNIX_SOCK) - if (unix_skt) { - wsi->unix_skt = 1; - wsi->desc.sockfd = socket(AF_UNIX, SOCK_STREAM, 0); - } else -#endif - { - -#ifdef LWS_WITH_IPV6 - if (wsi->ipv6) - wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0); - else -#endif - wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0); - } - - if (!lws_socket_is_valid(wsi->desc.sockfd)) { - lwsl_warn("Unable to open socket\n"); - cce = "unable to open socket"; - goto oom4; - } - - if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd, -#if defined(LWS_WITH_UNIX_SOCK) - unix_skt)) { #else - 0)) { + /* this is either FAILED, CONTINUING, or already called connect_4 */ + + n = lws_async_dns_query(wsi->context, wsi->tsi, ads, LWS_ADNS_RECORD_A, + lws_client_connect_3_connect, wsi, NULL); + if (n == LADNS_RET_FAILED_WSI_CLOSED) + return NULL; + + if (n == LADNS_RET_FAILED) + goto failed1; + + return wsi; #endif - lwsl_err("Failed to set wsi socket options\n"); - compatible_close(wsi->desc.sockfd); - cce = "set socket opts failed"; - goto oom4; - } - - lwsi_set_state(wsi, LRS_WAITING_CONNECT); - -#if !defined(LWS_AMAZON_RTOS) - if (wsi->context->event_loop_ops->accept) - if (wsi->context->event_loop_ops->accept(wsi)) { - compatible_close(wsi->desc.sockfd); - cce = "event loop accept failed"; - goto oom4; - } -#endif - - if (__insert_wsi_socket_into_fds(wsi->context, wsi)) { - compatible_close(wsi->desc.sockfd); - cce = "insert wsi failed"; - goto oom4; - } - - if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { - compatible_close(wsi->desc.sockfd); - cce = "change_pollfd failed"; - goto oom4; - } - - /* - * past here, we can't simply free the structs as error - * handling as oom4 does. We have to run the whole close flow. - */ - - if (!wsi->protocol) - wsi->protocol = &wsi->vhost->protocols[0]; - - wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE, - wsi->user_space, NULL, 0); - - lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, - AWAITING_TIMEOUT); - - if (wsi->stash) - iface = wsi->stash->cis[CIS_IFACE]; - else - iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); - - if (iface) { - n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, - iface, wsi->ipv6); - if (n < 0) { - cce = "unable to bind socket"; - goto failed; - } - } - } #if defined(LWS_WITH_UNIX_SOCK) - if (unix_skt) { - psa = (const struct sockaddr *)&sau; - n = sizeof(sau); - } else +next_step: #endif - - { -#ifdef LWS_WITH_IPV6 - if (wsi->ipv6) { - sa46.sa6.sin6_port = htons(port); - n = sizeof(struct sockaddr_in6); - psa = (const struct sockaddr *)&sa46; - } else -#endif - { - sa46.sa4.sin_port = htons(port); - n = sizeof(struct sockaddr); - psa = (const struct sockaddr *)&sa46; - } - } - - if (connect(wsi->desc.sockfd, (const struct sockaddr *)psa, n) == -1 || - LWS_ERRNO == LWS_EISCONN) { - if (LWS_ERRNO == LWS_EALREADY || - LWS_ERRNO == LWS_EINPROGRESS || - LWS_ERRNO == LWS_EWOULDBLOCK -#ifdef _WIN32 - || LWS_ERRNO == WSAEINVAL -#endif - ) { - lwsl_client("nonblocking connect retry (errno = %d)\n", - LWS_ERRNO); - - if (lws_plat_check_connection_error(wsi)) { - cce = "socket connect failed"; - goto failed; - } - - /* - * must do specifically a POLLOUT poll to hear - * about the connect completion - */ - if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { - cce = "POLLOUT set failed"; - goto failed; - } - - return wsi; - } - - if (LWS_ERRNO != LWS_EISCONN) { - lwsl_notice("Connect failed errno=%d\n", LWS_ERRNO); - cce = "connect failed"; - goto failed; - } - } - - - - return lws_client_connect_3(wsi, NULL, plen); - + return lws_client_connect_3_connect(wsi, ads, result, n, NULL); oom4: if (lwsi_role_client(wsi) && wsi->protocol /* && lwsi_state_est(wsi) */) @@ -782,16 +918,12 @@ oom4: return NULL; -failed: - lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce)); - failed1: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2"); return NULL; } - #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) static uint8_t hnames2[] = { @@ -909,7 +1041,7 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, lws_free_set_NULL(stash); - *pwsi = lws_client_connect_2(wsi); + *pwsi = lws_client_connect_2_dnsreq(wsi); return *pwsi; @@ -1095,7 +1227,7 @@ lws_http_client_connect_via_info2(struct lws *wsi) no_ah: wsi->context->count_wsi_allocated++; - return lws_client_connect_2(wsi); + return lws_client_connect_2_dnsreq(wsi); bail1: #if defined(LWS_WITH_SOCKS5) diff --git a/lib/roles/http/client/client-http.c b/lib/roles/http/client/client-http.c index 39c5c39b4..1a4b66b5a 100644 --- a/lib/roles/http/client/client-http.c +++ b/lib/roles/http/client/client-http.c @@ -137,14 +137,14 @@ lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd, switch (lwsi_state(wsi)) { - case LRS_WAITING_CONNECT: + case LRS_WAITING_DNS: /* * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE * timeout protection set in client-handshake.c */ - - if (!lws_client_connect_2(wsi)) { + lwsl_err("%s: wsi %p: WAITING_DNS\n", __func__, wsi); + if (!lws_client_connect_2_dnsreq(wsi)) { /* closed */ lwsl_client("closed\n"); return -1; @@ -152,6 +152,15 @@ lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd, /* either still pending connection, or changed mode */ return 0; + case LRS_WAITING_CONNECT: + + /* + * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE + * timeout protection set in client-handshake.c + */ + if (pollfd->revents & LWS_POLLOUT) + lws_client_connect_3_connect(wsi, NULL, NULL, 0, NULL); + break; #if defined(LWS_WITH_SOCKS5) /* SOCKS Greeting Reply */ diff --git a/lib/roles/private-lib-roles.h b/lib/roles/private-lib-roles.h index 898b9a061..d4664d3b7 100644 --- a/lib/roles/private-lib-roles.h +++ b/lib/roles/private-lib-roles.h @@ -87,43 +87,44 @@ enum lwsi_state { /* Phase 1: pre-transport */ LRS_UNCONNECTED = LWSIFS_NOT_EST | 0, - LRS_WAITING_CONNECT = LWSIFS_NOT_EST | 1, + LRS_WAITING_DNS = LWSIFS_NOT_EST | 1, + LRS_WAITING_CONNECT = LWSIFS_NOT_EST | 2, /* Phase 2: establishing intermediaries on top of transport */ - LRS_WAITING_PROXY_REPLY = LWSIFS_NOT_EST | 2, - LRS_WAITING_SSL = LWSIFS_NOT_EST | 3, - LRS_WAITING_SOCKS_GREETING_REPLY = LWSIFS_NOT_EST | 4, - LRS_WAITING_SOCKS_CONNECT_REPLY = LWSIFS_NOT_EST | 5, - LRS_WAITING_SOCKS_AUTH_REPLY = LWSIFS_NOT_EST | 6, + LRS_WAITING_PROXY_REPLY = LWSIFS_NOT_EST | 3, + LRS_WAITING_SSL = LWSIFS_NOT_EST | 4, + LRS_WAITING_SOCKS_GREETING_REPLY = LWSIFS_NOT_EST | 5, + LRS_WAITING_SOCKS_CONNECT_REPLY = LWSIFS_NOT_EST | 6, + LRS_WAITING_SOCKS_AUTH_REPLY = LWSIFS_NOT_EST | 7, /* Phase 3: establishing tls tunnel */ - LRS_SSL_INIT = LWSIFS_NOT_EST | 7, - LRS_SSL_ACK_PENDING = LWSIFS_NOT_EST | 8, - LRS_PRE_WS_SERVING_ACCEPT = LWSIFS_NOT_EST | 9, + LRS_SSL_INIT = LWSIFS_NOT_EST | 8, + LRS_SSL_ACK_PENDING = LWSIFS_NOT_EST | 9, + LRS_PRE_WS_SERVING_ACCEPT = LWSIFS_NOT_EST | 10, /* Phase 4: connected */ - LRS_WAITING_SERVER_REPLY = LWSIFS_NOT_EST | 10, - LRS_H2_AWAIT_PREFACE = LWSIFS_NOT_EST | 11, + LRS_WAITING_SERVER_REPLY = LWSIFS_NOT_EST | 11, + LRS_H2_AWAIT_PREFACE = LWSIFS_NOT_EST | 12, LRS_H2_AWAIT_SETTINGS = LWSIFS_NOT_EST | - LWSIFS_POCB | 12, + LWSIFS_POCB | 13, /* Phase 5: protocol logically established */ - LRS_H2_CLIENT_SEND_SETTINGS = LWSIFS_POCB | 13, - LRS_H2_WAITING_TO_SEND_HEADERS = LWSIFS_POCB | 14, - LRS_DEFERRING_ACTION = LWSIFS_POCB | 15, - LRS_IDLING = 16, - LRS_H1C_ISSUE_HANDSHAKE = 17, - LRS_H1C_ISSUE_HANDSHAKE2 = 18, - LRS_ISSUE_HTTP_BODY = 19, - LRS_ISSUING_FILE = 20, - LRS_HEADERS = 21, - LRS_BODY = 22, - LRS_DISCARD_BODY = 31, - LRS_ESTABLISHED = LWSIFS_POCB | 23, + LRS_H2_CLIENT_SEND_SETTINGS = LWSIFS_POCB | 14, + LRS_H2_WAITING_TO_SEND_HEADERS = LWSIFS_POCB | 15, + LRS_DEFERRING_ACTION = LWSIFS_POCB | 16, + LRS_IDLING = 17, + LRS_H1C_ISSUE_HANDSHAKE = 18, + LRS_H1C_ISSUE_HANDSHAKE2 = 19, + LRS_ISSUE_HTTP_BODY = 20, + LRS_ISSUING_FILE = 21, + LRS_HEADERS = 22, + LRS_BODY = 23, + LRS_DISCARD_BODY = 24, + LRS_ESTABLISHED = LWSIFS_POCB | 25, /* we are established, but we have embarked on serving a single * transaction. Other transaction input may be pending, but we will * not service it while we are busy dealing with the current @@ -132,19 +133,19 @@ enum lwsi_state { * When we complete the current transaction, we would reset our state * back to ESTABLISHED and start to process the next transaction. */ - LRS_DOING_TRANSACTION = LWSIFS_POCB | 24, + LRS_DOING_TRANSACTION = LWSIFS_POCB | 26, /* Phase 6: finishing */ - LRS_WAITING_TO_SEND_CLOSE = LWSIFS_POCB | 25, - LRS_RETURNED_CLOSE = LWSIFS_POCB | 26, - LRS_AWAITING_CLOSE_ACK = LWSIFS_POCB | 27, - LRS_FLUSHING_BEFORE_CLOSE = LWSIFS_POCB | 28, - LRS_SHUTDOWN = 29, + LRS_WAITING_TO_SEND_CLOSE = LWSIFS_POCB | 27, + LRS_RETURNED_CLOSE = LWSIFS_POCB | 28, + LRS_AWAITING_CLOSE_ACK = LWSIFS_POCB | 29, + LRS_FLUSHING_BEFORE_CLOSE = LWSIFS_POCB | 30, + LRS_SHUTDOWN = 31, /* Phase 7: dead */ - LRS_DEAD_SOCKET = 30, + LRS_DEAD_SOCKET = 32, LRS_MASK = 0xffff }; @@ -333,4 +334,8 @@ int lws_role_call_adoption_bind(struct lws *wsi, int type, const char *prot); struct lws * -lws_client_connect_3(struct lws *wsi, struct lws *wsi_piggyback, ssize_t plen); +lws_client_connect_4_established(struct lws *wsi, struct lws *wsi_piggyback, ssize_t plen); + +struct lws * +lws_client_connect_3_connect(struct lws *wsi, const char *ads, + const struct addrinfo *result, int n, void *opaque); diff --git a/lib/roles/raw-skt/ops-raw-skt.c b/lib/roles/raw-skt/ops-raw-skt.c index 86a568498..690775691 100644 --- a/lib/roles/raw-skt/ops-raw-skt.c +++ b/lib/roles/raw-skt/ops-raw-skt.c @@ -116,7 +116,7 @@ try_pollout: #if defined(LWS_WITH_CLIENT) if (lwsi_state(wsi) == LRS_WAITING_CONNECT) - lws_client_connect_3(wsi, NULL, 0); + lws_client_connect_4_established(wsi, NULL, 0); #endif /* one shot */ diff --git a/lib/system/async-dns/async-dns-parse.c b/lib/system/async-dns/async-dns-parse.c new file mode 100644 index 000000000..9f0af032f --- /dev/null +++ b/lib/system/async-dns/async-dns-parse.c @@ -0,0 +1,625 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2019 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. + */ + +#include "private-lib-core.h" +#include "private-lib-async-dns.h" + + +/* updates *dest, returns chars used from ls directly, else -1 for fail */ + +static int +lws_adns_parse_label(const uint8_t *pkt, int len, const uint8_t *ls, int budget, + char **dest, int dl) +{ + const uint8_t *e = pkt + len, *ols = ls; + char pointer = 0, first = 1; + uint8_t ll; + int n; + + if (budget < 1) + return 0; + + /* caller must catch end of labels */ + assert(*ls); + +again1: + if (ls >= e) + return -1; + + if (((*ls) & 0xc0) == 0xc0) { + if (budget < 2) + return -1; + /* pointer into message pkt to name to actually use */ + n = lws_ser_ru16be(ls) & 0x3fff; + if (n >= len) { + lwsl_notice("%s: illegal name pointer\n", __func__); + + return -1; + } + + /* dereference the label pointer */ + ls = pkt + n; + + /* are we being fuzzed or messed with? */ + if (((*ls) & 0xc0) == 0xc0) { + /* ... pointer to pointer is unreasonable */ + lwsl_notice("%s: label ptr to ptr invalid\n", __func__); + + return -1; + } + pointer = 1; + } + +again: + if (ls >= e) + return -1; + ll = *ls++; + if (ls + ll + 4 > e || ll > budget) { + lwsl_notice("%s: label len invalid\n", __func__); + + return -1; + } + + if (ll + 2 > dl) { + lwsl_notice("%s: qname too large\n", __func__); + + return -1; + } + + /* copy the label content into place */ + + memcpy(*dest, ls, ll); + (*dest)[ll] = '.'; + (*dest)[ll + 1] = '\0'; + *dest += ll + 1; + ls += ll; + + if (pointer) { + if (*ls) + goto again; + + /* + * special fun rule... if whole qname was a pointer label, + * it has no 00 terminator afterwards + */ + if (first) + return 2; /* we just took the 16-bit pointer */ + + return 3; + } + + first = 0; + + if (*ls) + goto again1; + + ls++; + + return ls - ols; +} + +typedef int (*lws_async_dns_find_t)(const char *name, void *opaque, + uint32_t ttl, adns_query_type_t type, + const uint8_t *payload); + +/* locally query the response packet */ + +struct label_stack { + char name[64]; + int enl; + const uint8_t *p; +}; + +/* + * Walk the response packet, calling back to the user-provided callback for each + * A (and AAAA if LWS_IPV6=1) record with a matching name found in there. + * + * Able to recurse using an explicit non-CPU stack to resolve CNAME usages + * + * Return -1: unexpectedly failed + * 0: found + * 1: didn't find anything matching + */ + +static int +lws_adns_iterate(const uint8_t *pkt, int len, const char *expname, + lws_async_dns_find_t cb, void *opaque) +{ + const uint8_t *e = pkt + len, *p, *pay; + struct label_stack stack[4]; + uint16_t rrtype, rrpaylen; + int n = 0, stp = 0, ansc; + char *sp, inq; + uint32_t ttl; + + lws_strncpy(stack[stp].name, expname, sizeof(stack[stp].name)); + stack[stp].enl = strlen(expname); + +start: + ansc = lws_ser_ru16be(pkt + DHO_NANSWERS); + p = pkt + DHO_SIZEOF; + inq = 1; + + /* + * The response also includes the query... and we have to parse it + * so we can understand we reached the response... there's a QNAME + * made up of labels and then 2 x 16-bit fields, for query type and + * query class + */ + +resume: + while (p + 14 < e && (inq || ansc)) { + + if (!inq && !stp) + ansc--; + + /* + * First is the name the query applies to... two main + * formats can appear here, one is a pointer to + * elsewhere in the message, the other separately + * provides len / data for each dotted "label", so for + * "warmcat.com" warmcat and com are given each with a + * prepended length byte. Any of those may be a pointer + * to somewhere else in the packet :-/ + * + * Paranoia is appropriate since the name length must be + * parsed out before the rest of the RR can be used and + * we can be attacked with absolutely any crafted + * content easily via UDP. + * + * So parse the name and additionally confirm it matches + * what the query the TID belongs to actually asked for. + */ + + sp = stack[0].name; + + /* while we have more labels */ + + n = lws_adns_parse_label(pkt, len, p, len, &sp, + sizeof(stack[0].name) - + lws_ptr_diff(sp, stack[0].name)); + /* includes case name won't fit */ + if (n < 0) + return -1; + + p += n; + + if (p + (inq ? 5 : 14) > e) + return -1; + + /* + * p is now just after the decoded RR name, pointing at: type + * + * We sent class = 1 = IN query... response must match + */ + + if (lws_ser_ru16be(&p[2]) != 1) { + lwsl_debug("%s: non-IN response 0x%x\n", __func__, + lws_ser_ru16be(&p[2])); + + return -1; + } + + if (inq) { + lwsl_debug("%s: reached end of inq\n", __func__); + inq = 0; + p += 4; + continue; + } + + /* carefully validate the claimed RR payload length */ + + rrpaylen = lws_ser_ru16be(&p[8]); + if (p + 10 + rrpaylen > e) { /* it may be == e */ + lwsl_notice("%s: invalid RR data length\n", __func__); + + return -1; + } + + ttl = lws_ser_ru32be(&p[4]); + rrtype = lws_ser_ru16be(&p[0]); + p += 10; /* point to the payload */ + pay = p; + + /* + * Compare the RR names, allowing for the decoded labelname + * to have an extra '.' at the end. + */ + + n = lws_ptr_diff(sp, stack[0].name); + if (stack[0].name[n - 1] == '.') + n--; + + if (n < 1 || n != stack[stp].enl || + strcmp(stack[0].name, stack[stp].name)) { + lwsl_debug("%s: skipping %s vs %s\n", __func__, + stack[0].name, stack[stp].name); + goto skip; + } + + /* + * It's something we could be interested in... + * + * We can skip RRs we don't understand. But we need to deal + * with at least these and their payloads: + * + * A: 4: ipv4 address + * AAAA: 16: ipv6 address (if asked for AAAA) + * CNAME: ?: labelized name + * + * If we hit a CNAME we need to try to dereference it with + * stuff that is in the same response packet and judge it + * from that, without losing our place here. CNAMEs may + * point to CNAMEs to whatever depth we're willing to handle. + */ + + switch (rrtype) { +#if defined(LWS_WITH_IPV6) + case LWS_ADNS_RECORD_AAAA: + if (rrpaylen != 16) { + lwsl_err("%s: unexpected rrpaylen\n", __func__); + return -1; + } + goto do_cb; +#endif + case LWS_ADNS_RECORD_A: + if (rrpaylen != 4) { + lwsl_err("%s: unexpected rrpaylen4\n", __func__); + + return -1; + } +#if defined(LWS_WITH_IPV6) +do_cb: +#endif + cb(stack[0].name, opaque, ttl, rrtype, p); + break; + + case LWS_ADNS_RECORD_CNAME: + /* + * The name the CNAME refers to should itself be + * included elsewhere in the response packet. + * + * So switch tack, stack where to resume from and + * search for the decoded CNAME label name definition + * instead. + * + * First decode the CNAME label payload into the next + * stack level buffer for it. + */ + + if (++stp == (int)LWS_ARRAY_SIZE(stack)) { + lwsl_notice("%s: CNAMEs too deep\n", __func__); + + return -1; + } + sp = stack[stp].name; + n = lws_adns_parse_label(pkt, len, p, rrpaylen, &sp, + sizeof(stack[stp].name) - + lws_ptr_diff(sp, stack[stp].name)); + /* includes case name won't fit */ + if (n < 0) + return -1; + + p += n; + + if (p + 14 > e) + return -1; + + /* it should have exactly reached rrpaylen */ + + if (p != pay + rrpaylen) { + lwsl_err("%s: cname name bad len\n", __func__); + + return -1; + } + + stack[stp].enl = lws_ptr_diff(sp, stack[stp].name); + /* when we unstack, resume from here */ + stack[stp].p = pay + rrpaylen; + goto start; + + default: + break; + } + +skip: + p += rrpaylen; + } + + if (!stp) + return 1; /* we didn't find anything, but we didn't error */ + + /* + * This implies there wasn't any usable definition for the + * CNAME in the end, eg, only AAAA when we needed and A. + * + * Short-circuit the whole stack and resume from after the + * original CNAME reference. + */ + p = stack[1].p; + stp = 0; + goto resume; +} + +int +lws_async_dns_estimate(const char *name, void *opaque, uint32_t ttl, + adns_query_type_t type, const uint8_t *payload) +{ + size_t *est = (size_t *)opaque, my; + + my = sizeof(struct addrinfo); + if (type == LWS_ADNS_RECORD_AAAA) + my += sizeof(struct sockaddr_in6); + else + my += sizeof(struct sockaddr_in); + + *est += my; + + return 0; +} + +struct adstore { + const char *name; + struct addrinfo *pos; + struct addrinfo *prev; + int ctr; + uint32_t smallest_ttl; + uint8_t flags; +}; + +/* + * Callback for each A or AAAA record, creating getaddrinfo-compatible results + * into the preallocated exact-sized storage. + */ +int +lws_async_dns_store(const char *name, void *opaque, uint32_t ttl, + adns_query_type_t type, const uint8_t *payload) +{ + struct adstore *adst = (struct adstore *)opaque; +#if defined(_DEBUG) + char buf[48]; +#endif + size_t i; + + if (ttl < adst->smallest_ttl || !adst->ctr) + adst->smallest_ttl = ttl; + + if (adst->prev) + adst->prev->ai_next = adst->pos; + adst->prev = adst->pos; + + adst->pos->ai_flags = 0; + adst->pos->ai_family = type == LWS_ADNS_RECORD_AAAA ? + AF_INET6 : AF_INET; + adst->pos->ai_socktype = SOCK_STREAM; + adst->pos->ai_protocol = IPPROTO_UDP; /* no meaning */ + adst->pos->ai_addrlen = type == LWS_ADNS_RECORD_AAAA ? + sizeof(struct sockaddr_in6) : + sizeof(struct sockaddr_in); + adst->pos->ai_canonname = (char *)adst->name; + adst->pos->ai_addr = (struct sockaddr *)&adst->pos[1]; + adst->pos->ai_next = NULL; + +#if defined(LWS_WITH_IPV6) + if (type == LWS_ADNS_RECORD_AAAA) { + struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&adst->pos[1]; + + i = sizeof(*in6); + memset(in6, 0, i); + in6->sin6_family = adst->pos->ai_family; + memcpy(in6->sin6_addr.s6_addr, payload, 16); + adst->flags |= 2; + } else +#endif + { + struct sockaddr_in *in = (struct sockaddr_in *)&adst->pos[1]; + + i = sizeof(*in); + memset(in, 0, i); + in->sin_family = adst->pos->ai_family; + memcpy(&in->sin_addr.s_addr, payload, 4); + adst->flags |= 1; + } + + adst->pos = (struct addrinfo *)((uint8_t *)adst->pos + + sizeof(struct addrinfo) + i); + +#if defined(_DEBUG) + if (lws_write_numeric_address(payload, + type == LWS_ADNS_RECORD_AAAA ? 16 : 4, + buf, sizeof(buf)) > 0) + lwsl_info("%s: %d: %s: %s\n", __func__, adst->ctr, + adst->name, buf); +#endif + adst->ctr++; + + return 0; +} + +/* + * We want to parse out all A or AAAA records + */ + +void +lws_adns_parse_udp(lws_async_dns_t *dns, const uint8_t *pkt, size_t len) +{ + lws_adns_cache_t *c; + struct adstore adst; + lws_adns_q_t *q; + const char *nm; + size_t est; + int n; + + // lwsl_hexdump_notice(pkt, len); + + /* we have to at least have the header */ + + if (len < DHO_SIZEOF) + return; + + /* we asked with one query, so anything else is bogus */ + + if (lws_ser_ru16be(pkt + DHO_NQUERIES) != 1) + return; + + /* match both A and AAAA queries if any */ + + q = lws_adns_get_query(dns, 0, &dns->waiting_resp, + lws_ser_ru16be(pkt + DHO_TID), NULL); + if (!q) { + /* + * if he's still waiting to send the second query, he's still + * on the .waiting_send list + */ + q = lws_adns_get_query(dns, 0, &dns->waiting_send, + lws_ser_ru16be(pkt + DHO_TID), NULL); + + if (!q) { + lwsl_notice("%s: dropping unknown query tid 0x%x\n", + __func__, lws_ser_ru16be(pkt + DHO_TID)); + + return; + } + } + + /* we can get dups... drop any that have already happened */ + + n = 1 << (lws_ser_ru16be(pkt + DHO_TID) & 1); + if (q->responded & n) { + lwsl_notice("%s: dup\n", __func__); + goto fail_out; + } + + q->responded |= n; + nm = (const char *)&q[1]; + + /* + * First walk the packet figuring out the allocation needed for all + * the results. Produce the following layout at c + * + * lws_adns_cache_t: new cache object + * [struct addrinfo + struct sockaddr_in or _in6]: for each A or AAAA + * char []: copy of resolved name + */ + + n = strlen(nm) + 1; + + est = sizeof(lws_adns_cache_t) + n; + if (lws_ser_ru16be(pkt + DHO_NANSWERS) && + lws_adns_iterate(pkt, len, nm, lws_async_dns_estimate, &est) < 0) + goto fail_out; + + c = lws_malloc(est, "async-dns-entry"); + if (!c) { + lwsl_err("%s: OOM %zu\n", __func__, est); + goto fail_out; + } + memset(c, 0, sizeof(*c)); + + /* place it at end, no need to care about alignment padding */ + adst.name = ((const char *)c) + est - n; + memcpy((char *)adst.name, nm, n); + + /* + * Then walk the packet again, placing the objects we accounted for + * the first time into the result allocation after the cache object + * and copy of the name + */ + + adst.pos = (struct addrinfo *)&c[1]; + adst.prev = NULL; + adst.ctr = 0; + adst.smallest_ttl = 3600; + adst.flags = 0; + + /* + * smallest_ttl applies as it is to empty results (NXDOMAIN), or is + * set to the minimum ttl seen in all the results. + */ + + if (lws_ser_ru16be(pkt + DHO_NANSWERS) && + lws_adns_iterate(pkt, len, nm, lws_async_dns_store, &adst) < 0) { + lws_free(c); + goto fail_out; + } + + if (lws_ser_ru16be(pkt + DHO_NANSWERS)) { + c->results = (struct addrinfo *)&c[1]; + if (q->last) /* chain the second one on */ + *q->last = c->results; + else /* first one had no results, set first guy's c->results */ + if (q->firstcache) + q->firstcache->results = c->results; + } + + if (adst.prev) /* so we know where to continue the addrinfo list */ + /* can be NULL if first resp empty */ + q->last = &adst.prev->ai_next; + + if (q->firstcache) { /* also need to free chain when we free this guy */ + q->firstcache->chain = c; + c->firstcache = q->firstcache; + } else { + + q->firstcache = c; + + /* + * Only register the first one into the cache... + * Trim the oldest cache entry if necessary + */ + + lws_async_dns_trim_cache(dns); + + /* + * cache the first results object... if a second one comes, + * we won't directly register it but will chain it on to this + * first one and continue to addinfo ai_next linked list from + * the first into the second + */ + + c->flags = adst.flags; + lws_dll2_add_head(&c->list, &dns->cached); + lws_sul_schedule(q->context, 0, &c->sul, sul_cb_expire, + lws_now_usecs() + + (adst.smallest_ttl * LWS_US_PER_SEC)); + } + + if (q->responded != q->asked) + return; + + /* + * Now we captured everything into the new object, return the + * addrinfo results, if any, to all interested wsi, if any... + */ + + lws_async_dns_complete(q, q->firstcache); + + /* + * the query is completely finished with + */ + +fail_out: + lws_adns_q_destroy(q); +} + diff --git a/lib/system/async-dns/async-dns.c b/lib/system/async-dns/async-dns.c new file mode 100644 index 000000000..2d44d153e --- /dev/null +++ b/lib/system/async-dns/async-dns.c @@ -0,0 +1,671 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2019 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. + */ + +#include "private-lib-core.h" +#include "private-lib-async-dns.h" + +void +lws_adns_q_destroy(lws_adns_q_t *q) +{ + lws_dll2_remove(&q->sul.list); + lws_dll2_remove(&q->list); + lws_free(q); +} + +lws_adns_q_t * +lws_adns_get_query(lws_async_dns_t *dns, adns_query_type_t qtype, + lws_dll2_owner_t *owner, uint16_t tid, const char *name) +{ + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + lws_dll2_get_head(owner)) { + lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list); + + if (!name && (tid & 0xfffe) == (q->tid & 0xfffe)) + return q; + + if (name && q->qtype == ((tid & 1) ? LWS_ADNS_RECORD_AAAA : + LWS_ADNS_RECORD_A) && + !strcasecmp(name, (const char *)&q[1])) { + if (owner == &dns->cached) { + /* Keep sorted by LRU: move to the head */ + lws_dll2_remove(&q->list); + lws_dll2_add_head(&q->list, &dns->cached); + } + + return q; + } + } lws_end_foreach_dll_safe(d, d1); + + return NULL; +} + +void +lws_async_dns_drop_server(struct lws_context *context) +{ + context->async_dns.dns_server_set = 0; + lws_set_timeout(context->async_dns.wsi, 1, LWS_TO_KILL_ASYNC); + context->async_dns.wsi = NULL; +} + +int +lws_async_dns_complete(lws_adns_q_t *q, lws_adns_cache_t *c) +{ + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + lws_dll2_get_head(&q->wsi_adns)) { + struct lws *w = lws_container_of(d, struct lws, adns); + + lws_dll2_remove(d); + if (c && c->results) { + lwsl_debug("%s: q: %p, c: %p, refcount %d -> %d\n", + __func__, q, c, c->refcount, c->refcount + 1); + c->refcount++; + } + w->adns_cb(w, (const char *)&q[1], c ? c->results : NULL, 0, + q->opaque); + } lws_end_foreach_dll_safe(d, d1); + + if (q->standalone_cb) { + if (c && c->results) { + lwsl_debug("%s: q: %p, c: %p, refcount %d -> %d\n", + __func__, q, c, c->refcount, c->refcount + 1); + c->refcount++; + } + + q->standalone_cb(NULL, (const char *)&q[1], + c ? c->results : NULL, 0, q->opaque); + } + + return 0; +} + +static void +sul_cb_timeout(struct lws_sorted_usec_list *sul) +{ + lws_adns_q_t *q = lws_container_of(sul, lws_adns_q_t, sul); + + lws_async_dns_complete(q, NULL); + lws_adns_q_destroy(q); + + /* + * our policy is to force reloading the dns server info if our + * connection ever timed out, in case it or the routing state changed + */ + + lws_async_dns_drop_server(q->context); +} + +static int +callback_async_dns(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + uint8_t pkt[LWS_PRE + DNS_PACKET_LEN], *e = &pkt[sizeof(pkt)], *p, *pl; + struct lws_async_dns *dns = &(lws_get_context(wsi)->async_dns); + struct lws_dll2 *d; + const char *name; + lws_adns_q_t *q; + int fd, m, n; + + switch (reason) { + + /* callbacks related to raw socket descriptor */ + + case LWS_CALLBACK_RAW_ADOPT: + // lwsl_user("LWS_CALLBACK_RAW_ADOPT\n"); + break; + + case LWS_CALLBACK_RAW_CLOSE: + // lwsl_user("LWS_CALLBACK_RAW_CLOSE\n"); + break; + + case LWS_CALLBACK_RAW_RX: + // lwsl_user("LWS_CALLBACK_RAW_RX (%d)\n", (int)len); + //lwsl_hexdump_level(LLL_NOTICE, in, len); + lws_adns_parse_udp(dns, in, len); + + return 0; + + case LWS_CALLBACK_RAW_WRITEABLE: + + d = lws_dll2_get_head(&dns->waiting_send); + if (!d) + return 0; + q = lws_container_of(d, lws_adns_q_t, list); + name = (const char *)&q[1]; + + p = &pkt[LWS_PRE]; + memset(p, 0, DHO_SIZEOF); + + /* we hack b0 of the tid to be 0 = A, 1 = AAAA */ + + lws_ser_wu16be(&p[DHO_TID], q->asked ? q->tid | 1 : + q->tid); + lws_ser_wu16be(&p[DHO_FLAGS], (1 << 8)); + lws_ser_wu16be(&p[DHO_NQUERIES], 1); + + p += DHO_SIZEOF; + + /* start of label-formatted qname */ + + pl = p++; + + do { + if (*name == '.' || !*name) { + *pl = lws_ptr_diff(p, pl + 1); + pl = p; + *p++ = 0; /* also serves as terminal length */ + if (!*name++) + break; + } else + *p++ = *name++; + } while (p + 6 < e); + + if (p + 6 >= e) { + assert(0); + lwsl_err("%s: name too big\n", __func__); + goto qfail; + } + + lws_ser_wu16be(p, q->asked ? LWS_ADNS_RECORD_AAAA : + LWS_ADNS_RECORD_A); + p += 2; + + lws_ser_wu16be(p, 1); /* IN class */ + p += 2; + + assert(p < pkt + sizeof(pkt) - LWS_PRE); + n = lws_ptr_diff(p, pkt + LWS_PRE); + + fd = lws_get_socket_fd(wsi); + if (fd < 0) + break; + + m = send(fd, pkt + LWS_PRE, n, 0); + if (m != n) { + lwsl_notice("%s: dns write failed %d %d\n", __func__, + m, n); + goto qfail; + } + + /* move us to the "waiting for response" list */ + +#if defined(LWS_WITH_IPV6) + /* don't move to waiting resp until we sent both */ + if (q->asked) { + q->asked |= 2; +#endif + lws_dll2_remove(&q->list); + lws_dll2_add_head(&q->list, &dns->waiting_resp); +#if defined(LWS_WITH_IPV6) + } else + lws_callback_on_writable(wsi); +#endif + q->asked |= 1; + + if (lws_dll2_get_head(&dns->waiting_send)) + /* more to do */ + lws_callback_on_writable(wsi); + + break; + + default: + break; + } + + return 0; + +qfail: + lws_async_dns_complete(q, NULL); + lws_adns_q_destroy(q); + + return 0; +} + +struct lws_protocols lws_async_dns_protocol = { + "lws-async-dns", callback_async_dns, 0, 0 +}; + +int +lws_async_dns_init(struct lws_context *context) +{ + lws_async_dns_t *dns = &context->async_dns; + char ads[48]; + int n; + + memset(&dns->sa46, 0, sizeof(dns->sa46)); + + n = lws_plat_asyncdns_init(context, &dns->sa46); + if (n < 0) { + lwsl_warn("%s: no valid dns server, retry\n", __func__); + + return 1; + } + + dns->sa46.sa4.sin_port = htons(53); + lws_write_numeric_address((uint8_t *)&dns->sa46.sa4.sin_addr.s_addr, 4, + ads, sizeof(ads)); + + context->async_dns.wsi = lws_create_adopt_udp(context->vhost_list, ads, + 53, 0, lws_async_dns_protocol.name, NULL); + if (!dns->wsi) { + lwsl_err("%s: foreign socket adoption failed\n", __func__); + return 1; + } + + dns->dns_server_set = 1; + + return 0; +} + +lws_adns_cache_t * +lws_adns_get_cache(lws_async_dns_t *dns, const char *name) +{ + lws_adns_cache_t *c; + const char *cn; + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + lws_dll2_get_head(&dns->cached)) { + c = lws_container_of(d, lws_adns_cache_t, list); + cn = (const char *)&c[1]; + + if (name && !strcasecmp(name, cn)) { + /* Keep sorted by LRU: move to the head */ + lws_dll2_remove(&c->list); + lws_dll2_add_head(&c->list, &dns->cached); + + return c; + } + } lws_end_foreach_dll_safe(d, d1); + + return NULL; +} + +void +lws_adns_cache_destroy(lws_adns_cache_t *c) +{ + lws_dll2_remove(&c->sul.list); + lws_dll2_remove(&c->list); + if (c->chain) + lws_free(c->chain); + lws_free(c); +} + +static int +cache_clean(struct lws_dll2 *d, void *user) +{ + lws_adns_cache_destroy(lws_container_of(d, lws_adns_cache_t, list)); + + return 0; +} + +void +sul_cb_expire(struct lws_sorted_usec_list *sul) +{ + lws_adns_cache_t *c = lws_container_of(sul, lws_adns_cache_t, sul); + + lws_adns_cache_destroy(c); +} + +void +lws_async_dns_freeaddrinfo(const struct addrinfo *ai) +{ + lws_adns_cache_t *c; + + if (!ai) + return; + + /* + * First query may have been empty... if second has something, we + * fixed up the first result to point to second... but it means + * looking backwards from ai, which is c->result, which is the second + * packet's results, doesn't get us to the firstcache pointer. + * + * Adjust c to the firstcache in this case. + */ + + c = &((lws_adns_cache_t *)ai)[-1]; + if (c->firstcache) + c = c->firstcache; + + lwsl_debug("%s: c %p, %s, refcount %d -> %d\n", __func__, c, + (c->results && c->results->ai_canonname) ? + c->results->ai_canonname : "none", + c->refcount, c->refcount - 1); + + assert(c->refcount > 0); + c->refcount--; +} + +void +lws_async_dns_trim_cache(lws_async_dns_t *dns) +{ + lws_adns_cache_t *c1; + + if (dns->cached.count + 1< MAX_CACHE_ENTRIES) + return; + + c1 = lws_container_of(lws_dll2_get_tail(&dns->cached), + lws_adns_cache_t, list); + if (c1->refcount) + lwsl_notice("%s: wsi %p: refcount %d on purge\n", + __func__, c1, c1->refcount); + else + lws_adns_cache_destroy(c1); +} + + +static int +clean(struct lws_dll2 *d, void *user) +{ + lws_adns_q_destroy(lws_container_of(d, lws_adns_q_t, list)); + + return 0; +} + +void +lws_async_dns_deinit(lws_async_dns_t *dns) +{ + lws_dll2_foreach_safe(&dns->waiting_send, NULL, clean); + lws_dll2_foreach_safe(&dns->waiting_resp, NULL, clean); + lws_dll2_foreach_safe(&dns->cached, NULL, cache_clean); +} + + +static int +cancel(struct lws_dll2 *d, void *user) +{ + lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list); + + lws_start_foreach_dll_safe(struct lws_dll2 *, d3, d4, + lws_dll2_get_head(&q->wsi_adns)) { + struct lws *w = lws_container_of(d3, struct lws, adns); + + if (user == w) { + lws_dll2_remove(d3); + if (!q->wsi_adns.count) + lws_adns_q_destroy(q); + return 1; + } + } lws_end_foreach_dll_safe(d3, d4); + + return 0; +} + +void +lws_async_dns_cancel(struct lws *wsi) +{ + lws_async_dns_t *dns = &wsi->context->async_dns; + + if (!lws_dll2_foreach_safe(&dns->waiting_send, wsi, cancel)) + lws_dll2_foreach_safe(&dns->waiting_resp, wsi, cancel); +} + + +static int +check_tid(struct lws_dll2 *d, void *user) +{ + lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list); + + return q->tid == (uint16_t)(long)user; +} + +static int +lws_async_dns_get_new_tid(struct lws_context *context, lws_adns_q_t *q) +{ + lws_async_dns_t *dns = &context->async_dns; + int budget = 10; + + /* + * Make the TID unpredictable, but must be unique amongst ongoing ones + */ + do { + if (lws_get_random(context, &q->tid, 2) != 2) + return -1; + + if (lws_dll2_foreach_safe(&dns->waiting_send, + (void *)(long)q->tid, check_tid)) + continue; + + if (lws_dll2_foreach_safe(&dns->waiting_resp, + (void *)(long)q->tid, check_tid)) + continue; + + return 0; + + } while (budget--); + + lwsl_err("%s: unable to get unique tid\n", __func__); + + return -1; +} + +struct temp_q { + lws_adns_q_t tq; + char name[48]; +}; + +lws_async_dns_retcode_t +lws_async_dns_query(struct lws_context *context, int tsi, const char *name, + adns_query_type_t qtype, lws_async_dns_cb_t cb, + struct lws *wsi, void *opaque) +{ + lws_async_dns_t *dns = &context->async_dns; + size_t nlen = strlen(name); + lws_sockaddr46 *sa46; + lws_adns_cache_t *c; + struct addrinfo *ai; + struct temp_q tmq; + lws_adns_q_t *q; + uint8_t ads[16]; + char *p; + int m; + +#if !defined(LWS_WITH_IPV6) + if (qtype == LWS_ADNS_RECORD_AAAA) { + lwsl_err("%s: ipv6 not enabled\n", __func__); + goto failed; + } +#endif + + /* + * we magically know 'localhost' and 'localhost6' if IPv6, this is a + * sort of canned /etc/hosts + */ + + if (!strcmp(name, "localhost")) + name = "127.0.0.1"; + +#if defined(LWS_WITH_IPV6) + if (!strcmp(name, "localhost6")) + name = "::1"; +#endif + + if (wsi) { + if (!lws_dll2_is_detached(&wsi->adns)) { + lwsl_err("%s: wsi %p already bound to query %p\n", + __func__, wsi, wsi->adns.owner); + goto failed; + } + wsi->adns_cb = cb; + } + + /* there's a done, cached query we can just reuse? */ + + c = lws_adns_get_cache(dns, name); + if (c) { + cb(wsi, name, c->results, 0, opaque); + + return LADNS_RET_FOUND; + } + + /* + * It's a 1.2.3.4 type IP address already? We don't need a dns + * server set up to be able to create an addrinfo result for that. + * + * Create it as a cached object so it follows the refcount lifecycle + * of any other result + */ + + m = lws_parse_numeric_address(name, ads, sizeof(ads)); + if (m == 4 +#if defined(LWS_WITH_IPV6) + || m == 16 +#endif + ) { + lws_async_dns_trim_cache(dns); + + c = lws_zalloc(sizeof(lws_adns_cache_t) + + sizeof(struct addrinfo) + + sizeof(lws_sockaddr46) + nlen + 1, "adns-numip"); + if (!c) + goto failed; + + ai = (struct addrinfo *)&c[1]; + sa46 = (lws_sockaddr46 *)&ai[1]; + + ai->ai_socktype = SOCK_STREAM; + memcpy(&sa46[1], name, nlen + 1); + ai->ai_canonname = (char *)&sa46[1]; + + c->results = ai; + memset(&tmq.tq, 0, sizeof(tmq.tq)); + tmq.tq.opaque = opaque; + if (wsi) { + wsi->adns_cb = cb; + lws_dll2_add_head(&wsi->adns, &tmq.tq.wsi_adns); + } else + tmq.tq.standalone_cb = cb; + lws_strncpy(tmq.name, name, sizeof(tmq.name)); + + lws_dll2_add_head(&c->list, &dns->cached); + lws_sul_schedule(context, 0, &c->sul, sul_cb_expire, + lws_now_usecs() + (3600ll * LWS_US_PER_SEC)); + } + + if (m == 4) { + ai->ai_family = sa46->sa4.sin_family = AF_INET; + ai->ai_addrlen = sizeof(sa46->sa4); + ai->ai_addr = (struct sockaddr *)&sa46->sa4; + memcpy(&sa46->sa4.sin_addr, ads, m); + + lws_async_dns_complete(&tmq.tq, c); + + return LADNS_RET_FOUND; + } + +#if defined(LWS_WITH_IPV6) + if (m == 16) { + ai->ai_family = sa46->sa6.sin6_family = AF_INET6; + ai->ai_addrlen = sizeof(sa46->sa6); + ai->ai_addr = (struct sockaddr *)&sa46->sa6; + memcpy(&sa46->sa6.sin6_addr, ads, m); + + lws_async_dns_complete(&tmq.tq, c); + + return LADNS_RET_FOUND; + } +#endif + + /* + * to try anything else we need a remote server configured... + */ + + if (!context->async_dns.dns_server_set && + lws_async_dns_init(context)) { + lwsl_notice("%s: init failed\n", __func__); + goto failed; + } + + /* there's an ongoing query we can share the result of */ + + q = lws_adns_get_query(dns, qtype, &dns->waiting_send, 0, name); + if (!q) + q = lws_adns_get_query(dns, qtype, &dns->waiting_resp, 0, name); + if (q) { + lwsl_debug("%s: dns piggybacking: %d:%s\n", __func__, + qtype, name); + if (wsi) + lws_dll2_add_head(&wsi->adns, &q->wsi_adns); + + return LADNS_RET_CONTINUING; + } + + /* + * Allocate new query / queries... this is a bit complicated because + * multiple queries in one packet are not supported peoperly in DNS + * itself, and there's no reliable other way to get both ipv6 and ipv4 + * (AAAA and A) responses in one hit. + * + * If we don't support ipv6, it's simple, we just ask for A and that's + * it. But if we do support ipv6, we need to ask twice, once for A + * and in a separate query, again for AAAA. + * + * For ipv6, A / ipv4 is routable over ipv6. So we always ask for A + * first and then if ipv6, AAAA separately. + * + * The results need binding into + */ + + q = (lws_adns_q_t *)lws_zalloc(sizeof(*q) + nlen + 1, __func__); + if (!q) + goto failed; + + if (wsi) + lws_dll2_add_head(&wsi->adns, &q->wsi_adns); + + q->qtype = (uint16_t)qtype; + + if (lws_async_dns_get_new_tid(context, q)) + goto failed; + + q->tid &= 0xfffe; + q->context = context; + q->tsi = tsi; + q->opaque = opaque; + + if (!wsi) + q->standalone_cb = cb; + + lws_sul_schedule(context, tsi, &q->sul, sul_cb_timeout, + lws_now_usecs() + + (DNS_QUERY_TIMEOUT * LWS_US_PER_SEC)); + + p = (char *)&q[1]; + while (nlen--) + *p++ = tolower(*name++); + *p = '\0'; + + lws_callback_on_writable(dns->wsi); + + lws_dll2_add_head(&q->list, &dns->waiting_send); + + lwsl_debug("%s: created new query\n", __func__); + + return LADNS_RET_CONTINUING; + +failed: + cb(wsi, NULL, NULL, LADNS_RET_FAILED, opaque); + + return LADNS_RET_FAILED; +} diff --git a/lib/system/async-dns/private-lib-async-dns.h b/lib/system/async-dns/private-lib-async-dns.h new file mode 100644 index 000000000..9165fd849 --- /dev/null +++ b/lib/system/async-dns/private-lib-async-dns.h @@ -0,0 +1,110 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2019 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. + */ + + +#define DNS_MAX 128 /* Maximum host name */ +#define DNS_PACKET_LEN 1400 /* Buffer size for DNS packet */ +#define MAX_CACHE_ENTRIES 10 /* Dont cache more than that */ +#define DNS_QUERY_TIMEOUT 30 /* Query timeout, seconds */ + +/* + * ... when we completed a query then the query object is destroyed and a + * cache object below is created with the results in getaddrinfo format + * appended to the allocation + */ + +typedef struct lws_adns_cache { + lws_sorted_usec_list_t sul; /* for cache TTL management */ + lws_dll2_t list; + + struct lws_adns_cache *firstcache; + struct lws_adns_cache *chain; + struct addrinfo *results; + uint8_t flags; /* b0 = has ipv4, b1 = has ipv6 */ + char refcount; + /* name, and then result struct addrinfos overallocated here */ +} lws_adns_cache_t; + +/* + * these objects are used while a query is ongoing... + */ + +typedef struct { + lws_sorted_usec_list_t sul; /* for query network timeout */ + lws_dll2_t list; + + lws_dll2_owner_t wsi_adns; + lws_async_dns_cb_t standalone_cb; /* if not associated to wsi */ + struct lws_context *context; + void *opaque; + struct addrinfo **last; + + lws_adns_cache_t *firstcache; + + lws_async_dns_retcode_t ret; + uint16_t tid; + uint16_t qtype; + uint8_t tsi; + + uint8_t asked; + uint8_t responded; + + /* name overallocated here */ +} lws_adns_q_t; + +enum { + DHO_TID, + DHO_FLAGS = 2, + DHO_NQUERIES = 4, + DHO_NANSWERS = 6, + DHO_NAUTH = 8, + DHO_NOTHER = 10, + + DHO_SIZEOF = 12 /* last */ +}; + +void +lws_adns_q_destroy(lws_adns_q_t *q); + +void +sul_cb_expire(struct lws_sorted_usec_list *sul); + +void +lws_adns_cache_destroy(lws_adns_cache_t *c); + +int +lws_async_dns_complete(lws_adns_q_t *q, lws_adns_cache_t *c); + +lws_adns_cache_t * +lws_adns_get_cache(lws_async_dns_t *dns, const char *name); + +void +lws_adns_parse_udp(lws_async_dns_t *dns, const uint8_t *pkt, size_t len); + +lws_adns_q_t * +lws_adns_get_query(lws_async_dns_t *dns, adns_query_type_t qtype, + lws_dll2_owner_t *owner, uint16_t tid, const char *name); + +void +lws_async_dns_trim_cache(lws_async_dns_t *dns); diff --git a/minimal-examples/api-tests/api-test-async-dns/CMakeLists.txt b/minimal-examples/api-tests/api-test-async-dns/CMakeLists.txt new file mode 100644 index 000000000..2d8d0c5c3 --- /dev/null +++ b/minimal-examples/api-tests/api-test-async-dns/CMakeLists.txt @@ -0,0 +1,78 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-api-test-async-dns) +set(SRCS main.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITH_CLIENT 1 requirements) +require_lws_config(LWS_WITH_SYS_ASYNC_DNS 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/api-tests/api-test-async-dns/main.c b/minimal-examples/api-tests/api-test-async-dns/main.c new file mode 100644 index 000000000..3761b1915 --- /dev/null +++ b/minimal-examples/api-tests/api-test-async-dns/main.c @@ -0,0 +1,315 @@ +/* + * lws-api-test-async-dns + * + * Written in 2019 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This api test confirms various kinds of async dns apis + */ + +#include +#include + +static int interrupted, dtest, ok, fail, exp = 26; +struct lws_context *context; + +/* + * These are used to test the apis to parse and print ipv4 / ipv6 literal + * address strings for various cases. + * + * Expected error cases are not used to test the ip data -> string api. + */ + +static const struct ipparser_tests { + const char *test; + int rlen; + const char *emit_test; + int emit_len; + uint8_t b[16]; +} ipt[] = { + { "2001:db8:85a3:0:0:8a2e:370:7334", 16, + "2001:db8:85a3::8a2e:370:7334", 28, + { 0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, + 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34 } }, + + { "2001:db8:85a3::8a2e:370:7334", 16, + "2001:db8:85a3::8a2e:370:7334", 28, + { 0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, + 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34 } }, + + { "::1", 16, "::1", 3, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + + { "::", 16, "::", 2, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, + + { "::ffff:192.0.2.128", 16, "::ffff:192.0.2.128", 18, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x02, 0x80 } }, + + { "cats", -1, "", 0, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + + { "onevalid.bogus.warmcat.com", -1, "", 0, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + + { "1.cat.dog.com", -1, "", 0, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + + { ":::1", -8, "", 0, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + + { "0:0::0:1", 16, "::1", 3, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + + { "1.2.3.4", 4, "1.2.3.4", 7, { 1, 2, 3, 4 } }, +}; + +static const struct async_dns_tests { + const char *dns_name; + int recordtype; + int addrlen; + uint8_t ads[16]; +} adt[] = { + { "warmcat.com", LWS_ADNS_RECORD_A, 4, + { 46, 105, 127, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }, + { "libwebsockets.org", LWS_ADNS_RECORD_A, 4, + { 46, 105, 127, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }, + { "doesntexist", LWS_ADNS_RECORD_A, 0, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }, + { "localhost", LWS_ADNS_RECORD_A, 4, + { 127, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }, + { "ipv4only.warmcat.com", LWS_ADNS_RECORD_A, 4, + { 46, 105, 127, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }, + { "onevalid.bogus.warmcat.com", LWS_ADNS_RECORD_A, 4, + { 46, 105, 127, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }, +#if defined(LWS_WITH_IPV6) + { "warmcat.com", LWS_ADNS_RECORD_AAAA, 16, /* check ipv6 */ + { 0x20, 0x01, 0x41, 0xd0, 0x00, 0x02, 0xee, 0x93, + 0, 0, 0, 0, 0, 0, 0, 0, } }, + { "ipv6only.warmcat.com", LWS_ADNS_RECORD_AAAA, 16, /* check ipv6 */ + { 0x20, 0x01, 0x41, 0xd0, 0x00, 0x02, 0xee, 0x93, + 0, 0, 0, 0, 0, 0, 0, 0, } }, +#endif +}; + +static lws_sorted_usec_list_t sul; + +struct lws * +cb1(struct lws *wsi_unused, const char *ads, const struct addrinfo *a, int n, + void *opaque); + +static void +next_test_cb(lws_sorted_usec_list_t *sul) +{ + int m; + + lwsl_notice("%s: querying %s\n", __func__, adt[dtest].dns_name); + + m = lws_async_dns_query(context, 0, + adt[dtest].dns_name, + adt[dtest].recordtype, cb1, NULL, + context); + if (m != LADNS_RET_CONTINUING && m != LADNS_RET_FOUND) { + lwsl_err("%s: adns 1 failed: %d\n", __func__, m); + interrupted = 1; + } +} + + +struct lws * +cb1(struct lws *wsi_unused, const char *ads, const struct addrinfo *a, int n, + void *opaque) +{ + const struct addrinfo *ac = a; + int ctr = 0, alen; + uint8_t *addr; + char buf[64]; + + dtest++; + + if (!ac) + lwsl_warn("%s: no results\n", __func__); + + /* dump the results */ + + while (ac) { + if (ac->ai_family == AF_INET) { + addr = (uint8_t *)&(((struct sockaddr_in *) + ac->ai_addr)->sin_addr.s_addr); + alen = 4; + } else { + addr = (uint8_t *)&(((struct sockaddr_in6 *) + ac->ai_addr)->sin6_addr.s6_addr); + alen = 16; + } + strcpy(buf, "unknown"); + lws_write_numeric_address(addr, alen, buf, sizeof(buf)); + + lwsl_warn("%s: %d: %s %d %s\n", __func__, ctr++, ads, alen, buf); + + ac = ac->ai_next; + } + + ac = a; + while (ac) { + if (ac->ai_family == AF_INET) { + addr = (uint8_t *)&(((struct sockaddr_in *) + ac->ai_addr)->sin_addr.s_addr); + alen = 4; + } else { +#if defined(LWS_WITH_IPV6) + addr = (uint8_t *)&(((struct sockaddr_in6 *) + ac->ai_addr)->sin6_addr.s6_addr); + alen = 16; +#else + goto again; +#endif + } + if (alen == adt[dtest - 1].addrlen && + !memcmp(adt[dtest - 1].ads, addr, alen)) { + ok++; + goto next; + } +#if !defined(LWS_WITH_IPV6) +again: +#endif + ac = ac->ai_next; + } + + /* testing for NXDOMAIN? */ + + if (!a && !adt[dtest - 1].addrlen) { + ok++; + goto next; + } + + lwsl_err("%s: dns test %d: no match\n", __func__, dtest); + fail++; + +next: + lws_async_dns_freeaddrinfo(a); + if (dtest == (int)LWS_ARRAY_SIZE(adt)) + interrupted = 1; + else + lws_sul_schedule(context, 0, &sul, next_test_cb, 1); + + return NULL; +} + + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int +main(int argc, const char **argv) +{ + int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; + const char *p; + + /* the normal lws init */ + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS API selftest: Async DNS\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.port = CONTEXT_PORT_NO_LISTEN; + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + + /* ip address parser tests */ + + for (n = 0; n < (int)LWS_ARRAY_SIZE(ipt); n++) { + uint8_t u[16]; + int m = lws_parse_numeric_address(ipt[n].test, u, sizeof(u)); + + if (m != ipt[n].rlen) { + lwsl_err("%s: fail %s ret %d\n", + __func__, ipt[n].test, m); + fail++; + continue; + } + + if (m > 0) { + if (memcmp(ipt[n].b, u, m)) { + lwsl_err("%s: fail %s compare\n", __func__, + ipt[n].test); + lwsl_hexdump_notice(u, m); + fail++; + continue; + } + } + ok++; + } + + /* ip address formatter tests */ + + for (n = 0; n < (int)LWS_ARRAY_SIZE(ipt); n++) { + char buf[64]; + int m; + + /* don't attempt to reverse the ones that are meant to fail */ + if (ipt[n].rlen < 0) + continue; + + m = lws_write_numeric_address(ipt[n].b, ipt[n].rlen, buf, + sizeof(buf)); + if (m != ipt[n].emit_len) { + lwsl_err("%s: fail %s ret %d\n", + __func__, ipt[n].emit_test, m); + fail++; + continue; + } + + if (m > 0) { + if (strcmp(ipt[n].emit_test, buf)) { + lwsl_err("%s: fail %s compare\n", __func__, + ipt[n].test); + lwsl_hexdump_notice(buf, m); + fail++; + continue; + } + } + ok++; + } + +#if !defined(LWS_WITH_IPV6) + exp -= 2; +#endif + + /* kick off the async dns tests */ + + lws_sul_schedule(context, 0, &sul, next_test_cb, 1); + + /* the usual lws event loop */ + + n = 1; + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + + if (fail || ok != exp) + lwsl_user("Completed: PASS: %d / %d, FAIL: %d\n", ok, exp, + fail); + else + lwsl_user("Completed: ALL PASS: %d / %d\n", ok, exp); + + return !(ok == exp && !fail); +} diff --git a/minimal-examples/api-tests/api-test-async-dns/selftest.sh b/minimal-examples/api-tests/api-test-async-dns/selftest.sh new file mode 100755 index 000000000..16d1e2e8e --- /dev/null +++ b/minimal-examples/api-tests/api-test-async-dns/selftest.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# $1: path to minimal example binaries... +# if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1 +# that will be ./bin from your build dir +# +# $2: path for logs and results. The results will go +# in a subdir named after the directory this script +# is in +# +# $3: offset for test index count +# +# $4: total test count +# +# $5: path to ./minimal-examples dir in lws +# +# Test return code 0: OK, 254: timed out, other: error indication + +. $5/selftests-library.sh + +COUNT_TESTS=1 + +dotest $1 $2 apiselftest +exit $FAILS diff --git a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c index de348f6ca..ffd1070fb 100644 --- a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c +++ b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c @@ -307,6 +307,9 @@ int main(int argc, const char **argv) if (lws_cmdline_option(argc, argv, "-n")) numbered = 1; + if ((p = lws_cmdline_option(argc, argv, "--server"))) + i.address = p; + if ((p = lws_cmdline_option(argc, argv, "--port"))) i.port = atoi(p); diff --git a/minimal-examples/raw/minimal-raw-adopt-udp/CMakeLists.txt b/minimal-examples/raw/minimal-raw-adopt-udp/CMakeLists.txt index cc6ac629c..aad6f0333 100644 --- a/minimal-examples/raw/minimal-raw-adopt-udp/CMakeLists.txt +++ b/minimal-examples/raw/minimal-raw-adopt-udp/CMakeLists.txt @@ -63,6 +63,8 @@ ENDMACRO() set(requirements 1) require_lws_config(LWS_WITH_SERVER 1 requirements) +require_lws_config(LWS_WITH_CLIENT 1 requirements) + if (requirements) add_executable(${SAMP} ${SRCS}) diff --git a/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c b/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c index 5689563b5..ae28835fd 100644 --- a/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c +++ b/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c @@ -169,7 +169,7 @@ int main(int argc, const char **argv) /* * Create our own "foreign" UDP socket bound to 7681/udp */ - if (!lws_create_adopt_udp(vhost, 7681, LWS_CAUDP_BIND, + if (!lws_create_adopt_udp(vhost, NULL, 7681, LWS_CAUDP_BIND, protocols[0].name, NULL)) { lwsl_err("%s: foreign socket adoption failed\n", __func__); goto bail;