123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758 |
- #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);
- }
|