diff --git a/CMakeLists-implied-options.txt b/CMakeLists-implied-options.txt index 975a231f1..5f56d867a 100644 --- a/CMakeLists-implied-options.txt +++ b/CMakeLists-implied-options.txt @@ -416,3 +416,7 @@ if (WIN32 AND NOT LWS_EXT_PTHREAD_LIBRARIES) set(LWS_WITH_SYS_SMD 0) endif() +if (LWS_WITH_RFC6724 AND (NOT LWS_WITH_CLIENT OR NOT LWS_IPV6)) + message(FATAL_ERROR "RFC6724 requires CLIENT and LWS_IPV6") +endif() + diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d21bab48..9fd4859a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,11 @@ option(LWS_WITH_HTTP_BASIC_AUTH "Support Basic Auth" ON) option(LWS_WITH_HTTP_UNCOMMON_HEADERS "Include less common http header support" ON) option(LWS_WITH_SYS_STATE "lws_system state support" ON) option(LWS_WITH_SYS_SMD "Lws System Message Distribution" ON) +if (LWS_IPV6) + option(LWS_WITH_RFC6724 "Enable RFC6724 DNS result sorting" ON) +else() + option(LWS_WITH_RFC6724 "Enable RFC6724 DNS result sorting" OFF) +endif() # # Secure Streams diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 05e183200..7492d71d9 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -166,6 +166,7 @@ #cmakedefine LWS_WITH_POLARSSL #cmakedefine LWS_WITH_POLL #cmakedefine LWS_WITH_RANGES +#cmakedefine LWS_WITH_RFC6724 #cmakedefine LWS_WITH_SECURE_STREAMS #cmakedefine LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM #cmakedefine LWS_WITH_SECURE_STREAMS_PROXY_API diff --git a/include/libwebsockets/lws-adopt.h b/include/libwebsockets/lws-adopt.h index e6caf0087..73cfdf45a 100644 --- a/include/libwebsockets/lws-adopt.h +++ b/include/libwebsockets/lws-adopt.h @@ -86,27 +86,38 @@ typedef union { typedef union { #if defined(LWS_WITH_IPV6) struct sockaddr_in6 sa6; +#else +#if defined(LWS_ESP_PLATFORM) + uint8_t _pad_sa6[28]; +#endif #endif struct sockaddr_in sa4; } lws_sockaddr46; #define sa46_sockaddr(_sa46) ((struct sockaddr *)(_sa46)) -#define sa46_address(_sa46) ((uint8_t *)((_sa46)->sa4.sin_family == AF_INET ? \ - &_sa46->sa4.sin_addr : &_sa46->sa6.sin6_addr )) - -#define sa46_address_len(_sa46) ((_sa46)->sa4.sin_family == AF_INET ? 4 : 16) - -#define sa46_socklen(_sa46) ((_sa46)->sa4.sin_family == AF_INET ? \ +#if defined(LWS_WITH_IPV6) +#define sa46_socklen(_sa46) (socklen_t)((_sa46)->sa4.sin_family == AF_INET ? \ sizeof(struct sockaddr_in) : \ sizeof(struct sockaddr_in6)) +#define sa46_sockport(_sa46, _sp) { if ((_sa46)->sa4.sin_family == AF_INET) \ + (_sa46)->sa4.sin_port = (_sp); else \ + (_sa46)->sa6.sin6_port = (_sp); } +#define sa46_address(_sa46) ((uint8_t *)((_sa46)->sa4.sin_family == AF_INET ? \ + &_sa46->sa4.sin_addr : &_sa46->sa6.sin6_addr )) +#else +#define sa46_socklen(_sa46) (socklen_t)sizeof(struct sockaddr_in) +#define sa46_sockport(_sa46, _sp) (_sa46)->sa4.sin_port = (_sp) +#define sa46_address(_sa46) (uint8_t *)&_sa46->sa4.sin_addr +#endif + +#define sa46_address_len(_sa46) ((_sa46)->sa4.sin_family == AF_INET ? 4 : 16) #if defined(LWS_WITH_UDP) struct lws_udp { lws_sockaddr46 sa46; lws_sockaddr46 sa46_pending; }; - #endif /** diff --git a/include/libwebsockets/lws-async-dns.h b/include/libwebsockets/lws-async-dns.h index 7387ca0f9..dc8b417da 100644 --- a/include/libwebsockets/lws-async-dns.h +++ b/include/libwebsockets/lws-async-dns.h @@ -43,8 +43,7 @@ typedef enum { struct addrinfo; typedef struct lws * (*lws_async_dns_cb_t)(struct lws *wsi, const char *ads, - const struct addrinfo *result, int n, - void *opaque); + const struct addrinfo *result, int n, void *opaque); /** * lws_async_dns_query() - perform a dns lookup using async dns diff --git a/include/libwebsockets/lws-dll2.h b/include/libwebsockets/lws-dll2.h index 151e7fcd1..a4542aa25 100644 --- a/include/libwebsockets/lws-dll2.h +++ b/include/libwebsockets/lws-dll2.h @@ -245,6 +245,11 @@ LWS_VISIBLE LWS_EXTERN void lws_dll2_add_sorted(lws_dll2_t *d, lws_dll2_owner_t *own, int (*compare)(const lws_dll2_t *d, const lws_dll2_t *i)); +LWS_VISIBLE LWS_EXTERN void +lws_dll2_add_sorted_priv(lws_dll2_t *d, lws_dll2_owner_t *own, void *priv, + int (*compare3)(void *priv, const lws_dll2_t *d, + const lws_dll2_t *i)); + LWS_VISIBLE LWS_EXTERN void * _lws_dll2_search_sz_pl(lws_dll2_owner_t *own, const char *name, size_t namelen, size_t dll2_ofs, size_t ptr_ofs); diff --git a/include/libwebsockets/lws-network-helper.h b/include/libwebsockets/lws-network-helper.h index cac5b9b54..7758ed6c4 100644 --- a/include/libwebsockets/lws-network-helper.h +++ b/include/libwebsockets/lws-network-helper.h @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 - 2019 Andy Green + * Copyright (C) 2010 - 2020 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -67,6 +67,13 @@ typedef struct lws_route { uint8_t source_ads:1; } lws_route_t; +/* + * We reuse the route object as the dns sort granule, so there's only one + * struct needs to know all the gnarly ipv6 details + */ + +typedef lws_route_t lws_dns_sort_t; + /** * lws_canonical_hostname() - returns this host's hostname * diff --git a/lib/core-net/CMakeLists.txt b/lib/core-net/CMakeLists.txt index f9a30c470..e401506d8 100644 --- a/lib/core-net/CMakeLists.txt +++ b/lib/core-net/CMakeLists.txt @@ -73,6 +73,7 @@ if (LWS_WITH_CLIENT) core-net/client/connect2.c core-net/client/connect3.c core-net/client/connect4.c + core-net/client/sort-dns.c ) endif() diff --git a/lib/core-net/adopt.c b/lib/core-net/adopt.c index ced90ca62..59908b82e 100644 --- a/lib/core-net/adopt.c +++ b/lib/core-net/adopt.c @@ -610,18 +610,21 @@ bail: #if defined(LWS_WITH_UDP) #if defined(LWS_WITH_CLIENT) + +/* + * This is the ASYNC_DNS callback target for udp client, it's analogous to + * connect3() + */ + 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; - int bc = 1; + int bc = 1, m; assert(wsi); - if (!wsi->dns_results) - wsi->dns_results_next = wsi->dns_results = r; - if (ads && (n < 0 || !r)) { /* * DNS lookup failed: there are no usable results. Fail the @@ -629,16 +632,28 @@ lws_create_adopt_udp2(struct lws *wsi, const char *ads, */ lwsl_notice("%s: bad: n %d, r %p\n", __func__, n, r); - /* - * We didn't get a callback on a cache item and bump the - * refcount. So don't let the cleanup continue to think it - * needs to decrement any refcount. - */ - wsi->dns_results_next = wsi->dns_results = NULL; goto bail; } - while (wsi->dns_results_next) { + m = lws_sort_dns(wsi, r); +#if defined(LWS_WITH_SYS_ASYNC_DNS) + lws_async_dns_freeaddrinfo(&r); +#else + freeaddrinfo((struct addrinfo *)r); +#endif + if (m) + goto bail; + + while (lws_dll2_get_head(&wsi->dns_sorted_list)) { + lws_dns_sort_t *s = lws_container_of( + lws_dll2_get_head(&wsi->dns_sorted_list), + lws_dns_sort_t, list); + + /* + * Remove it from the head, but don't free it yet... we are + * taking responsibility to free it + */ + lws_dll2_remove(&s->list); /* * We have done the dns lookup, identify the result we want @@ -651,30 +666,35 @@ lws_create_adopt_udp2(struct lws *wsi, const char *ads, */ #if !defined(__linux__) - /* PF_PACKET is linux-only */ - sock.sockfd = socket(wsi->dns_results_next->ai_family, + sock.sockfd = socket(s->dest.sa4.sin_family, SOCK_DGRAM, IPPROTO_UDP); #else + /* PF_PACKET is linux-only */ sock.sockfd = socket(wsi->pf_packet ? PF_PACKET : - wsi->dns_results_next->ai_family, + s->dest.sa4.sin_family, SOCK_DGRAM, wsi->pf_packet ? htons(0x800) : IPPROTO_UDP); #endif if (sock.sockfd == LWS_SOCK_INVALID) goto resume; - ((struct sockaddr_in *)wsi->dns_results_next->ai_addr)->sin_port = - htons(wsi->c_port); + /* ipv6 udp!!! */ - if (setsockopt(sock.sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&bc, - sizeof(bc)) < 0) + if (s->af == AF_INET) + s->dest.sa4.sin_port = htons(wsi->c_port); +#if defined(LWS_WITH_IPV6) + else + s->dest.sa6.sin6_port = htons(wsi->c_port); +#endif + + if (setsockopt(sock.sockfd, SOL_SOCKET, SO_REUSEADDR, + (const char *)&bc, sizeof(bc)) < 0) lwsl_err("%s: failed to set reuse\n", __func__); if (wsi->do_broadcast && - setsockopt(sock.sockfd, SOL_SOCKET, SO_BROADCAST, (const char *)&bc, - sizeof(bc)) < 0) - lwsl_err("%s: failed to set broadcast\n", - __func__); + setsockopt(sock.sockfd, SOL_SOCKET, SO_BROADCAST, + (const char *)&bc, sizeof(bc)) < 0) + lwsl_err("%s: failed to set broadcast\n", __func__); /* Bind the udp socket to a particular network interface */ @@ -683,52 +703,53 @@ lws_create_adopt_udp2(struct lws *wsi, const char *ads, goto resume; if (wsi->do_bind && - bind(sock.sockfd, wsi->dns_results_next->ai_addr, + bind(sock.sockfd, sa46_sockaddr(&s->dest), #if defined(_WIN32) - (int)wsi->dns_results_next->ai_addrlen + (int)sa46_socklen(&s->dest) #else - sizeof(struct sockaddr)//wsi->dns_results_next->ai_addrlen + sizeof(struct sockaddr) #endif - ) == -1) { + ) == -1) { lwsl_err("%s: bind failed\n", __func__); goto resume; } if (!wsi->do_bind && !wsi->pf_packet) { #if !defined(__APPLE__) - if (connect(sock.sockfd, wsi->dns_results_next->ai_addr, - (socklen_t)wsi->dns_results_next->ai_addrlen) == -1) { + if (connect(sock.sockfd, sa46_sockaddr(&s->dest), + sa46_socklen(&s->dest)) == -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, + "errno %d\n", __func__, sock.sockfd, + s->dest.sa4.sin_family, ads ? ads : "null", wsi->c_port, - (int)wsi->dns_results_next->ai_addrlen, LWS_ERRNO); compatible_close(sock.sockfd); goto resume; } #endif - memcpy(&wsi->udp->sa46, - wsi->dns_results_next->ai_addr, - wsi->dns_results_next->ai_addrlen); } + if (wsi->udp) + wsi->udp->sa46 = s->dest; + wsi->sa46_peer = s->dest; + /* we connected: complete the udp socket adoption flow */ + lws_free(s); 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; + lws_free(s); } lwsl_err("%s: unable to create INET socket %d\n", __func__, LWS_ERRNO); lws_addrinfo_clean(wsi); bail: - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "adopt udp2 fail"); + + /* caller must close */ return NULL; } @@ -747,12 +768,15 @@ lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port, /* create the logical wsi without any valid fd */ - wsi = lws_adopt_descriptor_vhost1(vhost, LWS_ADOPT_RAW_SOCKET_UDP, + wsi = lws_adopt_descriptor_vhost1(vhost, LWS_ADOPT_SOCKET | LWS_ADOPT_RAW_SOCKET_UDP, protocol_name, parent_wsi, opaque); if (!wsi) { lwsl_err("%s: udp wsi creation failed\n", __func__); goto bail; } + + // lwsl_notice("%s: role %s\n", __func__, wsi->role_ops->name); + wsi->do_bind = !!(flags & LWS_CAUDP_BIND); wsi->do_broadcast = !!(flags & LWS_CAUDP_BROADCAST); wsi->pf_packet = !!(flags & LWS_CAUDP_PF_PACKET); @@ -814,8 +838,9 @@ lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port, */ n = lws_async_dns_query(vhost->context, 0, ads, LWS_ADNS_RECORD_A, - lws_create_adopt_udp2, wsi, (void *)ifname); - lwsl_debug("%s: dns query returned %d\n", __func__, n); + lws_create_adopt_udp2, wsi, + (void *)ifname); + // lwsl_notice("%s: dns query returned %d\n", __func__, n); if (n == LADNS_RET_FAILED) { lwsl_err("%s: async dns failed\n", __func__); wsi = NULL; @@ -832,7 +857,7 @@ lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port, /* dns lookup is happening asynchronously */ - lwsl_debug("%s: returning wsi %p\n", __func__, wsi); + // lwsl_notice("%s: returning wsi %p\n", __func__, wsi); return wsi; #endif #if !defined(LWS_WITH_SYS_ASYNC_DNS) diff --git a/lib/core-net/client/connect2.c b/lib/core-net/client/connect2.c index 2bff0f222..48cc9ffb1 100644 --- a/lib/core-net/client/connect2.c +++ b/lib/core-net/client/connect2.c @@ -285,10 +285,13 @@ solo: wsi->detlat.earliest_write_req_pre_write = lws_now_usecs(); #endif #if !defined(LWS_WITH_SYS_ASYNC_DNS) - if (wsi->dns_results) - n = 0; - else + n = 0; + if (!wsi->dns_sorted_list.count) { + /* + * blocking dns resolution + */ n = lws_getaddrinfo46(wsi, ads, &result); + } #else lwsi_set_state(wsi, LRS_WAITING_DNS); /* this is either FAILED, CONTINUING, or already called connect_4 */ diff --git a/lib/core-net/client/connect3.c b/lib/core-net/client/connect3.c index a9abddedb..bd1975e91 100644 --- a/lib/core-net/client/connect3.c +++ b/lib/core-net/client/connect3.c @@ -108,21 +108,36 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, { #if defined(LWS_WITH_UNIX_SOCK) struct sockaddr_un sau; -#endif -#ifdef LWS_WITH_IPV6 - char ipv6only = lws_check_opt(wsi->a.vhost->options, - LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | - LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); #endif struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; const struct sockaddr *psa = NULL; uint16_t port = wsi->c_port; const char *cce, *iface; - lws_sockaddr46 sa46; + lws_dns_sort_t *curr; ssize_t plen = 0; + lws_dll2_t *d; int m; - if (n == LWS_CONNECT_COMPLETION_GOOD) + /* + * If we come here with result set, we need to convert getaddrinfo + * results to a lws_dns_sort_t list one time and free the results. + * + * We use this pattern because ASYNC_DNS will callback here with the + * results when it gets them (and may come here more than once, eg, for + * AAAA then A or vice-versa) + */ + + if (result) { + lws_sort_dns(wsi, result); +#if defined(LWS_WITH_SYS_ASYNC_DNS) + lws_async_dns_freeaddrinfo(&result); +#else + freeaddrinfo((struct addrinfo *)result); +#endif + result = NULL; + } + + if (n == LWS_CONNECT_COMPLETION_GOOD) goto conn_good; #if defined(LWS_WITH_IPV6) && defined(__ANDROID__) @@ -138,6 +153,16 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, if (!lws_dll2_is_detached(&wsi->dll2_cli_txn_queue)) return wsi; + if (n && /* calling back with a problem */ + !wsi->dns_sorted_list.count && /* there's no results */ + !lws_socket_is_valid(wsi->desc.sockfd) && /* no attempt ongoing */ + !wsi->speculative_connect_owner.count /* no spec attempt */ ) { + lwsl_notice("%s: dns lookup failed %d\n", __func__, n); + + cce = "dns lookup failed"; + goto oom4; + } + /* * We come back here again when we think the connect() may have * completed one way or the other, we can't proceed until we know we @@ -147,7 +172,8 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, if (lwsi_state(wsi) == LRS_WAITING_CONNECT && lws_socket_is_valid(wsi->desc.sockfd)) { - if (!result && !wsi->sul_connect_timeout.list.owner) + if (!wsi->dns_sorted_list.count && + !wsi->sul_connect_timeout.list.owner) /* no dns results and no ongoing timeout for one */ goto connect_to; @@ -169,7 +195,7 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, #if defined(LWS_WITH_UNIX_SOCK) if (ads && *ads == '+') { ads++; - memset(&sa46, 0, sizeof(sa46)); + memset(&wsi->sa46_peer, 0, sizeof(wsi->sa46_peer)); memset(&sau, 0, sizeof(sau)); sau.sun_family = AF_UNIX; strncpy(sau.sun_path, ads, sizeof(sau.sun_path)); @@ -195,13 +221,6 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, } #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_WITH_DETAILED_LATENCY) if (lwsi_state(wsi) == LRS_WAITING_DNS && wsi->a.context->detailed_latency_cb) { @@ -214,105 +233,47 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, wsi->detlat.earliest_write_req_pre_write = lws_now_usecs(); } #endif -#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->a.vhost->http.http_proxy_port) { - port = wsi->a.vhost->http.http_proxy_port; -#else - if (0) { -#endif - -#if defined(LWS_WITH_SOCKS5) - - /* - * Priority 2: Connect to SOCK5 Proxy - */ - - } else if (wsi->a.vhost->socks_proxy_port) { - if (lws_socks5c_generate_msg(wsi, SOCKS_MSG_GREETING, &plen)) { - cce = "socks msg too large"; - goto oom4; - } - - lwsl_client("Sending SOCKS Greeting\n"); - ads = wsi->a.vhost->socks_proxy_address; - port = wsi->a.vhost->socks_proxy_port; -#endif - } - - 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 directly connecting to each of the results in turn until * one works, or we run out of results... + * + * We have a sorted dll2 list with the head one most preferable */ next_dns_result: + if (!wsi->dns_sorted_list.count) + goto failed1; + /* - * Make a possibly 4->6 adapted copy of the next dns result in sa46 + * Copy the wsi head sorted dns result into the wsi->sa46_peer, and + * remove and free the original from the sorted list */ - psa = (const struct sockaddr *)&sa46; - n = sizeof(struct sockaddr_in6); - memset(&sa46, 0, sizeof(sa46)); + d = lws_dll2_get_head(&wsi->dns_sorted_list); + curr = lws_container_of(d, lws_dns_sort_t, list); - switch (wsi->dns_results_next->ai_family) { - case AF_INET: -#if defined(LWS_WITH_IPV6) - if (ipv6only) { - lws_sa46_4to6(&sa46, &((struct sockaddr_in *) - wsi->dns_results_next->ai_addr)->sin_addr, - port); - break; - } + lws_dll2_remove(&curr->list); + wsi->sa46_peer = curr->dest; +#if defined(LWS_WITH_NETLINK) + wsi->peer_route_uidx = curr->uidx; + lwsl_info("%s: peer_route_uidx %d\n", __func__, wsi->peer_route_uidx); #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; - sa46.sa4.sin_port = htons(port); - n = sizeof(struct sockaddr_in); - break; - case AF_INET6: -#if defined(LWS_WITH_IPV6) - if (!wsi->ipv6) - goto try_next_dns_result; - lws_sa46_copy_address(&sa46, &((struct sockaddr_in6 *) - wsi->dns_results_next->ai_addr)-> - sin6_addr, AF_INET6); + lws_free(curr); - 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(port); -#else - goto try_next_dns_result; /* ipv4 only can't use this */ -#endif - break; - } + sa46_sockport(&wsi->sa46_peer, htons(port)); + + psa = sa46_sockaddr(&wsi->sa46_peer); + n = sa46_socklen(&wsi->sa46_peer); #if defined(LWS_WITH_UNIX_SOCK) ads_known: #endif /* - * Now we prepared sa46, if not already connecting, create the related + * Now we prepared psa, if not already connecting, create the related * socket and add to the fds */ @@ -330,7 +291,7 @@ ads_known: wsi->desc.sockfd = socket(AF_UNIX, SOCK_STREAM, 0); else #endif - wsi->desc.sockfd = socket(sa46.sa4.sin_family, + wsi->desc.sockfd = socket(wsi->sa46_peer.sa4.sin_family, SOCK_STREAM, 0); if (!lws_socket_is_valid(wsi->desc.sockfd)) { @@ -457,12 +418,19 @@ ads_known: */ #if defined(_DEBUG) +#if defined(LWS_WITH_UNIX_SOCK) + if (!wsi->unix_skt) { +#endif + char nads[48]; - lws_sa46_write_numeric_address(&sa46, nads, + lws_sa46_write_numeric_address(&wsi->sa46_peer, nads, sizeof(nads)); lwsl_info("%s: Connect failed: %s port %d\n", __func__, nads, port); +#if defined(LWS_WITH_UNIX_SOCK) + } +#endif #endif goto try_next_dns_result_fds; @@ -506,7 +474,6 @@ conn_good: */ lws_sul_cancel(&wsi->sul_connect_timeout); - lwsl_info("%s: Connection started %p\n", __func__, wsi->dns_results); #if defined(LWS_WITH_DETAILED_LATENCY) if (wsi->a.context->detailed_latency_cb) { @@ -585,11 +552,9 @@ try_next_dns_result_closesock: try_next_dns_result: lws_sul_cancel(&wsi->sul_connect_timeout); - if (wsi->dns_results_next) { - wsi->dns_results_next = wsi->dns_results_next->ai_next; - if (wsi->dns_results_next) - goto next_dns_result; - } + if (lws_dll2_get_head(&wsi->dns_sorted_list)) + goto next_dns_result; + lws_addrinfo_clean(wsi); cce = "Unable to connect"; lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce)); diff --git a/lib/core-net/client/sort-dns.c b/lib/core-net/client/sort-dns.c new file mode 100644 index 000000000..31643cfd2 --- /dev/null +++ b/lib/core-net/client/sort-dns.c @@ -0,0 +1,774 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * + * Either the libc getaddrinfo() or ASYNC_DNS provides a chain of addrinfo, + * we use lws_sort_dns() to convert it to an lws_dll2 of lws_dns_sort_t, after + * which the addrinfo results are freed. + * + * If the system has no routing table info (from, eg, NETLINK), then that's + * it the sorted results are bound to the wsi and used. + * + * If the system has routing table info, we study the routing table and the + * DNS results in order to sort the lws_dns_sort_t result linked-list into + * most desirable at the head, and strip results we can't see a way to route. + */ + +#include "private-lib-core.h" + +#if defined(__linux__) +#include +#endif + +#if defined(__FreeBSD__) +#include +#include +#endif + +#if defined(LWS_WITH_IPV6) && defined(LWS_WITH_NETLINK) + +/* + * RFC6724 default policy table + * + * Prefix Precedence Label + * ::1/128 50 0 + * ::/0 40 1 + * ::ffff:0:0/96 35 4 (override prec to 100 to prefer ipv4) + * 2002::/16 30 2 + * 2001::/32 5 5 + * fc00::/7 3 13 + * ::/96 1 3 + * fec0::/10 1 11 + * 3ffe::/16 1 12 + * + * implemented using offsets into a combined 40-byte table below + */ + +static const uint8_t ma[] = { + /* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + /* 16 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, + /* 28 */ 0x20, 0x02, + /* 30 */ 0x20, 0x01, 0x00, 0x00, + /* 34 */ 0xfc, 0x00, + /* 36 */ 0xfe, 0xc0, + /* 38 */ 0x3f, 0xfe +}; + +static const uint8_t frac[] = { + 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe +}; + +/* 9 x 4 byte = 36 byte policy index table */ + +static const struct score_policy { + uint8_t ma_ofs; + uint8_t prefix; + lws_dns_score_t score; +} rfc6724_policy[] = { + + { 0, 128, { 50, 0 } }, /* ::1/128 */ + { 0, 0, { 40, 1 } }, /* ::0 */ +#if 1 + /* favour ipv6 as a general policy */ + { 16, 96, { 35, 4 } }, /* ::ffff:0:0/96 */ +#else + /* favour ipv4 as a general policy */ + { 16, 96, { 100, 4 } }, /* ::ffff:0:0/96 */ +#endif + { 28, 16, { 30, 2 } }, /* 2002::/16 */ + { 30, 32, { 5, 5 } }, /* 2001::/32 */ + { 34, 7, { 3, 13 } }, /* fc00::/7 */ + { 0, 96, { 1, 3 } }, /* ::/96 */ + { 36, 10, { 1, 11 } }, /* fec0::/10 */ + { 38, 16, { 1, 12 } }, /* 3ffe::/16 */ + +}; + +static int +lws_ipv6_prefix_match_len(const struct sockaddr_in6 *a, + const struct sockaddr_in6 *b) +{ + const uint8_t *ads_a = (uint8_t *)&a->sin6_addr, + *ads_b = (uint8_t *)&b->sin6_addr; + int n = 0, match = 0; + + for (n = 0; n < 16; n++) { + if (ads_a[n] == ads_b[n]) + match += 8; + else + break; + } + + if (match != 128) { + int m; + + for (m = 1; m < 8; m++) { + if ((ads_a[n] & frac[m]) == (ads_b[n] & frac[m])) + match++; + else + break; + } + } + + return match; +} + +static int +lws_ipv6_unicast_scope(const struct sockaddr_in6 *sa) +{ + uint64_t *u; + + u = (uint64_t *)&sa->sin6_addr; + if (*u == 0xfe80000000000000ull) + return 2; /* link-local */ + + return 0xe; +} + +static int +lws_sort_dns_scope(lws_sockaddr46 *sa46) +{ + if (sa46->sa4.sin_family == AF_INET) { + uint8_t *p = (uint8_t *)&sa46->sa4.sin_addr; + + /* RFC6724 3.2 */ + + if (p[0] == 127 || (p[0] == 169 && p[1] == 254)) + return 2; /* link-local */ + + return 0xe; /* global */ + } + + return lws_ipv6_unicast_scope(&sa46->sa6); +} + +static int +lws_sort_dns_classify(lws_sockaddr46 *sa46, lws_dns_score_t *score) +{ + const struct score_policy *pol = rfc6724_policy; + const uint8_t *p, *po; + lws_sockaddr46 s; + int n, m; + + if (sa46->sa4.sin_family == AF_INET) { + memset(&s, 0, sizeof(s)); + s.sa6.sin6_family = AF_INET6; + lws_4to6((uint8_t *)s.sa6.sin6_addr.s6_addr, + (const uint8_t *)&sa46->sa4.sin_addr); + + /* use the v6 version of the v4 address */ + sa46 = &s; + } + + for (n = 0; n < (int)LWS_ARRAY_SIZE(rfc6724_policy); n++) { + po = (uint8_t *)&sa46->sa6.sin6_addr.s6_addr; + p = &ma[pol->ma_ofs]; + for (m = 0; m < pol->prefix >> 3; m++) + if (*p++ != *po++) + goto next; + + if ((pol->prefix & 7) && (*p & frac[pol->prefix & 7]) != + (*po & frac[pol->prefix & 7])) + goto next; + + *score = pol->score; + + return 0; + +next: + pol++; + } + + return 1; +} + + +enum { + SAS_PREFER_A = 1, + SAS_SAME = 0, + SAS_PREFER_B = -1 +}; + +/* ifa is laid out with types for ipv4, if it's AF_INET6 case to sockaddr_in6 */ +#define to_v6_sa(x) ((struct sockaddr_in6 *)x) +#define to_sa46_sa(x) ((lws_sockaddr46 *)x) + +/* + * The source address selection algorithm produces as output a single + * source address for use with a given destination address. This + * algorithm only applies to IPv6 destination addresses, not IPv4 + * addresses. + * + * This implements RFC6724 Section 5. + * + * Either or both sa and sb can be dest or gateway routes + */ + +static int +lws_sort_dns_scomp(struct lws_context_per_thread *pt, const lws_route_t *sa, + const lws_route_t *sb, const struct sockaddr_in6 *dst) +{ + const struct sockaddr_in6 *sa6 = to_v6_sa(&sa->dest), + *sb6 = to_v6_sa(&sb->dest); + lws_dns_score_t scorea, scoreb, scoredst; + int scopea, scopeb, scoped, mla, mlb; + lws_route_t *rd; + + if (!sa->dest.sa4.sin_family) + sa6 = to_v6_sa(&sa->gateway); + if (!sb->dest.sa4.sin_family) + sb6 = to_v6_sa(&sb->gateway); + + /* + * We shouldn't come here unless sa and sb both have AF_INET6 addresses + */ + + assert(sa6->sin6_family == AF_INET6); + assert(sb6->sin6_family == AF_INET6); + + /* + * Rule 1: Prefer same address. + * If SA = D, then prefer SA. Similarly, if SB = D, then prefer SB. + */ + + if (!memcmp(&sa6->sin6_addr, &dst->sin6_addr, 16)) + return SAS_PREFER_A; + if (!memcmp(&sb6->sin6_addr, &dst->sin6_addr, 16)) + return SAS_PREFER_B; + + /* + * Rule 2: Prefer appropriate scope. + * If Scope(SA) < Scope(SB): If Scope(SA) < Scope(D), then prefer SB + * and otherwise prefer SA. + * + * Similarly, if Scope(SB) < Scope(SA): If Scope(SB) < Scope(D), then + * prefer SA and otherwise prefer SB. + */ + + scopea = lws_sort_dns_scope(to_sa46_sa(sa6)); + scopeb = lws_sort_dns_scope(to_sa46_sa(sb6)); + scoped = lws_sort_dns_scope(to_sa46_sa(dst)); + + if (scopea < scopeb) + return scopea < scoped ? SAS_PREFER_B : SAS_PREFER_A; + + if (scopeb < scopea) + return scopeb < scoped ? SAS_PREFER_A : SAS_PREFER_B; + + /* + * Rule 3: Avoid deprecated addresses. + * If one of the two source addresses is "preferred" and one of them + * is "deprecated" (in the RFC 4862 sense), then prefer the one that + * is "preferred". + */ + + if (!(sa->ifa_flags & IFA_F_DEPRECATED) && + (sb->ifa_flags & IFA_F_DEPRECATED)) + return SAS_PREFER_A; + + if ( (sa->ifa_flags & IFA_F_DEPRECATED) && + !(sb->ifa_flags & IFA_F_DEPRECATED)) + return SAS_PREFER_B; + + /* + * Rule 4: Prefer home addresses. + * If SA is simultaneously a home address and care-of address and SB is + * not, then prefer SA. Similarly, if SB is simultaneously a home + * address and care-of address and SA is not, then prefer SB. If SA is + * just a home address and SB is just a care-of address, then prefer SA. + * Similarly, if SB is just a home address and SA is just a care-of + * address, then prefer SB. + * + * !!! not sure how to determine if care-of address + */ + + if ( (sa->ifa_flags & IFA_F_HOMEADDRESS) && + !(sb->ifa_flags & IFA_F_HOMEADDRESS)) + return SAS_PREFER_A; + + if (!(sa->ifa_flags & IFA_F_HOMEADDRESS) && + (sb->ifa_flags & IFA_F_HOMEADDRESS)) + return SAS_PREFER_B; + + /* + * Rule 5: Prefer outgoing interface. + * If SA is assigned to the interface that will be used to send to D + * and SB is assigned to a different interface, then prefer SA. + * Similarly, if SB is assigned to the interface that will be used + * to send to D and SA is assigned to a different interface, then + * prefer SB. + */ + + rd = _lws_route_est_outgoing(pt, (lws_sockaddr46 *)dst); + if (rd) { + if (rd->if_idx == sa->if_idx) + return SAS_PREFER_A; + if (rd->if_idx == sb->if_idx) + return SAS_PREFER_B; + } + + /* + * Rule 6: Prefer matching label. + * If Label(SA) = Label(D) and Label(SB) <> Label(D), then prefer SA. + * Similarly, if Label(SB) = Label(D) and Label(SA) <> Label(D), then + * prefer SB. + */ + + lws_sort_dns_classify(to_sa46_sa(sa6), &scorea); + lws_sort_dns_classify(to_sa46_sa(sb6), &scoreb); + lws_sort_dns_classify(to_sa46_sa(dst), &scoredst); + + if (scorea.label == scoredst.label && scoreb.label != scoredst.label) + return SAS_PREFER_A; + if (scoreb.label == scoredst.label && scorea.label != scoredst.label) + return SAS_PREFER_B; + + /* + * Rule 7: Prefer temporary addresses. + * If SA is a temporary address and SB is a public address, then + * prefer SA. Similarly, if SB is a temporary address and SA is a + * public address, then prefer SB. + */ + + if ( (sa->ifa_flags & IFA_F_TEMPORARY) && + !(sb->ifa_flags & IFA_F_TEMPORARY)) + return SAS_PREFER_A; + + if (!(sa->ifa_flags & IFA_F_TEMPORARY) && + (sb->ifa_flags & IFA_F_TEMPORARY)) + return SAS_PREFER_B; + + /* + * Rule 8: Use longest matching prefix. + * If CommonPrefixLen(SA, D) > CommonPrefixLen(SB, D), then prefer SA. + * Similarly, if CommonPrefixLen(SB, D) > CommonPrefixLen(SA, D), then + * prefer SB. + */ + + mla = lws_ipv6_prefix_match_len(sa6, dst); + mlb = lws_ipv6_prefix_match_len(sb6, dst); + + if (mla > mlb) + return SAS_PREFER_A; + + return SAS_SAME; +} + +/* + * Given two possible source addresses and the destination address, we attempt + * to pick which one is "better". + * + * This implements RFC6724 Section 6. + */ + +static int +lws_sort_dns_dcomp(const lws_dns_sort_t *da, const lws_dns_sort_t *db) +{ + int scopea, scopeb, scope_srca, scope_srcb, cpla, cplb; + const uint8_t *da_ads = (const uint8_t *)&da->dest.sa6.sin6_addr, + *db_ads = (const uint8_t *)&db->dest.sa6.sin6_addr; + lws_dns_score_t score_srca, score_srcb; + + /* + * Rule 1: Avoid unusable destinations + * + * We already strip destinations with no usable source + */ + + /* + * Rule 2: Prefer matching scope + * + * If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)), + * then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and + * Scope(DB) = Scope(Source(DB)), then prefer DB. + */ + + scopea = lws_ipv6_unicast_scope(to_v6_sa(&da->dest)); + scopeb = lws_ipv6_unicast_scope(to_v6_sa(&db)); + scope_srca = lws_ipv6_unicast_scope(to_v6_sa(&da->source)); + scope_srcb = lws_ipv6_unicast_scope(to_v6_sa(&db->source)); + + if (scopea == scope_srca && scopeb != scope_srcb) + return SAS_PREFER_A; + + if (scopea != scope_srca && scopeb == scope_srcb) + return SAS_PREFER_B; + +#if defined(IFA_F_DEPRECATED) + /* + * Rule 3: Avoid deprecated addresses. + * + * If Source(DA) is deprecated and Source(DB) is not, then prefer DB. + * Similarly, if Source(DA) is not deprecated and Source(DB) is + * deprecated, then prefer DA. + */ + + if (!(da->ifa_flags & IFA_F_DEPRECATED) && + (db->ifa_flags & IFA_F_DEPRECATED)) + return SAS_PREFER_A; + + if ( (da->ifa_flags & IFA_F_DEPRECATED) && + !(db->ifa_flags & IFA_F_DEPRECATED)) + return SAS_PREFER_B; +#endif + + /* + * Rule 4: Prefer home addresses. + * + * If Source(DA) is simultaneously a home address and care-of address + * and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is + * simultaneously a home address and care-of address and Source(DA) is + * not, then prefer DB. + * + * If Source(DA) is just a home address and Source(DB) is just a care-of + * address, then prefer DA. Similarly, if Source(DA) is just a care-of + * address and Source(DB) is just a home address, then prefer DB. + * + * !!! not sure how to determine if care-of address + */ + + if ( (da->ifa_flags & IFA_F_HOMEADDRESS) && + !(db->ifa_flags & IFA_F_HOMEADDRESS)) + return SAS_PREFER_A; + + if (!(da->ifa_flags & IFA_F_HOMEADDRESS) && + (db->ifa_flags & IFA_F_HOMEADDRESS)) + return SAS_PREFER_B; + + /* + * Rule 5: Prefer matching label. + * + * If Label(Source(DA)) = Label(DA) and Label(Source(DB)) <> Label(DB), + * then prefer DA. Similarly, if Label(Source(DA)) <> Label(DA) and + * Label(Source(DB)) = Label(DB), then prefer DB + */ + + lws_sort_dns_classify(&da->source->dest, &score_srca); + lws_sort_dns_classify(&db->source->dest, &score_srcb); + + if (score_srca.label == da->score.label && + score_srcb.label != db->score.label) + return SAS_PREFER_A; + if (score_srca.label != da->score.label && + score_srcb.label == db->score.label) + return SAS_PREFER_B; + + /* + * Rule 6: Prefer higher precedence. + * + * If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if + * Precedence(DA) < Precedence(DB), then prefer DB. + */ + + if (da->score.precedence > db->score.precedence) + return SAS_PREFER_A; + + if (da->score.precedence < db->score.precedence) + return SAS_PREFER_B; + + /* + * Rule 7: Prefer native transport. + * If DA is reached via an encapsulating transition mechanism (e.g., + * IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is + * reached via encapsulation and DA is not, then prefer DA. + */ + + if (!memcmp(&ma[16], da_ads, 12) && memcmp(&ma[16], db_ads, 12)) + return SAS_PREFER_B; + + if (memcmp(&ma[16], da_ads, 12) && !memcmp(&ma[16], db_ads, 12)) + return SAS_PREFER_A; + + /* + * Rule 8: Prefer smaller scope. + * If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) > + * Scope(DB), then prefer DB. + */ + + if (scopea < scopeb) + return SAS_PREFER_A; + + if (scopea > scopeb) + return SAS_PREFER_B; + + /* + * Rule 9: Use longest matching prefix. + * When DA and DB belong to the same address family (both are IPv6 or + * both are IPv4): If CommonPrefixLen(Source(DA), DA) > + * CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if + * CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB), + * then prefer DB. + */ + + cpla = lws_ipv6_prefix_match_len(&da->source->dest.sa6, &da->dest.sa6); + cplb = lws_ipv6_prefix_match_len(&db->source->dest.sa6, &db->dest.sa6); + + if (cpla > cplb) + return SAS_PREFER_A; + + if (cpla < cplb) + return SAS_PREFER_B; + + /* + * Rule 10: Otherwise, leave the order unchanged. + */ + + return SAS_SAME; +} + +static int +lws_sort_dns_compare(const lws_dll2_t *a, const lws_dll2_t *b) +{ + const lws_dns_sort_t *sa = lws_container_of(a, lws_dns_sort_t, list), + *sb = lws_container_of(b, lws_dns_sort_t, list); + + return lws_sort_dns_dcomp(sa, sb); +} + +#endif /* ipv6 + netlink */ + +#if defined(_DEBUG) + +static void +lws_sort_dns_dump(struct lws *wsi) +{ + int n = 1; + + (void)n; /* nologs */ + + if (!lws_dll2_get_head(&wsi->dns_sorted_list)) + lwsl_notice("%s: empty\n", __func__); + + lws_start_foreach_dll(struct lws_dll2 *, d, + lws_dll2_get_head(&wsi->dns_sorted_list)) { + lws_dns_sort_t *s = lws_container_of(d, lws_dns_sort_t, list); + char dest[48], gw[48]; + + lws_sa46_write_numeric_address(&s->dest, dest, sizeof(dest)); + lws_sa46_write_numeric_address(&s->gateway, gw, sizeof(gw)); + + lwsl_notice("%s: %d: (%d)%s, gw (%d)%s, idi: %d, " + "lbl: %d, prec: %d\n", + __func__, n++, s->dest.sa4.sin_family, dest, + s->gateway.sa4.sin_family, gw, + s->if_idx, s->score.label, s->score.precedence); + + } lws_end_foreach_dll(d); +} + +#endif + +int +lws_sort_dns(struct lws *wsi, const struct addrinfo *result) +{ +#if defined(LWS_WITH_NETLINK) + struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; +#endif + const struct addrinfo *ai = result; + + lwsl_info("%s: sort_dns: %p\n", __func__, result); + + /* + * We're going to take the dns results and produce our own linked-list + * of them, if we can sorted into descending preferability order, and + * possibly filtered. + * + * First let's just convert the addrinfo list into our expanded + * lws_dns_sort_t list, we can discard the addrinfo list then + */ + + while (ai) { +#if defined(LWS_WITH_NETLINK) || \ + (defined(LWS_WITH_NETLINK) && defined(LWS_WITH_IPV6)) + lws_route_t +#if defined(LWS_WITH_NETLINK) + *estr = NULL +#endif +#if defined(LWS_WITH_NETLINK) && defined(LWS_WITH_IPV6) + , *bestsrc = NULL +#endif + ; +#endif + lws_dns_sort_t *ds; + char afip[48]; + + /* + * Only transfer address families we can cope with + */ + if ((int)ai->ai_addrlen > (int)sizeof(lws_sockaddr46) || + (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)) { + lwsl_info("%s: skip %d %d %d\n", __func__, + ai->ai_family, (int)ai->ai_addrlen, + (int)sizeof(lws_sockaddr46)); + goto next; + } + + ds = lws_zalloc(sizeof(*ds), __func__); + if (!ds) + return 1; + + memcpy(&ds->dest, ai->ai_addr, ai->ai_addrlen); + ds->dest.sa4.sin_family = ai->ai_family; + + lws_sa46_write_numeric_address(&ds->dest, afip, sizeof(afip)); + + lwsl_info("%s: unsorted entry (af %d) %s\n", __func__, + ds->dest.sa4.sin_family, afip); + +#if defined(LWS_WITH_NETLINK) + + /* + * Let's assess this DNS result in terms of route + * selection, eg, if no usable net route or gateway for it, + * we don't have a way to use it if we listed it + */ + + if (pt->routing_table.count) { + + estr = _lws_route_est_outgoing(pt, &ds->dest); + if (!estr) { + lws_free(ds); + lwsl_notice("%s: no route out\n", __func__); + /* + * There's no outbound route for this, it's + * unusable, so don't add it to the list + */ + goto next; + } + + ds->if_idx = estr->if_idx; + ds->uidx = estr->uidx; + + /* + * ...evidently, there's a way for it to go out... + */ + } +#endif + +#if defined(LWS_WITH_NETLINK) && defined(LWS_WITH_IPV6) + + /* + * These sorting rules only apply to ipv6. If we have ipv4 + * dest and estimate we will use an ipv4 source address to + * route it, then skip this. + * + * However if we have ipv4 dest and estimate we will use an + * ipv6 source address to route it, because of ipv6-only + * egress, then promote it to ipv6 and sort it + */ + + if (ds->dest.sa4.sin_family == AF_INET) { + if (!estr || + estr->dest.sa4.sin_family == AF_INET || + estr->gateway.sa4.sin_family == AF_INET) + /* + * No estimated route, or v4 estimated route, + * just add it to sorted list + */ + goto just_add; + + /* + * v4 dest on estimated v6 source ads route, because + * eg, there's no active v4 source ads just ipv6... + * promote v4 -> v6 address using ::ffff:xx:yy + */ + + lwsl_info("%s: promoting v4->v6\n", __func__); + + lws_sa46_4to6(&ds->dest, + (uint8_t *)&ds->dest.sa4.sin_addr, 0); + } + + /* first, classify this destination ads */ + lws_sort_dns_classify(&ds->dest, &ds->score); + + /* + * RFC6724 Section 5: Source Address Selection + * + * Go through the source options choosing the best for this + * destination... this can only operate on ipv6 destination + * address + */ + + lws_start_foreach_dll(struct lws_dll2 *, d, + lws_dll2_get_head(&pt->routing_table)) { + lws_route_t *r = lws_container_of(d, lws_route_t, list); + + /* gateway routes are skipped here */ + + if (ds->dest.sa6.sin6_family == AF_INET6 && + r->dest.sa4.sin_family == AF_INET6 && (!bestsrc || + lws_sort_dns_scomp(pt, bestsrc, r, &ds->dest.sa6) == + SAS_PREFER_B)) + bestsrc = r; + + } lws_end_foreach_dll(d); + + /* bestsrc is the best source route, or NULL if none */ + + if (!bestsrc && pt->routing_table.count) { + /* drop it, no usable source route */ + lws_free(ds); + goto next; + } + +just_add: + if (!bestsrc) { + lws_dll2_add_tail(&ds->list, &wsi->dns_sorted_list); + goto next; + } + + ds->source = bestsrc; + + /* + * RFC6724 Section 6: Destination Address Selection + * + * Insert the destination into the list at a position reflecting + * its preferability, so the head entry is the most preferred + */ + + lws_dll2_add_sorted(&ds->list, &wsi->dns_sorted_list, + lws_sort_dns_compare); +#else + /* + * We don't have the routing table + source address details in + * order to sort the DNS results... simply make entries in the + * order of the addrinfo results + */ + + lws_dll2_add_tail(&ds->list, &wsi->dns_sorted_list); +#endif + +next: + ai = ai->ai_next; + } + + //lwsl_notice("%s: sorted table: %d\n", __func__, + // wsi->dns_sorted_list.count); + +#if defined(_DEBUG) + lws_sort_dns_dump(wsi); +#endif + + return !wsi->dns_sorted_list.count; +} diff --git a/lib/core-net/close.c b/lib/core-net/close.c index b8f6650a7..0ffa0b740 100644 --- a/lib/core-net/close.c +++ b/lib/core-net/close.c @@ -240,15 +240,17 @@ void lws_addrinfo_clean(struct lws *wsi) { #if defined(LWS_WITH_CLIENT) - if (!wsi->dns_results) - return; + struct lws_dll2 *d = lws_dll2_get_head(&wsi->dns_sorted_list), *d1; -#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; + while (d) { + lws_dns_sort_t *r = lws_container_of(d, lws_dns_sort_t, list); + + d1 = d->next; + lws_dll2_remove(d); + lws_free(r); + + d = d1; + } #endif } @@ -276,6 +278,11 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, context = wsi->a.context; pt = &context->pt[(int)wsi->tsi]; +#if defined(LWS_WITH_SYS_ASYNC_DNS) + if (wsi == context->async_dns.wsi) + context->async_dns.wsi = NULL; +#endif + lws_pt_assert_lock_held(pt); lws_stats_bump(pt, LWSSTATS_C_API_CLOSE, 1); diff --git a/lib/core-net/network.c b/lib/core-net/network.c index 73a07a64b..d95e7aedc 100644 --- a/lib/core-net/network.c +++ b/lib/core-net/network.c @@ -879,13 +879,14 @@ lws_sa46_compare_ads(const lws_sockaddr46 *sa46a, const lws_sockaddr46 *sa46b) void lws_4to6(uint8_t *v6addr, const uint8_t *v4addr) { - memset(v6addr, 0, 10); - - v6addr[10] = v6addr[11] = 0xff; v6addr[12] = v4addr[0]; v6addr[13] = v4addr[1]; v6addr[14] = v4addr[2]; v6addr[15] = v4addr[3]; + + memset(v6addr, 0, 10); + + v6addr[10] = v6addr[11] = 0xff; } #if defined(LWS_WITH_IPV6) diff --git a/lib/core-net/output.c b/lib/core-net/output.c index ce683a219..7780fe3da 100644 --- a/lib/core-net/output.c +++ b/lib/core-net/output.c @@ -362,6 +362,7 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len) goto post_send; } } + if (lws_has_buffered_out(wsi)) n = sendto(wsi->desc.sockfd, (const char *)buf, len, 0, sa46_sockaddr(&wsi->udp->sa46_pending), diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index 66ea4eb5b..ee4d574e2 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -719,6 +719,13 @@ struct lws { struct lws_dll2 dll_cli_active_conns; struct lws_dll2 dll2_cli_txn_queue; struct lws_dll2_owner dll2_cli_txn_queue_owner; + + lws_dll2_t speculative_list; + lws_dll2_owner_t speculative_connect_owner; + /* wsis: additional connection candidates */ + lws_dll2_owner_t dns_sorted_list; + /* lws_dns_sort_t: dns results wrapped and sorted in a linked-list... + * deleted as they are tried, list empty == everything tried */ #endif lws_sockaddr46 sa46_peer; @@ -746,8 +753,6 @@ struct lws { #if defined(LWS_WITH_CLIENT) struct client_info_stash *stash; char *cli_hostname_copy; - const struct addrinfo *dns_results; - const struct addrinfo *dns_results_next; #endif void *user_space; void *opaque_parent_data; @@ -1396,6 +1401,9 @@ lws_route_t * _lws_route_est_outgoing(struct lws_context_per_thread *pt, const lws_sockaddr46 *dest); +int +lws_sort_dns(struct lws *wsi, const struct addrinfo *result); + int lws_broadcast(struct lws_context_per_thread *pt, int reason, void *in, size_t len); @@ -1536,6 +1544,10 @@ lws_sul_nonmonotonic_adjust(struct lws_context *ctx, int64_t step_us); void lws_netdev_instance_remove_destroy(struct lws_netdev_instance *ni); +int +lws_score_dns_results(struct lws_context *ctx, + const struct addrinfo **result); + #if defined(LWS_WITH_SYS_SMD) int lws_netdev_smd_cb(void *opaque, lws_smd_class_t _class, lws_usec_t timestamp, diff --git a/lib/core-net/route.c b/lib/core-net/route.c index 227a029df..ce81dcf88 100644 --- a/lib/core-net/route.c +++ b/lib/core-net/route.c @@ -72,11 +72,16 @@ _lws_routing_table_dump(struct lws_context_per_thread *pt) * * It's OK if the route uidx wraps, we explicitly confirm nobody else is using * the uidx before assigning one to a new route. + * + * We won't use uidx 0, so it can be understood to mean the uidx was never set. */ lws_route_uidx_t _lws_route_get_uidx(struct lws_context_per_thread *pt) { + if (!pt->route_uidx) + pt->route_uidx++; + while (1) { char again = 0; @@ -89,6 +94,8 @@ _lws_route_get_uidx(struct lws_context_per_thread *pt) if (rou->uidx == pt->route_uidx) { /* if so, bump and restart the check */ pt->route_uidx++; + if (!pt->route_uidx) + pt->route_uidx++; again = 1; } } lws_end_foreach_dll(d); @@ -236,10 +243,6 @@ _lws_route_check_wsi(struct lws *wsi) return !_lws_route_est_outgoing(pt, &wsi->sa46_peer); } -/* - * priority_deleted_route should be -1 if no deleted route - */ - int _lws_route_pt_close_unroutable(struct lws_context_per_thread *pt) { @@ -271,6 +274,11 @@ _lws_route_pt_close_route_users(struct lws_context_per_thread *pt, struct lws *wsi; unsigned int n; + if (!uidx) + return 0; + + lwsl_info("%s: closing users of route %d\n", __func__, uidx); + for (n = 0; n < pt->fds_count; n++) { wsi = wsi_from_fd(pt->context, pt->fds[n].fd); if (!wsi) diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index bf664866f..9799aef8b 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -184,6 +184,8 @@ lws_role_call_adoption_bind(struct lws *wsi, int type, const char *prot) #if defined(LWS_ROLE_RAW_FILE) + lwsl_notice("%s: falling back to raw file role bind\n", __func__); + /* fall back to raw file role if, eg, h1 not configured */ if (role_ops_raw_file.adoption_bind && diff --git a/lib/core/lws_dll2.c b/lib/core/lws_dll2.c index adca56590..9b9e6b37e 100644 --- a/lib/core/lws_dll2.c +++ b/lib/core/lws_dll2.c @@ -183,6 +183,31 @@ lws_dll2_owner_clear(struct lws_dll2_owner *d) d->count = 0; } +void +lws_dll2_add_sorted_priv(lws_dll2_t *d, lws_dll2_owner_t *own, void *priv, + int (*compare3)(void *priv, const lws_dll2_t *d, + const lws_dll2_t *i)) +{ + lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp, + lws_dll2_get_head(own)) { + assert(p != d); + + if (compare3(priv, p, d) >= 0) { + /* drop us in before this guy */ + lws_dll2_add_before(d, p); + + return; + } + } lws_end_foreach_dll_safe(p, tp); + + /* + * Either nobody on the list yet to compare him to, or he's the + * furthest away timeout... stick him at the tail end + */ + + lws_dll2_add_tail(d, own); +} + void lws_dll2_add_sorted(lws_dll2_t *d, lws_dll2_owner_t *own, int (*compare)(const lws_dll2_t *d, const lws_dll2_t *i)) @@ -195,8 +220,6 @@ lws_dll2_add_sorted(lws_dll2_t *d, lws_dll2_owner_t *own, /* drop us in before this guy */ lws_dll2_add_before(d, p); - // lws_dll2_describe(own, "post-insert"); - return; } } lws_end_foreach_dll_safe(p, tp); diff --git a/lib/plat/windows/windows-service.c b/lib/plat/windows/windows-service.c index 598d0f932..ab81e9c34 100644 --- a/lib/plat/windows/windows-service.c +++ b/lib/plat/windows/windows-service.c @@ -270,7 +270,7 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) * the connection has definitively failed... but * do we have more DNS entries to try? */ - if (wsi_from_fd(context, pfd->fd)->dns_results_next) { + if (wsi_from_fd(context, pfd->fd)->dns_sorted_list.count) { lws_sul_schedule(context, 0, &wsi_from_fd(context, pfd->fd)-> sul_connect_timeout, diff --git a/lib/roles/private-lib-roles.h b/lib/roles/private-lib-roles.h index 6dca19bf8..f4bf43e14 100644 --- a/lib/roles/private-lib-roles.h +++ b/lib/roles/private-lib-roles.h @@ -348,8 +348,9 @@ int lws_role_call_adoption_bind(struct lws *wsi, int type, const char *prot); struct lws * -lws_client_connect_4_established(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); + 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 fc5ff6a70..2a594bbd3 100644 --- a/lib/roles/raw-skt/ops-raw-skt.c +++ b/lib/roles/raw-skt/ops-raw-skt.c @@ -234,17 +234,24 @@ fail: static int rops_adoption_bind_raw_skt(struct lws *wsi, int type, const char *vh_prot_name) { + + // lwsl_notice("%s: bind type %d\n", __func__, type); + /* no http but socket... must be raw skt */ if ((type & LWS_ADOPT_HTTP) || !(type & LWS_ADOPT_SOCKET) || - (type & _LWS_ADOPT_FINISH)) + ((type & _LWS_ADOPT_FINISH) && (!(type & LWS_ADOPT_FLAG_UDP)))) return 0; /* no match */ #if defined(LWS_WITH_UDP) - if (type & LWS_ADOPT_FLAG_UDP) + if ((type & LWS_ADOPT_FLAG_UDP) && !wsi->udp) { /* * these can be >128 bytes, so just alloc for UDP */ wsi->udp = lws_malloc(sizeof(*wsi->udp), "udp struct"); + if (!wsi->udp) + return 0; + memset(wsi->udp, 0, sizeof(*wsi->udp)); + } #endif lws_role_transition(wsi, 0, (type & LWS_ADOPT_ALLOW_SSL) ? LRS_SSL_INIT : diff --git a/lib/system/async-dns/async-dns.c b/lib/system/async-dns/async-dns.c index 68faeec4a..26c3a7524 100644 --- a/lib/system/async-dns/async-dns.c +++ b/lib/system/async-dns/async-dns.c @@ -25,7 +25,7 @@ #include "private-lib-core.h" #include "private-lib-async-dns.h" -static const uint32_t botable[] = { 500, 1000, 1250, 5000 +static const uint32_t botable[] = { 300, 500, 700, 1250, 5000 /* in case everything just dog slow */ }; static const lws_retry_bo_t retry_policy = { botable, LWS_ARRAY_SIZE(botable), LWS_ARRAY_SIZE(botable), @@ -88,8 +88,13 @@ lws_async_dns_complete(lws_adns_q_t *q, lws_adns_cache_t *c) __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); + if (w->adns_cb(w, (const char *)&q[1], c ? c->results : NULL, 0, + q->opaque) == NULL) + lwsl_notice("%s: failed\n", __func__); + // lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, + // "adopt udp2 fail"); + + lws_set_timeout(w, NO_PENDING_TIMEOUT, 0); } lws_end_foreach_dll_safe(d, d1); if (q->standalone_cb) { @@ -111,8 +116,6 @@ lws_async_dns_sul_cb_retry(struct lws_sorted_usec_list *sul) { lws_adns_q_t *q = lws_container_of(sul, lws_adns_q_t, sul); - // lwsl_notice("%s\n", __func__); - lws_callback_on_writable(q->dns->wsi); } @@ -203,8 +206,7 @@ lws_async_dns_writeable(struct lws *wsi, lws_adns_q_t *q) goto qfail; } - lws_ser_wu16be(p, which ? LWS_ADNS_RECORD_AAAA : - LWS_ADNS_RECORD_A); + lws_ser_wu16be(p, which ? LWS_ADNS_RECORD_AAAA : LWS_ADNS_RECORD_A); p += 2; lws_ser_wu16be(p, 1); /* IN class */ @@ -212,6 +214,7 @@ lws_async_dns_writeable(struct lws *wsi, lws_adns_q_t *q) assert(p < pkt + sizeof(pkt) - LWS_PRE); n = lws_ptr_diff(p, pkt + LWS_PRE); + m = lws_write(wsi, pkt + LWS_PRE, n, 0); if (m != n) { lwsl_notice("%s: dns write failed %d %d errno %d\n", __func__, @@ -267,7 +270,6 @@ callback_async_dns(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_RAW_WRITEABLE: - // lwsl_notice("%s: WRITABLE\n", __func__); lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, dns->waiting.head) { @@ -298,6 +300,9 @@ lws_async_dns_init(struct lws_context *context) char ads[48]; int n; + if (dns->wsi) + return 0; + if (!context->vhost_list) { /* coverity... system vhost always present */ lwsl_err("%s: no system vhost\n", __func__); return 1; @@ -327,14 +332,16 @@ ok: 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, - NULL, NULL, &retry_policy); + dns->wsi = lws_create_adopt_udp(context->vhost_list, ads, 53, 0, + lws_async_dns_protocol.name, NULL, + NULL, NULL, &retry_policy); if (!dns->wsi) { lwsl_err("%s: foreign socket adoption failed\n", __func__); return 1; } + context->async_dns.wsi->udp->sa46 = dns->sa46; + dns->dns_server_set = 1; return 0; @@ -588,7 +595,7 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name, } /* - * It's a 1.2.3.4 type IP address already? We don't need a dns + * It's a 1.2.3.4 or ::1 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 @@ -743,11 +750,12 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name, lws_dll2_add_head(&q->list, &dns->waiting); - lwsl_debug("%s: created new query\n", __func__); + lwsl_info("%s: created new query\n", __func__); return LADNS_RET_CONTINUING; failed: + lwsl_notice("%s: failed\n", __func__); cb(wsi, NULL, NULL, LADNS_RET_FAILED, opaque); return LADNS_RET_FAILED; diff --git a/minimal-examples/ws-client/minimal-ws-client/minimal-ws-client.c b/minimal-examples/ws-client/minimal-ws-client/minimal-ws-client.c index 153f9abef..8c46739de 100644 --- a/minimal-examples/ws-client/minimal-ws-client/minimal-ws-client.c +++ b/minimal-examples/ws-client/minimal-ws-client/minimal-ws-client.c @@ -180,6 +180,9 @@ int main(int argc, const char **argv) if ((p = lws_cmdline_option(argc, argv, "-p"))) port = atoi(p); + if (lws_cmdline_option(argc, argv, "-n")) + ssl_connection &= ~LCCSCF_USE_SSL; + if (lws_cmdline_option(argc, argv, "-j")) ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;