2
0
mirror of https://github.com/openssl/openssl.git synced 2025-04-24 20:51:14 +08:00

Add initial demo-driven design demos

Reviewed-by: Paul Dale <pauli@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/17991)
This commit is contained in:
Hugo Landau 2022-03-29 13:53:58 +01:00 committed by Tomas Mraz
parent 9454423bf1
commit ec36534cbb
10 changed files with 2189 additions and 0 deletions

24
doc/designs/ddd/Makefile Normal file

@ -0,0 +1,24 @@
#
# To run the demos when linked with a shared library (default):
#
# LD_LIBRARY_PATH=../.. make test
TESTS=ddd-01-conn-blocking ddd-02-conn-nonblocking ddd-03-fd-blocking ddd-04-fd-nonblocking ddd-05-mem-nonblocking ddd-06-mem-uv
CFLAGS = -I../include -O3 -g -Wall
LDFLAGS = -L..
LDLIBS = -lcrypto -lssl
all: $(TESTS)
clean:
rm -f $(TESTS) *.o
test: all
for x in $(TESTS); do echo "$$x"; LD_LIBRARY_PATH="$$(pwd)/.." ./$$x | grep -q '</html>' || { echo >&2 'Error'; exit 1; }; done
ddd-06-mem-uv: ddd-06-mem-uv.c
$(CC) $(CFLAGS) $(LDFLAGS) -o "$@" "$<" $(LDLIBS) -luv
ddd-%: ddd-%.c
$(CC) $(CFLAGS) $(LDFLAGS) -o "$@" "$<" $(LDLIBS)

115
doc/designs/ddd/README.md Normal file

@ -0,0 +1,115 @@
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 consitute
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 innumerate functions and myriad 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.
Background
----------
These demos were developed after analysis of the following open source
applications to determine libssl API usage patterns. The modally occuring 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
* A: Nonblocking
* 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 |
Availability of a default certificate store is assumed. `SSL_CERT_DIR` may be
set when running the demos if necessary.

@ -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 impedence 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 demonstate 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.

@ -0,0 +1,161 @@
#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;
ctx = SSL_CTX_new(TLS_client_method());
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;
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;
}
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)
{
const char msg[] = "GET / HTTP/1.0\r\nHost: www.openssl.org\r\n\r\n";
SSL_CTX *ctx = NULL;
BIO *b = NULL;
char buf[2048];
int l, res = 1;
ctx = create_ssl_ctx();
if (ctx == NULL) {
fprintf(stderr, "could not create context\n");
goto fail;
}
b = new_conn(ctx, "www.openssl.org:443");
if (b == NULL) {
fprintf(stderr, "could not create conn\n");
goto fail;
}
if (tx(b, msg, sizeof(msg)) < sizeof(msg)) {
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;
}

@ -0,0 +1,289 @@
#include <sys/poll.h>
#include <openssl/ssl.h>
/*
* Demo 2: Client Managed Connection Asynchronous 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;
ctx = SSL_CTX_new(TLS_client_method());
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;
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;
}
/* 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)
{
return BIO_get_fd(conn->ssl_bio, NULL);
}
/*
* 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)
{
return (conn->tx_need_rx ? POLLIN : 0) | POLLOUT | POLLERR;
}
int get_conn_pending_rx(APP_CONN *conn)
{
return (conn->rx_need_tx ? POLLOUT : 0) | POLLIN | POLLERR;
}
/*
* 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)
{
const char tx_msg[] = "GET / HTTP/1.0\r\nHost: www.openssl.org\r\n\r\n";
const char *tx_p = tx_msg;
char rx_buf[2048];
int res = 1, l, tx_len = sizeof(tx_msg)-1;
int timeout = 2000 /* ms */;
APP_CONN *conn = NULL;
SSL_CTX *ctx;
ctx = create_ssl_ctx();
if (ctx == NULL) {
fprintf(stderr, "cannot create SSL context\n");
goto fail;
}
conn = new_conn(ctx, "www.openssl.org:443");
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;
}

@ -0,0 +1,188 @@
#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;
ctx = SSL_CTX_new(TLS_client_method());
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;
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;
}
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, res = 1;
const char msg[] = "GET / HTTP/1.0\r\nHost: www.openssl.org\r\n\r\n";
struct addrinfo hints = {0}, *result = NULL;
SSL *ssl = NULL;
SSL_CTX *ctx;
char buf[2048];
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("www.openssl.org", "443", &hints, &result);
if (rc < 0) {
fprintf(stderr, "cannot resolve\n");
goto fail;
}
signal(SIGPIPE, SIG_IGN);
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
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, "www.openssl.org");
if (ssl == NULL) {
fprintf(stderr, "cannot create connection\n");
goto fail;
}
if (tx(ssl, msg, sizeof(msg)-1) < sizeof(msg)-1) {
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;
}

@ -0,0 +1,323 @@
#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 wouldn 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;
ctx = SSL_CTX_new(TLS_client_method());
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;
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;
}
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)
{
return (conn->tx_need_rx ? POLLIN : 0) | POLLOUT | POLLERR;
}
int get_conn_pending_rx(APP_CONN *conn)
{
return (conn->rx_need_tx ? POLLOUT : 0) | POLLIN | POLLERR;
}
/*
* 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>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int rc, fd = -1, res = 1;
const char tx_msg[] = "GET / HTTP/1.0\r\nHost: www.openssl.org\r\n\r\n";
const char *tx_p = tx_msg;
char rx_buf[2048];
int l, tx_len = sizeof(tx_msg)-1;
int timeout = 2000 /* ms */;
APP_CONN *conn = NULL;
struct addrinfo hints = {0}, *result = NULL;
SSL_CTX *ctx;
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("www.openssl.org", "443", &hints, &result);
if (rc < 0) {
fprintf(stderr, "cannot resolve\n");
goto fail;
}
signal(SIGPIPE, SIG_IGN);
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
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, "www.openssl.org");
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) {
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);
if (result != NULL)
freeaddrinfo(result);
return res;
}

