diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index 268b3d5a9..3022aea72 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -402,6 +402,10 @@ struct lws_context_creation_info { * library are protected from hanging forever by timeouts. If * nonzero, this member lets you set the timeout used in seconds. * Otherwise a default timeout is used. */ + unsigned int connect_timeout_secs; + /**< VHOST: client connections have this long to find a working server + * from the DNS results, or the whole connection times out. If zero, + * a default timeout is used */ const char *ecdh_curve; /**< VHOST: if NULL, defaults to initializing server with * "prime256v1" */ diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index 652d98327..57c0c605a 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -590,6 +590,7 @@ struct lws_vhost { int ka_interval; int keepalive_timeout; int timeout_secs_ah_idle; + int connect_timeout_secs; int count_bound_wsi; diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index ff73d0194..c0b6de2ee 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -512,6 +512,12 @@ lws_create_vhost(struct lws_context *context, vh->iface = info->iface; #if !defined(LWS_PLAT_FREERTOS) && !defined(OPTEE_TA) && !defined(WIN32) vh->bind_iface = info->bind_iface; +#endif +#if defined(LWS_WITH_CLIENT) + if (info->connect_timeout_secs) + vh->connect_timeout_secs = info->connect_timeout_secs; + else + vh->connect_timeout_secs = 20; #endif /* apply the context default lws_retry */ diff --git a/lib/core/context.c b/lib/core/context.c index 89c2f3a5b..f0383a5e7 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -548,7 +548,7 @@ lws_create_context(const struct lws_context_creation_info *info) if (info->timeout_secs) context->timeout_secs = info->timeout_secs; else - context->timeout_secs = AWAITING_TIMEOUT; + context->timeout_secs = 5; context->ws_ping_pong_interval = info->ws_ping_pong_interval; diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index 4ac7ef84f..f908a2f48 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -82,9 +82,6 @@ #ifndef SPEC_LATEST_SUPPORTED #define SPEC_LATEST_SUPPORTED 13 #endif -#ifndef AWAITING_TIMEOUT -#define AWAITING_TIMEOUT 30 -#endif #ifndef CIPHERS_LIST_STRING #define CIPHERS_LIST_STRING "DEFAULT" #endif diff --git a/lib/plat/windows/windows-fds.c b/lib/plat/windows/windows-fds.c index 2e7d6c9a4..05a662087 100644 --- a/lib/plat/windows/windows-fds.c +++ b/lib/plat/windows/windows-fds.c @@ -73,7 +73,7 @@ delete_from_fd(struct lws_context *context, lws_sockfd_type fd) return 0; } - lwsl_err("Failed to find fd %d requested for " + lwsl_debug("Failed to find fd %d requested for " "delete in hashtable\n", fd); return 1; } diff --git a/lib/plat/windows/windows-service.c b/lib/plat/windows/windows-service.c index f265877bf..1c3266e29 100644 --- a/lib/plat/windows/windows-service.c +++ b/lib/plat/windows/windows-service.c @@ -194,13 +194,34 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) pfd->revents = (short)networkevents.lNetworkEvents; err = networkevents.iErrorCode[FD_CONNECT_BIT]; + if ((networkevents.lNetworkEvents & FD_CONNECT) && wsi_from_fd(context, pfd->fd) && !wsi_from_fd(context, pfd->fd)->udp) { + lwsl_debug("%s: FD_CONNECT: %p\n", __func__, wsi_from_fd(context, pfd->fd)); + pfd->revents &= ~LWS_POLLOUT; + if (err && err != LWS_EALREADY && + err != LWS_EINPROGRESS && err != LWS_EWOULDBLOCK && + err != WSAEINVAL) { + lwsl_debug("Unable to connect errno=%d\n", err); - if ((networkevents.lNetworkEvents & FD_CONNECT) && - err && err != LWS_EALREADY && - err != LWS_EINPROGRESS && err != LWS_EWOULDBLOCK && - err != WSAEINVAL) { - lwsl_debug("Unable to connect errno=%d\n", err); - pfd->revents |= LWS_POLLHUP; + /* + * the connection has definitively failed... but + * do we have more DNS entries to try? + */ + if (wsi_from_fd(context, pfd->fd)->dns_results_next) { + lws_sul_schedule(context, 0, &wsi_from_fd(context, pfd->fd)-> + sul_connect_timeout, + lws_client_conn_wait_timeout, 1); + continue; + } else + pfd->revents |= LWS_POLLHUP; + } else + if (wsi_from_fd(context, pfd->fd)) { + if (wsi_from_fd(context, pfd->fd)->udp) + pfd->revents |= LWS_POLLHUP; + else + lws_client_connect_3_connect(wsi_from_fd(context, pfd->fd), + NULL, NULL, LWS_CONNECT_COMPLETION_GOOD, + NULL); + } } if (pfd->revents & LWS_POLLOUT) { diff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c index 5a4370d9b..479ca6ff8 100644 --- a/lib/roles/http/client/client-handshake.c +++ b/lib/roles/http/client/client-handshake.c @@ -280,7 +280,7 @@ send_hs: * provoke service to issue the CONNECT directly. */ lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, - AWAITING_TIMEOUT); + wsi->context->timeout_secs); assert(lws_socket_is_valid(wsi->desc.sockfd)); @@ -356,6 +356,10 @@ lws_client_conn_wait_timeout(lws_sorted_usec_list_t *sul) * connection before giving up on it and retrying. */ +#if defined(WIN32) + wsi->dns_results_next = wsi->dns_results_next->ai_next; +#endif + lwsl_info("%s: connect wait timeout has fired\n", __func__); lws_client_connect_3_connect(wsi, NULL, NULL, 0, NULL); } @@ -380,6 +384,9 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, char ni[48]; int m; + if (n == LWS_CONNECT_COMPLETION_GOOD) + goto conn_good; + #if defined(LWS_WITH_IPV6) && defined(__ANDROID__) ipv6only = 0; #endif @@ -393,7 +400,6 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, if (!lws_dll2_is_detached(&wsi->dll2_cli_txn_queue)) return wsi; -#if !defined(WIN32) /* * We can check using getsockopt if our connect actually completed. * Posix connect() allows nonblocking to redo the connect to @@ -406,22 +412,31 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, socklen_t sl = sizeof(int); int e = 0; - if (!result && !wsi->sul_connect_timeout.list.owner) + if (!result && /* no dns results... */ + !wsi->sul_connect_timeout.list.owner /* no ongoing connect timeout */) goto connect_to; - +#if defined(WIN32) + if (!connect(wsi->desc.sockfd, NULL, 0)) { + goto conn_good; + } else { + if (!LWS_ERRNO || LWS_ERRNO == WSAEINVAL || + LWS_ERRNO == WSAEWOULDBLOCK || LWS_ERRNO == WSAEALREADY) { + lwsl_info("%s: errno %d\n", __func__, errno); + return NULL; + } + lwsl_info("%s: connect check take as FAILED\n", __func__); + } +#else /* * 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, -#if defined(WIN32) - (char *) -#endif &e, &sl)) { if (!e) { - lwsl_info("%s: getsockopt check: conn OK\n", - __func__); + lwsl_debug("%s: getsockopt check: conn OK errno %d\n", + __func__, errno); goto conn_good; } @@ -429,12 +444,12 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, lwsl_debug("%s: getsockopt fd %d says err %d\n", __func__, wsi->desc.sockfd, e); } +#endif lwsl_debug("%s: getsockopt check: conn fail: errno %d\n", __func__, LWS_ERRNO); goto try_next_result_fds; } -#endif #if defined(LWS_WITH_UNIX_SOCK) if (ads && *ads == '+') { @@ -551,7 +566,8 @@ next_result: 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); + lwsl_info("%s: %s ipv4->ipv6 %s\n", __func__, + ads ? ads : "(null)", ni); break; } #endif @@ -564,7 +580,7 @@ next_result: 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); + lwsl_info("%s: %s ipv4 %s\n", __func__, ads ? ads : "(null)", ni); break; case AF_INET6: #if defined(LWS_WITH_IPV6) @@ -581,7 +597,7 @@ next_result: sa46.sa6.sin6_port = htons(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); + lwsl_info("%s: %s ipv6 %s\n", __func__, ads ? ads : "(null)", ni); #else goto try_next_result; /* ipv4 only can't use this */ #endif @@ -653,7 +669,7 @@ ads_known: wsi->protocol = &wsi->vhost->protocols[0]; lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, - wsi->context->timeout_secs); + wsi->vhost->connect_timeout_secs); iface = lws_wsi_client_stash_item(wsi, CIS_IFACE, _WSI_TOKEN_CLIENT_IFACE); @@ -685,9 +701,6 @@ ads_known: wsi->detlat.earliest_write_req_pre_write = lws_now_usecs(); #endif - if (!result && !wsi->sul_connect_timeout.list.owner) - goto connect_to; - m = connect(wsi->desc.sockfd, (const struct sockaddr *)psa, n); if (m == -1) { int errno_copy = LWS_ERRNO; @@ -719,8 +732,9 @@ ads_known: #endif /* - * Let's set a specialized timeout for the connect completion, - * it uses wsi->sul_connect_timeout just for this purpose + * Let's set a specialized timeout for this connect attempt + * completion, it uses wsi->sul_connect_timeout just for this + * purpose */ lws_sul_schedule(wsi->context, 0, &wsi->sul_connect_timeout, @@ -802,7 +816,6 @@ oom4: return NULL; - connect_to: /* * It looks like the sul_connect_timeout fired diff --git a/lib/roles/private-lib-roles.h b/lib/roles/private-lib-roles.h index 0d5008d33..78328b9ff 100644 --- a/lib/roles/private-lib-roles.h +++ b/lib/roles/private-lib-roles.h @@ -341,6 +341,8 @@ enum { LWS_UPG_RET_BAIL }; +#define LWS_CONNECT_COMPLETION_GOOD (-99) + int lws_role_call_adoption_bind(struct lws *wsi, int type, const char *prot); diff --git a/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c index 4d0d88a63..b16a0ce4c 100644 --- a/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c +++ b/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c @@ -259,6 +259,8 @@ int main(int argc, const char **argv) info.protocols = protocols; info.user = &args; info.register_notifier_list = na; + info.timeout_secs = 10; + info.connect_timeout_secs = 30; /* * since we know this lws context is only ever going to be used with diff --git a/minimal-examples/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c b/minimal-examples/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c index f0b90a282..7a02a9b7a 100644 --- a/minimal-examples/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c +++ b/minimal-examples/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c @@ -162,6 +162,8 @@ int main(int argc, const char **argv) info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; info.pt_serv_buf_size = 8192; + info.timeout_secs = 10; + info.connect_timeout_secs = 30; /* * since we know this lws context is only ever going to be used with * one client wsis / fds / sockets at a time, let lws know it doesn't diff --git a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c index f75d30422..248ddad08 100644 --- a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c +++ b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c @@ -282,6 +282,7 @@ int main(int argc, const char **argv) info.protocols = protocols; info.user = &args; info.register_notifier_list = na; + info.connect_timeout_secs = 30; /* * since we know this lws context is only ever going to be used with diff --git a/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c b/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c index e30926027..e827526f7 100644 --- a/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c +++ b/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c @@ -99,6 +99,8 @@ int main(int argc, const char **argv) info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; + info.timeout_secs = 10; + info.connect_timeout_secs = 30; #if defined(LWS_WITH_MBEDTLS) /* * OpenSSL uses the system trust store. mbedTLS has to be told which