Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/openssl/ssl/quic/quic_reactor.c
108175 views
1
/*
2
* Copyright 2022-2026 The OpenSSL Project Authors. All Rights Reserved.
3
*
4
* Licensed under the Apache License 2.0 (the "License"). You may not use
5
* this file except in compliance with the License. You can obtain a copy
6
* in the file LICENSE in the source distribution or at
7
* https://www.openssl.org/source/license.html
8
*/
9
#include "internal/quic_reactor.h"
10
#include "internal/common.h"
11
#include "internal/thread_arch.h"
12
#include <assert.h>
13
14
#if defined(OPENSSL_SYS_WINDOWS)
15
#include <winsock2.h>
16
#include <mstcpip.h>
17
#include <mswsock.h>
18
#endif
19
20
/*
21
* Core I/O Reactor Framework
22
* ==========================
23
*/
24
static void rtor_notify_other_threads(QUIC_REACTOR *rtor);
25
26
int ossl_quic_reactor_init(QUIC_REACTOR *rtor,
27
void (*tick_cb)(QUIC_TICK_RESULT *res, void *arg,
28
uint32_t flags),
29
void *tick_cb_arg,
30
CRYPTO_MUTEX *mutex,
31
OSSL_TIME initial_tick_deadline,
32
uint64_t flags)
33
{
34
rtor->poll_r.type = BIO_POLL_DESCRIPTOR_TYPE_NONE;
35
rtor->poll_w.type = BIO_POLL_DESCRIPTOR_TYPE_NONE;
36
rtor->net_read_desired = 0;
37
rtor->net_write_desired = 0;
38
rtor->can_poll_r = 0;
39
rtor->can_poll_w = 0;
40
rtor->tick_deadline = initial_tick_deadline;
41
42
rtor->tick_cb = tick_cb;
43
rtor->tick_cb_arg = tick_cb_arg;
44
rtor->mutex = mutex;
45
46
rtor->cur_blocking_waiters = 0;
47
48
if ((flags & QUIC_REACTOR_FLAG_USE_NOTIFIER) != 0) {
49
if (!ossl_rio_notifier_init(&rtor->notifier))
50
return 0;
51
52
if ((rtor->notifier_cv = ossl_crypto_condvar_new()) == NULL) {
53
ossl_rio_notifier_cleanup(&rtor->notifier);
54
return 0;
55
}
56
57
rtor->have_notifier = 1;
58
} else {
59
rtor->have_notifier = 0;
60
}
61
62
return 1;
63
}
64
65
void ossl_quic_reactor_cleanup(QUIC_REACTOR *rtor)
66
{
67
if (rtor == NULL)
68
return;
69
70
if (rtor->have_notifier) {
71
ossl_rio_notifier_cleanup(&rtor->notifier);
72
rtor->have_notifier = 0;
73
74
ossl_crypto_condvar_free(&rtor->notifier_cv);
75
}
76
}
77
78
#if defined(OPENSSL_SYS_WINDOWS)
79
/*
80
* On Windows recvfrom() may return WSAECONNRESET when destination port
81
* used in preceding call to sendto() is no longer reachable. The reset
82
* error received from UDP socket takes the whole port down. This behavior
83
* must be suppressed for QUIC protocol so QUIC applications may rely on
84
* QUIC protocol itself to detect network failures.
85
*/
86
static void rtor_configure_winsock(BIO_POLL_DESCRIPTOR *bpd)
87
{
88
BOOL bNewBehavior = FALSE;
89
DWORD dwBytesReturned = 0;
90
91
if (bpd->type == BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD) {
92
WSAIoctl(bpd->value.fd, SIO_UDP_CONNRESET, &bNewBehavior,
93
sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL, NULL);
94
WSAIoctl(bpd->value.fd, SIO_UDP_NETRESET, &bNewBehavior,
95
sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL, NULL);
96
}
97
}
98
#endif
99
100
void ossl_quic_reactor_set_poll_r(QUIC_REACTOR *rtor, const BIO_POLL_DESCRIPTOR *r)
101
{
102
if (r == NULL)
103
rtor->poll_r.type = BIO_POLL_DESCRIPTOR_TYPE_NONE;
104
else
105
rtor->poll_r = *r;
106
107
#if defined(OPENSSL_SYS_WINDOWS)
108
rtor_configure_winsock(&rtor->poll_r);
109
#endif
110
111
rtor->can_poll_r
112
= ossl_quic_reactor_can_support_poll_descriptor(rtor, &rtor->poll_r);
113
}
114
115
void ossl_quic_reactor_set_poll_w(QUIC_REACTOR *rtor, const BIO_POLL_DESCRIPTOR *w)
116
{
117
if (w == NULL)
118
rtor->poll_w.type = BIO_POLL_DESCRIPTOR_TYPE_NONE;
119
else
120
rtor->poll_w = *w;
121
122
#if defined(OPENSSL_SYS_WINDOWS)
123
rtor_configure_winsock(&rtor->poll_w);
124
#endif
125
126
rtor->can_poll_w
127
= ossl_quic_reactor_can_support_poll_descriptor(rtor, &rtor->poll_w);
128
}
129
130
const BIO_POLL_DESCRIPTOR *ossl_quic_reactor_get_poll_r(const QUIC_REACTOR *rtor)
131
{
132
return &rtor->poll_r;
133
}
134
135
const BIO_POLL_DESCRIPTOR *ossl_quic_reactor_get_poll_w(const QUIC_REACTOR *rtor)
136
{
137
return &rtor->poll_w;
138
}
139
140
int ossl_quic_reactor_can_support_poll_descriptor(const QUIC_REACTOR *rtor,
141
const BIO_POLL_DESCRIPTOR *d)
142
{
143
return d->type == BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD;
144
}
145
146
int ossl_quic_reactor_can_poll_r(const QUIC_REACTOR *rtor)
147
{
148
return rtor->can_poll_r;
149
}
150
151
int ossl_quic_reactor_can_poll_w(const QUIC_REACTOR *rtor)
152
{
153
return rtor->can_poll_w;
154
}
155
156
int ossl_quic_reactor_net_read_desired(QUIC_REACTOR *rtor)
157
{
158
return rtor->net_read_desired;
159
}
160
161
int ossl_quic_reactor_net_write_desired(QUIC_REACTOR *rtor)
162
{
163
return rtor->net_write_desired;
164
}
165
166
OSSL_TIME ossl_quic_reactor_get_tick_deadline(QUIC_REACTOR *rtor)
167
{
168
return rtor->tick_deadline;
169
}
170
171
int ossl_quic_reactor_tick(QUIC_REACTOR *rtor, uint32_t flags)
172
{
173
QUIC_TICK_RESULT res = { 0 };
174
175
/*
176
* Note that the tick callback cannot fail; this is intentional. Arguably it
177
* does not make that much sense for ticking to 'fail' (in the sense of an
178
* explicit error indicated to the user) because ticking is by its nature
179
* best effort. If something fatal happens with a connection we can report
180
* it on the next actual application I/O call.
181
*/
182
rtor->tick_cb(&res, rtor->tick_cb_arg, flags);
183
184
rtor->net_read_desired = res.net_read_desired;
185
rtor->net_write_desired = res.net_write_desired;
186
rtor->tick_deadline = res.tick_deadline;
187
if (res.notify_other_threads)
188
rtor_notify_other_threads(rtor);
189
190
return 1;
191
}
192
193
RIO_NOTIFIER *ossl_quic_reactor_get0_notifier(QUIC_REACTOR *rtor)
194
{
195
return rtor->have_notifier ? &rtor->notifier : NULL;
196
}
197
198
/*
199
* Blocking I/O Adaptation Layer
200
* =============================
201
*/
202
203
/*
204
* Utility which can be used to poll on up to two FDs. This is designed to
205
* support use of split FDs (e.g. with SSL_set_rfd and SSL_set_wfd where
206
* different FDs are used for read and write).
207
*
208
* Generally use of poll(2) is preferred where available. Windows, however,
209
* hasn't traditionally offered poll(2), only select(2). WSAPoll() was
210
* introduced in Vista but has seemingly been buggy until relatively recent
211
* versions of Windows 10. Moreover we support XP so this is not a suitable
212
* target anyway. However, the traditional issues with select(2) turn out not to
213
* be an issue on Windows; whereas traditional *NIX select(2) uses a bitmap of
214
* FDs (and thus is limited in the magnitude of the FDs expressible), Windows
215
* select(2) is very different. In Windows, socket handles are not allocated
216
* contiguously from zero and thus this bitmap approach was infeasible. Thus in
217
* adapting the Berkeley sockets API to Windows a different approach was taken
218
* whereby the fd_set contains a fixed length array of socket handles and an
219
* integer indicating how many entries are valid; thus Windows select()
220
* ironically is actually much more like *NIX poll(2) than *NIX select(2). In
221
* any case, this means that the relevant limit for Windows select() is the
222
* number of FDs being polled, not the magnitude of those FDs. Since we only
223
* poll for two FDs here, this limit does not concern us.
224
*
225
* Usage: rfd and wfd may be the same or different. Either or both may also be
226
* -1. If rfd_want_read is 1, rfd is polled for readability, and if
227
* wfd_want_write is 1, wfd is polled for writability. Note that since any
228
* passed FD is always polled for error conditions, setting rfd_want_read=0 and
229
* wfd_want_write=0 is not the same as passing -1 for both FDs.
230
*
231
* deadline is a timestamp to return at. If it is ossl_time_infinite(), the call
232
* never times out.
233
*
234
* Returns 0 on error and 1 on success. Timeout expiry is considered a success
235
* condition. We don't elaborate our return values here because the way we are
236
* actually using this doesn't currently care.
237
*
238
* If mutex is non-NULL, it is assumed to be held for write and is unlocked for
239
* the duration of the call.
240
*
241
* Precondition: mutex is NULL or is held for write (unchecked)
242
* Postcondition: mutex is NULL or is held for write (unless
243
* CRYPTO_THREAD_write_lock fails)
244
*/
245
static int poll_two_fds(int rfd, int rfd_want_read,
246
int wfd, int wfd_want_write,
247
int notify_rfd,
248
OSSL_TIME deadline,
249
CRYPTO_MUTEX *mutex)
250
{
251
#if defined(OPENSSL_SYS_WINDOWS) || !defined(POLLIN)
252
fd_set rfd_set, wfd_set, efd_set;
253
OSSL_TIME now, timeout;
254
struct timeval tv, *ptv;
255
int maxfd, pres;
256
257
#ifndef OPENSSL_SYS_WINDOWS
258
/*
259
* On Windows there is no relevant limit to the magnitude of a fd value (see
260
* above). On *NIX the fd_set uses a bitmap and we must check the limit.
261
*/
262
if (rfd >= FD_SETSIZE || wfd >= FD_SETSIZE)
263
return 0;
264
#endif
265
266
FD_ZERO(&rfd_set);
267
FD_ZERO(&wfd_set);
268
FD_ZERO(&efd_set);
269
270
if (rfd != INVALID_SOCKET && rfd_want_read)
271
openssl_fdset(rfd, &rfd_set);
272
if (wfd != INVALID_SOCKET && wfd_want_write)
273
openssl_fdset(wfd, &wfd_set);
274
275
/* Always check for error conditions. */
276
if (rfd != INVALID_SOCKET)
277
openssl_fdset(rfd, &efd_set);
278
if (wfd != INVALID_SOCKET)
279
openssl_fdset(wfd, &efd_set);
280
281
/* Check for notifier FD readability. */
282
if (notify_rfd != INVALID_SOCKET) {
283
openssl_fdset(notify_rfd, &rfd_set);
284
openssl_fdset(notify_rfd, &efd_set);
285
}
286
287
maxfd = rfd;
288
if (wfd > maxfd)
289
maxfd = wfd;
290
if (notify_rfd > maxfd)
291
maxfd = notify_rfd;
292
293
if (!ossl_assert(rfd != INVALID_SOCKET || wfd != INVALID_SOCKET
294
|| !ossl_time_is_infinite(deadline)))
295
/* Do not block forever; should not happen. */
296
return 0;
297
298
/*
299
* The mutex dance (unlock/re-locak after poll/seclect) is
300
* potentially problematic. This may create a situation when
301
* two threads arrive to select/poll with the same file
302
* descriptors. We just need to be aware of this.
303
*/
304
#if defined(OPENSSL_THREADS)
305
if (mutex != NULL)
306
ossl_crypto_mutex_unlock(mutex);
307
#endif
308
309
do {
310
/*
311
* select expects a timeout, not a deadline, so do the conversion.
312
* Update for each call to ensure the correct value is used if we repeat
313
* due to EINTR.
314
*/
315
if (ossl_time_is_infinite(deadline)) {
316
ptv = NULL;
317
} else {
318
now = ossl_time_now();
319
/*
320
* ossl_time_subtract saturates to zero so we don't need to check if
321
* now > deadline.
322
*/
323
timeout = ossl_time_subtract(deadline, now);
324
tv = ossl_time_to_timeval(timeout);
325
ptv = &tv;
326
}
327
328
pres = select(maxfd + 1, &rfd_set, &wfd_set, &efd_set, ptv);
329
} while (pres == -1 && get_last_socket_error_is_eintr());
330
331
#if defined(OPENSSL_THREADS)
332
if (mutex != NULL)
333
ossl_crypto_mutex_lock(mutex);
334
#endif
335
336
return pres < 0 ? 0 : 1;
337
#else
338
int pres, timeout_ms;
339
OSSL_TIME now, timeout;
340
struct pollfd pfds[3] = { 0 };
341
size_t npfd = 0;
342
343
if (rfd == wfd) {
344
pfds[npfd].fd = rfd;
345
pfds[npfd].events = (rfd_want_read ? POLLIN : 0)
346
| (wfd_want_write ? POLLOUT : 0);
347
if (rfd >= 0 && pfds[npfd].events != 0)
348
++npfd;
349
} else {
350
pfds[npfd].fd = rfd;
351
pfds[npfd].events = (rfd_want_read ? POLLIN : 0);
352
if (rfd >= 0 && pfds[npfd].events != 0)
353
++npfd;
354
355
pfds[npfd].fd = wfd;
356
pfds[npfd].events = (wfd_want_write ? POLLOUT : 0);
357
if (wfd >= 0 && pfds[npfd].events != 0)
358
++npfd;
359
}
360
361
if (notify_rfd >= 0) {
362
pfds[npfd].fd = notify_rfd;
363
pfds[npfd].events = POLLIN;
364
++npfd;
365
}
366
367
if (!ossl_assert(npfd != 0 || !ossl_time_is_infinite(deadline)))
368
/* Do not block forever; should not happen. */
369
return 0;
370
371
#if defined(OPENSSL_THREADS)
372
if (mutex != NULL)
373
ossl_crypto_mutex_unlock(mutex);
374
#endif
375
376
do {
377
if (ossl_time_is_infinite(deadline)) {
378
timeout_ms = -1;
379
} else {
380
now = ossl_time_now();
381
timeout = ossl_time_subtract(deadline, now);
382
timeout_ms = ossl_time2ms(timeout);
383
}
384
385
pres = poll(pfds, npfd, timeout_ms);
386
} while (pres == -1 && get_last_socket_error_is_eintr());
387
388
#if defined(OPENSSL_THREADS)
389
if (mutex != NULL)
390
ossl_crypto_mutex_lock(mutex);
391
#endif
392
393
return pres < 0 ? 0 : 1;
394
#endif
395
}
396
397
static int poll_descriptor_to_fd(const BIO_POLL_DESCRIPTOR *d, int *fd)
398
{
399
if (d == NULL || d->type == BIO_POLL_DESCRIPTOR_TYPE_NONE) {
400
*fd = INVALID_SOCKET;
401
return 1;
402
}
403
404
if (d->type != BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD
405
|| d->value.fd == INVALID_SOCKET)
406
return 0;
407
408
*fd = d->value.fd;
409
return 1;
410
}
411
412
/*
413
* Poll up to two abstract poll descriptors, as well as an optional notify FD.
414
* Currently we only support poll descriptors which represent FDs.
415
*
416
* If mutex is non-NULL, it is assumed be a lock currently held for write and is
417
* unlocked for the duration of any wait.
418
*
419
* Precondition: mutex is NULL or is held for write (unchecked)
420
* Postcondition: mutex is NULL or is held for write (unless
421
* CRYPTO_THREAD_write_lock fails)
422
*/
423
static int poll_two_descriptors(const BIO_POLL_DESCRIPTOR *r, int r_want_read,
424
const BIO_POLL_DESCRIPTOR *w, int w_want_write,
425
int notify_rfd,
426
OSSL_TIME deadline,
427
CRYPTO_MUTEX *mutex)
428
{
429
int rfd, wfd;
430
431
if (!poll_descriptor_to_fd(r, &rfd)
432
|| !poll_descriptor_to_fd(w, &wfd))
433
return 0;
434
435
return poll_two_fds(rfd, r_want_read, wfd, w_want_write,
436
notify_rfd, deadline, mutex);
437
}
438
439
/*
440
* Notify other threads currently blocking in
441
* ossl_quic_reactor_block_until_pred() calls that a predicate they are using
442
* might now be met due to state changes.
443
*
444
* This function must be called after state changes which might cause a
445
* predicate in another thread to now be met (i.e., ticking). It is a no-op if
446
* inter-thread notification is not being used.
447
*
448
* The reactor mutex must be held while calling this function.
449
*/
450
static void rtor_notify_other_threads(QUIC_REACTOR *rtor)
451
{
452
if (!rtor->have_notifier)
453
return;
454
455
/*
456
* This function is called when we have done anything on this thread which
457
* might allow a predicate for a block_until_pred call on another thread to
458
* now be met.
459
*
460
* When this happens, we need to wake those threads using the notifier.
461
* However, we do not want to wake *this* thread (if/when it subsequently
462
* enters block_until_pred) due to the notifier FD becoming readable.
463
* Therefore, signal the notifier, and use a CV to detect when all other
464
* threads have woken.
465
*/
466
467
if (rtor->cur_blocking_waiters == 0)
468
/* Nothing to do in this case. */
469
return;
470
471
/* Signal the notifier to wake up all threads. */
472
if (!rtor->signalled_notifier) {
473
ossl_rio_notifier_signal(&rtor->notifier);
474
rtor->signalled_notifier = 1;
475
}
476
477
/*
478
* Wait on the CV until all threads have finished the first phase of the
479
* wakeup process and the last thread out has taken responsibility for
480
* unsignalling the notifier.
481
*/
482
while (rtor->signalled_notifier)
483
ossl_crypto_condvar_wait(rtor->notifier_cv, rtor->mutex);
484
}
485
486
/*
487
* Block until a predicate function evaluates to true.
488
*
489
* If mutex is non-NULL, it is assumed be a lock currently held for write and is
490
* unlocked for the duration of any wait.
491
*
492
* Precondition: Must hold channel write lock (unchecked)
493
* Precondition: mutex is NULL or is held for write (unchecked)
494
* Postcondition: mutex is NULL or is held for write (unless
495
* CRYPTO_THREAD_write_lock fails)
496
*/
497
int ossl_quic_reactor_block_until_pred(QUIC_REACTOR *rtor,
498
int (*pred)(void *arg), void *pred_arg,
499
uint32_t flags)
500
{
501
int res, net_read_desired, net_write_desired, notifier_fd;
502
OSSL_TIME tick_deadline;
503
504
notifier_fd
505
= (rtor->have_notifier ? ossl_rio_notifier_as_fd(&rtor->notifier)
506
: INVALID_SOCKET);
507
508
for (;;) {
509
if ((flags & SKIP_FIRST_TICK) != 0)
510
flags &= ~SKIP_FIRST_TICK;
511
else
512
/* best effort */
513
ossl_quic_reactor_tick(rtor, 0);
514
515
if ((res = pred(pred_arg)) != 0)
516
return res;
517
518
net_read_desired = ossl_quic_reactor_net_read_desired(rtor);
519
net_write_desired = ossl_quic_reactor_net_write_desired(rtor);
520
tick_deadline = ossl_quic_reactor_get_tick_deadline(rtor);
521
if (!net_read_desired && !net_write_desired
522
&& ossl_time_is_infinite(tick_deadline))
523
/* Can't wait if there is nothing to wait for. */
524
return 0;
525
526
ossl_quic_reactor_enter_blocking_section(rtor);
527
528
res = poll_two_descriptors(ossl_quic_reactor_get_poll_r(rtor),
529
net_read_desired,
530
ossl_quic_reactor_get_poll_w(rtor),
531
net_write_desired,
532
notifier_fd,
533
tick_deadline,
534
rtor->mutex);
535
536
/*
537
* We have now exited the OS poller call. We may have
538
* (rtor->signalled_notifier), and other threads may still be blocking.
539
* This means that cur_blocking_waiters may still be non-zero. As such,
540
* we cannot unsignal the notifier until all threads have had an
541
* opportunity to wake up.
542
*
543
* At the same time, we cannot unsignal in the case where
544
* cur_blocking_waiters is now zero because this condition may not occur
545
* reliably. Consider the following scenario:
546
*
547
* T1 enters block_until_pred, cur_blocking_waiters -> 1
548
* T2 enters block_until_pred, cur_blocking_waiters -> 2
549
* T3 enters block_until_pred, cur_blocking_waiters -> 3
550
*
551
* T4 enters block_until_pred, does not block, ticks,
552
* sees that cur_blocking_waiters > 0 and signals the notifier
553
*
554
* T3 wakes, cur_blocking_waiters -> 2
555
* T3 predicate is not satisfied, cur_blocking_waiters -> 3, block again
556
*
557
* Notifier is still signalled, so T3 immediately wakes again
558
* and is stuck repeating the above steps.
559
*
560
* T1, T2 are also woken by the notifier but never see
561
* cur_blocking_waiters drop to 0, so never unsignal the notifier.
562
*
563
* As such, a two phase approach is chosen when designalling the
564
* notifier:
565
*
566
* First, all of the poll_two_descriptor calls on all threads are
567
* allowed to exit due to the notifier being signalled.
568
*
569
* Second, the thread which happened to be the one which decremented
570
* cur_blocking_waiters to 0 unsignals the notifier and is then
571
* responsible for broadcasting to a CV to indicate to the other
572
* threads that the synchronised wakeup has been completed. Other
573
* threads wait for this CV to be signalled.
574
*
575
*/
576
ossl_quic_reactor_leave_blocking_section(rtor);
577
578
if (!res)
579
/*
580
* We don't actually care why the call succeeded (timeout, FD
581
* readiness), we just call reactor_tick and start trying to do I/O
582
* things again. If poll_two_fds returns 0, this is some other
583
* non-timeout failure and we should stop here.
584
*
585
* TODO(QUIC FUTURE): In the future we could avoid unnecessary
586
* syscalls by not retrying network I/O that isn't ready based
587
* on the result of the poll call. However this might be difficult
588
* because it requires we do the call to poll(2) or equivalent
589
* syscall ourselves, whereas in the general case the application
590
* does the polling and just calls SSL_handle_events().
591
* Implementing this optimisation in the future will probably
592
* therefore require API changes.
593
*/
594
return 0;
595
}
596
597
return res;
598
}
599
600
void ossl_quic_reactor_enter_blocking_section(QUIC_REACTOR *rtor)
601
{
602
++rtor->cur_blocking_waiters;
603
}
604
605
void ossl_quic_reactor_leave_blocking_section(QUIC_REACTOR *rtor)
606
{
607
assert(rtor->cur_blocking_waiters > 0);
608
--rtor->cur_blocking_waiters;
609
610
if (rtor->have_notifier && rtor->signalled_notifier) {
611
if (rtor->cur_blocking_waiters == 0) {
612
ossl_rio_notifier_unsignal(&rtor->notifier);
613
rtor->signalled_notifier = 0;
614
615
/*
616
* Release the other threads which have woken up (and possibly
617
* rtor_notify_other_threads as well).
618
*/
619
ossl_crypto_condvar_broadcast(rtor->notifier_cv);
620
} else {
621
/* We are not the last waiter out - so wait for that one. */
622
while (rtor->signalled_notifier)
623
ossl_crypto_condvar_wait(rtor->notifier_cv, rtor->mutex);
624
}
625
}
626
}
627
628