Path: blob/main/crypto/openssl/doc/designs/ddd/ddd-06-mem-uv.c
34889 views
#include <sys/poll.h>1#include <openssl/ssl.h>2#include <uv.h>3#include <assert.h>4#ifdef USE_QUIC5# include <sys/time.h>6#endif78typedef struct app_conn_st APP_CONN;9typedef struct upper_write_op_st UPPER_WRITE_OP;10typedef struct lower_write_op_st LOWER_WRITE_OP;1112typedef void (app_connect_cb)(APP_CONN *conn, int status, void *arg);13typedef void (app_write_cb)(APP_CONN *conn, int status, void *arg);14typedef void (app_read_cb)(APP_CONN *conn, void *buf, size_t buf_len, void *arg);1516#ifdef USE_QUIC17static void set_timer(APP_CONN *conn);18#else19static void tcp_connect_done(uv_connect_t *tcp_connect, int status);20#endif21static void net_connect_fail_close_done(uv_handle_t *handle);22static int handshake_ssl(APP_CONN *conn);23static void flush_write_buf(APP_CONN *conn);24static void set_rx(APP_CONN *conn);25static int try_write(APP_CONN *conn, UPPER_WRITE_OP *op);26static void handle_pending_writes(APP_CONN *conn);27static int write_deferred(APP_CONN *conn, const void *buf, size_t buf_len, app_write_cb *cb, void *arg);28static void teardown_continued(uv_handle_t *handle);29static int setup_ssl(APP_CONN *conn, const char *hostname);3031#ifdef USE_QUIC32static inline int timeval_to_ms(const struct timeval *t)33{34return t->tv_sec*1000 + t->tv_usec/1000;35}36#endif3738/*39* Structure to track an application-level write request. Only created40* if SSL_write does not accept the data immediately, typically because41* it is in WANT_READ.42*/43struct upper_write_op_st {44struct upper_write_op_st *prev, *next;45const uint8_t *buf;46size_t buf_len, written;47APP_CONN *conn;48app_write_cb *cb;49void *cb_arg;50};5152/*53* Structure to track a network-level write request.54*/55struct lower_write_op_st {56#ifdef USE_QUIC57uv_udp_send_t w;58#else59uv_write_t w;60#endif61uv_buf_t b;62uint8_t *buf;63APP_CONN *conn;64};6566/*67* Application connection object.68*/69struct app_conn_st {70SSL_CTX *ctx;71SSL *ssl;72BIO *net_bio;73#ifdef USE_QUIC74uv_udp_t udp;75uv_timer_t timer;76#else77uv_stream_t *stream;78uv_tcp_t tcp;79uv_connect_t tcp_connect;80#endif81app_connect_cb *app_connect_cb; /* called once handshake is done */82void *app_connect_arg;83app_read_cb *app_read_cb; /* application's on-RX callback */84void *app_read_arg;85const char *hostname;86char init_handshake, done_handshake, closed;87char *teardown_done;8889UPPER_WRITE_OP *pending_upper_write_head, *pending_upper_write_tail;90};9192/*93* The application is initializing and wants an SSL_CTX which it will use for94* some number of outgoing connections, which it creates in subsequent calls to95* new_conn. The application may also call this function multiple times to96* create multiple SSL_CTX.97*/98SSL_CTX *create_ssl_ctx(void)99{100SSL_CTX *ctx;101102#ifdef USE_QUIC103ctx = SSL_CTX_new(OSSL_QUIC_client_method());104#else105ctx = SSL_CTX_new(TLS_client_method());106#endif107if (ctx == NULL)108return NULL;109110/* Enable trust chain verification. */111SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);112113/* Load default root CA store. */114if (SSL_CTX_set_default_verify_paths(ctx) == 0) {115SSL_CTX_free(ctx);116return NULL;117}118119return ctx;120}121122/*123* The application wants to create a new outgoing connection using a given124* SSL_CTX. An outgoing TCP connection is started and the callback is called125* asynchronously when the TLS handshake is complete.126*127* hostname is a string like "openssl.org" used for certificate validation.128*/129130APP_CONN *new_conn(SSL_CTX *ctx, const char *hostname,131struct sockaddr *sa, socklen_t sa_len,132app_connect_cb *cb, void *arg)133{134int rc;135APP_CONN *conn = NULL;136137conn = calloc(1, sizeof(APP_CONN));138if (!conn)139return NULL;140141#ifdef USE_QUIC142uv_udp_init(uv_default_loop(), &conn->udp);143conn->udp.data = conn;144145uv_timer_init(uv_default_loop(), &conn->timer);146conn->timer.data = conn;147#else148uv_tcp_init(uv_default_loop(), &conn->tcp);149conn->tcp.data = conn;150151conn->stream = (uv_stream_t *)&conn->tcp;152#endif153154conn->app_connect_cb = cb;155conn->app_connect_arg = arg;156#ifdef USE_QUIC157rc = uv_udp_connect(&conn->udp, sa);158#else159conn->tcp_connect.data = conn;160rc = uv_tcp_connect(&conn->tcp_connect, &conn->tcp, sa, tcp_connect_done);161#endif162if (rc < 0) {163#ifdef USE_QUIC164uv_close((uv_handle_t *)&conn->udp, net_connect_fail_close_done);165#else166uv_close((uv_handle_t *)&conn->tcp, net_connect_fail_close_done);167#endif168return NULL;169}170171conn->ctx = ctx;172conn->hostname = hostname;173174#ifdef USE_QUIC175rc = setup_ssl(conn, hostname);176if (rc < 0) {177uv_close((uv_handle_t *)&conn->udp, net_connect_fail_close_done);178return NULL;179}180#endif181182return conn;183}184185/*186* The application wants to start reading from the SSL stream.187* The callback is called whenever data is available.188*/189int app_read_start(APP_CONN *conn, app_read_cb *cb, void *arg)190{191conn->app_read_cb = cb;192conn->app_read_arg = arg;193set_rx(conn);194return 0;195}196197/*198* The application wants to write. The callback is called once the199* write is complete. The callback should free the buffer.200*/201int app_write(APP_CONN *conn, const void *buf, size_t buf_len, app_write_cb *cb, void *arg)202{203write_deferred(conn, buf, buf_len, cb, arg);204handle_pending_writes(conn);205return buf_len;206}207208/*209* The application wants to close the connection and free bookkeeping210* structures.211*/212void teardown(APP_CONN *conn)213{214char teardown_done = 0;215216if (conn == NULL)217return;218219BIO_free_all(conn->net_bio);220SSL_free(conn->ssl);221222#ifndef USE_QUIC223uv_cancel((uv_req_t *)&conn->tcp_connect);224#endif225226conn->teardown_done = &teardown_done;227#ifdef USE_QUIC228uv_close((uv_handle_t *)&conn->udp, teardown_continued);229uv_close((uv_handle_t *)&conn->timer, teardown_continued);230#else231uv_close((uv_handle_t *)conn->stream, teardown_continued);232#endif233234/* Just wait synchronously until teardown completes. */235#ifdef USE_QUIC236while (teardown_done < 2)237#else238while (!teardown_done)239#endif240uv_run(uv_default_loop(), UV_RUN_DEFAULT);241}242243/*244* The application is shutting down and wants to free a previously245* created SSL_CTX.246*/247void teardown_ctx(SSL_CTX *ctx)248{249SSL_CTX_free(ctx);250}251252/*253* ============================================================================254* Internal implementation functions.255*/256static void enqueue_upper_write_op(APP_CONN *conn, UPPER_WRITE_OP *op)257{258op->prev = conn->pending_upper_write_tail;259if (op->prev)260op->prev->next = op;261262conn->pending_upper_write_tail = op;263if (conn->pending_upper_write_head == NULL)264conn->pending_upper_write_head = op;265}266267static void dequeue_upper_write_op(APP_CONN *conn)268{269if (conn->pending_upper_write_head == NULL)270return;271272if (conn->pending_upper_write_head->next == NULL) {273conn->pending_upper_write_head = NULL;274conn->pending_upper_write_tail = NULL;275} else {276conn->pending_upper_write_head = conn->pending_upper_write_head->next;277conn->pending_upper_write_head->prev = NULL;278}279}280281static void net_read_alloc(uv_handle_t *handle,282size_t suggested_size, uv_buf_t *buf)283{284#ifdef USE_QUIC285if (suggested_size < 1472)286suggested_size = 1472;287#endif288289buf->base = malloc(suggested_size);290buf->len = suggested_size;291}292293static void on_rx_push(APP_CONN *conn)294{295int srd, rc;296int buf_len = 4096;297298do {299if (!conn->app_read_cb)300return;301302void *buf = malloc(buf_len);303if (!buf)304return;305306srd = SSL_read(conn->ssl, buf, buf_len);307flush_write_buf(conn);308if (srd <= 0) {309rc = SSL_get_error(conn->ssl, srd);310if (rc == SSL_ERROR_WANT_READ) {311free(buf);312return;313}314}315316conn->app_read_cb(conn, buf, srd, conn->app_read_arg);317} while (srd == buf_len);318}319320static void net_error(APP_CONN *conn)321{322conn->closed = 1;323set_rx(conn);324325if (conn->app_read_cb)326conn->app_read_cb(conn, NULL, 0, conn->app_read_arg);327}328329static void handle_pending_writes(APP_CONN *conn)330{331int rc;332333if (conn->pending_upper_write_head == NULL)334return;335336do {337UPPER_WRITE_OP *op = conn->pending_upper_write_head;338rc = try_write(conn, op);339if (rc <= 0)340break;341342dequeue_upper_write_op(conn);343free(op);344} while (conn->pending_upper_write_head != NULL);345346set_rx(conn);347}348349#ifdef USE_QUIC350static void net_read_done(uv_udp_t *stream, ssize_t nr, const uv_buf_t *buf,351const struct sockaddr *addr, unsigned int flags)352#else353static void net_read_done(uv_stream_t *stream, ssize_t nr, const uv_buf_t *buf)354#endif355{356int rc;357APP_CONN *conn = (APP_CONN *)stream->data;358359if (nr < 0) {360free(buf->base);361net_error(conn);362return;363}364365if (nr > 0) {366int wr = BIO_write(conn->net_bio, buf->base, nr);367assert(wr == nr);368}369370free(buf->base);371372if (!conn->done_handshake) {373rc = handshake_ssl(conn);374if (rc < 0) {375fprintf(stderr, "handshake error: %d\n", rc);376return;377}378379if (!conn->done_handshake)380return;381}382383handle_pending_writes(conn);384on_rx_push(conn);385}386387static void set_rx(APP_CONN *conn)388{389#ifdef USE_QUIC390if (!conn->closed)391uv_udp_recv_start(&conn->udp, net_read_alloc, net_read_done);392else393uv_udp_recv_stop(&conn->udp);394#else395if (!conn->closed && (conn->app_read_cb || (!conn->done_handshake && conn->init_handshake) || conn->pending_upper_write_head != NULL))396uv_read_start(conn->stream, net_read_alloc, net_read_done);397else398uv_read_stop(conn->stream);399#endif400}401402#ifdef USE_QUIC403static void net_write_done(uv_udp_send_t *req, int status)404#else405static void net_write_done(uv_write_t *req, int status)406#endif407{408LOWER_WRITE_OP *op = (LOWER_WRITE_OP *)req->data;409APP_CONN *conn = op->conn;410411if (status < 0) {412fprintf(stderr, "UV write failed %d\n", status);413return;414}415416free(op->buf);417free(op);418419flush_write_buf(conn);420}421422static void flush_write_buf(APP_CONN *conn)423{424int rc, rd;425LOWER_WRITE_OP *op;426uint8_t *buf;427428buf = malloc(4096);429if (!buf)430return;431432rd = BIO_read(conn->net_bio, buf, 4096);433if (rd <= 0) {434free(buf);435return;436}437438op = calloc(1, sizeof(LOWER_WRITE_OP));439if (!op)440return;441442op->buf = buf;443op->conn = conn;444op->w.data = op;445op->b.base = (char *)buf;446op->b.len = rd;447448#ifdef USE_QUIC449rc = uv_udp_send(&op->w, &conn->udp, &op->b, 1, NULL, net_write_done);450#else451rc = uv_write(&op->w, conn->stream, &op->b, 1, net_write_done);452#endif453if (rc < 0) {454free(buf);455free(op);456fprintf(stderr, "UV write failed\n");457return;458}459}460461static void handshake_done_ssl(APP_CONN *conn)462{463#ifdef USE_QUIC464set_timer(conn);465#endif466conn->app_connect_cb(conn, 0, conn->app_connect_arg);467}468469static int handshake_ssl(APP_CONN *conn)470{471int rc, rcx;472473conn->init_handshake = 1;474475rc = SSL_do_handshake(conn->ssl);476if (rc > 0) {477conn->done_handshake = 1;478handshake_done_ssl(conn);479set_rx(conn);480return 0;481}482483flush_write_buf(conn);484rcx = SSL_get_error(conn->ssl, rc);485if (rcx == SSL_ERROR_WANT_READ) {486set_rx(conn);487return 0;488}489490fprintf(stderr, "Handshake error: %d\n", rcx);491return -rcx;492}493494static int setup_ssl(APP_CONN *conn, const char *hostname)495{496BIO *internal_bio = NULL, *net_bio = NULL;497SSL *ssl = NULL;498#ifdef USE_QUIC499static const unsigned char alpn[] = {5, 'd', 'u', 'm', 'm', 'y'};500#endif501502ssl = SSL_new(conn->ctx);503if (!ssl)504return -1;505506SSL_set_connect_state(ssl);507508#ifdef USE_QUIC509if (BIO_new_bio_dgram_pair(&internal_bio, 0, &net_bio, 0) <= 0) {510SSL_free(ssl);511return -1;512}513#else514if (BIO_new_bio_pair(&internal_bio, 0, &net_bio, 0) <= 0) {515SSL_free(ssl);516return -1;517}518#endif519520SSL_set_bio(ssl, internal_bio, internal_bio);521522if (SSL_set1_host(ssl, hostname) <= 0) {523SSL_free(ssl);524return -1;525}526527if (SSL_set_tlsext_host_name(ssl, hostname) <= 0) {528SSL_free(ssl);529return -1;530}531532#ifdef USE_QUIC533/* Configure ALPN, which is required for QUIC. */534if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn))) {535/* Note: SSL_set_alpn_protos returns 1 for failure. */536SSL_free(ssl);537return -1;538}539#endif540541conn->net_bio = net_bio;542conn->ssl = ssl;543return handshake_ssl(conn);544}545546#ifndef USE_QUIC547static void tcp_connect_done(uv_connect_t *tcp_connect, int status)548{549int rc;550APP_CONN *conn = (APP_CONN *)tcp_connect->data;551552if (status < 0) {553uv_stop(uv_default_loop());554return;555}556557rc = setup_ssl(conn, conn->hostname);558if (rc < 0) {559fprintf(stderr, "cannot init SSL\n");560uv_stop(uv_default_loop());561return;562}563}564#endif565566static void net_connect_fail_close_done(uv_handle_t *handle)567{568APP_CONN *conn = (APP_CONN *)handle->data;569570free(conn);571}572573#ifdef USE_QUIC574575static void timer_done(uv_timer_t *timer)576{577APP_CONN *conn = (APP_CONN *)timer->data;578579SSL_handle_events(conn->ssl);580handle_pending_writes(conn);581flush_write_buf(conn);582set_rx(conn);583set_timer(conn); /* repeat timer */584}585586static void set_timer(APP_CONN *conn)587{588struct timeval tv;589int ms, is_infinite;590591if (!SSL_get_event_timeout(conn->ssl, &tv, &is_infinite))592return;593594ms = is_infinite ? -1 : timeval_to_ms(&tv);595if (ms > 0)596uv_timer_start(&conn->timer, timer_done, ms, 0);597}598599#endif600601static int try_write(APP_CONN *conn, UPPER_WRITE_OP *op)602{603int rc, rcx;604size_t written = op->written;605606while (written < op->buf_len) {607rc = SSL_write(conn->ssl, op->buf + written, op->buf_len - written);608if (rc <= 0) {609rcx = SSL_get_error(conn->ssl, rc);610if (rcx == SSL_ERROR_WANT_READ) {611op->written = written;612return 0;613} else {614if (op->cb != NULL)615op->cb(conn, -rcx, op->cb_arg);616return 1; /* op should be freed */617}618}619620written += rc;621}622623if (op->cb != NULL)624op->cb(conn, 0, op->cb_arg);625626flush_write_buf(conn);627return 1; /* op should be freed */628}629630static int write_deferred(APP_CONN *conn, const void *buf, size_t buf_len, app_write_cb *cb, void *arg)631{632UPPER_WRITE_OP *op = calloc(1, sizeof(UPPER_WRITE_OP));633if (!op)634return -1;635636op->buf = buf;637op->buf_len = buf_len;638op->conn = conn;639op->cb = cb;640op->cb_arg = arg;641642enqueue_upper_write_op(conn, op);643set_rx(conn);644flush_write_buf(conn);645return buf_len;646}647648static void teardown_continued(uv_handle_t *handle)649{650APP_CONN *conn = (APP_CONN *)handle->data;651UPPER_WRITE_OP *op, *next_op;652char *teardown_done = conn->teardown_done;653654#ifdef USE_QUIC655if (++*teardown_done < 2)656return;657#endif658659for (op=conn->pending_upper_write_head; op; op=next_op) {660next_op = op->next;661free(op);662}663664free(conn);665#ifndef USE_QUIC666*teardown_done = 1;667#endif668}669670/*671* ============================================================================672* Example driver for the above code. This is just to demonstrate that the code673* works and is not intended to be representative of a real application.674*/675static void post_read(APP_CONN *conn, void *buf, size_t buf_len, void *arg)676{677if (!buf_len) {678free(buf);679uv_stop(uv_default_loop());680return;681}682683fwrite(buf, 1, buf_len, stdout);684free(buf);685}686687static void post_write_get(APP_CONN *conn, int status, void *arg)688{689if (status < 0) {690fprintf(stderr, "write failed: %d\n", status);691return;692}693694app_read_start(conn, post_read, NULL);695}696697char tx_msg[300];698int mlen;699700static void post_connect(APP_CONN *conn, int status, void *arg)701{702int wr;703704if (status < 0) {705fprintf(stderr, "failed to connect: %d\n", status);706uv_stop(uv_default_loop());707return;708}709710wr = app_write(conn, tx_msg, mlen, post_write_get, NULL);711if (wr < mlen) {712fprintf(stderr, "error writing request");713return;714}715}716717int main(int argc, char **argv)718{719int rc = 1;720SSL_CTX *ctx = NULL;721APP_CONN *conn = NULL;722struct addrinfo hints = {0}, *result = NULL;723724if (argc < 3) {725fprintf(stderr, "usage: %s host port\n", argv[0]);726goto fail;727}728729mlen = snprintf(tx_msg, sizeof(tx_msg),730"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", argv[1]);731732ctx = create_ssl_ctx();733if (!ctx)734goto fail;735736hints.ai_family = AF_INET;737hints.ai_socktype = SOCK_STREAM;738hints.ai_flags = AI_PASSIVE;739rc = getaddrinfo(argv[1], argv[2], &hints, &result);740if (rc < 0) {741fprintf(stderr, "cannot resolve\n");742goto fail;743}744745conn = new_conn(ctx, argv[1], result->ai_addr, result->ai_addrlen, post_connect, NULL);746if (!conn)747goto fail;748749uv_run(uv_default_loop(), UV_RUN_DEFAULT);750751rc = 0;752fail:753teardown(conn);754freeaddrinfo(result);755uv_loop_close(uv_default_loop());756teardown_ctx(ctx);757}758759760