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:
parent
9454423bf1
commit
ec36534cbb
24
doc/designs/ddd/Makefile
Normal file
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
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.
|
80
doc/designs/ddd/WINDOWS.md
Normal file
80
doc/designs/ddd/WINDOWS.md
Normal 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 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.
|
161
doc/designs/ddd/ddd-01-conn-blocking.c
Normal file
161
doc/designs/ddd/ddd-01-conn-blocking.c
Normal file
@ -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;
|
||||
}
|
289
doc/designs/ddd/ddd-02-conn-nonblocking.c
Normal file
289
doc/designs/ddd/ddd-02-conn-nonblocking.c
Normal file
@ -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;
|
||||
}
|
188
doc/designs/ddd/ddd-03-fd-blocking.c
Normal file
188
doc/designs/ddd/ddd-03-fd-blocking.c
Normal file
@ -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;
|
||||
}
|
323
doc/designs/ddd/ddd-04-fd-nonblocking.c
Normal file
323
doc/designs/ddd/ddd-04-fd-nonblocking.c
Normal file
@ -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;
|
||||
}
|
408
doc/designs/ddd/ddd-05-mem-nonblocking.c
Normal file
408
doc/designs/ddd/ddd-05-mem-nonblocking.c
Normal file
@ -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;
|
||||
}
|
599
doc/designs/ddd/ddd-06-mem-uv.c
Normal file
599
doc/designs/ddd/ddd-06-mem-uv.c
Normal file
@ -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'
|
||||
|
Loading…
x
Reference in New Issue
Block a user