// Copyright (C) 2018-2019, Cloudflare, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOCAL_CONN_ID_LEN 16 #define MAX_DATAGRAM_SIZE 1350 #define MAX_TOKEN_LEN \ sizeof("quiche") - 1 + \ sizeof(struct sockaddr_storage) + \ QUICHE_MAX_CONN_ID_LEN struct connections { int sock; struct conn_io *h; }; struct conn_io { ev_timer timer; int sock; uint8_t cid[LOCAL_CONN_ID_LEN]; quiche_conn *conn; struct sockaddr_storage peer_addr; socklen_t peer_addr_len; UT_hash_handle hh; }; static quiche_config *config = NULL; static struct connections *conns = NULL; static void timeout_cb(EV_P_ ev_timer *w, int revents); static void debug_log(const char *line, void *argp) { fprintf(stderr, "%s\n", line); } static void flush_egress(struct ev_loop *loop, struct conn_io *conn_io) { static uint8_t out[MAX_DATAGRAM_SIZE]; while (1) { ssize_t written = quiche_conn_send(conn_io->conn, out, sizeof(out)); if (written == QUICHE_ERR_DONE) { fprintf(stderr, "done writing\n"); break; } if (written < 0) { fprintf(stderr, "failed to create packet: %zd\n", written); return; } ssize_t sent = sendto(conn_io->sock, out, written, 0, (struct sockaddr *) &conn_io->peer_addr, conn_io->peer_addr_len); if (sent != written) { perror("failed to send"); return; } fprintf(stderr, "sent %zd bytes\n", sent); } double t = quiche_conn_timeout_as_nanos(conn_io->conn) / 1e9f; conn_io->timer.repeat = t; ev_timer_again(loop, &conn_io->timer); } static void mint_token(const uint8_t *dcid, size_t dcid_len, struct sockaddr_storage *addr, socklen_t addr_len, uint8_t *token, size_t *token_len) { memcpy(token, "quiche", sizeof("quiche") - 1); memcpy(token + sizeof("quiche") - 1, addr, addr_len); memcpy(token + sizeof("quiche") - 1 + addr_len, dcid, dcid_len); *token_len = sizeof("quiche") - 1 + addr_len + dcid_len; } static bool validate_token(const uint8_t *token, size_t token_len, struct sockaddr_storage *addr, socklen_t addr_len, uint8_t *odcid, size_t *odcid_len) { if ((token_len < sizeof("quiche") - 1) || memcmp(token, "quiche", sizeof("quiche") - 1)) { return false; } token += sizeof("quiche") - 1; token_len -= sizeof("quiche") - 1; if ((token_len < addr_len) || memcmp(token, addr, addr_len)) { return false; } token += addr_len; token_len -= addr_len; if (*odcid_len < token_len) { return false; } memcpy(odcid, token, token_len); *odcid_len = token_len; return true; } static uint8_t *gen_cid(uint8_t *cid, size_t cid_len) { int rng = open("/dev/urandom", O_RDONLY); if (rng < 0) { perror("failed to open /dev/urandom"); return NULL; } ssize_t rand_len = read(rng, cid, cid_len); if (rand_len < 0) { perror("failed to create connection ID"); return NULL; } return cid; } static struct conn_io *create_conn(uint8_t *dcid, size_t dcid_len, uint8_t *odcid, size_t odcid_len) { struct conn_io *conn_io = malloc(sizeof(*conn_io)); if (conn_io == NULL) { fprintf(stderr, "failed to allocate connection IO\n"); return NULL; } memcpy(conn_io->cid, dcid, LOCAL_CONN_ID_LEN); quiche_conn *conn = quiche_accept(conn_io->cid, LOCAL_CONN_ID_LEN, odcid, odcid_len, config); if (conn == NULL) { fprintf(stderr, "failed to create connection\n"); return NULL; } conn_io->sock = conns->sock; conn_io->conn = conn; ev_init(&conn_io->timer, timeout_cb); conn_io->timer.data = conn_io; HASH_ADD(hh, conns->h, cid, LOCAL_CONN_ID_LEN, conn_io); fprintf(stderr, "new connection\n"); return conn_io; } static void recv_cb(EV_P_ ev_io *w, int revents) { struct conn_io *tmp, *conn_io = NULL; static uint8_t buf[65535]; static uint8_t out[MAX_DATAGRAM_SIZE]; while (1) { struct sockaddr_storage peer_addr; socklen_t peer_addr_len = sizeof(peer_addr); memset(&peer_addr, 0, peer_addr_len); ssize_t read = recvfrom(conns->sock, buf, sizeof(buf), 0, (struct sockaddr *) &peer_addr, &peer_addr_len); if (read < 0) { if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) { fprintf(stderr, "recv would block\n"); break; } perror("failed to read"); return; } uint8_t type; uint32_t version; uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; size_t scid_len = sizeof(scid); uint8_t dcid[QUICHE_MAX_CONN_ID_LEN]; size_t dcid_len = sizeof(dcid); uint8_t odcid[QUICHE_MAX_CONN_ID_LEN]; size_t odcid_len = sizeof(odcid); uint8_t token[MAX_TOKEN_LEN]; size_t token_len = sizeof(token); int rc = quiche_header_info(buf, read, LOCAL_CONN_ID_LEN, &version, &type, scid, &scid_len, dcid, &dcid_len, token, &token_len); if (rc < 0) { fprintf(stderr, "failed to parse header: %d\n", rc); continue; } HASH_FIND(hh, conns->h, dcid, dcid_len, conn_io); if (conn_io == NULL) { if (!quiche_version_is_supported(version)) { fprintf(stderr, "version negotiation\n"); ssize_t written = quiche_negotiate_version(scid, scid_len, dcid, dcid_len, out, sizeof(out)); if (written < 0) { fprintf(stderr, "failed to create vneg packet: %zd\n", written); continue; } ssize_t sent = sendto(conns->sock, out, written, 0, (struct sockaddr *) &peer_addr, peer_addr_len); if (sent != written) { perror("failed to send"); continue; } fprintf(stderr, "sent %zd bytes\n", sent); continue; } if (token_len == 0) { fprintf(stderr, "stateless retry\n"); mint_token(dcid, dcid_len, &peer_addr, peer_addr_len, token, &token_len); uint8_t new_cid[LOCAL_CONN_ID_LEN]; if (gen_cid(new_cid, LOCAL_CONN_ID_LEN) == NULL) { continue; } ssize_t written = quiche_retry(scid, scid_len, dcid, dcid_len, new_cid, LOCAL_CONN_ID_LEN, token, token_len, version, out, sizeof(out)); if (written < 0) { fprintf(stderr, "failed to create retry packet: %zd\n", written); continue; } ssize_t sent = sendto(conns->sock, out, written, 0, (struct sockaddr *) &peer_addr, peer_addr_len); if (sent != written) { perror("failed to send"); continue; } fprintf(stderr, "sent %zd bytes\n", sent); continue; } if (!validate_token(token, token_len, &peer_addr, peer_addr_len, odcid, &odcid_len)) { fprintf(stderr, "invalid address validation token\n"); continue; } conn_io = create_conn(dcid, dcid_len, odcid, odcid_len); if (conn_io == NULL) { continue; } memcpy(&conn_io->peer_addr, &peer_addr, peer_addr_len); conn_io->peer_addr_len = peer_addr_len; } ssize_t done = quiche_conn_recv(conn_io->conn, buf, read); if (done < 0) { fprintf(stderr, "failed to process packet: %zd\n", done); continue; } fprintf(stderr, "recv %zd bytes\n", done); if (quiche_conn_is_established(conn_io->conn)) { uint64_t s = 0; quiche_stream_iter *readable = quiche_conn_readable(conn_io->conn); while (quiche_stream_iter_next(readable, &s)) { fprintf(stderr, "stream %" PRIu64 " is readable\n", s); bool fin = false; ssize_t recv_len = quiche_conn_stream_recv(conn_io->conn, s, buf, sizeof(buf), &fin); if (recv_len < 0) { break; } if (fin) { static const char *resp = "byez\n"; quiche_conn_stream_send(conn_io->conn, s, (uint8_t *) resp, 5, true); } } quiche_stream_iter_free(readable); } } HASH_ITER(hh, conns->h, conn_io, tmp) { flush_egress(loop, conn_io); if (quiche_conn_is_closed(conn_io->conn)) { quiche_stats stats; quiche_conn_stats(conn_io->conn, &stats); fprintf(stderr, "connection closed, recv=%zu sent=%zu lost=%zu rtt=%" PRIu64 "ns cwnd=%zu\n", stats.recv, stats.sent, stats.lost, stats.rtt, stats.cwnd); HASH_DELETE(hh, conns->h, conn_io); ev_timer_stop(loop, &conn_io->timer); quiche_conn_free(conn_io->conn); free(conn_io); } } } static void timeout_cb(EV_P_ ev_timer *w, int revents) { struct conn_io *conn_io = w->data; quiche_conn_on_timeout(conn_io->conn); fprintf(stderr, "timeout\n"); flush_egress(loop, conn_io); if (quiche_conn_is_closed(conn_io->conn)) { quiche_stats stats; quiche_conn_stats(conn_io->conn, &stats); fprintf(stderr, "connection closed, recv=%zu sent=%zu lost=%zu rtt=%" PRIu64 "ns cwnd=%zu\n", stats.recv, stats.sent, stats.lost, stats.rtt, stats.cwnd); HASH_DELETE(hh, conns->h, conn_io); ev_timer_stop(loop, &conn_io->timer); quiche_conn_free(conn_io->conn); free(conn_io); return; } } int main(int argc, char *argv[]) { const char *host = argv[1]; const char *port = argv[2]; const struct addrinfo hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_DGRAM, .ai_protocol = IPPROTO_UDP }; quiche_enable_debug_logging(debug_log, NULL); struct addrinfo *local; if (getaddrinfo(host, port, &hints, &local) != 0) { perror("failed to resolve host"); return -1; } int sock = socket(local->ai_family, SOCK_DGRAM, 0); if (sock < 0) { perror("failed to create socket"); return -1; } if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) { perror("failed to make socket non-blocking"); return -1; } if (bind(sock, local->ai_addr, local->ai_addrlen) < 0) { perror("failed to connect socket"); return -1; } config = quiche_config_new(QUICHE_PROTOCOL_VERSION); if (config == NULL) { fprintf(stderr, "failed to create config\n"); return -1; } quiche_config_load_cert_chain_from_pem_file(config, "./cert.crt"); quiche_config_load_priv_key_from_pem_file(config, "./cert.key"); quiche_config_set_application_protos(config, (uint8_t *) "\x05hq-29\x05hq-28\x05hq-27\x08http/0.9", 27); quiche_config_set_max_idle_timeout(config, 5000); quiche_config_set_max_udp_payload_size(config, MAX_DATAGRAM_SIZE); quiche_config_set_initial_max_data(config, 10000000); quiche_config_set_initial_max_stream_data_bidi_local(config, 1000000); quiche_config_set_initial_max_stream_data_bidi_remote(config, 1000000); quiche_config_set_initial_max_streams_bidi(config, 100); quiche_config_set_cc_algorithm(config, QUICHE_CC_RENO); struct connections c; c.sock = sock; c.h = NULL; conns = &c; ev_io watcher; struct ev_loop *loop = ev_default_loop(0); ev_io_init(&watcher, recv_cb, sock, EV_READ); ev_io_start(loop, &watcher); watcher.data = &c; ev_loop(loop, 0); freeaddrinfo(local); quiche_config_free(config); return 0; }