@ -0,0 +1,408 @@
#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;
ctx = SSL_CTX_new(TLS_client_method());
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;
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 (BIO_new_bio_pair(&internal_bio, 0, &net_bio, 0) <= 0) {
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;
}
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.
*/
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.
*/
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)
{
return (conn->tx_need_rx ? POLLIN : 0) | POLLOUT | POLLERR;
}
int get_conn_pending_rx(APP_CONN *conn)
{
return (conn->rx_need_tx ? POLLOUT : 0) | POLLIN | POLLERR;
}
/*
* 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];
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;
const char tx_msg[] = "GET / HTTP/1.0\r\nHost: www.openssl.org\r\n\r\n";
const char *tx_p = tx_msg;
char rx_buf[2048];
int l, tx_len = sizeof(tx_msg)-1;
int timeout = 2000 /* ms */;
APP_CONN *conn = NULL;
struct addrinfo hints = {0}, *result = NULL;
SSL_CTX *ctx;
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("www.openssl.org", "443", &hints, &result);
if (rc < 0) {
fprintf(stderr, "cannot resolve\n");
goto fail;
}
signal(SIGPIPE, SIG_IGN);
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
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, "www.openssl.org");
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;
}

@ -0,0 +1,599 @@
#include <sys/poll.h>
#include <openssl/ssl.h>
#include <uv.h>
#include <assert.h>
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);
static void tcp_connect_done(uv_connect_t *tcp_connect, int status);
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);
/*
* 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 {
uv_write_t w;
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;
uv_stream_t *stream;
uv_tcp_t tcp;
uv_connect_t tcp_connect;
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;
ctx = SSL_CTX_new(TLS_client_method());
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;
uv_tcp_init(uv_default_loop(), &conn->tcp);
conn->tcp.data = conn;
conn->stream = (uv_stream_t *)&conn->tcp;
conn->app_connect_cb = cb;
conn->app_connect_arg = arg;
conn->tcp_connect.data = conn;
rc = uv_tcp_connect(&conn->tcp_connect, &conn->tcp, sa, tcp_connect_done);
if (rc < 0) {
uv_close((uv_handle_t *)&conn->tcp, net_connect_fail_close_done);
return NULL;
}
conn->ctx = ctx;
conn->hostname = hostname;
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);
uv_cancel((uv_req_t *)&conn->tcp_connect);
conn->teardown_done = &teardown_done;
uv_close((uv_handle_t *)conn->stream, teardown_continued);
/* Just wait synchronously until teardown completes. */
while (!teardown_done)
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)
{
buf->base = malloc(suggested_size);
buf->len = suggested_size;
}
static void on_rx_push(APP_CONN *conn)
{
int srd, rc;
size_t 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) {
free(buf);
rc = SSL_get_error(conn->ssl, srd);
if (rc == SSL_ERROR_WANT_READ)
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);
}
static void net_read_done(uv_stream_t *stream, ssize_t nr, const uv_buf_t *buf)
{
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)
{
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);
}
static void net_write_done(uv_write_t *req, int status)
{
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;
rc = uv_write(&op->w, conn->stream, &op->b, 1, net_write_done);
if (rc < 0) {
free(buf);
free(op);
fprintf(stderr, "UV write failed\n");
return;
}
}
static void handshake_done_ssl(APP_CONN *conn)
{
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;
ssl = SSL_new(conn->ctx);
if (!ssl)
return -1;
SSL_set_connect_state(ssl);
if (BIO_new_bio_pair(&internal_bio, 0, &net_bio, 0) <= 0) {
SSL_free(ssl);
return -1;
}
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;
}
conn->net_bio = net_bio;
conn->ssl = ssl;
return handshake_ssl(conn);
}
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;
}
}
static void net_connect_fail_close_done(uv_handle_t *handle)
{
APP_CONN *conn = (APP_CONN *)handle->data;
free(conn);
}
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;
for (op=conn->pending_upper_write_head; op; op=next_op) {
next_op = op->next;
free(op);
}
free(conn);
*teardown_done = 1;
}
/*
* ============================================================================
* 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);
}
static void post_connect(APP_CONN *conn, int status, void *arg)
{
int wr;
const char tx_msg[] = "GET / HTTP/1.0\r\nHost: www.openssl.org\r\n\r\n";
if (status < 0) {
fprintf(stderr, "failed to connect: %d\n", status);
uv_stop(uv_default_loop());
return;
}
wr = app_write(conn, tx_msg, sizeof(tx_msg)-1, post_write_get, NULL);
if (wr < sizeof(tx_msg)-1) {
fprintf(stderr, "error writing request");
return;
}
}
int main(int argc, char **argv)
{
int rc = 1;
SSL_CTX *ctx;
APP_CONN *conn = NULL;
struct addrinfo hints = {0}, *result = NULL;
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("www.openssl.org", "443", &hints, &result);
if (rc < 0) {
fprintf(stderr, "cannot resolve\n");
goto fail;
}
conn = new_conn(ctx, "www.openssl.org", 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);
}

@ -8,6 +8,8 @@ rule 'MD003', :style => :setext_with_atx
# Code blocks may be fenced or indented, both are OK...
# but they must be consistent throughout each file.
rule 'MD046', :style => :consistent
# Not possible to line-break tables.
rule 'MD013', :tables => false
# Bug in mdl, https://github.com/markdownlint/markdownlint/issues/313
exclude_rule 'MD007'