Adding in curl and openssl repos

This commit is contained in:
2025-08-14 12:09:30 -04:00
parent af2117b574
commit 0ace93e303
21174 changed files with 3607720 additions and 2 deletions

View File

@@ -0,0 +1,43 @@
#
# To run the demos when linked with a shared library (default) ensure that
# libcrypto and libssl are on the library path. For example to run the
# ddd-01-conn-blocking-tls demo:
#
# LD_LIBRARY_PATH=../../.. ./ddd-01-conn-blocking-tls
#
# Building ddd-06-mem-uv-tls and ddd-06-mem-uv-quic requires the
# library libuv and header file. On Ubuntu, they are provided by the
# package "libuv1-dev".
TESTS_BASE = ddd-01-conn-blocking \
ddd-02-conn-nonblocking \
ddd-02-conn-nonblocking-threads \
ddd-03-fd-blocking \
ddd-04-fd-nonblocking \
ddd-05-mem-nonblocking \
ddd-06-mem-uv
TESTS = $(foreach x,$(TESTS_BASE),$(x)-tls $(x)-quic)
CFLAGS = -I../../../include -g -Wall -Wsign-compare
LDFLAGS = -L../../..
LDLIBS = -lcrypto -lssl
CC_CMD = $(CC) $(CFLAGS) $(LDFLAGS) -o "$@" "$<" $(LDLIBS)
all: $(TESTS)
clean:
rm -f $(TESTS) *.o
ddd-%-tls: ddd-%.c
$(CC_CMD)
ddd-%-quic: ddd-%.c
$(CC_CMD) -DUSE_QUIC
ddd-%-uv-tls: ddd-%-uv.c
$(CC_CMD) -luv
ddd-%-uv-quic: ddd-%-uv.c
$(CC_CMD) -luv -DUSE_QUIC

View File

