blob: 2b473df653fab12b8c3c0fac14bb02046b1ced51 [file] [log] [blame]
// Copyright (C) 2018, Cloudflare, Inc.
// Copyright (C) 2018, Alessandro Ghedini
// 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 <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ev.h>
#include <uthash.h>
#include <quiche.h>
#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 struct conn_io *create_conn(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;
}
int rng = open("/dev/urandom", O_RDONLY);
if (rng < 0) {
perror("failed to open /dev/urandom");
return NULL;
}
ssize_t rand_len = read(rng, conn_io->cid, LOCAL_CONN_ID_LEN);
if (rand_len < 0) {
perror("failed to create connection ID");
return NULL;
}
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);
return;
}
HASH_FIND(hh, conns->h, dcid, dcid_len, conn_io);
if (conn_io == NULL) {
if (version != QUICHE_PROTOCOL_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);
return;
}
ssize_t sent = sendto(conns->sock, out, written, 0,
(struct sockaddr *) &peer_addr,
peer_addr_len);
if (sent != written) {
perror("failed to send");
return;
}
fprintf(stderr, "sent %zd bytes\n", sent);
return;
}
if (token_len == 0) {
fprintf(stderr, "stateless retry\n");
mint_token(dcid, dcid_len, &peer_addr, peer_addr_len,
token, &token_len);
ssize_t written = quiche_retry(scid, scid_len,
dcid, dcid_len,
dcid, dcid_len,
token, token_len,
out, sizeof(out));
if (written < 0) {
fprintf(stderr, "failed to create retry packet: %zd\n",
written);
return;
}
ssize_t sent = sendto(conns->sock, out, written, 0,
(struct sockaddr *) &peer_addr,
peer_addr_len);
if (sent != written) {
perror("failed to send");
return;
}
fprintf(stderr, "sent %zd bytes\n", sent);
return;
}
if (!validate_token(token, token_len, &peer_addr, peer_addr_len,
odcid, &odcid_len)) {
fprintf(stderr, "invalid address validation token\n");
return;
}
conn_io = create_conn(odcid, odcid_len);
if (conn_io == NULL) {
return;
}
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 == QUICHE_ERR_DONE) {
fprintf(stderr, "done reading\n");
break;
}
if (done < 0) {
fprintf(stderr, "failed to process packet: %zd\n", done);
return;
}
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)) {
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)) {
fprintf(stderr, "connection closed\n");
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, "examples/cert.crt");
quiche_config_load_priv_key_from_pem_file(config, "examples/cert.key");
quiche_config_set_application_protos(config,
(uint8_t *) "\x05hq-23\x08http/0.9", 15);
quiche_config_set_idle_timeout(config, 5000);
quiche_config_set_max_packet_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);
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;
}