/src/libwebsockets/lib/core-net/wsi-timeout.c
Line | Count | Source |
1 | | /* |
2 | | * libwebsockets - small server side websockets and web server implementation |
3 | | * |
4 | | * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com> |
5 | | * |
6 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | | * of this software and associated documentation files (the "Software"), to |
8 | | * deal in the Software without restriction, including without limitation the |
9 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
10 | | * sell copies of the Software, and to permit persons to whom the Software is |
11 | | * furnished to do so, subject to the following conditions: |
12 | | * |
13 | | * The above copyright notice and this permission notice shall be included in |
14 | | * all copies or substantial portions of the Software. |
15 | | * |
16 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
19 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
21 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
22 | | * IN THE SOFTWARE. |
23 | | */ |
24 | | |
25 | | #include "private-lib-core.h" |
26 | | |
27 | | void |
28 | | __lws_wsi_remove_from_sul(struct lws *wsi) |
29 | 0 | { |
30 | 0 | lws_sul_cancel(&wsi->sul_timeout); |
31 | 0 | lws_sul_cancel(&wsi->sul_hrtimer); |
32 | 0 | lws_sul_cancel(&wsi->sul_validity); |
33 | 0 | lws_sul_cancel(&wsi->sul_connect_timeout); |
34 | 0 | #if defined(LWS_WITH_CLIENT) |
35 | | /* |
36 | | * The h3 grace and happy-eyeballs timers hold the wsi too... |
37 | | * without cancelling them here, a wsi that dies while its QUIC |
38 | | * race is still pending leaves them scheduled against freed |
39 | | * memory |
40 | | */ |
41 | 0 | lws_sul_cancel(&wsi->sul_h3_grace); |
42 | 0 | lws_sul_cancel(&wsi->sul_happy_eyeballs); |
43 | 0 | #endif |
44 | | #if defined(WIN32) |
45 | | lws_sul_cancel(&wsi->win32_sul_connect_async_check); |
46 | | #endif |
47 | | #if defined(LWS_WITH_HTTP_PROXY) |
48 | | lws_sul_cancel(&wsi->sul_ws_proxy_est); |
49 | | #endif |
50 | | #if defined(LWS_WITH_SYS_FAULT_INJECTION) |
51 | | lws_sul_cancel(&wsi->sul_fault_timedclose); |
52 | | #endif |
53 | | #if defined(LWS_TLS_SYNTHESIZE_CB) |
54 | | lws_sul_cancel(&wsi->tls.sul_cb_synth); |
55 | | #endif |
56 | 0 | } |
57 | | |
58 | | /* |
59 | | * hrtimer |
60 | | */ |
61 | | |
62 | | static void |
63 | | lws_sul_hrtimer_cb(lws_sorted_usec_list_t *sul) |
64 | 0 | { |
65 | 0 | struct lws *wsi = lws_container_of(sul, struct lws, sul_hrtimer); |
66 | |
|
67 | 0 | if (wsi->a.protocol && |
68 | 0 | wsi->a.protocol->callback(wsi, LWS_CALLBACK_TIMER, |
69 | 0 | wsi->user_space, NULL, 0)) |
70 | 0 | __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, |
71 | 0 | "hrtimer cb errored"); |
72 | 0 | } |
73 | | |
74 | | void |
75 | | __lws_set_timer_usecs(struct lws *wsi, lws_usec_t us) |
76 | 0 | { |
77 | 0 | struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; |
78 | |
|
79 | 0 | wsi->sul_hrtimer.cb = lws_sul_hrtimer_cb; |
80 | 0 | __lws_sul_insert_us(&pt->pt_sul_owner[LWSSULLI_MISS_IF_SUSPENDED], |
81 | 0 | &wsi->sul_hrtimer, us); |
82 | 0 | } |
83 | | |
84 | | void |
85 | | lws_set_timer_usecs(struct lws *wsi, lws_usec_t usecs) |
86 | 0 | { |
87 | 0 | if ((int64_t)usecs == (int64_t)LWS_SET_TIMER_USEC_CANCEL) |
88 | 0 | lws_sul_cancel(&wsi->sul_hrtimer); |
89 | 0 | else |
90 | 0 | __lws_set_timer_usecs(wsi, usecs); |
91 | 0 | } |
92 | | |
93 | | /* |
94 | | * wsi timeout |
95 | | */ |
96 | | |
97 | | static void |
98 | | lws_sul_wsitimeout_cb(lws_sorted_usec_list_t *sul) |
99 | 0 | { |
100 | 0 | struct lws *wsi = lws_container_of(sul, struct lws, sul_timeout); |
101 | 0 | struct lws_context *cx = wsi->a.context; |
102 | 0 | struct lws_context_per_thread *pt = &cx->pt[(int)wsi->tsi]; |
103 | | |
104 | | /* no need to log normal idle keepalive timeout */ |
105 | | // if (wsi->pending_timeout != PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE) |
106 | 0 | if (wsi->pending_timeout == PENDING_TIMEOUT_HTTP_RESPONSE) |
107 | | /* |
108 | | * We started sending the response (response headers went out) |
109 | | * but never completed it: under h2 / h3 no END_STREAM was sent |
110 | | * on a HEADERS frame, or the response body was never finished. |
111 | | * The usual cause is a headers-only response written without |
112 | | * LWS_WRITE_H2_STREAM_END -- the stream then hangs open with no |
113 | | * further write ever closing it. Call out the reason explicitly |
114 | | * since this is a user-code bug, not a network condition. |
115 | | */ |
116 | 0 | lwsl_wsi_warn(wsi, "HTTP response started but never completed " |
117 | 0 | "(no END_STREAM); user code likely wrote a " |
118 | 0 | "headers-only response without " |
119 | 0 | "LWS_WRITE_H2_STREAM_END, protocol=%s, uri=\"%s\"", |
120 | 0 | wsi->a.protocol ? wsi->a.protocol->name : "none", |
121 | 0 | lws_wsi_request_uri(wsi) ? |
122 | 0 | lws_wsi_request_uri(wsi) : ""); |
123 | 0 | #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) |
124 | 0 | else if (wsi->pending_timeout != PENDING_TIMEOUT_USER_OK) |
125 | 0 | lwsl_wsi_info(wsi, "TIMEDOUT WAITING %d, dhdr %d, ah %p, wl %d", |
126 | 0 | wsi->pending_timeout, |
127 | 0 | wsi->hdr_parsing_completed, wsi->http.ah, |
128 | 0 | pt->http.ah_wait_list_length); |
129 | | #if defined(LWS_WITH_CGI) |
130 | | if (wsi->http.cgi) |
131 | | lwsl_wsi_notice(wsi, "CGI timeout: %s", wsi->http.cgi->summary); |
132 | | #endif |
133 | | #else |
134 | | if (wsi->pending_timeout != PENDING_TIMEOUT_USER_OK) |
135 | | lwsl_wsi_info(wsi, "TIMEDOUT WAITING on %d ", |
136 | | wsi->pending_timeout); |
137 | | #endif |
138 | | /* cgi timeout */ |
139 | 0 | if (wsi->pending_timeout != PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE) |
140 | | /* |
141 | | * Since he failed a timeout, he already had a chance to |
142 | | * do something and was unable to... that includes |
143 | | * situations like half closed connections. So process |
144 | | * this "failed timeout" close as a violent death and |
145 | | * don't try to do protocol cleanup like flush partials. |
146 | | */ |
147 | 0 | wsi->socket_is_permanently_unusable = 1; |
148 | 0 | #if defined(LWS_WITH_CLIENT) |
149 | 0 | if (lwsi_state(wsi) == LRS_WAITING_SSL) |
150 | 0 | lws_inform_client_conn_fail(wsi, |
151 | 0 | (void *)"Timed out waiting SSL", 21); |
152 | 0 | if (lwsi_state(wsi) == LRS_WAITING_SERVER_REPLY) |
153 | 0 | lws_inform_client_conn_fail(wsi, |
154 | 0 | (void *)"Timed out waiting server reply", 30); |
155 | 0 | #endif |
156 | |
|
157 | 0 | lws_context_lock(cx, __func__); |
158 | 0 | lws_pt_lock(pt, __func__); |
159 | 0 | __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "timeout"); |
160 | 0 | lws_pt_unlock(pt); |
161 | 0 | lws_context_unlock(cx); |
162 | 0 | } |
163 | | |
164 | | void |
165 | | __lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) |
166 | 0 | { |
167 | 0 | struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; |
168 | |
|
169 | 0 | if (reason == PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE && secs > 0) { |
170 | 0 | if (wsi->immortal_substream_count > 0) { |
171 | 0 | lwsl_wsi_info(wsi, "Refusing to set idle keepalive timeout because it has %d immortal substreams", wsi->immortal_substream_count); |
172 | 0 | return; |
173 | 0 | } |
174 | 0 | } |
175 | 0 | wsi->sul_timeout.cb = lws_sul_wsitimeout_cb; |
176 | 0 | __lws_sul_insert_us(&pt->pt_sul_owner[LWSSULLI_MISS_IF_SUSPENDED], |
177 | 0 | &wsi->sul_timeout, |
178 | 0 | ((lws_usec_t)secs) * LWS_US_PER_SEC); |
179 | |
|
180 | 0 | lwsl_wsi_debug(wsi, "%d secs, reason %d\n", secs, reason); |
181 | |
|
182 | 0 | wsi->pending_timeout = (char)reason; |
183 | 0 | } |
184 | | |
185 | | void |
186 | | lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) |
187 | 0 | { |
188 | 0 | struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; |
189 | |
|
190 | 0 | lws_context_lock(pt->context, __func__); |
191 | 0 | lws_pt_lock(pt, __func__); |
192 | 0 | lws_dll2_remove(&wsi->sul_timeout.list); |
193 | 0 | lws_pt_unlock(pt); |
194 | |
|
195 | 0 | if (!secs) |
196 | 0 | goto bail; |
197 | | |
198 | 0 | if (secs == LWS_TO_KILL_SYNC) { |
199 | 0 | lwsl_wsi_debug(wsi, "TO_KILL_SYNC"); |
200 | 0 | lws_context_unlock(pt->context); |
201 | 0 | lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, |
202 | 0 | "to sync kill"); |
203 | 0 | return; |
204 | 0 | } |
205 | | |
206 | 0 | if (secs == LWS_TO_KILL_ASYNC) |
207 | 0 | secs = 0; |
208 | |
|
209 | 0 | if (reason == PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE && secs > 0) { |
210 | 0 | if (wsi->immortal_substream_count > 0) { |
211 | 0 | lwsl_wsi_info(wsi, "Refusing to set idle keepalive timeout because it has %d immortal substreams", wsi->immortal_substream_count); |
212 | 0 | lws_context_unlock(pt->context); |
213 | 0 | return; |
214 | 0 | } |
215 | 0 | } |
216 | 0 | if (secs && wsi->mux_stream_immortal) |
217 | 0 | lwsl_wsi_err(wsi, "on immortal stream %d %d", reason, secs); |
218 | |
|
219 | 0 | lws_pt_lock(pt, __func__); |
220 | 0 | __lws_set_timeout(wsi, reason, secs); |
221 | 0 | lws_pt_unlock(pt); |
222 | |
|
223 | 0 | bail: |
224 | 0 | lws_context_unlock(pt->context); |
225 | 0 | } |
226 | | |
227 | | void |
228 | | lws_set_timeout_us(struct lws *wsi, enum pending_timeout reason, lws_usec_t us) |
229 | 0 | { |
230 | 0 | struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; |
231 | |
|
232 | 0 | lws_pt_lock(pt, __func__); |
233 | 0 | lws_dll2_remove(&wsi->sul_timeout.list); |
234 | 0 | lws_pt_unlock(pt); |
235 | |
|
236 | 0 | if (!us) |
237 | 0 | return; |
238 | | |
239 | 0 | lws_pt_lock(pt, __func__); |
240 | 0 | __lws_sul_insert_us(&pt->pt_sul_owner[LWSSULLI_MISS_IF_SUSPENDED], |
241 | 0 | &wsi->sul_timeout, us); |
242 | |
|
243 | 0 | lwsl_wsi_info(wsi, "%llu us, reason %d", |
244 | 0 | (unsigned long long)us, reason); |
245 | |
|
246 | 0 | wsi->pending_timeout = (char)reason; |
247 | 0 | lws_pt_unlock(pt); |
248 | 0 | } |
249 | | |
250 | | static void |
251 | | lws_validity_cb(lws_sorted_usec_list_t *sul) |
252 | 0 | { |
253 | 0 | struct lws *wsi = lws_container_of(sul, struct lws, sul_validity); |
254 | 0 | struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; |
255 | 0 | const lws_retry_bo_t *rbo = wsi->retry_policy; |
256 | | |
257 | | /* one of either the ping or hangup validity threshold was crossed */ |
258 | |
|
259 | 0 | if (wsi->validity_hup) { |
260 | 0 | char buf[128]; |
261 | 0 | buf[0] = '\0'; |
262 | 0 | lws_get_peer_simple(wsi, buf, sizeof(buf)); |
263 | |
|
264 | 0 | lwsl_wsi_notice(wsi, "VALIDITY TIMEOUT EXPIRED ON (protocol %s, peer %s)! Server is closing connection. (ping=%d, hangup=%d)\n", |
265 | 0 | wsi->a.protocol ? wsi->a.protocol->name : "none", buf, |
266 | 0 | rbo ? rbo->secs_since_valid_ping : 0, rbo ? rbo->secs_since_valid_hangup : 0); |
267 | 0 | struct lws_context *cx = wsi->a.context; |
268 | 0 | struct lws_context_per_thread *pt = &cx->pt[(int)wsi->tsi]; |
269 | |
|
270 | 0 | lws_context_lock(cx, __func__); |
271 | 0 | lws_pt_lock(pt, __func__); |
272 | 0 | __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, |
273 | 0 | "validity timeout"); |
274 | 0 | lws_pt_unlock(pt); |
275 | 0 | lws_context_unlock(cx); |
276 | 0 | return; |
277 | 0 | } |
278 | | |
279 | | /* schedule a protocol-dependent ping */ |
280 | | |
281 | 0 | lwsl_wsi_info(wsi, "scheduling validity check"); |
282 | |
|
283 | 0 | if (lws_rops_fidx(wsi->role_ops, LWS_ROPS_issue_keepalive)) |
284 | 0 | lws_rops_func_fidx(wsi->role_ops, LWS_ROPS_issue_keepalive). |
285 | 0 | issue_keepalive(wsi, 0); |
286 | | |
287 | | /* |
288 | | * We arrange to come back here after the additional ping to hangup time |
289 | | * and do the hangup, unless we get validated (by, eg, a PONG) and |
290 | | * reset the timer |
291 | | */ |
292 | |
|
293 | 0 | assert(rbo->secs_since_valid_hangup > rbo->secs_since_valid_ping); |
294 | |
|
295 | 0 | wsi->validity_hup = 1; |
296 | 0 | __lws_sul_insert_us(&pt->pt_sul_owner[!!wsi->conn_validity_wakesuspend], |
297 | 0 | &wsi->sul_validity, |
298 | 0 | ((uint64_t)rbo->secs_since_valid_hangup - |
299 | 0 | rbo->secs_since_valid_ping) * LWS_US_PER_SEC); |
300 | 0 | } |
301 | | |
302 | | /* |
303 | | * The role calls this back to actually confirm validity on a particular wsi |
304 | | * (which may not be the original wsi) |
305 | | */ |
306 | | |
307 | | void |
308 | | _lws_validity_confirmed_role(struct lws *wsi) |
309 | 0 | { |
310 | 0 | struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; |
311 | 0 | const lws_retry_bo_t *rbo = wsi->retry_policy; |
312 | |
|
313 | 0 | if (!rbo || !rbo->secs_since_valid_hangup) |
314 | 0 | return; |
315 | | |
316 | 0 | wsi->validity_hup = 0; |
317 | 0 | wsi->sul_validity.cb = lws_validity_cb; |
318 | |
|
319 | 0 | wsi->validity_hup = rbo->secs_since_valid_ping >= |
320 | 0 | rbo->secs_since_valid_hangup; |
321 | |
|
322 | 0 | lwsl_wsi_info(wsi, "setting validity timer %ds (hup %d)", |
323 | 0 | wsi->validity_hup ? rbo->secs_since_valid_hangup : |
324 | 0 | rbo->secs_since_valid_ping, |
325 | 0 | wsi->validity_hup); |
326 | |
|
327 | 0 | __lws_sul_insert_us(&pt->pt_sul_owner[!!wsi->conn_validity_wakesuspend], |
328 | 0 | &wsi->sul_validity, |
329 | 0 | ((uint64_t)(wsi->validity_hup ? |
330 | 0 | rbo->secs_since_valid_hangup : |
331 | 0 | rbo->secs_since_valid_ping)) * LWS_US_PER_SEC); |
332 | 0 | } |
333 | | |
334 | | void |
335 | | lws_validity_confirmed(struct lws *wsi) |
336 | 0 | { |
337 | | /* |
338 | | * This may be a stream inside a muxed network connection... leave it |
339 | | * to the role to figure out who actually needs to understand their |
340 | | * validity was confirmed. |
341 | | */ |
342 | 0 | if (wsi->role_ops && |
343 | 0 | lws_rops_fidx(wsi->role_ops, LWS_ROPS_issue_keepalive)) |
344 | 0 | lws_rops_func_fidx(wsi->role_ops, LWS_ROPS_issue_keepalive). |
345 | 0 | issue_keepalive(wsi, 1); |
346 | 0 | } |