@@ -0,0 +1,120 @@
Demo-Driven Design
==================
The OpenSSL project from time to time must evolve its public API surface in
order to support new functionality and deprecate old functionality. When this
occurs, the changes to OpenSSL's public API must be planned, discussed and
agreed. One significant dimension which must be considered when considering any
proposed API change is how a broad spectrum of real-world OpenSSL applications
uses the APIs which exist today, as this determines the ways in which those
applications will be affected by any proposed changes, the extent to which they
will be affected, and the extent of any changes which will need to be made by
codebases using OpenSSL to remain current with best practices for OpenSSL API
usage.
As such, it is useful for the OpenSSL project to have a good understanding of
the usage patterns common in codebases which use OpenSSL, so that it can
anticipate the impact of any evolution of its API on those codebases. This
directory seeks to maintain a set of **API usage demos** which demonstrate a
full spectrum of ways in which real-world applications use the OpenSSL APIs.
This allows the project to discuss any proposed API changes in terms of the
changes that would need to be made to each demo. Since the demos are
representative of a broad spectrum of real-world OpenSSL-based applications,
this ensures that API evolution is made both with reference to real-world API
usage patterns and with reference to the impact on existing applications.
As such, these demos are maintained in the OpenSSL repository because they are
useful both to current and any future proposed API changes. The set of demos may
be expanded over time, and the demos in this directory at any one time constitute
a present body of understanding of API usage patterns, which can be used to plan
API changes.
For further background information on the premise of this approach, see [API
long-term evolution](https://github.com/openssl/openssl/issues/17939).
Scope
-----
The current emphasis is on client demos. Server support for QUIC is deferred to
subsequent OpenSSL releases, and therefore is (currently) out of scope for this
design exercise.
The demos also deliberately focus on aspects of libssl usage which are likely to
be relevant to QUIC and require changes; for example, how varied applications
have libssl perform network I/O, and how varied applications create sockets and
connections for use with libssl. The libssl API as a whole has a much larger
scope and includes numerous functions and features; the intention is
not to demonstrate all of these, because most of them will not be touched by
QUIC. For example, while many users of OpenSSL may make use of APIs for client
certificates or other TLS functionality, the use of QUIC is unlikely to have
implications for these APIs and demos demonstrating such functionality are
therefore out of scope.
[A report is available](REPORT.md) on the results of the DDD process following
the completion of the development of the QUIC MVP (minimum viable product).
Background
----------
These demos were developed after analysis of the following open source
applications to determine libssl API usage patterns. The commonly occurring usage
patterns were determined and used to determine categories into which to classify
the applications:
| | Blk? | FD |
|------------------|------|----|
| mutt | S | AOSF |
| vsftpd | S | AOSF |
| exim | S | AOSFx |
| wget | S | AOSF |
| Fossil | S | BIOc |
| librabbitmq | A | BIOx |
| ngircd | A | AOSF |
| stunnel | A | AOSFx |
| Postfix | A | AOSF |
| socat | A | AOSF |
| HAProxy | A | BIOx |
| Dovecot | A | BIOm |
| Apache httpd | A | BIOx |
| UnrealIRCd | A | AOSF |
| wpa_supplicant | A | BIOm |
| icecast | A | AOSF |
| nginx | A | AOSF |
| curl | A | AOSF |
| Asterisk | A | AOSF |
| Asterisk (DTLS) | A | BIOm/x |
| pgbouncer | A | AOSF, BIOc |
* Blk: Whether the application uses blocking or non-blocking I/O.
* S: Blocking (Synchronous)
* A: Nonblocking (Asynchronous)
* FD: Whether the application creates and owns its own FD.
* AOSF: Application owns, calls SSL_set_fd.
* AOSFx: Application owns, calls SSL_set_[rw]fd, different FDs for read/write.
* BIOs: Application creates a socket/FD BIO and calls SSL_set_bio.
Application created the connection.
* BIOx: Application creates a BIO with a custom BIO method and calls SSL_set_bio.
* BIOm: Application creates a memory BIO and does its own
pumping to/from actual socket, treating libssl as a pure state machine which
does no I/O itself.
* BIOc: Application uses BIO_s_connect-based methods such as BIO_new_ssl_connect
and leaves connection establishment to OpenSSL.
Demos
-----
The demos found in this directory are:
| | Type | Description |
|-----------------|-------|-------------|
| [ddd-01-conn-blocking](ddd-01-conn-blocking.c) | S-BIOc | A `BIO_s_connect`-based blocking example demonstrating exemplary OpenSSL API usage |
| [ddd-02-conn-nonblocking](ddd-02-conn-nonblocking.c) | A-BIOc | A `BIO_s_connect`-based nonblocking example demonstrating exemplary OpenSSL API usage, with use of a buffering BIO |
| [ddd-03-fd-blocking](ddd-03-fd-blocking.c) | S-AOSF | A `SSL_set_fd`-based blocking example demonstrating real-world OpenSSL API usage (corresponding to S-AOSF applications above) |
| [ddd-04-fd-nonblocking](ddd-04-fd-nonblocking.c) | A-AOSF | A `SSL_set_fd`-based non-blocking example demonstrating real-world OpenSSL API usage (corresponding to A-AOSF applications above) |
| [ddd-05-mem-nonblocking](ddd-05-mem-nonblocking.c) | A-BIOm | A non-blocking example based on use of a memory buffer to feed OpenSSL encrypted data (corresponding to A-BIOm applications above) |
| [ddd-06-mem-uv](ddd-06-mem-uv.c) | A-BIOm | A non-blocking example based on use of a memory buffer to feed OpenSSL encrypted data; uses libuv, a real-world async I/O library |
On Ubuntu, libuv can be obtained by installing the package "libuv1-dev".
Availability of a default certificate store is assumed. `SSL_CERT_DIR` may be
set when running the demos if necessary.

View File

@@ -0,0 +1,340 @@
Report on the Conclusions of the QUIC DDD Process
=================================================
The [QUIC Demo-Driven Design process](README.md) was undertaken to meet the OMC
requirement to develop a QUIC API that required only minimal changes to existing
applications to be able to adapt their code to use QUIC. The demo-driven design
process developed a set of representative demos modelling a variety of common
OpenSSL usage patterns based on analysis of a broad spectrum of open source
software projects using OpenSSL.
As part of this process, a set of proposed diffs were produced. These proposed
diffs were the expected changes which would be needed to the baseline demos to
support QUIC based on theoretical analysis of the minimum requirements to be
able to support QUIC. This analysis concluded that the changes needed to
applications could be kept very small in many circumstances, with only minimal
diff sizes to the baseline demos.
Following the development of QUIC MVP, these demos have been revisited and the
correspondence of our actual final API and usage patterns with the planned diffs
have been reviewed.
This document discusses the planned changes and the actual changes for each demo
and draws conclusions on the level of disparity.
Since tracking a set of diffs separately is unwieldy, both the planned and
unplanned changes have been folded into the original baseline demo files guarded
with `#ifdef USE_QUIC`. Viewing these files therefore is informative to
application writers as it provides a clear view of what is different when using
QUIC. (The originally planned changes, and the final changes, are added in
separate, clearly-labelled commits; to view the originally planned changes only,
view the commit history for a given demo file.)
ddd-01-conn-blocking
--------------------
This demo exists to demonstrate the simplest possible usage of OpenSSL, whether
with TLS or QUIC.
### Originally planned changes
The originally planned change to enable applications for QUIC amounted to just a
single line:
```diff
+ ctx = SSL_CTX_new(QUIC_client_method());
- ctx = SSL_CTX_new(TLS_client_method());
```
### Actual changes
The following additional changes needed to be made:
- `QUIC_client_method` was renamed to `OSSL_QUIC_client_method` for namespacing
reasons.
- A call to `SSL_set_alpn_protos` to configure ALPN was added. This is necessary
because QUIC mandates the use of ALPN, and this was not noted during the
DDD process.
ddd-02-conn-nonblocking
-----------------------
This demo exists to demonstrate simple non-blocking usage. As with
ddd-01-conn-blocking, the name resolution process is managed by `BIO_s_connect`.
It also arbitrarily adds a `BIO_f_buffer` pushed onto the BIO stack
as this is a common application usage pattern.
### Originally planned changes
The originally planned changes to enable applications for QUIC amounted to:
- Change of method (as for ddd-01-conn-blocking);
- Use of a `BIO_f_dgram_buffer` BIO method instead of a `BIO_f_buffer`;
- Use of a `BIO_get_poll_fd` function to get the FD to poll rather than
`BIO_get_fd`;
- A change to how the `POLLIN`/`POLLOUT`/`POLLERR` flags to pass to poll(2)
need to be determined.
- Additional functions in application code to determine event handling
timeouts related to QUIC (`get_conn_pump_timeout`) and to pump
the QUIC event loop (`pump`).
- Timeout computation code which involves merging and comparing different
timeouts and calling `pump` as needed, based on deadlines reported
by libssl.
Note that some of these changes are unnecessary when using the thread assisted
mode (see the variant ddd-02-conn-nonblocking-threads below).
### Actual changes
The following additional changes needed to be made:
- Change of method name (as for ddd-01-conn-blocking);
- Use of ALPN (as for ddd-01-conn-blocking);
- The strategy for how to expose pollable OS resource handles
to applications to determine I/O readiness has changed substantially since the
original DDD process. As such, applications now use `BIO_get_rpoll_descriptor`
and `BIO_get_wpoll_descriptor` to determine I/O readiness, rather than the
originally hypothesised `SSL_get_poll_fd`.
- The strategy for how to determine when to poll for `POLLIN`, when to
poll for `POLLOUT`, etc. has changed since the original DDD process.
This information is now exposed via `SSL_net_read_desired` and
`SSL_net_write_desired`.
- The API to expose the event handling deadline for the QUIC engine
has evolved since the original DDD process. The new API
`SSL_get_event_timeout` is used, rather than the originally hypothesised
`BIO_get_timeout`/`SSL_get_timeout`.
- The API to perform QUIC event processing has been renamed to be
more descriptive. It is now called `SSL_handle_events` rather than
the originally hypothesised `BIO_pump`/`SSL_pump`.
The following changes were foreseen to be necessary, but turned out to actually
not be necessary:
- The need to change code which pushes a `BIO_f_buffer()` after a SSL BIO
was foreseen as use of buffering on the network side is unworkable with
QUIC. This turned out not to be necessary since we can just reject the
BIO_push() call. The buffer should still be freed eventually when the
SSL BIO is freed. The buffer is not used and is unnecessary, so it is
still desirable for applications to remove this code.
ddd-02-conn-nonblocking-threads
-------------------------------
This is a variant of the ddd-02-conn-nonblocking demo. The base is the same, but
the changes made are different. The use of thread-assisted mode, in which an
internal assist thread is used to perform QUIC event handling, enables an
application to make fewer changes than are needed in the ddd-02-conn-nonblocking
demo.
### Originally planned changes
The originally planned changes to enable applications for QUIC amounted to:
- Change of method, this time using method `QUIC_client_thread_method` rather
than `QUIC_client_method`;
- Use of a `BIO_get_poll_fd` function to get the FD to poll rather than
`BIO_get_fd`;
- A change to how the `POLLIN`/`POLLOUT`/`POLLERR` flags to pass to poll(2)
need to be determined.
Note that this is a substantially smaller list of changes than for
ddd-02-conn-nonblocking.
### Actual changes
The following additional changes needed to be made:
- Change of method name (`QUIC_client_thread_method` was renamed to
`OSSL_QUIC_client_thread_method` for namespacing reasons);
- Use of ALPN (as for ddd-01-conn-blocking);
- Use of `BIO_get_rpoll_descriptor` rather than `BIO_get_poll_fd` (as for
ddd-02-conn-nonblocking).
- Use of `SSL_net_read_desired` and `SSL_net_write_desired` (as for
ddd-02-conn-nonblocking).
ddd-03-fd-blocking
------------------
This demo is similar to ddd-01-conn-blocking but uses a file descriptor passed
directly by the application rather than BIO_s_connect.
### Originally planned changes
- Change of method (as for ddd-01-conn-blocking);
- The arguments to the `socket(2)` call are changed from `(AF_INET, SOCK_STREAM,
IPPROTO_TCP)` to `(AF_INET, SOCK_DGRAM, IPPROTO_UDP)`.
### Actual changes
The following additional changes needed to be made:
- Change of method name (as for ddd-01-conn-blocking);
- Use of ALPN (as for ddd-01-conn-blocking).
ddd-04-fd-nonblocking
---------------------
This demo is similar to ddd-01-conn-nonblocking but uses a file descriptor
passed directly by the application rather than BIO_s_connect.
### Originally planned changes
- Change of method (as for ddd-01-conn-blocking);
- The arguments to the `socket(2)` call are changed from `(AF_INET, SOCK_STREAM,
IPPROTO_TCP)` to `(AF_INET, SOCK_DGRAM, IPPROTO_UDP)`;
- A change to how the `POLLIN`/`POLLOUT`/`POLLERR` flags to pass to poll(2)
need to be determined.
- Additional functions in application code to determine event handling
timeouts related to QUIC (`get_conn_pump_timeout`) and to pump
the QUIC event loop (`pump`).
- Timeout computation code which involves merging and comparing different
timeouts and calling `pump` as needed, based on deadlines reported
by libssl.
### Actual changes
The following additional changes needed to be made:
- Change of method name (as for ddd-01-conn-blocking);
- Use of ALPN (as for ddd-01-conn-blocking);
- `SSL_get_timeout` replaced with `SSL_get_event_timeout` (as for
ddd-02-conn-nonblocking);
- `SSL_pump` renamed to `SSL_handle_events` (as for ddd-02-conn-nonblocking);
- The strategy for how to determine when to poll for `POLLIN`, when to
poll for `POLLOUT`, etc. has changed since the original DDD process.
This information is now exposed via `SSL_net_read_desired` and
`SSL_net_write_desired` (as for ddd-02-conn-nonblocking).
ddd-05-mem-nonblocking
----------------------
This demo is more elaborate. It uses memory buffers created and managed by an
application as an intermediary between libssl and the network, which is a common
usage pattern for applications. Managing this pattern for QUIC is more elaborate
since datagram semantics on the network channel need to be maintained.
### Originally planned changes
- Change of method (as for ddd-01-conn-blocking);
- Call to `BIO_new_bio_pair` is changed to `BIO_new_dgram_pair`, which
provides a bidirectional memory buffer BIO with datagram semantics.
- A change to how the `POLLIN`/`POLLOUT`/`POLLERR` flags to pass to poll(2)
need to be determined.
- Potential changes to buffer sizes used by applications to buffer
datagrams, if those buffers are smaller than 1472 bytes.
- The arguments to the `socket(2)` call are changed from `(AF_INET, SOCK_STREAM,
IPPROTO_TCP)` to `(AF_INET, SOCK_DGRAM, IPPROTO_UDP)`;
### Actual changes
The following additional changes needed to be made:
- Change of method name (as for ddd-01-conn-blocking);
- Use of ALPN (as for ddd-01-conn-blocking);
- The API to construct a `BIO_s_dgram_pair` ended up being named
`BIO_new_bio_dgram_pair` rather than `BIO_new_dgram_pair`;
- Use of `SSL_net_read_desired` and `SSL_net_write_desired` (as for
ddd-02-conn-nonblocking).
ddd-06-mem-uv
-------------
This demo is the most elaborate of the set. It uses a real-world asynchronous
I/O reactor, namely libuv (the engine used by Node.js). In doing so it seeks to
demonstrate and prove the viability of our API design with a real-world
asynchronous I/O system. It operates wholly in non-blocking mode and uses memory
buffers on either side of the QUIC stack to feed data to and from the
application and the network.
### Originally planned changes
- Change of method (as for ddd-01-conn-blocking);
- Various changes to use of libuv needed to switch to using UDP;
- Additional use of libuv to configure a timer event;
- Call to `BIO_new_bio_pair` is changed to `BIO_new_dgram_pair`
(as for ddd-05-mem-nonblocking);
- Some reordering of code required by the design of libuv.
### Actual changes
The following additional changes needed to be made:
- Change of method name (as for ddd-01-conn-blocking);
- Use of ALPN (as for ddd-01-conn-blocking);
- `BIO_new_dgram_pair` renamed to `BIO_new_bio_dgram_pair` (as for
ddd-05-mem-nonblocking);
- `SSL_get_timeout` replaced with `SSL_get_event_timeout` (as for
ddd-02-conn-nonblocking);
- `SSL_pump` renamed to `SSL_handle_events` (as for ddd-02-conn-nonblocking);
- Fixes to use of libuv based on a corrected understanding
of its operation, and changes that necessarily ensue.
Conclusions
-----------
The DDD process has successfully delivered on the objective of delivering a QUIC
API which can be used with only minimal API changes. The additional changes on
top of those originally planned which were required to successfully execute the
demos using QUIC were highly limited in scope and mostly constituted only minor
changes. The sum total of the changes required for each demo (both planned and
additional), as denoted in each DDD demo file under `#ifdef USE_QUIC` guards,
are both minimal and limited in scope.
“Minimal” and “limited” are distinct criteria. If inexorable technical
requirements dictate, an enormous set of changes to an application could be
considered “minimal”. The changes required to representative applications, as
demonstrated by the DDD demos, are not merely minimal but also limited.
For example, while the extent of these necessary changes varies by the
sophistication of each demo and the kind of application usage pattern it
represents, some demos in particular demonstrate exceptionally small changesets;
for example, ddd-01-conn-blocking and ddd-02-conn-nonblocking-threads, with
ddd-01-conn-blocking literally being enabled by a single line change assuming
ALPN is already configured.
This report concludes the DDD process for the single-stream QUIC client API
design process, which sought to validate our API design and API ease of use for
existing applications seeking to adopt QUIC.

View File

@@ -0,0 +1,80 @@
Windows-related issues
======================
Supporting Windows introduces some complications due to some "fun" peculiarities
of Windows's socket API.
In general, Windows does not provide a poll(2) call. WSAPoll(2) was introduced
in Vista and supposed to bring this functionality, but it had a bug in it which
Microsoft refused to fix, making it rather pointless. However Microsoft has now
finally fixed this bug in a build of Windows 10. So WSAPoll(2) is a viable
method, but only on fairly new versions of Windows.
Traditionally, polling has been done on windows using select(). However, this
call works a little differently than on POSIX platforms. Whereas on POSIX
platforms select() accepts a bitmask of FDs, on Windows select() accepts a
structure which embeds a fixed-length array of socket handles. This is necessary
because sockets are NT kernel handles on Windows and thus are not allocated
contiguously like FDs. As such, Windows select() is actually very similar to
POSIX poll(), making select() a viable option for polling on Windows.
Neither select() nor poll() are, of course, high performance polling options.
Windows does not provide anything like epoll or kqueue. For high performance
network I/O, you are expected to use a Windows API called I/O Completion Ports
(IOCP).
Supporting these is a pain for applications designed around polling. The reason
is that IOCPs are a higher-level interface; it is easy to build an IOCP-like
interface on top of polling, but it is not really possible to build a
polling-like interface on top of IOCPs.
For this reason it's actually common for asynchronous I/O libraries to basically
contain two separate implementations of their APIs internally, or at least a
substantial chunk of their code (e.g. libuv, nanomsg). It turns out to be easier
just to write a poll-based implementation of an I/O reactor and an IOCP-based
implementation than try to overcome the impedance discontinuities.
The difference between polling and IOCPs is that polling reports *readiness*
whereas IOCPs report *completion of an operation*. For example, in the IOCP
model, you make a read or write on a socket and an event is posted to the IOCP
when the read or write is complete. This is a fundamentally different model and
actually more similar to a high-level asynchronous I/O library such as libuv or
so on.
Evaluation of the existing demos and their applicability to Windows IOCP:
- ddd-01-conn-blocking: Blocking example, use of IOCP is not applicable.
- ddd-02-conn-nonblocking: Socket is managed by OpenSSL, and IOCP is not
supported.
- ddd-03-fd-blocking: Blocking example, use of IOCP is not applicable.
- ddd-04-fd-nonblocking: libssl is passed an FD with BIO_set_fd.
BIO_s_sock doesn't appear to support overlapped (that is, IOCP-based) I/O
as this requires use of special WSASend() and WSARecv() functions, rather
than standard send()/recv().
Since libssl already doesn't support IOCP for use of BIO_s_sock,
we might say here that any existing application using BIO_s_sock
obviously isn't trying to use IOCP, and therefore we don't need to
worry about the adapability of this example to IOCP.
- ddd-05-mem-nonblocking: Since the application is in full control of passing
data from the memory BIO to the network, or vice versa, the application
can use IOCP if it wishes.
This is demonstrated in the following demo:
- ddd-06-mem-uv: This demo uses a memory BIO and libuv. Since libuv supports
IOCP, it proves that a memory BIO can be used to support IOCP-based usage.
Further, a cursory examination of code on GitHub seems to suggest that when
people do use IOCP with libssl, they do it using memory BIOs passed to libssl.
So ddd-05 and ddd-06 essentially demonstrate this use case, especially ddd-06 as
it uses IOCP internally on Windows.
My conclusion here is that since libssl does not support IOCP in the first
place, we don't need to be particularly worried about this. But in the worst
case there are always workable solutions, as in demos 5 and 6.

View File

@@ -0,0 +1,187 @@
#include <openssl/ssl.h>
/*
* Demo 1: Client — Managed Connection — Blocking
* ==============================================
*
* This is an example of (part of) an application which uses libssl in a simple,
* synchronous, blocking fashion. The functions show all interactions with
* libssl the application makes, and would hypothetically be linked into a
* larger application.
*/
/*
* The application is initializing and wants an SSL_CTX which it will use for
* some number of outgoing connections, which it creates in subsequent calls to
* new_conn. The application may also call this function multiple times to
* create multiple SSL_CTX.
*/
SSL_CTX *create_ssl_ctx(void)
{
SSL_CTX *ctx;
#ifdef USE_QUIC
ctx = SSL_CTX_new(OSSL_QUIC_client_method());
#else
ctx = SSL_CTX_new(TLS_client_method());
#endif
if (ctx == NULL)
return NULL;
/* Enable trust chain verification. */
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
/* Load default root CA store. */
if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
SSL_CTX_free(ctx);
return NULL;
}
return ctx;
}
/*
* The application wants to create a new outgoing connection using a given
* SSL_CTX.
*
* hostname is a string like "openssl.org:443" or "[::1]:443".
*/
BIO *new_conn(SSL_CTX *ctx, const char *hostname)
{
BIO *out;
SSL *ssl = NULL;
const char *bare_hostname;
#ifdef USE_QUIC
static const unsigned char alpn[] = {5, 'd', 'u', 'm', 'm', 'y'};
#endif
out = BIO_new_ssl_connect(ctx);
if (out == NULL)
return NULL;
if (BIO_get_ssl(out, &ssl) == 0) {
BIO_free_all(out);
return NULL;
}
if (BIO_set_conn_hostname(out, hostname) == 0) {
BIO_free_all(out);
return NULL;
}
/* Returns the parsed hostname extracted from the hostname:port string. */
bare_hostname = BIO_get_conn_hostname(out);
if (bare_hostname == NULL) {
BIO_free_all(out);
return NULL;
}
/* Tell the SSL object the hostname to check certificates against. */
if (SSL_set1_host(ssl, bare_hostname) <= 0) {
BIO_free_all(out);
return NULL;
}
#ifdef USE_QUIC
/* Configure ALPN, which is required for QUIC. */
if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn))) {
/* Note: SSL_set_alpn_protos returns 1 for failure. */
BIO_free_all(out);
return NULL;
}
#endif
return out;
}
/*
* The application wants to send some block of data to the peer.
* This is a blocking call.
*/
int tx(BIO *bio, const void *buf, int buf_len)
{
return BIO_write(bio, buf, buf_len);
}
/*
* The application wants to receive some block of data from
* the peer. This is a blocking call.
*/
int rx(BIO *bio, void *buf, int buf_len)
{
return BIO_read(bio, buf, buf_len);
}
/*
* The application wants to close the connection and free bookkeeping
* structures.
*/
void teardown(BIO *bio)
{
BIO_free_all(bio);
}
/*
* The application is shutting down and wants to free a previously
* created SSL_CTX.
*/
void teardown_ctx(SSL_CTX *ctx)
{
SSL_CTX_free(ctx);
}
/*
* ============================================================================
* Example driver for the above code. This is just to demonstrate that the code
* works and is not intended to be representative of a real application.
*/
int main(int argc, char **argv)
{
static char msg[384], host_port[300];
SSL_CTX *ctx = NULL;
BIO *b = NULL;
char buf[2048];
int l, mlen, res = 1;
if (argc < 3) {
fprintf(stderr, "usage: %s host port\n", argv[0]);
goto fail;
}
snprintf(host_port, sizeof(host_port), "%s:%s", argv[1], argv[2]);
mlen = snprintf(msg, sizeof(msg),
"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", argv[1]);
ctx = create_ssl_ctx();
if (ctx == NULL) {
fprintf(stderr, "could not create context\n");
goto fail;
}
b = new_conn(ctx, host_port);
if (b == NULL) {
fprintf(stderr, "could not create connection\n");
goto fail;
}
l = tx(b, msg, mlen);
if (l < mlen) {
fprintf(stderr, "tx error\n");
goto fail;
}
for (;;) {
l = rx(b, buf, sizeof(buf));
if (l <= 0)
break;
fwrite(buf, 1, l, stdout);
}
res = 0;
fail:
if (b != NULL)
teardown(b);
if (ctx != NULL)
teardown_ctx(ctx);
return res;
}

