diff --git a/README.md b/README.md index 79676247f..b1b5f7cd6 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,20 @@ various scenarios, CC0-licensed (public domain) for cut-and-paste, allow you to News ---- +## Connection Validity tracking + +Lws now allows you to apply a policy for how long a network connection may go +without seeing something on it that confirms it's still valid in the sense of +passing traffic cohernetly both ways. There's a global policy in the context +which defaults to 5m before it produces a PING if possible, and 5m10 before +the connection will be hung up, user code can override this in the context, +vhost (for server) and client connection info (for client). + +An api `lws_validity_confirmed(wsi)` is provided so user code can indicate +that it observed traffic that must mean the connection is passing traffic in +both directions to and from the peer. In the absence of these confirmations +lws will generate PINGs and take PONGs as the indication of validity. + ## Async DNS support Master now provides optional Asynchronous (ie, nonblocking) DNS resolving. Enable diff --git a/READMEs/README.lws_retry.md b/READMEs/README.lws_retry.md new file mode 100644 index 000000000..03afcc29b --- /dev/null +++ b/READMEs/README.lws_retry.md @@ -0,0 +1,98 @@ +# `lws_retry_bo_t` client connection management + +This struct sets the policy for delays between retries, and for +how long a connection may be 'idle' before it first tries to +ping / pong on it to confirm it's up, or drops the connection +if still idle. + +## Retry rate limiting + +You can define a table of ms-resolution delays indexed by which +connection attempt number is ongoing, this is pointed to by +`.retry_ms_table` with `.retry_ms_table_count` containing the +count of table entries. + +`.conceal_count` is the number of retries that should be allowed +before informing the parent that the connection has failed. If it's +greater than the number of entries in the table, the last entry is +reused for the additional attempts. + +`.jitter_percent` controls how much additional random delay is +added to the actual interval to be used... this stops a lot of +devices all synchronizing when they try to connect after a single +trigger event and DDoS-ing the server. + +The struct and apis are provided for user implementations, lws does +not offer reconnection itself. + +## Connection validity management + +Lws has a sophisticated idea of connection validity and the need to +reconfirm that a connection is still operable if proof of validity +has not been seen for some time. It concerns itself only with network +connections rather than streams, for example, it only checks h2 +network connections rather than the individual streams inside (which +is consistent with h2 PING frames only working at the network stream +level itself). + +Connections may fail in a variety of ways, these include that no traffic +at all is passing, or, eg, incoming traffic may be received but no +outbound traffic is making it to the network, and vice versa. In the +case that tx is not failing at any point but just isn't getting sent, +endpoints can potentially kid themselves that since "they are sending" +and they are seeing RX, the combination means the connection is valid. +This can potentially continue for a long time if the peer is not +performing keepalives. + +"Connection validity" is proven when one side sends something and later +receives a response that can only have been generated by the peer +receiving what was just sent. This can happen for some kinds of user +transactions on any stream using the connection, or by sending PING / +PONG protocol packets where the PONG is only returned for a received PING. + +To ensure that the generated traffic is only sent when necessary, user +code can report for any stream that it has observed a transaction amounting +to a proof of connection validity using an api. This resets the timer for +the associated network connection before the validity is considered +expired. + +`.secs_since_valid_ping` in the retry struct sets the number of seconds since +the last validity after which lws will issue a protocol-specific PING of some +kind on the connection. `.secs_since_valid_hangup` specifies how long lws +will allow the connection to go without a confirmation of validity before +simply hanging up on it. + +## Defaults + +The context defaults to having a 5m valid ping interval and 5m10s hangup interval, +ie, it'll send a ping at 5m idle if the protocol supports it, and if no response +validating the connection arrives in another 10s, hang up the connection. + +User code can set this in the context creation info and can individually set the +retry policy per vhost for server connections. Client connections can set it +per connection in the client creation info `.retry_and_idle_policy`. + +## Checking for h2 and ws + +Check using paired minimal examples with the -v flag on one or both sides to get a +small validity check period set of 3s / 10s + +Also give, eg, -d1039 to see info level debug logging + +### h2 + +``` +$ lws-minimal-http-server-h2-long-poll -v + +$ lws-minimal-http-client -l -v +``` + +### ws + +``` +$ lws-minimal-ws-server-h2 -s -v + +$ lws-minimal-ws-client-ping -n --server 127.0.0.1 --port 7681 -v +``` + + diff --git a/include/libwebsockets.h b/include/libwebsockets.h index ccadd2c94..c93cd5bbf 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -529,6 +529,7 @@ struct lws_tokens; struct lws_vhost; struct lws; +#include #include #include #include @@ -565,7 +566,6 @@ struct lws; #include #include #include -#include #include #include diff --git a/include/libwebsockets/lws-client.h b/include/libwebsockets/lws-client.h index d28a2d0ca..64b089bdc 100644 --- a/include/libwebsockets/lws-client.h +++ b/include/libwebsockets/lws-client.h @@ -133,6 +133,11 @@ struct lws_client_connect_info { * an lws_seq_t. */ + const lws_retry_bo_t *retry_and_idle_policy; + /**< optional retry and idle policy to apply to this connection. + * Currently only the idle parts are applied to the connection. + */ + /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility * diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index 4c193813e..c0b7b8a75 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -688,6 +688,10 @@ struct lws_context_creation_info { * collected for each read and write */ const char *detailed_latency_filepath; /**< CONTEXT: NULL, or filepath to put latency data into */ + const lws_retry_bo_t *retry_and_idle_policy; + /**< VHOST: optional retry and idle policy to apply to this vhost. + * Currently only the idle parts are applied to the connections. + */ /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility diff --git a/include/libwebsockets/lws-retry.h b/include/libwebsockets/lws-retry.h index c74cf21c7..8845e8bc4 100644 --- a/include/libwebsockets/lws-retry.h +++ b/include/libwebsockets/lws-retry.h @@ -23,10 +23,12 @@ */ typedef struct lws_retry_bo { - const uint32_t *retry_ms_table; /* base delay in ms */ - uint16_t retry_ms_table_count; /* entries in table */ - uint16_t conceal_count; /* max retries to conceal */ - uint8_t jitter_percent; /* % additional random jitter */ + const uint32_t *retry_ms_table; /* base delay in ms */ + uint16_t retry_ms_table_count; /* entries in table */ + uint16_t conceal_count; /* max retries to conceal */ + uint16_t secs_since_valid_ping; /* idle before PING issued */ + uint16_t secs_since_valid_hangup; /* idle before hangup conn */ + uint8_t jitter_percent; /* % additional random jitter */ } lws_retry_bo_t; /** diff --git a/include/libwebsockets/lws-timeout-timer.h b/include/libwebsockets/lws-timeout-timer.h index 2253a40e4..f6b15dc43 100644 --- a/include/libwebsockets/lws-timeout-timer.h +++ b/include/libwebsockets/lws-timeout-timer.h @@ -229,4 +229,26 @@ LWS_VISIBLE LWS_EXTERN void lws_sul_schedule(struct lws_context *context, int tsi, lws_sorted_usec_list_t *sul, sul_cb_t cb, lws_usec_t us); +/* + * lws_validity_confirmed() - reset the validity timer for a network connection + * + * \param wsi: the connection that saw traffic proving the connection valid + * + * Network connections are subject to intervals defined by the context, the + * vhost if server connections, or the client connect info if a client + * connection. If the connection goes longer than the specified time since + * last observing traffic that can only happen if traffic is passing in both + * directions, then lws will try to create a PING transaction on the network + * connection. + * + * If the connection reaches the specified `.secs_since_valid_hangup` time + * still without any proof of validity, the connection will be closed. + * + * If the PONG comes, or user code observes traffic that satisfies the proof + * that both directions are passing traffic to the peer and calls this api, + * the connection validity timer is reset and the scheme repeats. + */ +LWS_VISIBLE LWS_EXTERN void +lws_validity_confirmed(struct lws *wsi); + ///@} diff --git a/lib/core-net/adopt.c b/lib/core-net/adopt.c index d356b9045..ebe590a91 100644 --- a/lib/core-net/adopt.c +++ b/lib/core-net/adopt.c @@ -71,6 +71,7 @@ lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi) new_wsi->context = vhost->context; new_wsi->pending_timeout = NO_PENDING_TIMEOUT; new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + new_wsi->retry_policy = vhost->retry_policy; #if defined(LWS_WITH_DETAILED_LATENCY) if (vhost->context->detailed_latency_cb) diff --git a/lib/core-net/connect.c b/lib/core-net/connect.c index 2bbcfaafe..9d0a793f1 100644 --- a/lib/core-net/connect.c +++ b/lib/core-net/connect.c @@ -60,6 +60,10 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) wsi->context = i->context; wsi->desc.sockfd = LWS_SOCK_INVALID; wsi->seq = i->seq; + if (i->retry_and_idle_policy) + wsi->retry_policy = i->retry_and_idle_policy; + else + wsi->retry_policy = &i->context->default_retry; #if defined(LWS_WITH_DETAILED_LATENCY) if (i->context->detailed_latency_cb) diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index bcb73f01b..0f2310ced 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -354,6 +354,9 @@ struct lws_context_per_thread { #if defined(LWS_PLAT_UNIX) lws_sorted_usec_list_t sul_plat; #endif +#if defined(LWS_ROLE_CGI) + lws_sorted_usec_list_t sul_cgi; +#endif #if defined(LWS_WITH_STATS) uint64_t lws_stats[LWSSTATS_SIZE]; int updated; @@ -508,6 +511,8 @@ struct lws_vhost { struct lws_context *context; struct lws_vhost *vhost_next; + const lws_retry_bo_t *retry_policy; + struct lws *lserv_wsi; const char *name; const char *iface; @@ -584,7 +589,6 @@ struct lws { #endif #if defined(LWS_ROLE_WS) struct _lws_websocket_related *ws; /* allocated if we upgrade to ws */ - lws_sorted_usec_list_t sul_ping; #endif #if defined(LWS_ROLE_DBUS) struct _lws_dbus_mode_related dbus; @@ -606,6 +610,8 @@ struct lws { lws_sorted_usec_list_t sul_timeout; lws_sorted_usec_list_t sul_hrtimer; + lws_sorted_usec_list_t sul_validity; + struct lws_dll2 dll_buflist; /* guys with pending rxflow */ struct lws_dll2 same_vh_protocol; #if defined(LWS_WITH_SYS_ASYNC_DNS) @@ -627,6 +633,7 @@ struct lws { const struct lws_role_ops *role_ops; const struct lws_protocols *protocol; struct lws_sequencer *seq; /* associated sequencer if any */ + const lws_retry_bo_t *retry_policy; #if defined(LWS_WITH_THREADPOOL) struct lws_threadpool_task *tp_task; @@ -710,6 +717,7 @@ struct lws { unsigned int proxied_ws_parent:1; unsigned int do_bind:1; unsigned int oom4:1; + unsigned int validity_hup:1; unsigned int could_have_pending:1; /* detect back-to-back writes */ unsigned int outer_will_close:1; @@ -961,9 +969,6 @@ lws_plat_plugins_init(struct lws_context * context, const char * const *d); LWS_VISIBLE LWS_EXTERN int lws_plat_plugins_destroy(struct lws_context * context); -LWS_EXTERN void -lws_restart_ws_ping_pong_timer(struct lws *wsi); - struct lws * lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd); @@ -1197,6 +1202,11 @@ lws_threadpool_tsi_context(struct lws_context *context, int tsi); void __lws_wsi_remove_from_sul(struct lws *wsi); +void +lws_validity_confirmed(struct lws *wsi); +void +_lws_validity_confirmed_role(struct lws *wsi); + int lws_seq_pt_init(struct lws_context_per_thread *pt); diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index e2dc64e37..357da4aeb 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -475,6 +475,12 @@ lws_create_vhost(struct lws_context *context, #if !defined(LWS_PLAT_FREERTOS) && !defined(OPTEE_TA) && !defined(WIN32) vh->bind_iface = info->bind_iface; #endif + /* apply the context default lws_retry */ + + if (info->retry_and_idle_policy) + vh->retry_policy = info->retry_and_idle_policy; + else + vh->retry_policy = &context->default_retry; /* * let's figure out how many protocols the user is handing us, using the diff --git a/lib/core-net/wsi-timeout.c b/lib/core-net/wsi-timeout.c index db026c444..594800be5 100644 --- a/lib/core-net/wsi-timeout.c +++ b/lib/core-net/wsi-timeout.c @@ -35,9 +35,7 @@ __lws_wsi_remove_from_sul(struct lws *wsi) // lws_dll2_describe(&pt->pt_sul_owner, "pre-remove"); lws_dll2_remove(&wsi->sul_timeout.list); lws_dll2_remove(&wsi->sul_hrtimer.list); -#if defined(LWS_ROLE_WS) - lws_dll2_remove(&wsi->sul_ping.list); -#endif + lws_dll2_remove(&wsi->sul_validity.list); // lws_dll2_describe(&pt->pt_sul_owner, "post-remove"); } @@ -227,7 +225,7 @@ lws_sul_timed_callback_vh_protocol_cb(lws_sorted_usec_list_t *sul) __lws_timed_callback_remove(tvp->vhost, tvp); } -LWS_VISIBLE LWS_EXTERN int +int lws_timed_callback_vh_protocol_us(struct lws_vhost *vh, const struct lws_protocols *prot, int reason, lws_usec_t us) @@ -267,7 +265,7 @@ lws_timed_callback_vh_protocol_us(struct lws_vhost *vh, return 0; } -LWS_VISIBLE LWS_EXTERN int +int lws_timed_callback_vh_protocol(struct lws_vhost *vh, const struct lws_protocols *prot, int reason, int secs) @@ -275,3 +273,84 @@ lws_timed_callback_vh_protocol(struct lws_vhost *vh, return lws_timed_callback_vh_protocol_us(vh, prot, reason, ((lws_usec_t)secs) * LWS_US_PER_SEC); } + +static void +lws_validity_cb(lws_sorted_usec_list_t *sul) +{ + struct lws *wsi = lws_container_of(sul, struct lws, sul_validity); + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + const lws_retry_bo_t *rbo = wsi->retry_policy; + + /* one of either the ping or hangup validity threshold was crossed */ + + if (wsi->validity_hup) { + lwsl_info("%s: wsi %p: validity too old\n", __func__, wsi); + __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "validity timeout"); + return; + } + + /* schedule a protocol-dependent ping */ + + lwsl_info("%s: wsi %p: scheduling validity check\n", __func__, wsi); + + if (wsi->role_ops && wsi->role_ops->issue_keepalive) + wsi->role_ops->issue_keepalive(wsi, 0); + + /* + * We arrange to come back here after the additional ping to hangup time + * and do the hangup, unless we get validated (by, eg, a PONG) and + * reset the timer + */ + + assert(rbo->secs_since_valid_hangup > rbo->secs_since_valid_ping); + + wsi->validity_hup = 1; + __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_validity, + ((uint64_t)rbo->secs_since_valid_hangup - + rbo->secs_since_valid_ping) * LWS_US_PER_SEC); +} + +/* + * The role calls this back to actually confirm validity on a particular wsi + * (which may not be the original wsi) + */ + +void +_lws_validity_confirmed_role(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + const lws_retry_bo_t *rbo = wsi->retry_policy; + + if (!rbo || !rbo->secs_since_valid_hangup) + return; + + wsi->validity_hup = 0; + wsi->sul_validity.cb = lws_validity_cb; + + wsi->validity_hup = rbo->secs_since_valid_ping >= + rbo->secs_since_valid_hangup; + + lwsl_info("%s: wsi %p: setting validity timer %ds (hup %d)\n", + __func__, wsi, + wsi->validity_hup ? rbo->secs_since_valid_hangup : + rbo->secs_since_valid_ping, + wsi->validity_hup); + + __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_validity, + ((uint64_t)(wsi->validity_hup ? + rbo->secs_since_valid_hangup : + rbo->secs_since_valid_ping)) * LWS_US_PER_SEC); +} + +void +lws_validity_confirmed(struct lws *wsi) +{ + /* + * This may be a stream inside a muxed network connection... leave it + * to the role to figure out who actually needs to understand their + * validity was confirmed. + */ + if (wsi->role_ops && wsi->role_ops->issue_keepalive) + wsi->role_ops->issue_keepalive(wsi, 1); +} diff --git a/lib/core/context.c b/lib/core/context.c index 0372c88be..3a5b00783 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -28,9 +28,13 @@ #define LWS_BUILD_HASH "unknown-build-hash" #endif - static const char *library_version = LWS_LIBRARY_VERSION " " LWS_BUILD_HASH; +#if defined(LWS_WITH_NETWORK) +/* in ms */ +static uint32_t default_backoff_table[] = { 1000, 3000, 9000, 17000 }; +#endif + /** * lws_get_library_version: get version and git hash library built from * @@ -164,11 +168,6 @@ lws_create_context(const struct lws_context_creation_info *info) #endif #endif - -#if defined(LWS_ROLE_H2) - role_ops_h2.init_context(context, info); -#endif - #if LWS_MAX_SMP > 1 lws_mutex_refcount_init(&context->mr); #endif @@ -373,6 +372,15 @@ lws_create_context(const struct lws_context_creation_info *info) context->count_threads; #if defined(LWS_WITH_NETWORK) + + context->default_retry.retry_ms_table = default_backoff_table; + context->default_retry.conceal_count = + context->default_retry.retry_ms_table_count = + LWS_ARRAY_SIZE(default_backoff_table); + context->default_retry.jitter_percent = 20; + context->default_retry.secs_since_valid_ping = 300; + context->default_retry.secs_since_valid_hangup = 310; + /* * Allocate the per-thread storage for scratchpad buffers, * and header data pool @@ -405,6 +413,12 @@ lws_create_context(const struct lws_context_creation_info *info) #if defined(LWS_WITH_SEQUENCER) lws_seq_pt_init(&context->pt[n]); #endif + + LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) { + if (ar->pt_init_destroy) + ar->pt_init_destroy(context, info, + &context->pt[n], 0); + } LWS_FOR_EVERY_AVAILABLE_ROLE_END; } lwsl_info(" Threads: %d each %d fds\n", context->count_threads, @@ -634,6 +648,10 @@ lws_context_destroy3(struct lws_context *context) #if defined(LWS_WITH_SEQUENCER) lws_seq_destroy_all_on_pt(pt); #endif + LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) { + if (ar->pt_init_destroy) + ar->pt_init_destroy(context, NULL, pt, 1); + } LWS_FOR_EVERY_AVAILABLE_ROLE_END; if (context->event_loop_ops->destroy_pt) context->event_loop_ops->destroy_pt(context, n); diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index 6aaf5462f..d65e2dcca 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -274,6 +274,7 @@ struct lws_context { #if defined(LWS_WITH_NETWORK) struct lws_context_per_thread pt[LWS_MAX_SMP]; + lws_retry_bo_t default_retry; #if defined(LWS_WITH_HTTP2) struct http2_settings set; diff --git a/lib/plat/unix/unix-init.c b/lib/plat/unix/unix-init.c index eb61ea84b..1ae066b40 100644 --- a/lib/plat/unix/unix-init.c +++ b/lib/plat/unix/unix-init.c @@ -44,9 +44,6 @@ lws_sul_plat_unix(lws_sorted_usec_list_t *sul) struct lws_context_per_thread *pt = lws_container_of(sul, struct lws_context_per_thread, sul_plat); struct lws_context *context = pt->context; -#if defined(LWS_ROLE_CGI) || defined(LWS_ROLE_DBUS) - time_t now = time(NULL); -#endif #if !defined(LWS_NO_DAEMONIZE) /* if our parent went down, don't linger around */ @@ -83,13 +80,6 @@ lws_sul_plat_unix(lws_sorted_usec_list_t *sul) lws_context_unlock(context); #endif -#if defined(LWS_ROLE_CGI) - role_ops_cgi.periodic_checks(context, 0, now); -#endif -#if defined(LWS_ROLE_DBUS) - role_ops_dbus.periodic_checks(context, 0, now); -#endif - __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_plat, 30 * LWS_US_PER_SEC); } #endif diff --git a/lib/roles/cgi/cgi-server.c b/lib/roles/cgi/cgi-server.c index 3a2c72298..3c1095d52 100644 --- a/lib/roles/cgi/cgi-server.c +++ b/lib/roles/cgi/cgi-server.c @@ -1064,7 +1064,7 @@ handled: return 0; } -LWS_EXTERN int +int lws_cgi_kill_terminated(struct lws_context_per_thread *pt) { struct lws_cgi **pcgi, *cgi = NULL; diff --git a/lib/roles/cgi/ops-cgi.c b/lib/roles/cgi/ops-cgi.c index 4479ed811..878580164 100644 --- a/lib/roles/cgi/ops-cgi.c +++ b/lib/roles/cgi/ops-cgi.c @@ -68,16 +68,6 @@ rops_handle_POLLOUT_cgi(struct lws *wsi) return LWS_HP_RET_USER_SERVICE; } -static int -rops_periodic_checks_cgi(struct lws_context *context, int tsi, time_t now) -{ - struct lws_context_per_thread *pt = &context->pt[tsi]; - - lws_cgi_kill_terminated(pt); - - return 0; -} - static int rops_destroy_role_cgi(struct lws *wsi) { @@ -94,14 +84,43 @@ rops_destroy_role_cgi(struct lws *wsi) return 0; } +static void +lws_cgi_sul_cb(lws_sorted_usec_list_t *sul) +{ + struct lws_context_per_thread *pt = lws_container_of(sul, + struct lws_context_per_thread, sul_cgi); + + lws_cgi_kill_terminated(pt); + + __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_cgi, + 3 * LWS_US_PER_SEC); +} + +static int +rops_pt_init_destroy_cgi(struct lws_context *context, + const struct lws_context_creation_info *info, + struct lws_context_per_thread *pt, int destroy) +{ + if (!destroy) { + + pt->sul_cgi.cb = lws_cgi_sul_cb; + + __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_cgi, + 3 * LWS_US_PER_SEC); + } else + lws_dll2_remove(&pt->sul_cgi.list); + + return 0; +} + + struct lws_role_ops role_ops_cgi = { /* role name */ "cgi", /* alpn id */ NULL, /* check_upgrades */ NULL, - /* init_context */ NULL, + /* pt_init_destroy */ rops_pt_init_destroy_cgi, /* init_vhost */ NULL, /* destroy_vhost */ NULL, - /* periodic_checks */ rops_periodic_checks_cgi, /* service_flag_pending */ NULL, /* handle_POLLIN */ rops_handle_POLLIN_cgi, /* handle_POLLOUT */ rops_handle_POLLOUT_cgi, @@ -117,6 +136,7 @@ struct lws_role_ops role_ops_cgi = { /* destroy_role */ rops_destroy_role_cgi, /* adoption_bind */ NULL, /* client_bind */ NULL, + /* issue_keepalive */ NULL, /* adoption_cb clnt, srv */ { 0, 0 }, /* rx_cb clnt, srv */ { 0, 0 }, /* writeable cb clnt, srv */ { 0, 0 }, diff --git a/lib/roles/dbus/dbus.c b/lib/roles/dbus/dbus.c index 2dbc8f3d5..7721b8088 100644 --- a/lib/roles/dbus/dbus.c +++ b/lib/roles/dbus/dbus.c @@ -472,23 +472,18 @@ rops_handle_POLLIN_dbus(struct lws_context_per_thread *pt, struct lws *wsi, return LWS_HPI_RET_HANDLED; } -static int -rops_periodic_checks_dbus(struct lws_context *context, int tsi, time_t now) +static void +lws_dbus_sul_cb(lws_sorted_usec_list_t *sul) { - struct lws_context_per_thread *pt = &context->pt[tsi]; - - /* - * locking shouldn't be needed here, because periodic_checks is called - * from the tsi-specific service thread context, and only the same - * service thread can modify stuff on the same pt. - */ + struct lws_context_per_thread *pt = lws_container_of(sul, + struct lws_context_per_thread, dbus.sul); lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx, lws_dll2_get_head(&pt->dbus.timer_list_owner)) { struct lws_role_dbus_timer *r = lws_container_of(rdt, struct lws_role_dbus_timer, timer_list); - if (now > r->fire) { + if (time(NULL) > r->fire) { lwsl_notice("%s: firing timer\n", __func__); dbus_timeout_handle(r->data); lws_dll2_remove(rdt); @@ -496,6 +491,24 @@ rops_periodic_checks_dbus(struct lws_context *context, int tsi, time_t now) } } lws_end_foreach_dll_safe(rdt, nx); + __lws_sul_insert(&pt->pt_sul_owner, &pt->dbus.sul, + 3 * LWS_US_PER_SEC); +} + +static int +rops_pt_init_destroy_dbus(struct lws_context *context, + const struct lws_context_creation_info *info, + struct lws_context_per_thread *pt, int destroy) +{ + if (!destroy) { + + pt->dbus.sul.cb = lws_dbus_sul_cb; + + __lws_sul_insert(&pt->pt_sul_owner, &pt->dbus.sul, + 3 * LWS_US_PER_SEC); + } else + lws_dll2_remove(&pt->dbus.sul.list); + return 0; } @@ -503,10 +516,9 @@ struct lws_role_ops role_ops_dbus = { /* role name */ "dbus", /* alpn id */ NULL, /* check_upgrades */ NULL, - /* init_context */ NULL, + /* pt_init_destroy */ rops_pt_init_destroy_dbus, /* init_vhost */ NULL, /* destroy_vhost */ NULL, - /* periodic_checks */ rops_periodic_checks_dbus, /* service_flag_pending */ NULL, /* handle_POLLIN */ rops_handle_POLLIN_dbus, /* handle_POLLOUT */ NULL, @@ -522,6 +534,7 @@ struct lws_role_ops role_ops_dbus = { /* destroy_role */ NULL, /* adoption_bind */ NULL, /* client_bind */ NULL, + /* issue_keepalive */ NULL, /* adoption_cb clnt, srv */ { 0, 0 }, /* rx_cb clnt, srv */ { 0, 0 }, /* writeable cb clnt, srv */ { 0, 0 }, diff --git a/lib/roles/dbus/private-lib-roles-dbus.h b/lib/roles/dbus/private-lib-roles-dbus.h index eb75d9fdd..8245fbfcc 100644 --- a/lib/roles/dbus/private-lib-roles-dbus.h +++ b/lib/roles/dbus/private-lib-roles-dbus.h @@ -38,6 +38,7 @@ struct lws_role_dbus_timer { struct lws_pt_role_dbus { struct lws_dll2_owner timer_list_owner; + lws_sorted_usec_list_t sul; }; struct _lws_dbus_mode_related { diff --git a/lib/roles/h1/ops-h1.c b/lib/roles/h1/ops-h1.c index 4a6fbf08f..cc48aaae6 100644 --- a/lib/roles/h1/ops-h1.c +++ b/lib/roles/h1/ops-h1.c @@ -1105,24 +1105,23 @@ rops_close_kill_connection_h1(struct lws *wsi, enum lws_close_status reason) } int -rops_init_context_h1(struct lws_context *context, - const struct lws_context_creation_info *info) +rops_pt_init_destroy_h1(struct lws_context *context, + const struct lws_context_creation_info *info, + struct lws_context_per_thread *pt, int destroy) { /* * We only want to do this once... we will do it if no h2 support * otherwise let h2 ops do it. */ #if !defined(LWS_ROLE_H2) && defined(LWS_WITH_SERVER) - int n; - - for (n = 0; n < context->count_threads; n++) { - struct lws_context_per_thread *pt = &context->pt[n]; + if (!destroy) { pt->sul_ah_lifecheck.cb = lws_sul_http_ah_lifecheck; __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_ah_lifecheck, 30 * LWS_US_PER_SEC); - } + } else + lws_dll2_remove(&pt->sul_ah_lifecheck.list); #endif return 0; @@ -1132,10 +1131,9 @@ struct lws_role_ops role_ops_h1 = { /* role name */ "h1", /* alpn id */ "http/1.1", /* check_upgrades */ NULL, - /* init_context */ rops_init_context_h1, + /* pt_init_destroy */ rops_pt_init_destroy_h1, /* init_vhost */ NULL, /* destroy_vhost */ NULL, - /* periodic_checks */ NULL, /* service_flag_pending */ NULL, /* handle_POLLIN */ rops_handle_POLLIN_h1, /* handle_POLLOUT */ rops_handle_POLLOUT_h1, @@ -1159,6 +1157,7 @@ struct lws_role_ops role_ops_h1 = { #else NULL, #endif + /* issue_keepalive */ NULL, /* adoption_cb clnt, srv */ { LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED, LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED }, /* rx_cb clnt, srv */ { LWS_CALLBACK_RECEIVE_CLIENT_HTTP, diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 29aa9e746..71bd09804 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -120,7 +120,7 @@ lws_h2_dump_settings(struct http2_settings *set) } #endif -static struct lws_h2_protocol_send * +struct lws_h2_protocol_send * lws_h2_new_pps(enum lws_h2_protocol_send_type type) { struct lws_h2_protocol_send *pps = lws_malloc(sizeof(*pps), "pps"); @@ -212,6 +212,9 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi, wsi->vhost->conn_stats.h2_subs++; #endif + /* get the ball rolling */ + lws_validity_confirmed(wsi); + lwsl_info("%s: %p new ch %p, sid %d, usersp=%p, tx cr %d, " "peer_credit %d (nwsi tx_cr %d)\n", __func__, parent_wsi, wsi, sid, wsi->user_space, @@ -727,17 +730,27 @@ int lws_h2_do_pps_send(struct lws *wsi) break; } break; + + /* + * h2 only has PING... ACK = 0 = ping, ACK = 1 = pong + */ + + case LWS_H2_PPS_PING: case LWS_H2_PPS_PONG: - lwsl_debug("sending PONG\n"); + if (pps->type == LWS_H2_PPS_PING) + lwsl_info("sending PING\n"); + else { + lwsl_info("sending PONG\n"); + flags = LWS_H2_FLAG_SETTINGS_ACK; + } + memcpy(&set[LWS_PRE], pps->u.ping.ping_payload, 8); - n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_PING, - LWS_H2_FLAG_SETTINGS_ACK, + n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_PING, flags, LWS_H2_STREAM_ID_MASTER, 8, &set[LWS_PRE]); - if (n != 8) { - lwsl_info("send %d %d\n", n, m); + if (n != 8) goto bail; - } + break; case LWS_H2_PPS_GOAWAY: @@ -1599,6 +1612,7 @@ lws_h2_parse_end_of_frame(struct lws *wsi) case LWS_H2_FRAME_TYPE_PING: if (h2n->flags & LWS_H2_FLAG_SETTINGS_ACK) { // ack + lws_validity_confirmed(wsi); } else {/* they're sending us a ping request */ struct lws_h2_protocol_send *pps = lws_h2_new_pps(LWS_H2_PPS_PONG); @@ -1737,6 +1751,7 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen, lwsl_info("http2: %p: established\n", wsi); lwsi_set_state(wsi, LRS_H2_AWAIT_SETTINGS); + lws_validity_confirmed(wsi); h2n->count = 0; wsi->h2.tx_cr = 65535; @@ -2339,6 +2354,8 @@ lws_h2_ws_handshake(struct lws *wsi) (void *)hit->cgienv, 0)) return 1; + lws_validity_confirmed(wsi); + return 0; } diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c index de6573912..628898db9 100644 --- a/lib/roles/h2/ops-h2.c +++ b/lib/roles/h2/ops-h2.c @@ -538,33 +538,32 @@ rops_init_vhost_h2(struct lws_vhost *vh, return 0; } -static int -rops_init_context_h2(struct lws_context *context, - const struct lws_context_creation_info *info) +int +rops_pt_init_destroy_h2(struct lws_context *context, + const struct lws_context_creation_info *info, + struct lws_context_per_thread *pt, int destroy) { - int n; - context->set = lws_h2_stock_settings; -#if defined(LWS_WITH_SERVER) /* * We only want to do this once... we will do it if we are built * otherwise h1 ops will do it (or nobody if no http at all) */ - - for (n = 0; n < context->count_threads; n++) { - struct lws_context_per_thread *pt = &context->pt[n]; +#if !defined(LWS_ROLE_H2) && defined(LWS_WITH_SERVER) + if (!destroy) { pt->sul_ah_lifecheck.cb = lws_sul_http_ah_lifecheck; __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_ah_lifecheck, 30 * LWS_US_PER_SEC); - } + } else + lws_dll2_remove(&pt->sul_ah_lifecheck.list); #endif return 0; } + static lws_fileofs_t rops_tx_credit_h2(struct lws *wsi) { @@ -1242,14 +1241,51 @@ rops_alpn_negotiated_h2(struct lws *wsi, const char *alpn) return 0; } +static int +rops_issue_keepalive_h2(struct lws *wsi, int isvalid) +{ + struct lws *nwsi = lws_get_network_wsi(wsi); + struct lws_h2_protocol_send *pps; + uint64_t us = lws_now_usecs(); + + if (isvalid) { + _lws_validity_confirmed_role(nwsi); + + return 0; + } + + /* + * We can only send these frames on the network connection itself... + * we shouldn't be tracking validity on anything else + */ + + assert(wsi == nwsi); + + pps = lws_h2_new_pps(LWS_H2_PPS_PING); + if (!pps) + return 1; + + /* + * The peer is defined to copy us back the unchanged payload in another + * PING frame this time with ACK set. So by sending that out with the + * current time, it's an interesting opportunity to learn the effective + * RTT on the link when the PONG comes in, plus or minus the time to + * schedule the PPS. + */ + + memcpy(pps->u.ping.ping_payload, &us, 8); + lws_pps_schedule(nwsi, pps); + + return 0; +} + struct lws_role_ops role_ops_h2 = { /* role name */ "h2", /* alpn id */ "h2", /* check_upgrades */ rops_check_upgrades_h2, - /* init_context */ rops_init_context_h2, + /* pt_init_destroy */ rops_pt_init_destroy_h2, /* init_vhost */ rops_init_vhost_h2, /* destroy_vhost */ NULL, - /* periodic_checks */ NULL, /* service_flag_pending */ NULL, /* handle_POLLIN */ rops_handle_POLLIN_h2, /* handle_POLLOUT */ rops_handle_POLLOUT_h2, @@ -1265,6 +1301,7 @@ struct lws_role_ops role_ops_h2 = { /* destroy_role */ rops_destroy_role_h2, /* adoption_bind */ NULL, /* client_bind */ NULL, + /* issue_keepalive */ rops_issue_keepalive_h2, /* adoption_cb clnt, srv */ { LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED, LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED }, /* rx cb clnt, srv */ { LWS_CALLBACK_RECEIVE_CLIENT_HTTP, diff --git a/lib/roles/h2/private-lib-roles-h2.h b/lib/roles/h2/private-lib-roles-h2.h index ff169ee79..6c3b382d3 100644 --- a/lib/roles/h2/private-lib-roles-h2.h +++ b/lib/roles/h2/private-lib-roles-h2.h @@ -211,6 +211,7 @@ enum lws_h2_protocol_send_type { LWS_PPS_NONE, LWS_H2_PPS_MY_SETTINGS, LWS_H2_PPS_ACK_SETTINGS, + LWS_H2_PPS_PING, LWS_H2_PPS_PONG, LWS_H2_PPS_GOAWAY, LWS_H2_PPS_RST_STREAM, @@ -406,3 +407,5 @@ int lws_handle_POLLOUT_event_h2(struct lws *wsi); int lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len); +struct lws_h2_protocol_send * +lws_h2_new_pps(enum lws_h2_protocol_send_type type); diff --git a/lib/roles/listen/ops-listen.c b/lib/roles/listen/ops-listen.c index a153cb202..cc1fa519f 100644 --- a/lib/roles/listen/ops-listen.c +++ b/lib/roles/listen/ops-listen.c @@ -176,10 +176,9 @@ struct lws_role_ops role_ops_listen = { /* role name */ "listen", /* alpn id */ NULL, /* check_upgrades */ NULL, - /* init_context */ NULL, + /* pt_init_destroy */ NULL, /* init_vhost */ NULL, /* destroy_vhost */ NULL, - /* periodic_checks */ NULL, /* service_flag_pending */ NULL, /* handle_POLLIN */ rops_handle_POLLIN_listen, /* handle_POLLOUT */ rops_handle_POLLOUT_listen, @@ -195,6 +194,7 @@ struct lws_role_ops role_ops_listen = { /* destroy_role */ NULL, /* adoption_bind */ NULL, /* client_bind */ NULL, + /* issue_keepalive */ NULL, /* adoption_cb clnt, srv */ { 0, 0 }, /* rx_cb clnt, srv */ { 0, 0 }, /* writeable cb clnt, srv */ { 0, 0 }, diff --git a/lib/roles/pipe/ops-pipe.c b/lib/roles/pipe/ops-pipe.c index 5736f54df..16a952e9e 100644 --- a/lib/roles/pipe/ops-pipe.c +++ b/lib/roles/pipe/ops-pipe.c @@ -72,10 +72,9 @@ struct lws_role_ops role_ops_pipe = { /* role name */ "pipe", /* alpn id */ NULL, /* check_upgrades */ NULL, - /* init_context */ NULL, + /* pt_init_destroy */ NULL, /* init_vhost */ NULL, /* destroy_vhost */ NULL, - /* periodic_checks */ NULL, /* service_flag_pending */ NULL, /* handle_POLLIN */ rops_handle_POLLIN_pipe, /* handle_POLLOUT */ NULL, @@ -91,6 +90,7 @@ struct lws_role_ops role_ops_pipe = { /* destroy_role */ NULL, /* adoption_bind */ NULL, /* client_bind */ NULL, + /* issue_keepalive */ NULL, /* adoption_cb clnt, srv */ { 0, 0 }, /* rx_cb clnt, srv */ { 0, 0 }, /* writeable cb clnt, srv */ { 0, 0 }, diff --git a/lib/roles/private-lib-roles.h b/lib/roles/private-lib-roles.h index d4664d3b7..9d09596ef 100644 --- a/lib/roles/private-lib-roles.h +++ b/lib/roles/private-lib-roles.h @@ -180,16 +180,14 @@ struct lws_role_ops { */ int (*check_upgrades)(struct lws *wsi); /* role-specific context init during context creation */ - int (*init_context)(struct lws_context *context, - const struct lws_context_creation_info *info); + int (*pt_init_destroy)(struct lws_context *context, + const struct lws_context_creation_info *info, + struct lws_context_per_thread *pt, int destroy); /* role-specific per-vhost init during vhost creation */ int (*init_vhost)(struct lws_vhost *vh, const struct lws_context_creation_info *info); /* role-specific per-vhost destructor during vhost destroy */ int (*destroy_vhost)(struct lws_vhost *vh); - /* generic 1Hz callback for the role itself */ - int (*periodic_checks)(struct lws_context *context, int tsi, - time_t now); /* chance for the role to force POLLIN without network activity */ int (*service_flag_pending)(struct lws_context *context, int tsi); /* an fd using this role has POLLIN signalled */ @@ -233,6 +231,9 @@ struct lws_role_ops { * case ret 0 = OK, 1 = fail, wsi needs freeing, -1 = fail, wsi freed */ int (*client_bind)(struct lws *wsi, const struct lws_client_connect_info *i); + /* isvalid = 0: request a role-specific keepalive (PING etc) + * = 1: reset any related validity timer */ + int (*issue_keepalive)(struct lws *wsi, int isvalid); /* * the callback reasons for adoption for client, server diff --git a/lib/roles/raw-file/ops-raw-file.c b/lib/roles/raw-file/ops-raw-file.c index 20dfda4a1..7bd0e777d 100644 --- a/lib/roles/raw-file/ops-raw-file.c +++ b/lib/roles/raw-file/ops-raw-file.c @@ -81,10 +81,9 @@ struct lws_role_ops role_ops_raw_file = { /* role name */ "raw-file", /* alpn id */ NULL, /* check_upgrades */ NULL, - /* init_context */ NULL, + /* pt_init_destroy */ NULL, /* init_vhost */ NULL, /* destroy_vhost */ NULL, - /* periodic_checks */ NULL, /* service_flag_pending */ NULL, /* handle_POLLIN */ rops_handle_POLLIN_raw_file, /* handle_POLLOUT */ NULL, @@ -100,6 +99,7 @@ struct lws_role_ops role_ops_raw_file = { /* destroy_role */ NULL, /* adoption_bind */ rops_adoption_bind_raw_file, /* client_bind */ NULL, + /* issue_keepalive */ NULL, /* adoption_cb clnt, srv */ { LWS_CALLBACK_RAW_ADOPT_FILE, LWS_CALLBACK_RAW_ADOPT_FILE }, /* rx_cb clnt, srv */ { LWS_CALLBACK_RAW_RX_FILE, diff --git a/lib/roles/raw-proxy/ops-raw-proxy.c b/lib/roles/raw-proxy/ops-raw-proxy.c index d5368778f..5c098843f 100644 --- a/lib/roles/raw-proxy/ops-raw-proxy.c +++ b/lib/roles/raw-proxy/ops-raw-proxy.c @@ -188,10 +188,9 @@ struct lws_role_ops role_ops_raw_proxy = { /* role name */ "raw-proxy", /* alpn id */ NULL, /* check_upgrades */ NULL, - /* init_context */ NULL, + /* pt_init_destroy */ NULL, /* init_vhost */ NULL, /* destroy_vhost */ NULL, - /* periodic_checks */ NULL, /* service_flag_pending */ NULL, /* handle_POLLIN */ rops_handle_POLLIN_raw_proxy, /* handle_POLLOUT */ rops_handle_POLLOUT_raw_proxy, @@ -207,6 +206,7 @@ struct lws_role_ops role_ops_raw_proxy = { /* destroy_role */ NULL, /* adoption_bind */ rops_adoption_bind_raw_proxy, /* client_bind */ rops_client_bind_raw_proxy, + /* issue_keepalive */ NULL, /* adoption_cb clnt, srv */ { LWS_CALLBACK_RAW_PROXY_CLI_ADOPT, LWS_CALLBACK_RAW_PROXY_SRV_ADOPT }, /* rx_cb clnt, srv */ { LWS_CALLBACK_RAW_PROXY_CLI_RX, diff --git a/lib/roles/raw-skt/ops-raw-skt.c b/lib/roles/raw-skt/ops-raw-skt.c index d34ae94d7..ad91f27a4 100644 --- a/lib/roles/raw-skt/ops-raw-skt.c +++ b/lib/roles/raw-skt/ops-raw-skt.c @@ -219,10 +219,9 @@ struct lws_role_ops role_ops_raw_skt = { /* role name */ "raw-skt", /* alpn id */ NULL, /* check_upgrades */ NULL, - /* init_context */ NULL, + /* pt_init_destroy */ NULL, /* init_vhost */ NULL, /* destroy_vhost */ NULL, - /* periodic_checks */ NULL, /* service_flag_pending */ NULL, /* handle_POLLIN */ rops_handle_POLLIN_raw_skt, /* handle_POLLOUT */ NULL, @@ -246,6 +245,7 @@ struct lws_role_ops role_ops_raw_skt = { #else NULL, #endif + /* issue_keepalive */ NULL, /* adoption_cb clnt, srv */ { LWS_CALLBACK_RAW_CONNECTED, LWS_CALLBACK_RAW_ADOPT }, /* rx_cb clnt, srv */ { LWS_CALLBACK_RAW_RX, diff --git a/lib/roles/ws/client-parser-ws.c b/lib/roles/ws/client-parser-ws.c index 17c0031b3..208e80d68 100644 --- a/lib/roles/ws/client-parser-ws.c +++ b/lib/roles/ws/client-parser-ws.c @@ -31,7 +31,6 @@ int lws_ws_client_rx_sm(struct lws *wsi, unsigned char c) { - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; int callback_action = LWS_CALLBACK_CLIENT_RECEIVE; struct lws_ext_pm_deflate_rx_ebufs pmdrx; unsigned short close_code; @@ -488,20 +487,7 @@ ping_drop: lwsl_hexdump(&wsi->ws->rx_ubuf[LWS_PRE], wsi->ws->rx_ubuf_head); - if (wsi->ws->await_pong) { - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - wsi->ws->await_pong = 0; - - /* - * prepare to send the ping again if nothing - * sent to countermand it - */ - - __lws_sul_insert(&pt->pt_sul_owner, - &wsi->sul_ping, - (lws_usec_t)wsi->context->ws_ping_pong_interval * - LWS_USEC_PER_SEC); - } + lws_validity_confirmed(wsi); /* issue it */ callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG; break; diff --git a/lib/roles/ws/client-ws.c b/lib/roles/ws/client-ws.c index 6dc6d80f4..67498da13 100644 --- a/lib/roles/ws/client-ws.c +++ b/lib/roles/ws/client-ws.c @@ -234,7 +234,6 @@ lws_generate_client_ws_handshake(struct lws *wsi, char *p, const char *conn1) int lws_client_ws_upgrade(struct lws *wsi, const char **cce) { - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; struct lws_context *context = wsi->context; struct lws_tokenize ts; int n, len, okay = 0; @@ -242,6 +241,7 @@ lws_client_ws_upgrade(struct lws *wsi, const char **cce) char *p, buf[64]; const char *pc; #if !defined(LWS_WITHOUT_EXTENSIONS) + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; char *sb = (char *)&pt->serv_buf[0]; const struct lws_ext_options *opts; const struct lws_extension *ext; @@ -630,15 +630,8 @@ check_accept: /* free up his parsing allocations */ lws_header_table_detach(wsi, 0); - lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED, - &role_ops_ws); - - if (wsi->context->ws_ping_pong_interval && !wsi->http2_substream ) { - wsi->sul_ping.cb = lws_sul_wsping_cb; - __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_ping, - (lws_usec_t)wsi->context->ws_ping_pong_interval * - LWS_USEC_PER_SEC); - } + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED, &role_ops_ws); + lws_validity_confirmed(wsi); wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c index 0ca5a723c..27109a624 100644 --- a/lib/roles/ws/ops-ws.c +++ b/lib/roles/ws/ops-ws.c @@ -34,7 +34,6 @@ int lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c) { - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; int callback_action = LWS_CALLBACK_RECEIVE; struct lws_ext_pm_deflate_rx_ebufs pmdrx; unsigned short close_code; @@ -550,22 +549,7 @@ ping_drop: lwsl_hexdump(&wsi->ws->rx_ubuf[LWS_PRE], wsi->ws->rx_ubuf_head); - if (wsi->ws->await_pong) { - lwsl_info("received expected PONG on wsi %p\n", - wsi); - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - wsi->ws->await_pong = 0; - - /* - * prepare to send the ping again if nothing - * sent to countermand it - */ - - __lws_sul_insert(&pt->pt_sul_owner, - &wsi->sul_ping, - (lws_usec_t)wsi->context->ws_ping_pong_interval * - LWS_USEC_PER_SEC); - } + lws_validity_confirmed(wsi); /* issue it */ callback_action = LWS_CALLBACK_RECEIVE_PONG; @@ -839,52 +823,13 @@ lws_0405_frame_mask_generate(struct lws *wsi) return 0; } -void -lws_sul_wsping_cb(lws_sorted_usec_list_t *sul) -{ - struct lws *wsi = lws_container_of(sul, struct lws, sul_ping); - - /* - * The sul_ping timer came up... either it's time to send a PING - * (!wsi->ws->send_check_ping), or we didn't get the PONG in time - * (wsi->ws->send_check_ping) - */ - - if (!wsi->ws->send_check_ping) { - lwsl_info("%s: req pp on wsi %p\n", __func__, wsi); - - wsi->ws->send_check_ping = 1; - lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING, - wsi->context->timeout_secs); - lws_callback_on_writable(wsi); - - return; - } - - if (wsi->ws->await_pong) { - /* it didn't return the PONG in time */ - - lwsl_info("%s: wsi %p: failed to send PONG\n", __func__, wsi); - __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, - "PONG timeout"); - } -} - int lws_server_init_wsi_for_ws(struct lws *wsi) { - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; int n; lwsi_set_state(wsi, LRS_ESTABLISHED); - if (wsi->context->ws_ping_pong_interval && !wsi->http2_substream ) { - wsi->sul_ping.cb = lws_sul_wsping_cb; - __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_ping, - (lws_usec_t)wsi->context->ws_ping_pong_interval * - LWS_USEC_PER_SEC); - } - /* * create the frame buffer for this connection according to the * size mentioned in the protocol definition. If 0 there, use @@ -925,6 +870,7 @@ lws_server_init_wsi_for_ws(struct lws *wsi) wsi->h2_stream_carries_ws)) return 1; + lws_validity_confirmed(wsi); lwsl_debug("ws established\n"); return 0; @@ -1302,7 +1248,6 @@ drain: int rops_handle_POLLOUT_ws(struct lws *wsi) { - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; int write_type = LWS_WRITE_PONG; #if !defined(LWS_WITHOUT_EXTENSIONS) struct lws_ext_pm_deflate_rx_ebufs pmdrx; @@ -1380,24 +1325,17 @@ int rops_handle_POLLOUT_ws(struct lws *wsi) } if (!wsi->socket_is_permanently_unusable && - wsi->ws->send_check_ping && wsi->context->ws_ping_pong_interval) { + wsi->ws->send_check_ping) { lwsl_info("%s: issuing ping on wsi %p: %s %s h2: %d\n", __func__, wsi, wsi->role_ops->name, wsi->protocol->name, wsi->http2_substream); wsi->ws->send_check_ping = 0; - wsi->ws->await_pong = 1; n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE], 0, LWS_WRITE_PING); if (n < 0) return LWS_HP_RET_BAIL_DIE; - /* give it a few seconds to respond with the PONG */ - - __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_ping, - (lws_usec_t)wsi->context->timeout_secs * - LWS_USEC_PER_SEC); - return LWS_HP_RET_BAIL_OK; } @@ -1634,8 +1572,8 @@ static int rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len, enum lws_write_protocol *wp) { - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; #if !defined(LWS_WITHOUT_EXTENSIONS) + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; enum lws_write_protocol wpt; #endif struct lws_ext_pm_deflate_rx_ebufs pmdrx; @@ -1682,13 +1620,7 @@ rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len, // assert(0); } #endif - /* reset the ping wait */ - if (wsi->context->ws_ping_pong_interval) { - wsi->sul_ping.cb = lws_sul_wsping_cb; - __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_ping, - (lws_usec_t)wsi->context->ws_ping_pong_interval * - LWS_USEC_PER_SEC); - } + if (((*wp) & 0x1f) == LWS_WRITE_HTTP || ((*wp) & 0x1f) == LWS_WRITE_HTTP_FINAL || ((*wp) & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION || @@ -1980,7 +1912,6 @@ send_raw: static int rops_close_kill_connection_ws(struct lws *wsi, enum lws_close_status reason) { - lws_dll2_remove(&wsi->sul_ping.list); /* deal with ws encapsulation in h2 */ #if defined(LWS_WITH_HTTP2) if (wsi->http2_substream && wsi->h2_stream_carries_ws) @@ -2087,14 +2018,41 @@ rops_destroy_role_ws(struct lws *wsi) return 0; } +static int +rops_issue_keepalive_ws(struct lws *wsi, int isvalid) +{ + uint64_t us; + +#if defined(LWS_WITH_HTTP2) + if (lwsi_role_h2_ENCAPSULATION(wsi)) { + /* we know then that it has an h2 parent */ + struct lws *enc = role_ops_h2.encapsulation_parent(wsi); + + assert(enc); + if (enc->role_ops->issue_keepalive(wsi, isvalid)) + return 1; + } +#endif + + if (isvalid) + _lws_validity_confirmed_role(wsi); + else { + us = lws_now_usecs(); + memcpy(&wsi->ws->ping_payload_buf[LWS_PRE], &us, 8); + wsi->ws->send_check_ping = 1; + lws_callback_on_writable(wsi); + } + + return 0; +} + struct lws_role_ops role_ops_ws = { /* role name */ "ws", /* alpn id */ NULL, /* check_upgrades */ NULL, - /* init_context */ NULL, + /* pt_init_destroy */ NULL, /* init_vhost */ rops_init_vhost_ws, /* destroy_vhost */ rops_destroy_vhost_ws, - /* periodic_checks */ NULL, /* service_flag_pending */ rops_service_flag_pending_ws, /* handle_POLLIN */ rops_handle_POLLIN_ws, /* handle_POLLOUT */ rops_handle_POLLOUT_ws, @@ -2110,6 +2068,7 @@ struct lws_role_ops role_ops_ws = { /* destroy_role */ rops_destroy_role_ws, /* adoption_bind */ NULL, /* client_bind */ NULL, + /* issue_keepalive */ rops_issue_keepalive_ws, /* adoption_cb clnt, srv */ { LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED, LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED }, /* rx_cb clnt, srv */ { LWS_CALLBACK_CLIENT_RECEIVE, diff --git a/lib/roles/ws/private-lib-roles-ws.h b/lib/roles/ws/private-lib-roles-ws.h index b1cfd8966..287b0a9e3 100644 --- a/lib/roles/ws/private-lib-roles-ws.h +++ b/lib/roles/ws/private-lib-roles-ws.h @@ -104,23 +104,6 @@ struct _lws_websocket_related { /* Also used for close content... control opcode == < 128 */ uint8_t ping_payload_buf[128 - 3 + LWS_PRE]; - uint8_t mask[4]; - - size_t rx_packet_length; - uint32_t rx_ubuf_head; - uint32_t rx_ubuf_alloc; - - uint8_t ping_payload_len; - uint8_t mask_idx; - uint8_t opcode; - uint8_t rsv; - uint8_t rsv_first_msg; - /* zero if no info, or length including 2-byte close code */ - uint8_t close_in_ping_buffer_len; - uint8_t utf8; - uint8_t stashed_write_type; - uint8_t tx_draining_stashed_wp; - uint8_t ietf_spec_revision; unsigned int final:1; unsigned int frame_is_binary:1; @@ -138,13 +121,31 @@ struct _lws_websocket_related { unsigned int send_check_ping:1; unsigned int first_fragment:1; unsigned int peer_has_sent_close:1; - unsigned int await_pong; #if !defined(LWS_WITHOUT_EXTENSIONS) unsigned int extension_data_pending:1; unsigned int rx_draining_ext:1; unsigned int tx_draining_ext:1; unsigned int pmd_trailer_application:1; +#endif + uint8_t mask[4]; + + size_t rx_packet_length; + uint32_t rx_ubuf_head; + uint32_t rx_ubuf_alloc; + + uint8_t ping_payload_len; + uint8_t mask_idx; + uint8_t opcode; + uint8_t rsv; + uint8_t rsv_first_msg; + /* zero if no info, or length including 2-byte close code */ + uint8_t close_in_ping_buffer_len; + uint8_t utf8; + uint8_t stashed_write_type; + uint8_t tx_draining_stashed_wp; + uint8_t ietf_spec_revision; +#if !defined(LWS_WITHOUT_EXTENSIONS) uint8_t count_act_ext; #endif }; diff --git a/minimal-examples/http-client/minimal-http-client/README.md b/minimal-examples/http-client/minimal-http-client/README.md index 3113aa022..9baedce5a 100644 --- a/minimal-examples/http-client/minimal-http-client/README.md +++ b/minimal-examples/http-client/minimal-http-client/README.md @@ -21,6 +21,7 @@ Commandline option|Meaning -j|Apply tls option LCCSCF_ALLOW_SELFSIGNED -m|Apply tls option LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK -e|Apply tls option LCCSCF_ALLOW_EXPIRED +-v|Connection validity use 3s / 10s instead of default 5m / 5m10s ``` $ ./lws-minimal-http-client 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 4da0d1ee8..141f23f2a 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 @@ -22,6 +22,11 @@ static int long_poll; #endif static struct lws *client_wsi; +static const lws_retry_bo_t retry = { + .secs_since_valid_ping = 3, + .secs_since_valid_hangup = 10, +}; + static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) @@ -207,6 +212,11 @@ int main(int argc, const char **argv) if (lws_cmdline_option(argc, argv, "-e")) i.ssl_connection |= LCCSCF_ALLOW_EXPIRED; + /* the default validity check is 5m / 5m10s... -v = 3s / 10s */ + + if (lws_cmdline_option(argc, argv, "-v")) + i.retry_and_idle_policy = &retry; + if ((p = lws_cmdline_option(argc, argv, "--server"))) i.address = p; diff --git a/minimal-examples/http-server/minimal-http-server-h2-long-poll/README.md b/minimal-examples/http-server/minimal-http-server-h2-long-poll/README.md index cc8794b8a..c327e8f6c 100644 --- a/minimal-examples/http-server/minimal-http-server-h2-long-poll/README.md +++ b/minimal-examples/http-server/minimal-http-server-h2-long-poll/README.md @@ -5,6 +5,13 @@ ``` $ cmake . && make ``` +## Commandline Options + +Option|Meaning +---|--- +-d|Set logging verbosity +-s|Serve using TLS selfsigned cert (ie, connect to it with https://...) +-v|Connection validity use 3s / 10s instead of default 5m / 5m10s ## usage diff --git a/minimal-examples/http-server/minimal-http-server-h2-long-poll/minimal-http-server.c b/minimal-examples/http-server/minimal-http-server-h2-long-poll/minimal-http-server.c index 197ce5d8c..b558dc968 100644 --- a/minimal-examples/http-server/minimal-http-server-h2-long-poll/minimal-http-server.c +++ b/minimal-examples/http-server/minimal-http-server-h2-long-poll/minimal-http-server.c @@ -33,6 +33,11 @@ struct pss { char pending; }; +static const lws_retry_bo_t retry = { + .secs_since_valid_ping = 5, + .secs_since_valid_hangup = 10, +}; + static void sul_cb(lws_sorted_usec_list_t *sul) { @@ -132,6 +137,11 @@ int main(int argc, const char **argv) LWS_SERVER_OPTION_VH_H2_HALF_CLOSED_LONG_POLL | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + /* the default validity check is 5m / 5m10s... -v = 5s / 10s */ + + if (lws_cmdline_option(argc, argv, "-v")) + info.retry_and_idle_policy = &retry; + context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); diff --git a/minimal-examples/ws-client/minimal-ws-client-ping/README.md b/minimal-examples/ws-client/minimal-ws-client-ping/README.md index 13be92c5a..014e67a23 100644 --- a/minimal-examples/ws-client/minimal-ws-client-ping/README.md +++ b/minimal-examples/ws-client/minimal-ws-client-ping/README.md @@ -19,6 +19,8 @@ Option|Meaning --port|Use a specific port instead of 443, eg `--port 7681` -z|Send zero-length pings for testing --protocol|Use a specific ws subprotocol rather than lws-mirror-protocol, eg, `--protocol myprotocol` +-v|Connection validity use 3s / 10s instead of default 5m / 5m10s + ## usage diff --git a/minimal-examples/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c b/minimal-examples/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c index 49b5d6cc9..63279ff2e 100644 --- a/minimal-examples/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c +++ b/minimal-examples/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c @@ -17,7 +17,7 @@ static struct lws_context *context; static struct lws *client_wsi; -static int interrupted, zero_length_ping, port = 443, +static int interrupted, zero_length_ping, port = 443, quick_pings, no_user_ping, ssl_connection = LCCSCF_USE_SSL; static const char *server_address = "libwebsockets.org", *pro = "lws-mirror-protocol"; @@ -25,6 +25,11 @@ struct pss { int send_a_ping; }; +static const lws_retry_bo_t retry = { + .secs_since_valid_ping = 3, + .secs_since_valid_hangup = 10, +}; + static int connect_client(void) { @@ -42,6 +47,8 @@ connect_client(void) i.protocol = pro; i.local_protocol_name = "lws-ping-test"; i.pwsi = &client_wsi; + if (quick_pings) + i.retry_and_idle_policy = &retry; return !lws_client_connect_via_info(&i); } @@ -110,10 +117,12 @@ callback_minimal_broker(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_TIMER: + if (no_user_ping) + break; /* we want to send a ws PING every few seconds */ pss->send_a_ping = 1; lws_callback_on_writable(wsi); - lws_set_timer_usecs(wsi, 5 * LWS_USEC_PER_SEC); + lws_set_timer_usecs(wsi, 10 * LWS_USEC_PER_SEC); break; /* rate-limited client connect retries */ @@ -185,6 +194,9 @@ int main(int argc, const char **argv) if (lws_cmdline_option(argc, argv, "-z")) zero_length_ping = 1; + if (lws_cmdline_option(argc, argv, "-n")) + no_user_ping = 1; + if ((p = lws_cmdline_option(argc, argv, "--protocol"))) pro = p; @@ -197,6 +209,11 @@ int main(int argc, const char **argv) if ((p = lws_cmdline_option(argc, argv, "--port"))) port = atoi(p); + /* the default validity check is 5m / 5m10s... -v = 5s / 10s */ + + if (lws_cmdline_option(argc, argv, "-v")) + quick_pings = 1; + /* * 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/ws-server/minimal-ws-server/README.md b/minimal-examples/ws-server/minimal-ws-server/README.md index 9b0a094bf..513207231 100644 --- a/minimal-examples/ws-server/minimal-ws-server/README.md +++ b/minimal-examples/ws-server/minimal-ws-server/README.md @@ -13,6 +13,7 @@ Option|Meaning -d|Set logging verbosity -s|Serve using TLS selfsigned cert (ie, connect to it with https://...) -h|Strict Host: header checking against vhost name (localhost) and port +-v|Connection validity use 3s / 10s instead of default 5m / 5m10s ## usage diff --git a/minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c b/minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c index 12e828a8d..ea29f3686 100644 --- a/minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c +++ b/minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c @@ -27,6 +27,11 @@ static struct lws_protocols protocols[] = { { NULL, NULL, 0, 0 } /* terminator */ }; +static const lws_retry_bo_t retry = { + .secs_since_valid_ping = 3, + .secs_since_valid_hangup = 10, +}; + static int interrupted; static const struct lws_http_mount mount = { @@ -94,6 +99,9 @@ int main(int argc, const char **argv) if (lws_cmdline_option(argc, argv, "-h")) info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK; + if (lws_cmdline_option(argc, argv, "-v")) + info.retry_and_idle_policy = &retry; + context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n");