Coverage Report

Created: 2026-08-31 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}