View File

@@ -0,0 +1,334 @@
#include <sys/poll.h>
#include <openssl/ssl.h>
/*
* Demo 2: Client — Managed Connection — Nonblocking
* ==============================================================
*
* This is an example of (part of) an application which uses libssl in an
* asynchronous, nonblocking fashion. The functions show all interactions with
* libssl the application makes, and would hypothetically be linked into a
* larger application.
*
* In this example, libssl still makes syscalls directly using an fd, which is
* configured in nonblocking mode. As such, the application can still be
* abstracted from the details of what that fd is (is it a TCP socket? is it a
* UDP socket?); this code passes the application an fd and the application
* simply calls back into this code when poll()/etc. indicates it is ready.
*/
typedef struct app_conn_st {
SSL *ssl;
BIO *ssl_bio;
int rx_need_tx, tx_need_rx;
} APP_CONN;
/*
* The application is initializing and wants an SSL_CTX which it will use for
* some number of outgoing connections, which it creates in subsequent calls to
* new_conn. The application may also call this function multiple times to
* create multiple SSL_CTX.
*/
SSL_CTX *create_ssl_ctx(void)
{
SSL_CTX *ctx;
#ifdef USE_QUIC
ctx = SSL_CTX_new(OSSL_QUIC_client_thread_method());
#else
ctx = SSL_CTX_new(TLS_client_method());
#endif
if (ctx == NULL)
return NULL;
/* Enable trust chain verification. */
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
/* Load default root CA store. */
if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
SSL_CTX_free(ctx);
return NULL;
}
return ctx;
}
/*
* The application wants to create a new outgoing connection using a given
* SSL_CTX.
*
* hostname is a string like "openssl.org:443" or "[::1]:443".
*/
APP_CONN *new_conn(SSL_CTX *ctx, const char *hostname)
{
APP_CONN *conn;
BIO *out, *buf;
SSL *ssl = NULL;
const char *bare_hostname;
#ifdef USE_QUIC
static const unsigned char alpn[] = {5, 'd', 'u', 'm', 'm', 'y'};
#endif
conn = calloc(1, sizeof(APP_CONN));
if (conn == NULL)
return NULL;
out = BIO_new_ssl_connect(ctx);
if (out == NULL) {
free(conn);
return NULL;
}
if (BIO_get_ssl(out, &ssl) == 0) {
BIO_free_all(out);
free(conn);
return NULL;
}
buf = BIO_new(BIO_f_buffer());
if (buf == NULL) {
BIO_free_all(out);
free(conn);
return NULL;
}
BIO_push(out, buf);
if (BIO_set_conn_hostname(out, hostname) == 0) {
BIO_free_all(out);
free(conn);
return NULL;
}
/* Returns the parsed hostname extracted from the hostname:port string. */
bare_hostname = BIO_get_conn_hostname(out);
if (bare_hostname == NULL) {
BIO_free_all(out);
free(conn);
return NULL;
}
/* Tell the SSL object the hostname to check certificates against. */
if (SSL_set1_host(ssl, bare_hostname) <= 0) {
BIO_free_all(out);
free(conn);
return NULL;
}
#ifdef USE_QUIC
/* Configure ALPN, which is required for QUIC. */
if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn))) {
/* Note: SSL_set_alpn_protos returns 1 for failure. */
BIO_free_all(out);
free(conn);
return NULL;
}
#endif
/* Make the BIO nonblocking. */
BIO_set_nbio(out, 1);
conn->ssl_bio = out;
return conn;
}
/*
* Non-blocking transmission.
*
* Returns -1 on error. Returns -2 if the function would block (corresponds to
* EWOULDBLOCK).
*/
int tx(APP_CONN *conn, const void *buf, int buf_len)
{
int l;
conn->tx_need_rx = 0;
l = BIO_write(conn->ssl_bio, buf, buf_len);
if (l <= 0) {
if (BIO_should_retry(conn->ssl_bio)) {
conn->tx_need_rx = BIO_should_read(conn->ssl_bio);
return -2;
} else {
return -1;
}
}
return l;
}
/*
* Non-blocking reception.
*
* Returns -1 on error. Returns -2 if the function would block (corresponds to
* EWOULDBLOCK).
*/
int rx(APP_CONN *conn, void *buf, int buf_len)
{
int l;
conn->rx_need_tx = 0;
l = BIO_read(conn->ssl_bio, buf, buf_len);
if (l <= 0) {
if (BIO_should_retry(conn->ssl_bio)) {
conn->rx_need_tx = BIO_should_write(conn->ssl_bio);
return -2;
} else {
return -1;
}
}
return l;
}
/*
* The application wants to know a fd it can poll on to determine when the
* SSL state machine needs to be pumped.
*/
int get_conn_fd(APP_CONN *conn)
{
#ifdef USE_QUIC
BIO_POLL_DESCRIPTOR d;
if (!BIO_get_rpoll_descriptor(conn->ssl_bio, &d))
return -1;
return d.value.fd;
#else
return BIO_get_fd(conn->ssl_bio, NULL);
#endif
}
/*
* These functions returns zero or more of:
*
* POLLIN: The SSL state machine is interested in socket readability events.
*
* POLLOUT: The SSL state machine is interested in socket writeability events.
*
* POLLERR: The SSL state machine is interested in socket error events.
*
* get_conn_pending_tx returns events which may cause SSL_write to make
* progress and get_conn_pending_rx returns events which may cause SSL_read
* to make progress.
*/
int get_conn_pending_tx(APP_CONN *conn)
{
#ifdef USE_QUIC
return (SSL_net_read_desired(conn->ssl) ? POLLIN : 0)
| (SSL_net_write_desired(conn->ssl) ? POLLOUT : 0)
| POLLERR;
#else
return (conn->tx_need_rx ? POLLIN : 0) | POLLOUT | POLLERR;
#endif
}
int get_conn_pending_rx(APP_CONN *conn)
{
#ifdef USE_QUIC
return get_conn_pending_tx(conn);
#else
return (conn->rx_need_tx ? POLLOUT : 0) | POLLIN | POLLERR;
#endif
}
/*
* The application wants to close the connection and free bookkeeping
* structures.
*/
void teardown(APP_CONN *conn)
{
BIO_free_all(conn->ssl_bio);
free(conn);
}
/*
* The application is shutting down and wants to free a previously
* created SSL_CTX.
*/
void teardown_ctx(SSL_CTX *ctx)
{
SSL_CTX_free(ctx);
}
/*
* ============================================================================
* Example driver for the above code. This is just to demonstrate that the code
* works and is not intended to be representative of a real application.
*/
int main(int argc, char **argv)
{
static char tx_msg[384], host_port[300];
const char *tx_p = tx_msg;
char rx_buf[2048];
int res = 1, l, tx_len;
int timeout = 2000 /* ms */;
APP_CONN *conn = NULL;
SSL_CTX *ctx = NULL;
if (argc < 3) {
fprintf(stderr, "usage: %s host port\n", argv[0]);
goto fail;
}
snprintf(host_port, sizeof(host_port), "%s:%s", argv[1], argv[2]);
tx_len = snprintf(tx_msg, sizeof(tx_msg),
"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", argv[1]);
ctx = create_ssl_ctx();
if (ctx == NULL) {
fprintf(stderr, "cannot create SSL context\n");
goto fail;
}
conn = new_conn(ctx, host_port);
if (conn == NULL) {
fprintf(stderr, "cannot establish connection\n");
goto fail;
}
/* TX */
while (tx_len != 0) {
l = tx(conn, tx_p, tx_len);
if (l > 0) {
tx_p += l;
tx_len -= l;
} else if (l == -1) {
fprintf(stderr, "tx error\n");
} else if (l == -2) {
struct pollfd pfd = {0};
pfd.fd = get_conn_fd(conn);
pfd.events = get_conn_pending_tx(conn);
if (poll(&pfd, 1, timeout) == 0) {
fprintf(stderr, "tx timeout\n");
goto fail;
}
}
}
/* RX */
for (;;) {
l = rx(conn, rx_buf, sizeof(rx_buf));
if (l > 0) {
fwrite(rx_buf, 1, l, stdout);
} else if (l == -1) {
break;
} else if (l == -2) {
struct pollfd pfd = {0};
pfd.fd = get_conn_fd(conn);
pfd.events = get_conn_pending_rx(conn);
if (poll(&pfd, 1, timeout) == 0) {
fprintf(stderr, "rx timeout\n");
goto fail;
}
}
}
res = 0;
fail:
if (conn != NULL)
teardown(conn);
if (ctx != NULL)
teardown_ctx(ctx);
return res;
}

