openssl/doc/designs/ddd/ddd-06-mem-uv.c
James Muir d1338fcf12 quic ddd demos: update makefile and demo sources
Update makefile and fix some signedness issues in the demo sources.
Drop stray "\n" in the host-port format string that prevented ddd-01
from working (this was also noticed by Neil H). Also, determine the
length of the message we are sending and send that many bytes (rather
than send sizeof the buffer storing the message).

These changes are part of https://github.com/openssl/project/issues/253

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Hugo Landau <hlandau@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/22542)
2023-11-01 15:38:41 +00:00

759 lines
18 KiB
C

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