Skip to content
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

Open
wants to merge 25 commits into
base: feature/quic-server
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
59c8791
QUIC APL: Add QUIC Domain SSL Object: Basic Definitions
hlandau Apr 24, 2024
52da59a
QUIC APL: Add QUIC Domain SSL Object: Implementation
hlandau Apr 24, 2024
a550fc8
QUIC APL: Add support for configuring domain flags
hlandau Apr 24, 2024
133d14e
QUIC APL: Use domain flag to determine thread assisted mode
hlandau Apr 24, 2024
a8077c3
RIO: Add OS notifier
hlandau Apr 24, 2024
e3520f0
QUIC REACTOR: Integrate RIO NOTIFIER
hlandau Apr 24, 2024
940c3f1
QUIC APL: Default domain flags
hlandau Apr 24, 2024
80bf4a7
QUIC REACTOR: Inter-thread notification
hlandau Apr 24, 2024
f3d81e5
QUIC ENGINE: Notify when ticking
hlandau Apr 24, 2024
0f48f9d
QUIC REACTOR: Allow ticks to schedule notifications of other threads
hlandau Apr 24, 2024
7468e2f
QUIC CHANNEL: Notify other threads when needed
hlandau Apr 24, 2024
1cf3069
QUIC APL: Refine domain flag handling
hlandau Apr 24, 2024
8ca024a
QUIC: Document SSL_new_domain, etc.
hlandau Apr 24, 2024
ec9c2be
QUIC: Add documentation on concurrency model
hlandau Apr 24, 2024
36fad54
QUIC: Update listener documentation
hlandau Apr 24, 2024
aa15ea4
make update
hlandau Apr 24, 2024
e6e8abf
QUIC OBJ: Require blocking support in the domain flags to use blockin…
hlandau Apr 24, 2024
4e53b28
RIO NOTIFIER: Fix symbol usage
hlandau Apr 29, 2024
cf5a935
Minor fixes
hlandau Apr 29, 2024
8555f1f
Allow use of socketpair, WSASocketA
hlandau Apr 29, 2024
d7ed50e
Doc fixes
hlandau Apr 29, 2024
01303d0
Assorted bugfixes
hlandau Apr 29, 2024
17c6c70
QUIC: Add basic domain flags test
hlandau Apr 29, 2024
b25425b
QUIC RADIX: Test domain functions as well
hlandau Apr 29, 2024
d98dfb2
Minor fix for Windows
hlandau May 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 69 additions & 0 deletions include/internal/rio_notifier.h
@@ -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
1 change: 1 addition & 0 deletions ssl/rio/build.info
@@ -1,3 +1,4 @@
$LIBSSL=../../libssl

SOURCE[$LIBSSL]=poll_immediate.c
SOURCE[$LIBSSL]=rio_notifier.c
247 changes: 247 additions & 0 deletions ssl/rio/rio_notifier.c
@@ -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)
Copy link
Member

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)?

return -1;
Copy link
Member

Choose a reason for hiding this comment

The 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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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... BIO_socket does have an unused options flag so we could add this functionality to BIO_socket, of course, though that's a public API augmentation.


#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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compare with INVALID_SOCKET

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;
Copy link
Member

Choose a reason for hiding this comment

The 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;
}