View File

@@ -0,0 +1,449 @@
#include <sys/poll.h>
#include <openssl/ssl.h>
/*
* Demo 2: Client — Managed Connection — Nonblocking
* ==============================================================
*
* This is an example of (part of) an application which uses libssl in an
* asynchronous, nonblocking fashion. The functions show all interactions with
* libssl the application makes, and would hypothetically be linked into a
* larger application.
*
* In this example, libssl still makes syscalls directly using an fd, which is
* configured in nonblocking mode. As such, the application can still be
* abstracted from the details of what that fd is (is it a TCP socket? is it a
* UDP socket?); this code passes the application an fd and the application
* simply calls back into this code when poll()/etc. indicates it is ready.
*/
typedef struct app_conn_st {
SSL *ssl;
BIO *ssl_bio;
int rx_need_tx, tx_need_rx;
} APP_CONN;
/*
* The application is initializing and wants an SSL_CTX which it will use for
* some number of outgoing connections, which it creates in subsequent calls to
* new_conn. The application may also call this function multiple times to
* create multiple SSL_CTX.
*/
SSL_CTX *create_ssl_ctx(void)
{
SSL_CTX *ctx;
#ifdef USE_QUIC
ctx = SSL_CTX_new(OSSL_QUIC_client_method());
#else
ctx = SSL_CTX_new(TLS_client_method());
#endif
if (ctx == NULL)
return NULL;
/* Enable trust chain verification. */
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
/* Load default root CA store. */
if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
SSL_CTX_free(ctx);
return NULL;
}
return ctx;
}
/*
* The application wants to create a new outgoing connection using a given
* SSL_CTX.
*
* hostname is a string like "openssl.org:443" or "[::1]:443".
*/
APP_CONN *new_conn(SSL_CTX *ctx, const char *hostname)
{
APP_CONN *conn;
BIO *out, *buf;
SSL *ssl = NULL;
const char *bare_hostname;
#ifdef USE_QUIC
static const unsigned char alpn[] = {5, 'd', 'u', 'm', 'm', 'y'};
#endif
conn = calloc(1, sizeof(APP_CONN));
if (conn == NULL)
return NULL;
out = BIO_new_ssl_connect(ctx);
if (out == NULL) {
free(conn);
return NULL;
}
if (BIO_get_ssl(out, &ssl) == 0) {
BIO_free_all(out);
free(conn);
return NULL;
}
/*
* NOTE: QUIC cannot operate with a buffering BIO between the QUIC SSL
* object in the network. In this case, the call to BIO_push() is not
* supported by the QUIC SSL object and will be ignored, thus this code
* works without removing this line. However, the buffering BIO is not
* actually used as a result and should be removed when adapting code to use
* QUIC.
*
* Setting a buffer as the underlying BIO on the QUIC SSL object using
* SSL_set_bio() will not work, though BIO_s_dgram_pair is available for
* buffering the input and output to the QUIC SSL object on the network side
* if desired.
*/
buf = BIO_new(BIO_f_buffer());
if (buf == NULL) {
BIO_free_all(out);
free(conn);
return NULL;
}
BIO_push(out, buf);
if (BIO_set_conn_hostname(out, hostname) == 0) {
BIO_free_all(out);
free(conn);
return NULL;
}
/* Returns the parsed hostname extracted from the hostname:port string. */
bare_hostname = BIO_get_conn_hostname(out);
if (bare_hostname == NULL) {
BIO_free_all(out);
free(conn);
return NULL;
}
/* Tell the SSL object the hostname to check certificates against. */
if (SSL_set1_host(ssl, bare_hostname) <= 0) {
BIO_free_all(out);
free(conn);
return NULL;
}
#ifdef USE_QUIC
/* Configure ALPN, which is required for QUIC. */
if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn))) {
/* Note: SSL_set_alpn_protos returns 1 for failure. */
BIO_free_all(out);
return NULL;
}
#endif
/* Make the BIO nonblocking. */
BIO_set_nbio(out, 1);
conn->ssl_bio = out;
return conn;
}
/*
* Non-blocking transmission.
*
* Returns -1 on error. Returns -2 if the function would block (corresponds to
* EWOULDBLOCK).
*/
int tx(APP_CONN *conn, const void *buf, int buf_len)
{
int l;
conn->tx_need_rx = 0;
l = BIO_write(conn->ssl_bio, buf, buf_len);
if (l <= 0) {
if (BIO_should_retry(conn->ssl_bio)) {
conn->tx_need_rx = BIO_should_read(conn->ssl_bio);
return -2;
} else {
return -1;
}
}
return l;
}
/*
* Non-blocking reception.
*
* Returns -1 on error. Returns -2 if the function would block (corresponds to
* EWOULDBLOCK).
*/
int rx(APP_CONN *conn, void *buf, int buf_len)
{
int l;
conn->rx_need_tx = 0;
l = BIO_read(conn->ssl_bio, buf, buf_len);
if (l <= 0) {
if (BIO_should_retry(conn->ssl_bio)) {
conn->rx_need_tx = BIO_should_write(conn->ssl_bio);
return -2;
} else {
return -1;
}
}
return l;
}
/*
* The application wants to know a fd it can poll on to determine when the
* SSL state machine needs to be pumped.
*/
int get_conn_fd(APP_CONN *conn)
{
#ifdef USE_QUIC
BIO_POLL_DESCRIPTOR d;
if (!BIO_get_rpoll_descriptor(conn->ssl_bio, &d))
return -1;
return d.value.fd;
#else
return BIO_get_fd(conn->ssl_bio, NULL);
#endif
}
/*
* These functions returns zero or more of:
*
* POLLIN: The SSL state machine is interested in socket readability events.
*
* POLLOUT: The SSL state machine is interested in socket writeability events.
*
* POLLERR: The SSL state machine is interested in socket error events.
*
* get_conn_pending_tx returns events which may cause SSL_write to make
* progress and get_conn_pending_rx returns events which may cause SSL_read
* to make progress.
*/
int get_conn_pending_tx(APP_CONN *conn)
{
#ifdef USE_QUIC
return (SSL_net_read_desired(conn->ssl) ? POLLIN : 0)
| (SSL_net_write_desired(conn->ssl) ? POLLOUT : 0)
| POLLERR;
#else
return (conn->tx_need_rx ? POLLIN : 0) | POLLOUT | POLLERR;
#endif
}
int get_conn_pending_rx(APP_CONN *conn)
{
#ifdef USE_QUIC
return get_conn_pending_tx(conn);
#else
return (conn->rx_need_tx ? POLLOUT : 0) | POLLIN | POLLERR;
#endif
}
#ifdef USE_QUIC
/*
* Returns the number of milliseconds after which some call to libssl must be
* made. Any call (BIO_read/BIO_write/BIO_pump) will do. Returns -1 if
* there is no need for such a call. This may change after the next call
* to libssl.
*/
static inline int timeval_to_ms(const struct timeval *t);
int get_conn_pump_timeout(APP_CONN *conn)
{
struct timeval tv;
int is_infinite;
if (!SSL_get_event_timeout(conn->ssl, &tv, &is_infinite))
return -1;
return is_infinite ? -1 : timeval_to_ms(&tv);
}
/*
* Called to advance internals of libssl state machines without having to
* perform an application-level read/write.
*/
void pump(APP_CONN *conn)
{
SSL_handle_events(conn->ssl);
}
#endif
/*
* The application wants to close the connection and free bookkeeping
* structures.
*/
void teardown(APP_CONN *conn)
{
BIO_free_all(conn->ssl_bio);
free(conn);
}
/*
* The application is shutting down and wants to free a previously
* created SSL_CTX.
*/
void teardown_ctx(SSL_CTX *ctx)
{
SSL_CTX_free(ctx);
}
/*
* ============================================================================
* Example driver for the above code. This is just to demonstrate that the code
* works and is not intended to be representative of a real application.
*/
#include <sys/time.h>
static inline void ms_to_timeval(struct timeval *t, int ms)
{
t->tv_sec = ms < 0 ? -1 : ms/1000;
t->tv_usec = ms < 0 ? 0 : (ms%1000)*1000;
}
static inline int timeval_to_ms(const struct timeval *t)
{
return t->tv_sec*1000 + t->tv_usec/1000;
}
int main(int argc, char **argv)
{
static char tx_msg[384], host_port[300];
const char *tx_p = tx_msg;
char rx_buf[2048];
int res = 1, l, tx_len;
#ifdef USE_QUIC
struct timeval timeout;
#else
int timeout = 2000 /* ms */;
#endif
APP_CONN *conn = NULL;
SSL_CTX *ctx = NULL;
#ifdef USE_QUIC
ms_to_timeval(&timeout, 2000);
#endif
if (argc < 3) {
fprintf(stderr, "usage: %s host port\n", argv[0]);
goto fail;
}
snprintf(host_port, sizeof(host_port), "%s:%s", argv[1], argv[2]);
tx_len = snprintf(tx_msg, sizeof(tx_msg),
"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", argv[1]);
ctx = create_ssl_ctx();
if (ctx == NULL) {
fprintf(stderr, "cannot create SSL context\n");
goto fail;
}
conn = new_conn(ctx, host_port);
if (conn == NULL) {
fprintf(stderr, "cannot establish connection\n");
goto fail;
}
/* TX */
while (tx_len != 0) {
l = tx(conn, tx_p, tx_len);
if (l > 0) {
tx_p += l;
tx_len -= l;
} else if (l == -1) {
fprintf(stderr, "tx error\n");
} else if (l == -2) {
#ifdef USE_QUIC
struct timeval start, now, deadline, t;
#endif
struct pollfd pfd = {0};
#ifdef USE_QUIC
ms_to_timeval(&t, get_conn_pump_timeout(conn));
if (t.tv_sec < 0 || timercmp(&t, &timeout, >))
t = timeout;
gettimeofday(&start, NULL);
timeradd(&start, &timeout, &deadline);
#endif
pfd.fd = get_conn_fd(conn);
pfd.events = get_conn_pending_tx(conn);
#ifdef USE_QUIC
if (poll(&pfd, 1, timeval_to_ms(&t)) == 0)
#else
if (poll(&pfd, 1, timeout) == 0)
#endif
{
#ifdef USE_QUIC
pump(conn);
gettimeofday(&now, NULL);
if (timercmp(&now, &deadline, >=))
#endif
{
fprintf(stderr, "tx timeout\n");
goto fail;
}
}
}
}
/* RX */
for (;;) {
l = rx(conn, rx_buf, sizeof(rx_buf));
if (l > 0) {
fwrite(rx_buf, 1, l, stdout);
} else if (l == -1) {
break;
} else if (l == -2) {
#ifdef USE_QUIC
struct timeval start, now, deadline, t;
#endif
struct pollfd pfd = {0};
#ifdef USE_QUIC
ms_to_timeval(&t, get_conn_pump_timeout(conn));
if (t.tv_sec < 0 || timercmp(&t, &timeout, >))
t = timeout;
gettimeofday(&start, NULL);
timeradd(&start, &timeout, &deadline);
#endif
pfd.fd = get_conn_fd(conn);
pfd.events = get_conn_pending_rx(conn);
#ifdef USE_QUIC
if (poll(&pfd, 1, timeval_to_ms(&t)) == 0)
#else
if (poll(&pfd, 1, timeout) == 0)
#endif
{
#ifdef USE_QUIC
pump(conn);
gettimeofday(&now, NULL);
if (timercmp(&now, &deadline, >=))
#endif
{
fprintf(stderr, "rx timeout\n");
goto fail;
}
}
}
}
res = 0;
fail:
if (conn != NULL)
teardown(conn);
if (ctx != NULL)
teardown_ctx(ctx);
return res;
}

