New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
QUIC Concurrency API Implementation #24257
base: feature/quic-server
Are you sure you want to change the base?
Changes from 1 commit
59c8791
52da59a
a550fc8
133d14e
a8077c3
e3520f0
940c3f1
80bf4a7
f3d81e5
0f48f9d
7468e2f
1cf3069
8ca024a
ec9c2be
36fad54
aa15ea4
e6e8abf
4e53b28
cf5a935
8555f1f
d7ed50e
01303d0
17c6c70
b25425b
d98dfb2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* | ||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License 2.0 (the "License"). You may not use | ||
* this file except in compliance with the License. You can obtain a copy | ||
* in the file LICENSE in the source distribution or at | ||
* https://www.openssl.org/source/license.html | ||
*/ | ||
#ifndef OSSL_RIO_NOTIFIER_H | ||
# define OSSL_RIO_NOTIFIER_H | ||
|
||
# include "internal/common.h" | ||
# include "internal/sockets.h" | ||
|
||
/* | ||
* Pollable Notifier | ||
* ================= | ||
* | ||
* RIO_NOTIFIER provides an OS-pollable resource which can be plugged into an | ||
* OS's socket polling APIs to allow socket polling calls to be woken | ||
* artificially by other threads. | ||
*/ | ||
# define RIO_NOTIFIER_METHOD_SOCKET 1 | ||
# define RIO_NOTIFIER_METHOD_SOCKETPAIR 2 | ||
|
||
# if !defined(RIO_NOTIFIER_METHOD) | ||
# if defined(OPENSSL_SYS_WINDOWS) | ||
# define RIO_NOTIFIER_METHOD RIO_NOTIFIER_METHOD_SOCKET | ||
# elif defined(OPENSSL_SYS_UNIX) | ||
# define RIO_NOTIFIER_METHOD RIO_NOTIFIER_METHOD_SOCKETPAIR | ||
# else | ||
# define RIO_NOTIFIER_METHOD RIO_NOTIFIER_METHOD_SOCKET | ||
# endif | ||
# endif | ||
|
||
typedef struct rio_notifier_st { | ||
int rfd, wfd; | ||
} RIO_NOTIFIER; | ||
|
||
/* | ||
* Initialises a RIO_NOTIFIER. Returns 1 on success or 0 on failure. | ||
*/ | ||
int ossl_rio_notifier_init(RIO_NOTIFIER *nfy); | ||
|
||
/* | ||
* Cleans up a RIO_NOTIFIER, tearing down any allocated resources. | ||
*/ | ||
void ossl_rio_notifier_cleanup(RIO_NOTIFIER *nfy); | ||
|
||
/* | ||
* Signals a RIO_NOTIFIER, waking up any waiting threads. | ||
*/ | ||
int ossl_rio_notifier_signal(RIO_NOTIFIER *nfy); | ||
|
||
/* | ||
* Unsignals a RIO_NOTIFIER. | ||
*/ | ||
int ossl_rio_notifier_unsignal(RIO_NOTIFIER *nfy); | ||
|
||
/* | ||
* Returns an OS socket handle (FD or Win32 SOCKET) which can be polled for | ||
* readability to determine when the notifier has been signalled. | ||
*/ | ||
static ossl_inline ossl_unused int ossl_rio_notifier_as_fd(RIO_NOTIFIER *nfy) | ||
{ | ||
return nfy->rfd; | ||
} | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
$LIBSSL=../../libssl | ||
|
||
SOURCE[$LIBSSL]=poll_immediate.c | ||
SOURCE[$LIBSSL]=rio_notifier.c |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
#include "internal/rio_notifier.h" | ||
#include <openssl/bio.h> | ||
|
||
/* | ||
* Sets a socket as close-on-exec, except that this is a no-op if we are certain | ||
* we do not need to do this or the OS does not support the concept. | ||
*/ | ||
static int set_cloexec(int fd) | ||
{ | ||
#if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC) | ||
return fcntl(fd, F_SETFD, FD_CLOEXEC) >= 0; | ||
#else | ||
return 1; | ||
#endif | ||
} | ||
|
||
#if RIO_NOTIFIER_METHOD == RIO_NOTIFIER_METHOD_SOCKET | ||
|
||
/* Create a close-on-exec socket. */ | ||
static int create_socket(int domain, int socktype, int protocol) | ||
{ | ||
int fd; | ||
|
||
#if defined(OPENSSL_SYS_WINDOWS) | ||
static const int on = 1; | ||
|
||
/* | ||
* Use WSASocketA to create a socket which is immediately marked as | ||
* non-inheritable, avoiding race conditions if another thread is about to | ||
* call CreateProcess. | ||
*/ | ||
fd = WSASocketA(domain, socktype, protocol, NULL, 0, | ||
WSA_FLAG_NO_HANDLE_INHERIT); | ||
if (fd < 0) | ||
return -1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again shouldn't we use INVALID_SOCKET here? (and similar instances below) |
||
|
||
/* Prevent interference with the socket from other processes on Windows. */ | ||
if (setsockopt(lfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, &on, sizeof(on)) < 0) { | ||
BIO_closesocket(fd); | ||
return -1; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why WSAsocketA here? Seems like you could just use BIO_socket in both cases, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This allows WSA_FLAG_NO_HANDLE_INHERIT to be set, which is the equivalent of O_CLOEXEC... |
||
|
||
#else | ||
# if defined(SOCK_CLOEXEC) | ||
socktype |= SOCK_CLOEXEC; | ||
# endif | ||
|
||
fd = BIO_socket(domain, socktype, protocol, 0); | ||
if (fd < 0) | ||
return -1; | ||
|
||
/* | ||
* Make socket close-on-exec unless this was already done above at socket | ||
* creation time. | ||
*/ | ||
if (!set_cloexec(fd)) { | ||
BIO_closesocket(fd); | ||
return -1; | ||
} | ||
#endif | ||
|
||
return fd; | ||
} | ||
|
||
/* | ||
* The SOCKET notifier method manually creates a connected TCP socket pair by | ||
* temporarily creating a TCP listener on a random port and connecting back to | ||
* it. | ||
* | ||
* Win32 does not support socketpair(2), and Win32 pipes are not compatible with | ||
* Winsock select(2). This means our only means of making select(2) wakeable is | ||
* to artifically create a loopback TCP connection and send bytes to it. | ||
*/ | ||
int ossl_rio_notifier_init(RIO_NOTIFIER *nfy) | ||
{ | ||
int rc, lfd = -1, rfd = -1, wfd = -1; | ||
struct sockaddr_in sa = {0}, accept_sa; | ||
socklen_t sa_len = sizeof(sa), accept_sa_len = sizeof(accept_sa); | ||
|
||
/* Create a close-on-exec socket. */ | ||
lfd = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | ||
if (lfd < 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Compare with |
||
return 0; | ||
|
||
/* Bind the socket to a random loopback port. */ | ||
sa.sin_family = AF_INET; | ||
sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | ||
rc = bind(lfd, (const struct sockaddr *)&sa, sizeof(sa)); | ||
if (rc < 0) | ||
goto err; | ||
|
||
/* Determine what random port was allocated. */ | ||
rc = getsockname(lfd, (struct sockaddr *)&sa, &sa_len); | ||
if (rc < 0) | ||
goto err; | ||
|
||
/* Start listening. */ | ||
rc = listen(lfd, 1); | ||
if (rc < 0) | ||
goto err; | ||
|
||
/* Create another socket to connect to the listener. */ | ||
wfd = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | ||
if (wfd < 0) | ||
goto err; | ||
|
||
/* | ||
* Disable Nagle's algorithm on the writer so that wakeups happen | ||
* immediately. | ||
*/ | ||
if (!BIO_set_tcp_ndelay(wfd, 1)) | ||
goto err; | ||
|
||
/* | ||
* Connect the writer to the listener. | ||
*/ | ||
rc = connect(wfd, (struct sockaddr *)&sa, sizeof(sa)); | ||
if (rc < 0) | ||
goto err; | ||
|
||
/* | ||
* The connection accepted from the listener is the read side. | ||
*/ | ||
rfd = accept(lfd, (struct sockaddr *)&accept_sa, &accept_sa_len); | ||
if (rfd < 0) | ||
goto err; | ||
|
||
/* Close the listener, which we don't need anymore. */ | ||
BIO_closesocket(lfd); | ||
lfd = -1; | ||
|
||
/* | ||
* Sanity check - ensure someone else didn't connect to our listener during | ||
* the brief window of possibility above. | ||
*/ | ||
if (accept_sa.sin_family != AF_INET || accept_sa.sin_port != sa.sin_port) | ||
goto err; | ||
|
||
/* Make both sides of the connection non-blocking. */ | ||
if (!BIO_socket_nbio(rfd, 1)) | ||
goto err; | ||
|
||
if (!BIO_socket_nbio(wfd, 1)) | ||
goto err; | ||
|
||
nfy->rfd = rfd; | ||
nfy->wfd = wfd; | ||
return 1; | ||
|
||
err: | ||
if (lfd >= 0) | ||
BIO_closesocket(lfd); | ||
if (wfd >= 0) | ||
BIO_closesocket(wfd); | ||
if (rfd >= 0) | ||
BIO_closesocket(rfd); | ||
return 0; | ||
} | ||
|
||
#elif RIO_NOTIFIER_METHOD == RIO_NOTIFIER_METHOD_SOCKETPAIR | ||
|
||
int ossl_rio_notifier_init(RIO_NOTIFIER *nfy) | ||
{ | ||
int fds[2], domain = AF_INET, type = SOCK_STREAM; | ||
|
||
# if defined(SOCK_CLOEXEC) | ||
type |= SOCK_CLOEXEC; | ||
# endif | ||
# if defined(SOCK_NONBLOCK) | ||
type |= SOCK_NONBLOCK; | ||
# endif | ||
|
||
#if defined(OPENSSL_SYS_UNIX) && defined(AF_UNIX) | ||
domain = AF_UNIX; | ||
#endif | ||
|
||
if (socketpair(domain, type, 0, fds) < 0) | ||
return 0; | ||
|
||
if (!set_cloexec(fds[0]) || !set_cloexec(fds[1])) | ||
goto err; | ||
|
||
#if !defined(SOCK_NONBLOCK) | ||
if (!BIO_socket_nbio(fds[0], 1) || !BIO_socket_nbio(fds[1], 1)) | ||
goto err; | ||
#endif | ||
|
||
if (domain == AF_INET && !BIO_set_tcp_ndelay(fds[1], 1)) | ||
goto err; | ||
|
||
nfy->rfd = fds[0]; | ||
nfy->wfd = fds[1]; | ||
return 1; | ||
|
||
err: | ||
BIO_closesocket(fds[1]); | ||
BIO_closesocket(fds[0]); | ||
return 0; | ||
} | ||
|
||
#endif | ||
|
||
void ossl_rio_notifier_cleanup(RIO_NOTIFIER *nfy) | ||
{ | ||
if (nfy->rfd < 0) | ||
return; | ||
|
||
BIO_closesocket(nfy->wfd); | ||
BIO_closesocket(nfy->rfd); | ||
nfy->rfd = nfy->wfd = -1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. INVALID_SOCKET |
||
} | ||
|
||
int ossl_rio_notifier_signal(RIO_NOTIFIER *nfy) | ||
{ | ||
static const unsigned char ch = 0; | ||
ssize_t wr; | ||
|
||
do | ||
/* | ||
* Note: If wr returns 0 the buffer is already full so we don't need to | ||
* do anything. | ||
*/ | ||
wr = send(nfy->wfd, &ch, sizeof(ch), 0); | ||
while (wr < 0 && get_last_socket_error_is_eintr()); | ||
|
||
return 1; | ||
} | ||
|
||
int ossl_rio_notifier_unsignal(RIO_NOTIFIER *nfy) | ||
{ | ||
unsigned char buf[16]; | ||
ssize_t rd; | ||
|
||
/* | ||
* signal() might have been called multiple times. Drain the buffer until | ||
* it's empty. | ||
*/ | ||
do | ||
rd = recv(nfy->rfd, buf, sizeof(buf), 0); | ||
while (rd == sizeof(buf) | ||
|| (rd < 0 && get_last_socket_error_is_eintr())); | ||
|
||
if (rd < 0 && !BIO_fd_non_fatal_error(get_last_socket_error())) | ||
return 0; | ||
|
||
return 1; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be a check for INVALID_SOCKET here (which is != -1 on Windows)?