View File

@@ -0,0 +1,217 @@
#include <openssl/ssl.h>
/*
* Demo 3: Client — Client Creates FD — Blocking
* =============================================
*
* This is an example of (part of) an application which uses libssl in a simple,
* synchronous, blocking fashion. The client is responsible for creating the
* socket and passing it to libssl. The functions show all interactions with
* libssl the application makes, and would hypothetically be linked into a
* larger application.
*/
/*
* The application is initializing and wants an SSL_CTX which it will use for
* some number of outgoing connections, which it creates in subsequent calls to
* new_conn. The application may also call this function multiple times to
* create multiple SSL_CTX.
*/
SSL_CTX *create_ssl_ctx(void)
{
SSL_CTX *ctx;
#ifdef USE_QUIC
ctx = SSL_CTX_new(OSSL_QUIC_client_method());
#else
ctx = SSL_CTX_new(TLS_client_method());
#endif
if (ctx == NULL)
return NULL;
/* Enable trust chain verification. */
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
/* Load default root CA store. */
if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
SSL_CTX_free(ctx);
return NULL;
}
return ctx;
}
/*
* The application wants to create a new outgoing connection using a given
* SSL_CTX.
*
* hostname is a string like "openssl.org" used for certificate validation.
*/
SSL *new_conn(SSL_CTX *ctx, int fd, const char *bare_hostname)
{
SSL *ssl;
#ifdef USE_QUIC
static const unsigned char alpn[] = {5, 'd', 'u', 'm', 'm', 'y'};
#endif
ssl = SSL_new(ctx);
if (ssl == NULL)
return NULL;
SSL_set_connect_state(ssl); /* cannot fail */
if (SSL_set_fd(ssl, fd) <= 0) {
SSL_free(ssl);
return NULL;
}
if (SSL_set1_host(ssl, bare_hostname) <= 0) {
SSL_free(ssl);
return NULL;
}
if (SSL_set_tlsext_host_name(ssl, bare_hostname) <= 0) {
SSL_free(ssl);
return NULL;
}
#ifdef USE_QUIC
/* Configure ALPN, which is required for QUIC. */
if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn))) {
/* Note: SSL_set_alpn_protos returns 1 for failure. */
SSL_free(ssl);
return NULL;
}
#endif
return ssl;
}
/*
* The application wants to send some block of data to the peer.
* This is a blocking call.
*/
int tx(SSL *ssl, const void *buf, int buf_len)
{
return SSL_write(ssl, buf, buf_len);
}
/*
* The application wants to receive some block of data from
* the peer. This is a blocking call.
*/
int rx(SSL *ssl, void *buf, int buf_len)
{
return SSL_read(ssl, buf, buf_len);
}
/*
* The application wants to close the connection and free bookkeeping
* structures.
*/
void teardown(SSL *ssl)
{
SSL_free(ssl);
}
/*
* The application is shutting down and wants to free a previously
* created SSL_CTX.
*/
void teardown_ctx(SSL_CTX *ctx)
{
SSL_CTX_free(ctx);
}
/*
* ============================================================================
* Example driver for the above code. This is just to demonstrate that the code
* works and is not intended to be representative of a real application.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <netdb.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int rc, fd = -1, l, mlen, res = 1;
static char msg[300];
struct addrinfo hints = {0}, *result = NULL;
SSL *ssl = NULL;
SSL_CTX *ctx = NULL;
char buf[2048];
if (argc < 3) {
fprintf(stderr, "usage: %s host port\n", argv[0]);
goto fail;
}
mlen = snprintf(msg, sizeof(msg),
"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", argv[1]);
ctx = create_ssl_ctx();
if (ctx == NULL) {
fprintf(stderr, "cannot create context\n");
goto fail;
}
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
rc = getaddrinfo(argv[1], argv[2], &hints, &result);
if (rc < 0) {
fprintf(stderr, "cannot resolve\n");
goto fail;
}
signal(SIGPIPE, SIG_IGN);
#ifdef USE_QUIC
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
#else
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif
if (fd < 0) {
fprintf(stderr, "cannot create socket\n");
goto fail;
}
rc = connect(fd, result->ai_addr, result->ai_addrlen);
if (rc < 0) {
fprintf(stderr, "cannot connect\n");
goto fail;
}
ssl = new_conn(ctx, fd, argv[1]);
if (ssl == NULL) {
fprintf(stderr, "cannot create connection\n");
goto fail;
}
l = tx(ssl, msg, mlen);
if (l < mlen) {
fprintf(stderr, "tx error\n");
goto fail;
}
for (;;) {
l = rx(ssl, buf, sizeof(buf));
if (l <= 0)
break;
fwrite(buf, 1, l, stdout);
}
res = 0;
fail:
if (ssl != NULL)
teardown(ssl);
if (ctx != NULL)
teardown_ctx(ctx);
if (fd >= 0)
close(fd);
if (result != NULL)
freeaddrinfo(result);
return res;
}

View File

@@ -0,0 +1,465 @@
#include <sys/poll.h>
#include <openssl/ssl.h>
/*
* Demo 4: Client — Client Creates FD — Nonblocking
* ================================================
*
* This is an example of (part of) an application which uses libssl in an
* asynchronous, nonblocking fashion. The client is responsible for creating the
* socket and passing it to libssl. The functions show all interactions with
* libssl the application makes, and would hypothetically be linked into a
* larger application.
*/
typedef struct app_conn_st {
SSL *ssl;
int fd;
int rx_need_tx, tx_need_rx;
} APP_CONN;
/*
* The application is initializing and wants an SSL_CTX which it will use for
* some number of outgoing connections, which it creates in subsequent calls to
* new_conn. The application may also call this function multiple times to
* create multiple SSL_CTX.
*/
SSL_CTX *create_ssl_ctx(void)
{
SSL_CTX *ctx;
#ifdef USE_QUIC
ctx = SSL_CTX_new(OSSL_QUIC_client_method());
#else
ctx = SSL_CTX_new(TLS_client_method());
#endif
if (ctx == NULL)
return NULL;
/* Enable trust chain verification. */
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
/* Load default root CA store. */
if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
SSL_CTX_free(ctx);
return NULL;
}
return ctx;
}
/*
* The application wants to create a new outgoing connection using a given
* SSL_CTX.
*
* hostname is a string like "openssl.org" used for certificate validation.
*/
APP_CONN *new_conn(SSL_CTX *ctx, int fd, const char *bare_hostname)
{
APP_CONN *conn;
SSL *ssl;
#ifdef USE_QUIC
static const unsigned char alpn[] = {5, 'd', 'u', 'm', 'm', 'y'};
#endif
conn = calloc(1, sizeof(APP_CONN));
if (conn == NULL)
return NULL;
ssl = conn->ssl = SSL_new(ctx);
if (ssl == NULL) {
free(conn);
return NULL;
}
SSL_set_connect_state(ssl); /* cannot fail */
if (SSL_set_fd(ssl, fd) <= 0) {
SSL_free(ssl);
free(conn);
return NULL;
}
if (SSL_set1_host(ssl, bare_hostname) <= 0) {
SSL_free(ssl);
free(conn);
return NULL;
}
if (SSL_set_tlsext_host_name(ssl, bare_hostname) <= 0) {
SSL_free(ssl);
free(conn);
return NULL;
}
#ifdef USE_QUIC
/* Configure ALPN, which is required for QUIC. */
if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn))) {
/* Note: SSL_set_alpn_protos returns 1 for failure. */
SSL_free(ssl);
free(conn);
return NULL;
}
#endif
conn->fd = fd;
return conn;
}
/*
* Non-blocking transmission.
*
* Returns -1 on error. Returns -2 if the function would block (corresponds to
* EWOULDBLOCK).
*/
int tx(APP_CONN *conn, const void *buf, int buf_len)
{
int rc, l;
conn->tx_need_rx = 0;
l = SSL_write(conn->ssl, buf, buf_len);
if (l <= 0) {
rc = SSL_get_error(conn->ssl, l);
switch (rc) {
case SSL_ERROR_WANT_READ:
conn->tx_need_rx = 1;
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_WRITE:
return -2;
default:
return -1;
}
}
return l;
}
/*
* Non-blocking reception.
*
* Returns -1 on error. Returns -2 if the function would block (corresponds to
* EWOULDBLOCK).
*/
int rx(APP_CONN *conn, void *buf, int buf_len)
{
int rc, l;
conn->rx_need_tx = 0;
l = SSL_read(conn->ssl, buf, buf_len);
if (l <= 0) {
rc = SSL_get_error(conn->ssl, l);
switch (rc) {
case SSL_ERROR_WANT_WRITE:
conn->rx_need_tx = 1;
case SSL_ERROR_WANT_READ:
return -2;
default:
return -1;
}
}
return l;
}
/*
* The application wants to know a fd it can poll on to determine when the
* SSL state machine needs to be pumped.
*
* If the fd returned has:
*
* POLLIN: SSL_read *may* return data;
* if application does not want to read yet, it should call pump().
*
* POLLOUT: SSL_write *may* accept data
*
* POLLERR: An application should call pump() if it is not likely to call
* SSL_read or SSL_write soon.
*
*/
int get_conn_fd(APP_CONN *conn)
{
return conn->fd;
}
/*
* These functions returns zero or more of:
*
* POLLIN: The SSL state machine is interested in socket readability events.
*
* POLLOUT: The SSL state machine is interested in socket writeability events.
*
* POLLERR: The SSL state machine is interested in socket error events.
*
* get_conn_pending_tx returns events which may cause SSL_write to make
* progress and get_conn_pending_rx returns events which may cause SSL_read
* to make progress.
*/
int get_conn_pending_tx(APP_CONN *conn)
{
#ifdef USE_QUIC
return (SSL_net_read_desired(conn->ssl) ? POLLIN : 0)
| (SSL_net_write_desired(conn->ssl) ? POLLOUT : 0)
| POLLERR;
#else
return (conn->tx_need_rx ? POLLIN : 0) | POLLOUT | POLLERR;
#endif
}
int get_conn_pending_rx(APP_CONN *conn)
{
return get_conn_pending_tx(conn);
}
#ifdef USE_QUIC
/*
* Returns the number of milliseconds after which some call to libssl must be
* made. Any call (SSL_read/SSL_write/SSL_pump) will do. Returns -1 if there is
* no need for such a call. This may change after the next call
* to libssl.
*/
static inline int timeval_to_ms(const struct timeval *t);
int get_conn_pump_timeout(APP_CONN *conn)
{
struct timeval tv;
int is_infinite;
if (!SSL_get_event_timeout(conn->ssl, &tv, &is_infinite))
return -1;
return is_infinite ? -1 : timeval_to_ms(&tv);
}
/*
* Called to advance internals of libssl state machines without having to
* perform an application-level read/write.
*/
void pump(APP_CONN *conn)
{
SSL_handle_events(conn->ssl);
}
#endif
/*
* The application wants to close the connection and free bookkeeping
* structures.
*/
void teardown(APP_CONN *conn)
{
SSL_shutdown(conn->ssl);
SSL_free(conn->ssl);
free(conn);
}
/*
* The application is shutting down and wants to free a previously
* created SSL_CTX.
*/
void teardown_ctx(SSL_CTX *ctx)
{
SSL_CTX_free(ctx);
}
/*
* ============================================================================
* Example driver for the above code. This is just to demonstrate that the code
* works and is not intended to be representative of a real application.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/signal.h>
#ifdef USE_QUIC
# include <sys/time.h>
#endif
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef USE_QUIC
static inline void ms_to_timeval(struct timeval *t, int ms)
{
t->tv_sec = ms < 0 ? -1 : ms/1000;
t->tv_usec = ms < 0 ? 0 : (ms%1000)*1000;
}
static inline int timeval_to_ms(const struct timeval *t)
{
return t->tv_sec*1000 + t->tv_usec/1000;
}
#endif
int main(int argc, char **argv)
{
int rc, fd = -1, res = 1;
static char tx_msg[300];
const char *tx_p = tx_msg;
char rx_buf[2048];
int l, tx_len;
#ifdef USE_QUIC
struct timeval timeout;
#else
int timeout = 2000 /* ms */;
#endif
APP_CONN *conn = NULL;
struct addrinfo hints = {0}, *result = NULL;
SSL_CTX *ctx = NULL;
#ifdef USE_QUIC
ms_to_timeval(&timeout, 2000);
#endif
if (argc < 3) {
fprintf(stderr, "usage: %s host port\n", argv[0]);
goto fail;
}
tx_len = snprintf(tx_msg, sizeof(tx_msg),
"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", argv[1]);
ctx = create_ssl_ctx();
if (ctx == NULL) {
fprintf(stderr, "cannot create SSL context\n");
goto fail;
}
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
rc = getaddrinfo(argv[1], argv[2], &hints, &result);
if (rc < 0) {
fprintf(stderr, "cannot resolve\n");
goto fail;
}
signal(SIGPIPE, SIG_IGN);
#ifdef USE_QUIC
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
#else
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif
if (fd < 0) {
fprintf(stderr, "cannot create socket\n");
goto fail;
}
rc = connect(fd, result->ai_addr, result->ai_addrlen);
if (rc < 0) {
fprintf(stderr, "cannot connect\n");
goto fail;
}
rc = fcntl(fd, F_SETFL, O_NONBLOCK);
if (rc < 0) {
fprintf(stderr, "cannot make socket nonblocking\n");
goto fail;
}
conn = new_conn(ctx, fd, argv[1]);
if (conn == NULL) {
fprintf(stderr, "cannot establish connection\n");
goto fail;
}
/* TX */
while (tx_len != 0) {
l = tx(conn, tx_p, tx_len);
if (l > 0) {
tx_p += l;
tx_len -= l;
} else if (l == -1) {
fprintf(stderr, "tx error\n");
goto fail;
} else if (l == -2) {
#ifdef USE_QUIC
struct timeval start, now, deadline, t;
#endif
struct pollfd pfd = {0};
#ifdef USE_QUIC
ms_to_timeval(&t, get_conn_pump_timeout(conn));
if (t.tv_sec < 0 || timercmp(&t, &timeout, >))
t = timeout;
gettimeofday(&start, NULL);
timeradd(&start, &timeout, &deadline);
#endif
pfd.fd = get_conn_fd(conn);
pfd.events = get_conn_pending_tx(conn);
#ifdef USE_QUIC
if (poll(&pfd, 1, timeval_to_ms(&t)) == 0)
#else
if (poll(&pfd, 1, timeout) == 0)
#endif
{
#ifdef USE_QUIC
pump(conn);
gettimeofday(&now, NULL);
if (timercmp(&now, &deadline, >=))
#endif
{
fprintf(stderr, "tx timeout\n");
goto fail;
}
}
}
}
/* RX */
for (;;) {
l = rx(conn, rx_buf, sizeof(rx_buf));
if (l > 0) {
fwrite(rx_buf, 1, l, stdout);
} else if (l == -1) {
break;
} else if (l == -2) {
#ifdef USE_QUIC
struct timeval start, now, deadline, t;
#endif
struct pollfd pfd = {0};
#ifdef USE_QUIC
ms_to_timeval(&t, get_conn_pump_timeout(conn));
if (t.tv_sec < 0 || timercmp(&t, &timeout, >))
t = timeout;
gettimeofday(&start, NULL);
timeradd(&start, &timeout, &deadline);
#endif
pfd.fd = get_conn_fd(conn);
pfd.events = get_conn_pending_rx(conn);
#ifdef USE_QUIC
if (poll(&pfd, 1, timeval_to_ms(&t)) == 0)
#else
if (poll(&pfd, 1, timeout) == 0)
#endif
{
#ifdef USE_QUIC
pump(conn);
gettimeofday(&now, NULL);
if (timercmp(&now, &deadline, >=))
#endif
{
fprintf(stderr, "rx timeout\n");
goto fail;
}
}
}
}
res = 0;
fail:
if (conn != NULL)
teardown(conn);
if (ctx != NULL)
teardown_ctx(ctx);
if (result != NULL)
freeaddrinfo(result);
return res;
}

View File

@@ -0,0 +1,459 @@
#include <sys/poll.h>
#include <openssl/ssl.h>
/*
* Demo 5: Client — Client Uses Memory BIO — Nonblocking
* =====================================================
*
* This is an example of (part of) an application which uses libssl in an
* asynchronous, nonblocking fashion. The application passes memory BIOs to
* OpenSSL, meaning that it controls both when data is read/written from an SSL
* object on the decrypted side but also when encrypted data from the network is
* shunted to/from OpenSSL. In this way OpenSSL is used as a pure state machine
* which does not make its own network I/O calls. OpenSSL never sees or creates
* any file descriptor for a network socket. The functions below show all
* interactions with libssl the application makes, and would hypothetically be
* linked into a larger application.
*/
typedef struct app_conn_st {
SSL *ssl;
BIO *ssl_bio, *net_bio;
int rx_need_tx, tx_need_rx;
} APP_CONN;
/*
* The application is initializing and wants an SSL_CTX which it will use for
* some number of outgoing connections, which it creates in subsequent calls to
* new_conn. The application may also call this function multiple times to
* create multiple SSL_CTX.
*/
SSL_CTX *create_ssl_ctx(void)
{
SSL_CTX *ctx;
#ifdef USE_QUIC
ctx = SSL_CTX_new(OSSL_QUIC_client_method());
#else
ctx = SSL_CTX_new(TLS_client_method());
#endif
if (ctx == NULL)
return NULL;
/* Enable trust chain verification. */
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
/* Load default root CA store. */
if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
SSL_CTX_free(ctx);
return NULL;
}
return ctx;
}
/*
* The application wants to create a new outgoing connection using a given
* SSL_CTX.
*
* hostname is a string like "openssl.org" used for certificate validation.
*/
APP_CONN *new_conn(SSL_CTX *ctx, const char *bare_hostname)
{
BIO *ssl_bio, *internal_bio, *net_bio;
APP_CONN *conn;
SSL *ssl;
#ifdef USE_QUIC
static const unsigned char alpn[] = {5, 'd', 'u', 'm', 'm', 'y'};
#endif
conn = calloc(1, sizeof(APP_CONN));
if (conn == NULL)
return NULL;
ssl = conn->ssl = SSL_new(ctx);
if (ssl == NULL) {
free(conn);
return NULL;
}
SSL_set_connect_state(ssl); /* cannot fail */
#ifdef USE_QUIC
if (BIO_new_bio_dgram_pair(&internal_bio, 0, &net_bio, 0) <= 0) {
#else
if (BIO_new_bio_pair(&internal_bio, 0, &net_bio, 0) <= 0) {
#endif
SSL_free(ssl);
free(conn);
return NULL;
}
SSL_set_bio(ssl, internal_bio, internal_bio);
if (SSL_set1_host(ssl, bare_hostname) <= 0) {
SSL_free(ssl);
free(conn);
return NULL;
}
if (SSL_set_tlsext_host_name(ssl, bare_hostname) <= 0) {
SSL_free(ssl);
free(conn);
return NULL;
}
ssl_bio = BIO_new(BIO_f_ssl());
if (ssl_bio == NULL) {
SSL_free(ssl);
free(conn);
return NULL;
}
if (BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE) <= 0) {
SSL_free(ssl);
BIO_free(ssl_bio);
return NULL;
}
#ifdef USE_QUIC
/* Configure ALPN, which is required for QUIC. */
if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn))) {
/* Note: SSL_set_alpn_protos returns 1 for failure. */
SSL_free(ssl);
BIO_free(ssl_bio);
return NULL;
}
#endif
conn->ssl_bio = ssl_bio;
conn->net_bio = net_bio;
return conn;
}
/*
* Non-blocking transmission.
*
* Returns -1 on error. Returns -2 if the function would block (corresponds to
* EWOULDBLOCK).
*/
int tx(APP_CONN *conn, const void *buf, int buf_len)
{
int rc, l;
l = BIO_write(conn->ssl_bio, buf, buf_len);
if (l <= 0) {
rc = SSL_get_error(conn->ssl, l);
switch (rc) {
case SSL_ERROR_WANT_READ:
conn->tx_need_rx = 1;
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_WRITE:
return -2;
default:
return -1;
}
} else {
conn->tx_need_rx = 0;
}
return l;
}
/*
* Non-blocking reception.
*
* Returns -1 on error. Returns -2 if the function would block (corresponds to
* EWOULDBLOCK).
*/
int rx(APP_CONN *conn, void *buf, int buf_len)
{
int rc, l;
l = BIO_read(conn->ssl_bio, buf, buf_len);
if (l <= 0) {
rc = SSL_get_error(conn->ssl, l);
switch (rc) {
case SSL_ERROR_WANT_WRITE:
conn->rx_need_tx = 1;
case SSL_ERROR_WANT_READ:
return -2;
default:
return -1;
}
} else {
conn->rx_need_tx = 0;
}
return l;
}
/*
* Called to get data which has been enqueued for transmission to the network
* by OpenSSL. For QUIC, this always outputs a single datagram.
*
* IMPORTANT (QUIC): If buf_len is inadequate to hold the datagram, it is truncated
* (similar to read(2)). A buffer size of at least 1472 must be used by default
* to guarantee this does not occur.
*/
int read_net_tx(APP_CONN *conn, void *buf, int buf_len)
{
return BIO_read(conn->net_bio, buf, buf_len);
}
/*
* Called to feed data which has been received from the network to OpenSSL.
*
* QUIC: buf must contain the entirety of a single datagram. It will be consumed
* entirely (return value == buf_len) or not at all.
*/
int write_net_rx(APP_CONN *conn, const void *buf, int buf_len)
{
return BIO_write(conn->net_bio, buf, buf_len);
}
/*
* Determine how much data can be written to the network RX BIO.
*/
size_t net_rx_space(APP_CONN *conn)
{
return BIO_ctrl_get_write_guarantee(conn->net_bio);
}
/*
* Determine how much data is currently queued for transmission in the network
* TX BIO.
*/
size_t net_tx_avail(APP_CONN *conn)
{
return BIO_ctrl_pending(conn->net_bio);
}
/*
* These functions returns zero or more of:
*
* POLLIN: The SSL state machine is interested in socket readability events.
*
* POLLOUT: The SSL state machine is interested in socket writeability events.
*
* POLLERR: The SSL state machine is interested in socket error events.
*
* get_conn_pending_tx returns events which may cause SSL_write to make
* progress and get_conn_pending_rx returns events which may cause SSL_read
* to make progress.
*/
int get_conn_pending_tx(APP_CONN *conn)
{
#ifdef USE_QUIC
return (SSL_net_read_desired(conn->ssl) ? POLLIN : 0)
| (SSL_net_write_desired(conn->ssl) ? POLLOUT : 0)
| POLLERR;
#else
return (conn->tx_need_rx ? POLLIN : 0) | POLLOUT | POLLERR;
#endif
}
int get_conn_pending_rx(APP_CONN *conn)
{
#ifdef USE_QUIC
return get_conn_pending_tx(conn);
#else
return (conn->rx_need_tx ? POLLOUT : 0) | POLLIN | POLLERR;
#endif
}
/*
* The application wants to close the connection and free bookkeeping
* structures.
*/
void teardown(APP_CONN *conn)
{
BIO_free_all(conn->ssl_bio);
BIO_free_all(conn->net_bio);
free(conn);
}
/*
* The application is shutting down and wants to free a previously
* created SSL_CTX.
*/
void teardown_ctx(SSL_CTX *ctx)
{
SSL_CTX_free(ctx);
}
/*
* ============================================================================
* Example driver for the above code. This is just to demonstrate that the code
* works and is not intended to be representative of a real application.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
static int pump(APP_CONN *conn, int fd, int events, int timeout)
{
int l, l2;
char buf[2048]; /* QUIC: would need to be changed if < 1472 */
size_t wspace;
struct pollfd pfd = {0};
pfd.fd = fd;
pfd.events = (events & (POLLIN | POLLERR));
if (net_rx_space(conn) == 0)
pfd.events &= ~POLLIN;
if (net_tx_avail(conn) > 0)
pfd.events |= POLLOUT;
if ((pfd.events & (POLLIN|POLLOUT)) == 0)
return 1;
if (poll(&pfd, 1, timeout) == 0)
return -1;
if (pfd.revents & POLLIN) {
while ((wspace = net_rx_space(conn)) > 0) {
l = read(fd, buf, wspace > sizeof(buf) ? sizeof(buf) : wspace);
if (l <= 0) {
switch (errno) {
case EAGAIN:
goto stop;
default:
if (l == 0) /* EOF */
goto stop;
fprintf(stderr, "error on read: %d\n", errno);
return -1;
}
break;
}
l2 = write_net_rx(conn, buf, l);
if (l2 < l)
fprintf(stderr, "short write %d %d\n", l2, l);
} stop:;
}
if (pfd.revents & POLLOUT) {
for (;;) {
l = read_net_tx(conn, buf, sizeof(buf));
if (l <= 0)
break;
l2 = write(fd, buf, l);
if (l2 < l)
fprintf(stderr, "short read %d %d\n", l2, l);
}
}
return 1;
}
int main(int argc, char **argv)
{
int rc, fd = -1, res = 1;
static char tx_msg[300];
const char *tx_p = tx_msg;
char rx_buf[2048];
int l, tx_len;
int timeout = 2000 /* ms */;
APP_CONN *conn = NULL;
struct addrinfo hints = {0}, *result = NULL;
SSL_CTX *ctx = NULL;
if (argc < 3) {
fprintf(stderr, "usage: %s host port\n", argv[0]);
goto fail;
}
tx_len = snprintf(tx_msg, sizeof(tx_msg),
"GET / HTTP/1.0\r\nHost: %s\r\n\r\n",
argv[1]);
ctx = create_ssl_ctx();
if (ctx == NULL) {
fprintf(stderr, "cannot create SSL context\n");
goto fail;
}
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
rc = getaddrinfo(argv[1], argv[2], &hints, &result);
if (rc < 0) {
fprintf(stderr, "cannot resolve\n");
goto fail;
}
signal(SIGPIPE, SIG_IGN);
#ifdef USE_QUIC
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
#else
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif
if (fd < 0) {
fprintf(stderr, "cannot create socket\n");
goto fail;
}
rc = connect(fd, result->ai_addr, result->ai_addrlen);
if (rc < 0) {
fprintf(stderr, "cannot connect\n");
goto fail;
}
rc = fcntl(fd, F_SETFL, O_NONBLOCK);
if (rc < 0) {
fprintf(stderr, "cannot make socket nonblocking\n");
goto fail;
}
conn = new_conn(ctx, argv[1]);
if (conn == NULL) {
fprintf(stderr, "cannot establish connection\n");
goto fail;
}
/* TX */
while (tx_len != 0) {
l = tx(conn, tx_p, tx_len);
if (l > 0) {
tx_p += l;
tx_len -= l;
} else if (l == -1) {
fprintf(stderr, "tx error\n");
} else if (l == -2) {
if (pump(conn, fd, get_conn_pending_tx(conn), timeout) != 1) {
fprintf(stderr, "pump error\n");
goto fail;
}
}
}
/* RX */
for (;;) {
l = rx(conn, rx_buf, sizeof(rx_buf));
if (l > 0) {
fwrite(rx_buf, 1, l, stdout);
} else if (l == -1) {
break;
} else if (l == -2) {
if (pump(conn, fd, get_conn_pending_rx(conn), timeout) != 1) {
fprintf(stderr, "pump error\n");
goto fail;
}
}
}
res = 0;
fail:
if (conn != NULL)
teardown(conn);
if (ctx != NULL)
teardown_ctx(ctx);
if (result != NULL)
freeaddrinfo(result);
return res;
}

View File

@@ -0,0 +1,758 @@
#include <sys/poll.h>
#include <openssl/ssl.h>
#include <uv.h>
#include <assert.h>
#ifdef USE_QUIC
# include <sys/time.h>
#endif
typedef struct app_conn_st APP_CONN;
typedef struct upper_write_op_st UPPER_WRITE_OP;
typedef struct lower_write_op_st LOWER_WRITE_OP;
typedef void (app_connect_cb)(APP_CONN *conn, int status, void *arg);
typedef void (app_write_cb)(APP_CONN *conn, int status, void *arg);
typedef void (app_read_cb)(APP_CONN *conn, void *buf, size_t buf_len, void *arg);
#ifdef USE_QUIC
static void set_timer(APP_CONN *conn);
#else
static void tcp_connect_done(uv_connect_t *tcp_connect, int status);
#endif
static void net_connect_fail_close_done(uv_handle_t *handle);
static int handshake_ssl(APP_CONN *conn);
static void flush_write_buf(APP_CONN *conn);
static void set_rx(APP_CONN *conn);
static int try_write(APP_CONN *conn, UPPER_WRITE_OP *op);
static void handle_pending_writes(APP_CONN *conn);
static int write_deferred(APP_CONN *conn, const void *buf, size_t buf_len, app_write_cb *cb, void *arg);
static void teardown_continued(uv_handle_t *handle);
static int setup_ssl(APP_CONN *conn, const char *hostname);
#ifdef USE_QUIC
static inline int timeval_to_ms(const struct timeval *t)
{
return t->tv_sec*1000 + t->tv_usec/1000;
}
#endif
/*
* Structure to track an application-level write request. Only created
* if SSL_write does not accept the data immediately, typically because
* it is in WANT_READ.
*/
struct upper_write_op_st {
struct upper_write_op_st *prev, *next;
const uint8_t *buf;
size_t buf_len, written;
APP_CONN *conn;
app_write_cb *cb;
void *cb_arg;
};
/*
* Structure to track a network-level write request.
*/
struct lower_write_op_st {
#ifdef USE_QUIC
uv_udp_send_t w;
#else
uv_write_t w;
#endif
uv_buf_t b;
uint8_t *buf;
APP_CONN *conn;
};
/*
* Application connection object.
*/
struct app_conn_st {
SSL_CTX *ctx;
SSL *ssl;
BIO *net_bio;
#ifdef USE_QUIC
uv_udp_t udp;
uv_timer_t timer;
#else
uv_stream_t *stream;
uv_tcp_t tcp;
uv_connect_t tcp_connect;
#endif
app_connect_cb *app_connect_cb; /* called once handshake is done */
void *app_connect_arg;
app_read_cb *app_read_cb; /* application's on-RX callback */
void *app_read_arg;
const char *hostname;
char init_handshake, done_handshake, closed;
char *teardown_done;
UPPER_WRITE_OP *pending_upper_write_head, *pending_upper_write_tail;
};
/*
* The application is initializing and wants an SSL_CTX which it will use for
* some number of outgoing connections, which it creates in subsequent calls to
* new_conn. The application may also call this function multiple times to
* create multiple SSL_CTX.
*/
SSL_CTX *create_ssl_ctx(void)
{
SSL_CTX *ctx;
#ifdef USE_QUIC
ctx = SSL_CTX_new(OSSL_QUIC_client_method());
#else
ctx = SSL_CTX_new(TLS_client_method());
#endif
if (ctx == NULL)
return NULL;
/* Enable trust chain verification. */
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
/* Load default root CA store. */
if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
SSL_CTX_free(ctx);
return NULL;
}
return ctx;
}
/*
* The application wants to create a new outgoing connection using a given
* SSL_CTX. An outgoing TCP connection is started and the callback is called
* asynchronously when the TLS handshake is complete.
*
* hostname is a string like "openssl.org" used for certificate validation.
*/
APP_CONN *new_conn(SSL_CTX *ctx, const char *hostname,
struct sockaddr *sa, socklen_t sa_len,
app_connect_cb *cb, void *arg)
{
int rc;
APP_CONN *conn = NULL;
conn = calloc(1, sizeof(APP_CONN));
if (!conn)
return NULL;
#ifdef USE_QUIC
uv_udp_init(uv_default_loop(), &conn->udp);
conn->udp.data = conn;
uv_timer_init(uv_default_loop(), &conn->timer);
conn->timer.data = conn;
#else
uv_tcp_init(uv_default_loop(), &conn->tcp);
conn->tcp.data = conn;
conn->stream = (uv_stream_t *)&conn->tcp;
#endif
conn->app_connect_cb = cb;
conn->app_connect_arg = arg;
#ifdef USE_QUIC
rc = uv_udp_connect(&conn->udp, sa);
#else
conn->tcp_connect.data = conn;
rc = uv_tcp_connect(&conn->tcp_connect, &conn->tcp, sa, tcp_connect_done);
#endif
if (rc < 0) {
#ifdef USE_QUIC
uv_close((uv_handle_t *)&conn->udp, net_connect_fail_close_done);
#else
uv_close((uv_handle_t *)&conn->tcp, net_connect_fail_close_done);
#endif
return NULL;
}
conn->ctx = ctx;
conn->hostname = hostname;
#ifdef USE_QUIC
rc = setup_ssl(conn, hostname);
if (rc < 0) {
uv_close((uv_handle_t *)&conn->udp, net_connect_fail_close_done);
return NULL;
}
#endif
return conn;
}
/*
* The application wants to start reading from the SSL stream.
* The callback is called whenever data is available.
*/
int app_read_start(APP_CONN *conn, app_read_cb *cb, void *arg)
{
conn->app_read_cb = cb;
conn->app_read_arg = arg;
set_rx(conn);
return 0;
}
/*
* The application wants to write. The callback is called once the
* write is complete. The callback should free the buffer.
*/
int app_write(APP_CONN *conn, const void *buf, size_t buf_len, app_write_cb *cb, void *arg)
{
write_deferred(conn, buf, buf_len, cb, arg);
handle_pending_writes(conn);
return buf_len;
}
/*
* The application wants to close the connection and free bookkeeping
* structures.
*/
void teardown(APP_CONN *conn)
{
char teardown_done = 0;
if (conn == NULL)
return;
BIO_free_all(conn->net_bio);
SSL_free(conn->ssl);
#ifndef USE_QUIC
uv_cancel((uv_req_t *)&conn->tcp_connect);
#endif
conn->teardown_done = &teardown_done;
#ifdef USE_QUIC
uv_close((uv_handle_t *)&conn->udp, teardown_continued);
uv_close((uv_handle_t *)&conn->timer, teardown_continued);
#else
uv_close((uv_handle_t *)conn->stream, teardown_continued);
#endif
/* Just wait synchronously until teardown completes. */
#ifdef USE_QUIC
while (teardown_done < 2)
#else
while (!teardown_done)
#endif
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
}
/*
* The application is shutting down and wants to free a previously
* created SSL_CTX.
*/
void teardown_ctx(SSL_CTX *ctx)
{
SSL_CTX_free(ctx);
}
/*
* ============================================================================
* Internal implementation functions.
*/
static void enqueue_upper_write_op(APP_CONN *conn, UPPER_WRITE_OP *op)
{
op->prev = conn->pending_upper_write_tail;
if (op->prev)
op->prev->next = op;
conn->pending_upper_write_tail = op;
if (conn->pending_upper_write_head == NULL)
conn->pending_upper_write_head = op;
}
static void dequeue_upper_write_op(APP_CONN *conn)
{
if (conn->pending_upper_write_head == NULL)
return;
if (conn->pending_upper_write_head->next == NULL) {
conn->pending_upper_write_head = NULL;
conn->pending_upper_write_tail = NULL;
} else {
conn->pending_upper_write_head = conn->pending_upper_write_head->next;
conn->pending_upper_write_head->prev = NULL;
}
}
static void net_read_alloc(uv_handle_t *handle,
size_t suggested_size, uv_buf_t *buf)
{
#ifdef USE_QUIC
if (suggested_size < 1472)
suggested_size = 1472;
#endif
buf->base = malloc(suggested_size);
buf->len = suggested_size;
}
static void on_rx_push(APP_CONN *conn)
{
int srd, rc;
int buf_len = 4096;
do {
if (!conn->app_read_cb)
return;
void *buf = malloc(buf_len);
if (!buf)
return;
srd = SSL_read(conn->ssl, buf, buf_len);
flush_write_buf(conn);
if (srd <= 0) {
rc = SSL_get_error(conn->ssl, srd);
if (rc == SSL_ERROR_WANT_READ) {
free(buf);
return;
}
}
conn->app_read_cb(conn, buf, srd, conn->app_read_arg);
} while (srd == buf_len);
}
static void net_error(APP_CONN *conn)
{
conn->closed = 1;
set_rx(conn);
if (conn->app_read_cb)
conn->app_read_cb(conn, NULL, 0, conn->app_read_arg);
}
static void handle_pending_writes(APP_CONN *conn)
{
int rc;
if (conn->pending_upper_write_head == NULL)
return;
do {
UPPER_WRITE_OP *op = conn->pending_upper_write_head;
rc = try_write(conn, op);
if (rc <= 0)
break;
dequeue_upper_write_op(conn);
free(op);
} while (conn->pending_upper_write_head != NULL);
set_rx(conn);
}
#ifdef USE_QUIC
static void net_read_done(uv_udp_t *stream, ssize_t nr, const uv_buf_t *buf,
const struct sockaddr *addr, unsigned int flags)
#else
static void net_read_done(uv_stream_t *stream, ssize_t nr, const uv_buf_t *buf)
#endif
{
int rc;
APP_CONN *conn = (APP_CONN *)stream->data;
if (nr < 0) {
free(buf->base);
net_error(conn);
return;
}
if (nr > 0) {
int wr = BIO_write(conn->net_bio, buf->base, nr);
assert(wr == nr);
}
free(buf->base);
if (!conn->done_handshake) {
rc = handshake_ssl(conn);
if (rc < 0) {
fprintf(stderr, "handshake error: %d\n", rc);
return;
}
if (!conn->done_handshake)
return;
}
handle_pending_writes(conn);
on_rx_push(conn);
}
static void set_rx(APP_CONN *conn)
{
#ifdef USE_QUIC
if (!conn->closed)
uv_udp_recv_start(&conn->udp, net_read_alloc, net_read_done);
else
uv_udp_recv_stop(&conn->udp);
#else
if (!conn->closed && (conn->app_read_cb || (!conn->done_handshake && conn->init_handshake) || conn->pending_upper_write_head != NULL))
uv_read_start(conn->stream, net_read_alloc, net_read_done);
else
uv_read_stop(conn->stream);
#endif
}
#ifdef USE_QUIC
static void net_write_done(uv_udp_send_t *req, int status)
#else
static void net_write_done(uv_write_t *req, int status)
#endif
{
LOWER_WRITE_OP *op = (LOWER_WRITE_OP *)req->data;
APP_CONN *conn = op->conn;
if (status < 0) {
fprintf(stderr, "UV write failed %d\n", status);
return;
}
free(op->buf);
free(op);
flush_write_buf(conn);
}
static void flush_write_buf(APP_CONN *conn)
{
int rc, rd;
LOWER_WRITE_OP *op;
uint8_t *buf;
buf = malloc(4096);
if (!buf)
return;
rd = BIO_read(conn->net_bio, buf, 4096);
if (rd <= 0) {
free(buf);
return;
}
op = calloc(1, sizeof(LOWER_WRITE_OP));
if (!op)
return;
op->buf = buf;
op->conn = conn;
op->w.data = op;
op->b.base = (char *)buf;
op->b.len = rd;
#ifdef USE_QUIC
rc = uv_udp_send(&op->w, &conn->udp, &op->b, 1, NULL, net_write_done);
#else
rc = uv_write(&op->w, conn->stream, &op->b, 1, net_write_done);
#endif
if (rc < 0) {
free(buf);
free(op);
fprintf(stderr, "UV write failed\n");
return;
}
}
static void handshake_done_ssl(APP_CONN *conn)
{
#ifdef USE_QUIC
set_timer(conn);
#endif
conn->app_connect_cb(conn, 0, conn->app_connect_arg);
}
static int handshake_ssl(APP_CONN *conn)
{
int rc, rcx;
conn->init_handshake = 1;
rc = SSL_do_handshake(conn->ssl);
if (rc > 0) {
conn->done_handshake = 1;
handshake_done_ssl(conn);
set_rx(conn);
return 0;
}
flush_write_buf(conn);
rcx = SSL_get_error(conn->ssl, rc);
if (rcx == SSL_ERROR_WANT_READ) {
set_rx(conn);
return 0;
}
fprintf(stderr, "Handshake error: %d\n", rcx);
return -rcx;
}
static int setup_ssl(APP_CONN *conn, const char *hostname)
{
BIO *internal_bio = NULL, *net_bio = NULL;
SSL *ssl = NULL;
#ifdef USE_QUIC
static const unsigned char alpn[] = {5, 'd', 'u', 'm', 'm', 'y'};
#endif
ssl = SSL_new(conn->ctx);
if (!ssl)
return -1;
SSL_set_connect_state(ssl);
#ifdef USE_QUIC
if (BIO_new_bio_dgram_pair(&internal_bio, 0, &net_bio, 0) <= 0) {
SSL_free(ssl);
return -1;
}
#else
if (BIO_new_bio_pair(&internal_bio, 0, &net_bio, 0) <= 0) {
SSL_free(ssl);
return -1;
}
#endif
SSL_set_bio(ssl, internal_bio, internal_bio);
if (SSL_set1_host(ssl, hostname) <= 0) {
SSL_free(ssl);
return -1;
}
if (SSL_set_tlsext_host_name(ssl, hostname) <= 0) {
SSL_free(ssl);
return -1;
}
#ifdef USE_QUIC
/* Configure ALPN, which is required for QUIC. */
if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn))) {
/* Note: SSL_set_alpn_protos returns 1 for failure. */
SSL_free(ssl);
return -1;
}
#endif
conn->net_bio = net_bio;
conn->ssl = ssl;
return handshake_ssl(conn);
}
#ifndef USE_QUIC
static void tcp_connect_done(uv_connect_t *tcp_connect, int status)
{
int rc;
APP_CONN *conn = (APP_CONN *)tcp_connect->data;
if (status < 0) {
uv_stop(uv_default_loop());
return;
}
rc = setup_ssl(conn, conn->hostname);
if (rc < 0) {
fprintf(stderr, "cannot init SSL\n");
uv_stop(uv_default_loop());
return;
}
}
#endif
static void net_connect_fail_close_done(uv_handle_t *handle)
{
APP_CONN *conn = (APP_CONN *)handle->data;
free(conn);
}
#ifdef USE_QUIC
static void timer_done(uv_timer_t *timer)
{
APP_CONN *conn = (APP_CONN *)timer->data;
SSL_handle_events(conn->ssl);
handle_pending_writes(conn);
flush_write_buf(conn);
set_rx(conn);
set_timer(conn); /* repeat timer */
}
static void set_timer(APP_CONN *conn)
{
struct timeval tv;
int ms, is_infinite;
if (!SSL_get_event_timeout(conn->ssl, &tv, &is_infinite))
return;
ms = is_infinite ? -1 : timeval_to_ms(&tv);
if (ms > 0)
uv_timer_start(&conn->timer, timer_done, ms, 0);
}
#endif
static int try_write(APP_CONN *conn, UPPER_WRITE_OP *op)
{
int rc, rcx;
size_t written = op->written;
while (written < op->buf_len) {
rc = SSL_write(conn->ssl, op->buf + written, op->buf_len - written);
if (rc <= 0) {
rcx = SSL_get_error(conn->ssl, rc);
if (rcx == SSL_ERROR_WANT_READ) {
op->written = written;
return 0;
} else {
if (op->cb != NULL)
op->cb(conn, -rcx, op->cb_arg);
return 1; /* op should be freed */
}
}
written += rc;
}
if (op->cb != NULL)
op->cb(conn, 0, op->cb_arg);
flush_write_buf(conn);
return 1; /* op should be freed */
}
static int write_deferred(APP_CONN *conn, const void *buf, size_t buf_len, app_write_cb *cb, void *arg)
{
UPPER_WRITE_OP *op = calloc(1, sizeof(UPPER_WRITE_OP));
if (!op)
return -1;
op->buf = buf;
op->buf_len = buf_len;
op->conn = conn;
op->cb = cb;
op->cb_arg = arg;
enqueue_upper_write_op(conn, op);
set_rx(conn);
flush_write_buf(conn);
return buf_len;
}
static void teardown_continued(uv_handle_t *handle)
{
APP_CONN *conn = (APP_CONN *)handle->data;
UPPER_WRITE_OP *op, *next_op;
char *teardown_done = conn->teardown_done;
#ifdef USE_QUIC
if (++*teardown_done < 2)
return;
#endif
for (op=conn->pending_upper_write_head; op; op=next_op) {
next_op = op->next;
free(op);
}
free(conn);
#ifndef USE_QUIC
*teardown_done = 1;
#endif
}
/*
* ============================================================================
* Example driver for the above code. This is just to demonstrate that the code
* works and is not intended to be representative of a real application.
*/
static void post_read(APP_CONN *conn, void *buf, size_t buf_len, void *arg)
{
if (!buf_len) {
free(buf);
uv_stop(uv_default_loop());
return;
}
fwrite(buf, 1, buf_len, stdout);
free(buf);
}
static void post_write_get(APP_CONN *conn, int status, void *arg)
{
if (status < 0) {
fprintf(stderr, "write failed: %d\n", status);
return;
}
app_read_start(conn, post_read, NULL);
}
char tx_msg[300];
int mlen;
static void post_connect(APP_CONN *conn, int status, void *arg)
{
int wr;
if (status < 0) {
fprintf(stderr, "failed to connect: %d\n", status);
uv_stop(uv_default_loop());
return;
}
wr = app_write(conn, tx_msg, mlen, post_write_get, NULL);
if (wr < mlen) {
fprintf(stderr, "error writing request");
return;
}
}
int main(int argc, char **argv)
{
int rc = 1;
SSL_CTX *ctx = NULL;
APP_CONN *conn = NULL;
struct addrinfo hints = {0}, *result = NULL;
if (argc < 3) {
fprintf(stderr, "usage: %s host port\n", argv[0]);
goto fail;
}
mlen = snprintf(tx_msg, sizeof(tx_msg),
"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", argv[1]);
ctx = create_ssl_ctx();
if (!ctx)
goto fail;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
rc = getaddrinfo(argv[1], argv[2], &hints, &result);
if (rc < 0) {
fprintf(stderr, "cannot resolve\n");
goto fail;
}
conn = new_conn(ctx, argv[1], result->ai_addr, result->ai_addrlen, post_connect, NULL);
if (!conn)
goto fail;
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
rc = 0;
fail:
teardown(conn);
freeaddrinfo(result);
uv_loop_close(uv_default_loop());
teardown_ctx(ctx);
}