|
#include <arpa/inet.h> |
|
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <inttypes.h> |
|
#include <netdb.h> |
|
#include <openssl/err.h> |
|
#include <openssl/rand.h> |
|
#include <openssl/ssl.h> |
|
#include <poll.h> |
|
#include <stdbool.h> |
|
#include <stdint.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/socket.h> |
|
#include <sys/types.h> |
|
#include <time.h> |
|
#include <unistd.h> |
|
|
|
#include <nghttp3/nghttp3.h> |
|
#include <ngtcp2/ngtcp2.h> |
|
#include <ngtcp2/ngtcp2_crypto.h> |
|
#include <ngtcp2/ngtcp2_crypto_ossl.h> |
|
|
|
#define DEFAULT_PORT "4433" |
|
#define RXBUF_SIZE 65536 |
|
#define TXBUF_SIZE 2048 |
|
#define MAX_TX_PER_TICK 32 |
|
|
|
static const uint8_t RESP_BODY[] = |
|
"{\"message\":\"hello from ngtcp2/nghttp3 openssl server\"}\n"; |
|
|
|
typedef struct stream_ctx { |
|
int64_t stream_id; |
|
int body_sent; |
|
int response_submitted; |
|
int response_pending; |
|
struct stream_ctx *next; |
|
} stream_ctx; |
|
|
|
typedef struct server { |
|
int fd; |
|
|
|
struct sockaddr_storage local_addr; |
|
socklen_t local_addrlen; |
|
struct sockaddr_storage peer_addr; |
|
socklen_t peer_addrlen; |
|
int has_peer; |
|
|
|
ngtcp2_path_storage ps; |
|
ngtcp2_conn *qconn; |
|
nghttp3_conn *h3conn; |
|
|
|
SSL_CTX *ssl_ctx; |
|
SSL *ssl; |
|
ngtcp2_crypto_ossl_ctx *ossl_ctx; |
|
ngtcp2_crypto_conn_ref conn_ref; |
|
|
|
uint8_t rxbuf[RXBUF_SIZE]; |
|
uint8_t txbuf[TXBUF_SIZE]; |
|
|
|
ngtcp2_tstamp last_ts; |
|
int handshake_completed; |
|
int h3_streams_bound; |
|
int done; |
|
int had_error; |
|
|
|
stream_ctx *streams; |
|
} server; |
|
|
|
static void log_ssl_errors(const char *where) { |
|
unsigned long err; |
|
int found = 0; |
|
while ((err = ERR_get_error()) != 0) { |
|
char buf[256]; |
|
ERR_error_string_n(err, buf, sizeof(buf)); |
|
fprintf(stderr, "%s: openssl: %s\n", where, buf); |
|
found = 1; |
|
} |
|
if (!found) { |
|
fprintf(stderr, "%s: openssl: no error in queue\n", where); |
|
} |
|
} |
|
|
|
static ngtcp2_tstamp timestamp_now_raw(void) { |
|
struct timespec ts; |
|
clock_gettime(CLOCK_MONOTONIC, &ts); |
|
return (ngtcp2_tstamp)ts.tv_sec * NGTCP2_SECONDS + (ngtcp2_tstamp)ts.tv_nsec; |
|
} |
|
|
|
static ngtcp2_tstamp timestamp_now(server *s) { |
|
ngtcp2_tstamp ts = timestamp_now_raw(); |
|
if (ts <= s->last_ts) { |
|
ts = s->last_ts + 1; |
|
} |
|
s->last_ts = ts; |
|
return ts; |
|
} |
|
|
|
static void fail(server *s, const char *where) { |
|
if (!s->had_error) { |
|
fprintf(stderr, "error: %s\n", where); |
|
} |
|
s->had_error = 1; |
|
} |
|
|
|
static int sockaddr_eq(const struct sockaddr_storage *a, |
|
const struct sockaddr_storage *b) { |
|
if (a->ss_family != b->ss_family) { |
|
return 0; |
|
} |
|
if (a->ss_family == AF_INET) { |
|
const struct sockaddr_in *x = (const struct sockaddr_in *)a; |
|
const struct sockaddr_in *y = (const struct sockaddr_in *)b; |
|
return x->sin_port == y->sin_port && x->sin_addr.s_addr == y->sin_addr.s_addr; |
|
} |
|
if (a->ss_family == AF_INET6) { |
|
const struct sockaddr_in6 *x = (const struct sockaddr_in6 *)a; |
|
const struct sockaddr_in6 *y = (const struct sockaddr_in6 *)b; |
|
return x->sin6_port == y->sin6_port && |
|
memcmp(&x->sin6_addr, &y->sin6_addr, sizeof(x->sin6_addr)) == 0; |
|
} |
|
return 0; |
|
} |
|
|
|
static stream_ctx *get_stream_ctx(server *s, int64_t stream_id, int create) { |
|
stream_ctx *p; |
|
for (p = s->streams; p; p = p->next) { |
|
if (p->stream_id == stream_id) { |
|
return p; |
|
} |
|
} |
|
if (!create) { |
|
return NULL; |
|
} |
|
p = calloc(1, sizeof(*p)); |
|
if (!p) { |
|
return NULL; |
|
} |
|
p->stream_id = stream_id; |
|
p->next = s->streams; |
|
s->streams = p; |
|
return p; |
|
} |
|
|
|
static void remove_stream_ctx(server *s, int64_t stream_id) { |
|
stream_ctx **pp = &s->streams; |
|
while (*pp) { |
|
if ((*pp)->stream_id == stream_id) { |
|
stream_ctx *d = *pp; |
|
*pp = d->next; |
|
free(d); |
|
return; |
|
} |
|
pp = &(*pp)->next; |
|
} |
|
} |
|
|
|
static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *ref) { |
|
server *s = (server *)ref->user_data; |
|
return s->qconn; |
|
} |
|
|
|
static void rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { |
|
(void)rand_ctx; |
|
if (RAND_bytes(dest, (int)destlen) != 1) { |
|
memset(dest, 0, destlen); |
|
} |
|
} |
|
|
|
static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid, |
|
uint8_t *token, size_t cidlen, |
|
void *user_data) { |
|
(void)conn; |
|
(void)user_data; |
|
if (RAND_bytes(cid->data, (int)cidlen) != 1) { |
|
return NGTCP2_ERR_CALLBACK_FAILURE; |
|
} |
|
cid->datalen = cidlen; |
|
if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) { |
|
return NGTCP2_ERR_CALLBACK_FAILURE; |
|
} |
|
return 0; |
|
} |
|
|
|
static void extend_flow_control(server *s, int64_t stream_id, uint64_t n) { |
|
if (!n) { |
|
return; |
|
} |
|
if (ngtcp2_conn_extend_max_stream_offset(s->qconn, stream_id, n) != 0) { |
|
fail(s, "ngtcp2_conn_extend_max_stream_offset"); |
|
return; |
|
} |
|
ngtcp2_conn_extend_max_offset(s->qconn, n); |
|
} |
|
|
|
static nghttp3_ssize read_resp_data_cb(nghttp3_conn *conn, int64_t stream_id, |
|
nghttp3_vec *vec, size_t veccnt, |
|
uint32_t *pflags, void *conn_user_data, |
|
void *stream_user_data) { |
|
(void)conn; |
|
(void)stream_id; |
|
(void)conn_user_data; |
|
stream_ctx *st = (stream_ctx *)stream_user_data; |
|
if (!st || veccnt == 0) { |
|
*pflags = NGHTTP3_DATA_FLAG_EOF; |
|
return 0; |
|
} |
|
if (st->body_sent) { |
|
*pflags = NGHTTP3_DATA_FLAG_EOF; |
|
return 0; |
|
} |
|
vec[0].base = (uint8_t *)RESP_BODY; |
|
vec[0].len = sizeof(RESP_BODY) - 1; |
|
st->body_sent = 1; |
|
*pflags = NGHTTP3_DATA_FLAG_EOF; |
|
return 1; |
|
} |
|
|
|
static int h3_recv_data_cb(nghttp3_conn *conn, int64_t stream_id, |
|
const uint8_t *data, size_t datalen, |
|
void *conn_user_data, void *stream_user_data) { |
|
(void)conn; |
|
(void)data; |
|
(void)stream_user_data; |
|
server *s = (server *)conn_user_data; |
|
extend_flow_control(s, stream_id, (uint64_t)datalen); |
|
return s->had_error ? NGHTTP3_ERR_CALLBACK_FAILURE : 0; |
|
} |
|
|
|
static int h3_deferred_consume_cb(nghttp3_conn *conn, int64_t stream_id, |
|
size_t consumed, void *conn_user_data, |
|
void *stream_user_data) { |
|
(void)conn; |
|
(void)stream_user_data; |
|
server *s = (server *)conn_user_data; |
|
extend_flow_control(s, stream_id, (uint64_t)consumed); |
|
return s->had_error ? NGHTTP3_ERR_CALLBACK_FAILURE : 0; |
|
} |
|
|
|
static int h3_recv_header_cb(nghttp3_conn *conn, int64_t stream_id, |
|
int32_t token, nghttp3_rcbuf *name, |
|
nghttp3_rcbuf *value, uint8_t flags, |
|
void *conn_user_data, void *stream_user_data) { |
|
(void)conn; |
|
(void)stream_id; |
|
(void)token; |
|
(void)flags; |
|
(void)conn_user_data; |
|
(void)stream_user_data; |
|
|
|
nghttp3_vec n = nghttp3_rcbuf_get_buf(name); |
|
nghttp3_vec v = nghttp3_rcbuf_get_buf(value); |
|
if (n.len && n.base[0] == ':') { |
|
fprintf(stderr, "req hdr %.*s: %.*s\n", (int)n.len, n.base, (int)v.len, v.base); |
|
} |
|
return 0; |
|
} |
|
|
|
static int h3_stream_close_cb(nghttp3_conn *conn, int64_t stream_id, |
|
uint64_t app_error_code, void *conn_user_data, |
|
void *stream_user_data) { |
|
(void)conn; |
|
(void)stream_user_data; |
|
server *s = (server *)conn_user_data; |
|
fprintf(stderr, "HTTP/3 stream closed id=%" PRId64 " app_error=%" PRIu64 "\n", |
|
stream_id, app_error_code); |
|
remove_stream_ctx(s, stream_id); |
|
s->done = 1; |
|
return 0; |
|
} |
|
|
|
static int h3_stop_sending_cb(nghttp3_conn *conn, int64_t stream_id, |
|
uint64_t app_error_code, void *conn_user_data, |
|
void *stream_user_data) { |
|
(void)conn; |
|
(void)stream_user_data; |
|
server *s = (server *)conn_user_data; |
|
if (ngtcp2_conn_shutdown_stream_read(s->qconn, 0, stream_id, app_error_code) < 0) { |
|
return NGHTTP3_ERR_CALLBACK_FAILURE; |
|
} |
|
return 0; |
|
} |
|
|
|
static int h3_reset_stream_cb(nghttp3_conn *conn, int64_t stream_id, |
|
uint64_t app_error_code, void *conn_user_data, |
|
void *stream_user_data) { |
|
(void)conn; |
|
(void)stream_user_data; |
|
server *s = (server *)conn_user_data; |
|
if (ngtcp2_conn_shutdown_stream_write(s->qconn, 0, stream_id, app_error_code) < 0) { |
|
return NGHTTP3_ERR_CALLBACK_FAILURE; |
|
} |
|
return 0; |
|
} |
|
|
|
static int h3_end_stream_cb(nghttp3_conn *conn, int64_t stream_id, |
|
void *conn_user_data, void *stream_user_data) { |
|
(void)conn; |
|
(void)stream_user_data; |
|
server *s = (server *)conn_user_data; |
|
|
|
stream_ctx *st = get_stream_ctx(s, stream_id, 1); |
|
if (!st) { |
|
return NGHTTP3_ERR_NOMEM; |
|
} |
|
if (st->response_submitted || st->response_pending) { |
|
return 0; |
|
} |
|
st->response_pending = 1; |
|
return 0; |
|
} |
|
|
|
static int submit_pending_responses(server *s) { |
|
static const uint8_t status[] = "200"; |
|
static const uint8_t ctype[] = "application/json"; |
|
nghttp3_nv nva[] = { |
|
{(uint8_t *)":status", (uint8_t *)status, 7, sizeof(status) - 1, |
|
NGHTTP3_NV_FLAG_NONE}, |
|
{(uint8_t *)"content-type", (uint8_t *)ctype, 12, sizeof(ctype) - 1, |
|
NGHTTP3_NV_FLAG_NONE}, |
|
}; |
|
nghttp3_data_reader dr = {read_resp_data_cb}; |
|
|
|
stream_ctx *st; |
|
for (st = s->streams; st; st = st->next) { |
|
if (!st->response_pending || st->response_submitted) { |
|
continue; |
|
} |
|
if (nghttp3_conn_set_stream_user_data(s->h3conn, st->stream_id, st) != 0) { |
|
fprintf(stderr, "nghttp3_conn_set_stream_user_data failed\n"); |
|
return -1; |
|
} |
|
int rv = nghttp3_conn_submit_response(s->h3conn, st->stream_id, nva, |
|
sizeof(nva) / sizeof(nva[0]), &dr); |
|
if (rv != 0) { |
|
fprintf(stderr, "nghttp3_conn_submit_response: %s\n", nghttp3_strerror(rv)); |
|
return -1; |
|
} |
|
st->response_pending = 0; |
|
st->response_submitted = 1; |
|
fprintf(stderr, "submitted response on stream=%" PRId64 "\n", st->stream_id); |
|
} |
|
return 0; |
|
} |
|
|
|
static int q_recv_client_initial_cb(ngtcp2_conn *conn, const ngtcp2_cid *dcid, |
|
void *user_data) { |
|
int rv = ngtcp2_crypto_recv_client_initial_cb(conn, dcid, user_data); |
|
if (rv != 0) { |
|
log_ssl_errors("recv_client_initial"); |
|
} |
|
return rv; |
|
} |
|
|
|
static int q_recv_crypto_data_cb(ngtcp2_conn *conn, |
|
ngtcp2_encryption_level encryption_level, |
|
uint64_t offset, const uint8_t *data, |
|
size_t datalen, void *user_data) { |
|
int rv = ngtcp2_crypto_recv_crypto_data_cb(conn, encryption_level, offset, |
|
data, datalen, user_data); |
|
if (rv != 0) { |
|
log_ssl_errors("recv_crypto_data"); |
|
} |
|
return rv; |
|
} |
|
|
|
static int q_recv_stream_data_cb(ngtcp2_conn *conn, uint32_t flags, |
|
int64_t stream_id, uint64_t offset, |
|
const uint8_t *data, size_t datalen, |
|
void *user_data, void *stream_user_data) { |
|
(void)conn; |
|
(void)offset; |
|
(void)stream_user_data; |
|
server *s = (server *)user_data; |
|
|
|
int fin = !!(flags & NGTCP2_STREAM_DATA_FLAG_FIN); |
|
nghttp3_ssize nconsumed = nghttp3_conn_read_stream(s->h3conn, stream_id, data, |
|
datalen, fin); |
|
if (nconsumed < 0) { |
|
fprintf(stderr, "nghttp3_conn_read_stream: %s\n", nghttp3_strerror((int)nconsumed)); |
|
return NGTCP2_ERR_CALLBACK_FAILURE; |
|
} |
|
|
|
extend_flow_control(s, stream_id, (uint64_t)nconsumed); |
|
return s->had_error ? NGTCP2_ERR_CALLBACK_FAILURE : 0; |
|
} |
|
|
|
static int q_acked_stream_data_offset_cb(ngtcp2_conn *conn, int64_t stream_id, |
|
uint64_t offset, uint64_t datalen, |
|
void *user_data, |
|
void *stream_user_data) { |
|
(void)conn; |
|
(void)offset; |
|
(void)stream_user_data; |
|
server *s = (server *)user_data; |
|
if (nghttp3_conn_add_ack_offset(s->h3conn, stream_id, datalen) != 0) { |
|
return NGTCP2_ERR_CALLBACK_FAILURE; |
|
} |
|
return 0; |
|
} |
|
|
|
static int q_stream_close_cb(ngtcp2_conn *conn, uint32_t flags, |
|
int64_t stream_id, uint64_t app_error_code, |
|
void *user_data, void *stream_user_data) { |
|
(void)conn; |
|
(void)flags; |
|
(void)stream_user_data; |
|
server *s = (server *)user_data; |
|
if (nghttp3_conn_close_stream(s->h3conn, stream_id, app_error_code) != 0) { |
|
return NGTCP2_ERR_CALLBACK_FAILURE; |
|
} |
|
return 0; |
|
} |
|
|
|
static int q_handshake_completed_cb(ngtcp2_conn *conn, void *user_data) { |
|
(void)conn; |
|
server *s = (server *)user_data; |
|
s->handshake_completed = 1; |
|
fprintf(stderr, "QUIC handshake completed\n"); |
|
return 0; |
|
} |
|
|
|
static int alpn_select_cb(SSL *ssl, const unsigned char **out, |
|
unsigned char *outlen, const unsigned char *in, |
|
unsigned int inlen, void *arg) { |
|
(void)ssl; |
|
(void)arg; |
|
for (unsigned int i = 0; i < inlen;) { |
|
unsigned int l = in[i]; |
|
if (i + 1 + l > inlen) { |
|
break; |
|
} |
|
if (l == 2 && in[i + 1] == 'h' && in[i + 2] == '3') { |
|
*out = &in[i + 1]; |
|
*outlen = 2; |
|
return SSL_TLSEXT_ERR_OK; |
|
} |
|
i += 1 + l; |
|
} |
|
return SSL_TLSEXT_ERR_NOACK; |
|
} |
|
|
|
static int setup_ssl_ctx(server *s, const char *cert_path, const char *key_path) { |
|
if (ngtcp2_crypto_ossl_init() != 0) { |
|
fprintf(stderr, "ngtcp2_crypto_ossl_init failed\n"); |
|
return -1; |
|
} |
|
if (OPENSSL_init_ssl(0, NULL) != 1) { |
|
fprintf(stderr, "OPENSSL_init_ssl failed\n"); |
|
return -1; |
|
} |
|
|
|
s->ssl_ctx = SSL_CTX_new(TLS_server_method()); |
|
if (!s->ssl_ctx) { |
|
fprintf(stderr, "SSL_CTX_new failed\n"); |
|
return -1; |
|
} |
|
|
|
SSL_CTX_set_min_proto_version(s->ssl_ctx, TLS1_3_VERSION); |
|
SSL_CTX_clear_options(s->ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); |
|
SSL_CTX_set_alpn_select_cb(s->ssl_ctx, alpn_select_cb, NULL); |
|
|
|
if (SSL_CTX_use_certificate_file(s->ssl_ctx, cert_path, SSL_FILETYPE_PEM) != 1) { |
|
fprintf(stderr, "SSL_CTX_use_certificate_file failed: %s\n", cert_path); |
|
log_ssl_errors("certificate"); |
|
return -1; |
|
} |
|
if (SSL_CTX_use_PrivateKey_file(s->ssl_ctx, key_path, SSL_FILETYPE_PEM) != 1) { |
|
fprintf(stderr, "SSL_CTX_use_PrivateKey_file failed: %s\n", key_path); |
|
log_ssl_errors("privatekey"); |
|
return -1; |
|
} |
|
if (SSL_CTX_check_private_key(s->ssl_ctx) != 1) { |
|
fprintf(stderr, "SSL_CTX_check_private_key failed\n"); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int setup_socket(server *s, const char *port) { |
|
struct addrinfo hints; |
|
struct addrinfo *res = NULL; |
|
struct addrinfo *rp; |
|
int rv; |
|
|
|
memset(&hints, 0, sizeof(hints)); |
|
hints.ai_family = AF_INET; |
|
hints.ai_socktype = SOCK_DGRAM; |
|
hints.ai_flags = AI_PASSIVE; |
|
|
|
rv = getaddrinfo(NULL, port, &hints, &res); |
|
if (rv != 0) { |
|
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
s->fd = -1; |
|
for (rp = res; rp; rp = rp->ai_next) { |
|
int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); |
|
if (fd < 0) { |
|
continue; |
|
} |
|
|
|
int on = 1; |
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); |
|
|
|
if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) { |
|
s->fd = fd; |
|
break; |
|
} |
|
|
|
close(fd); |
|
} |
|
|
|
freeaddrinfo(res); |
|
|
|
if (s->fd < 0) { |
|
perror("bind"); |
|
return -1; |
|
} |
|
|
|
int fl = fcntl(s->fd, F_GETFL, 0); |
|
if (fl < 0 || fcntl(s->fd, F_SETFL, fl | O_NONBLOCK) < 0) { |
|
perror("fcntl(O_NONBLOCK)"); |
|
return -1; |
|
} |
|
|
|
s->local_addrlen = sizeof(s->local_addr); |
|
if (getsockname(s->fd, (struct sockaddr *)&s->local_addr, &s->local_addrlen) != 0) { |
|
perror("getsockname"); |
|
return -1; |
|
} |
|
|
|
fprintf(stderr, "listening UDP on port %s\n", port); |
|
return 0; |
|
} |
|
|
|
static int setup_h3(server *s) { |
|
nghttp3_callbacks h3cb; |
|
nghttp3_settings h3settings; |
|
|
|
memset(&h3cb, 0, sizeof(h3cb)); |
|
h3cb.stream_close = h3_stream_close_cb; |
|
h3cb.recv_data = h3_recv_data_cb; |
|
h3cb.deferred_consume = h3_deferred_consume_cb; |
|
h3cb.recv_header = h3_recv_header_cb; |
|
h3cb.stop_sending = h3_stop_sending_cb; |
|
h3cb.reset_stream = h3_reset_stream_cb; |
|
h3cb.end_stream = h3_end_stream_cb; |
|
|
|
nghttp3_settings_default(&h3settings); |
|
|
|
int rv = nghttp3_conn_server_new(&s->h3conn, &h3cb, &h3settings, NULL, s); |
|
if (rv != 0) { |
|
fprintf(stderr, "nghttp3_conn_server_new: %s\n", nghttp3_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int setup_tls_for_conn(server *s) { |
|
s->ssl = SSL_new(s->ssl_ctx); |
|
if (!s->ssl) { |
|
fprintf(stderr, "SSL_new failed\n"); |
|
return -1; |
|
} |
|
SSL_set_accept_state(s->ssl); |
|
|
|
s->conn_ref.get_conn = get_conn; |
|
s->conn_ref.user_data = s; |
|
SSL_set_app_data(s->ssl, &s->conn_ref); |
|
|
|
if (ngtcp2_crypto_ossl_configure_server_session(s->ssl) != 0) { |
|
fprintf(stderr, "ngtcp2_crypto_ossl_configure_server_session failed\n"); |
|
return -1; |
|
} |
|
|
|
if (ngtcp2_crypto_ossl_ctx_new(&s->ossl_ctx, s->ssl) != 0) { |
|
fprintf(stderr, "ngtcp2_crypto_ossl_ctx_new failed\n"); |
|
return -1; |
|
} |
|
|
|
ngtcp2_conn_set_tls_native_handle(s->qconn, s->ossl_ctx); |
|
return 0; |
|
} |
|
|
|
static int create_server_conn(server *s, const uint8_t *pkt, size_t pktlen, |
|
const struct sockaddr_storage *peer, |
|
socklen_t peerlen) { |
|
ngtcp2_pkt_hd hd; |
|
if (ngtcp2_accept(&hd, pkt, pktlen) != 0) { |
|
return -1; |
|
} |
|
|
|
uint8_t scid_data[18]; |
|
ngtcp2_cid scid; |
|
if (RAND_bytes(scid_data, (int)sizeof(scid_data)) != 1) { |
|
fprintf(stderr, "RAND_bytes(scid) failed\n"); |
|
return -1; |
|
} |
|
ngtcp2_cid_init(&scid, scid_data, sizeof(scid_data)); |
|
|
|
ngtcp2_callbacks cb; |
|
ngtcp2_settings settings; |
|
ngtcp2_transport_params params; |
|
|
|
memset(&cb, 0, sizeof(cb)); |
|
cb.recv_client_initial = q_recv_client_initial_cb; |
|
cb.recv_crypto_data = q_recv_crypto_data_cb; |
|
cb.handshake_completed = q_handshake_completed_cb; |
|
cb.encrypt = ngtcp2_crypto_encrypt_cb; |
|
cb.decrypt = ngtcp2_crypto_decrypt_cb; |
|
cb.hp_mask = ngtcp2_crypto_hp_mask_cb; |
|
cb.recv_stream_data = q_recv_stream_data_cb; |
|
cb.acked_stream_data_offset = q_acked_stream_data_offset_cb; |
|
cb.stream_close = q_stream_close_cb; |
|
cb.rand = rand_cb; |
|
cb.get_new_connection_id = get_new_connection_id_cb; |
|
cb.update_key = ngtcp2_crypto_update_key_cb; |
|
cb.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb; |
|
cb.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb; |
|
cb.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb; |
|
cb.version_negotiation = ngtcp2_crypto_version_negotiation_cb; |
|
|
|
ngtcp2_settings_default(&settings); |
|
settings.initial_ts = timestamp_now(s); |
|
|
|
ngtcp2_transport_params_default(¶ms); |
|
params.original_dcid = hd.dcid; |
|
params.original_dcid_present = 1; |
|
params.initial_max_data = 1024 * 1024; |
|
params.initial_max_stream_data_bidi_local = 256 * 1024; |
|
params.initial_max_stream_data_bidi_remote = 256 * 1024; |
|
params.initial_max_stream_data_uni = 256 * 1024; |
|
params.initial_max_streams_bidi = 100; |
|
params.initial_max_streams_uni = 10; |
|
|
|
ngtcp2_path_storage_init(&s->ps, |
|
(struct sockaddr *)&s->local_addr, s->local_addrlen, |
|
(const struct sockaddr *)peer, peerlen, NULL); |
|
|
|
int rv = ngtcp2_conn_server_new(&s->qconn, &hd.scid, &scid, &s->ps.path, |
|
hd.version, &cb, &settings, ¶ms, |
|
NULL, s); |
|
if (rv != 0) { |
|
fprintf(stderr, "ngtcp2_conn_server_new: %s\n", ngtcp2_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
if (setup_tls_for_conn(s) != 0) { |
|
return -1; |
|
} |
|
if (setup_h3(s) != 0) { |
|
return -1; |
|
} |
|
|
|
memcpy(&s->peer_addr, peer, peerlen); |
|
s->peer_addrlen = peerlen; |
|
s->has_peer = 1; |
|
|
|
fprintf(stderr, "accepted initial packet and created QUIC connection\n"); |
|
return 0; |
|
} |
|
|
|
static int bind_h3_unidirectional_streams(server *s) { |
|
int rv; |
|
int64_t ctrl_id; |
|
int64_t qenc_id; |
|
int64_t qdec_id; |
|
|
|
if (s->h3_streams_bound) { |
|
return 0; |
|
} |
|
|
|
rv = ngtcp2_conn_open_uni_stream(s->qconn, &ctrl_id, NULL); |
|
if (rv == NGTCP2_ERR_STREAM_ID_BLOCKED) { |
|
return 0; |
|
} |
|
if (rv != 0) { |
|
fprintf(stderr, "open control stream: %s\n", ngtcp2_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
rv = ngtcp2_conn_open_uni_stream(s->qconn, &qenc_id, NULL); |
|
if (rv != 0) { |
|
fprintf(stderr, "open qpack encoder stream: %s\n", ngtcp2_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
rv = ngtcp2_conn_open_uni_stream(s->qconn, &qdec_id, NULL); |
|
if (rv != 0) { |
|
fprintf(stderr, "open qpack decoder stream: %s\n", ngtcp2_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
rv = nghttp3_conn_bind_control_stream(s->h3conn, ctrl_id); |
|
if (rv != 0) { |
|
fprintf(stderr, "nghttp3_conn_bind_control_stream: %s\n", nghttp3_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
rv = nghttp3_conn_bind_qpack_streams(s->h3conn, qenc_id, qdec_id); |
|
if (rv != 0) { |
|
fprintf(stderr, "nghttp3_conn_bind_qpack_streams: %s\n", nghttp3_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
s->h3_streams_bound = 1; |
|
fprintf(stderr, "bound H3 control stream=%" PRId64 ", qenc=%" PRId64 ", qdec=%" PRId64 "\n", |
|
ctrl_id, qenc_id, qdec_id); |
|
return 0; |
|
} |
|
|
|
static int send_quic_packet(server *s, ngtcp2_ssize nwrite, ngtcp2_tstamp ts) { |
|
if (nwrite <= 0) { |
|
return 0; |
|
} |
|
if (!s->has_peer) { |
|
return -1; |
|
} |
|
|
|
ssize_t nw = sendto(s->fd, s->txbuf, (size_t)nwrite, 0, |
|
(struct sockaddr *)&s->peer_addr, s->peer_addrlen); |
|
if (nw < 0) { |
|
if (errno == EAGAIN || errno == EWOULDBLOCK) { |
|
return -1; |
|
} |
|
perror("sendto"); |
|
return -1; |
|
} |
|
if ((ngtcp2_ssize)nw != nwrite) { |
|
fprintf(stderr, "short UDP send\n"); |
|
return -1; |
|
} |
|
|
|
ngtcp2_conn_update_pkt_tx_time(s->qconn, ts); |
|
return 0; |
|
} |
|
|
|
static int flush_h3_to_quic(server *s) { |
|
size_t sent = 0; |
|
while (sent < MAX_TX_PER_TICK) { |
|
int64_t stream_id = -1; |
|
int fin = 0; |
|
nghttp3_vec h3v[8]; |
|
nghttp3_ssize veccnt = |
|
nghttp3_conn_writev_stream(s->h3conn, &stream_id, &fin, h3v, |
|
sizeof(h3v) / sizeof(h3v[0])); |
|
if (veccnt < 0) { |
|
fprintf(stderr, "nghttp3_conn_writev_stream: %s\n", nghttp3_strerror((int)veccnt)); |
|
return -1; |
|
} |
|
|
|
if (veccnt == 0 && stream_id == -1) { |
|
return 0; |
|
} |
|
|
|
ngtcp2_vec qv[8]; |
|
for (nghttp3_ssize i = 0; i < veccnt; ++i) { |
|
qv[i].base = h3v[i].base; |
|
qv[i].len = h3v[i].len; |
|
} |
|
|
|
ngtcp2_tstamp ts = timestamp_now(s); |
|
ngtcp2_ssize pdatalen = -1; |
|
ngtcp2_ssize nwrite = ngtcp2_conn_writev_stream( |
|
s->qconn, &s->ps.path, NULL, s->txbuf, sizeof(s->txbuf), &pdatalen, |
|
fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : NGTCP2_WRITE_STREAM_FLAG_NONE, |
|
stream_id, veccnt ? qv : NULL, (size_t)veccnt, ts); |
|
|
|
if (nwrite < 0) { |
|
if (nwrite == NGTCP2_ERR_STREAM_DATA_BLOCKED || |
|
nwrite == NGTCP2_ERR_STREAM_SHUT_WR || |
|
nwrite == NGTCP2_ERR_STREAM_NOT_FOUND) { |
|
return 0; |
|
} |
|
fprintf(stderr, "ngtcp2_conn_writev_stream(H3): %s\n", ngtcp2_strerror((int)nwrite)); |
|
log_ssl_errors("writev_stream(H3)"); |
|
return -1; |
|
} |
|
|
|
if (pdatalen >= 0) { |
|
int rv = nghttp3_conn_add_write_offset(s->h3conn, stream_id, (size_t)pdatalen); |
|
if (rv != 0) { |
|
fprintf(stderr, "nghttp3_conn_add_write_offset: %s\n", nghttp3_strerror(rv)); |
|
return -1; |
|
} |
|
} |
|
|
|
if (send_quic_packet(s, nwrite, ts) != 0) { |
|
return -1; |
|
} |
|
++sent; |
|
|
|
if (nwrite == 0) { |
|
return 0; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int flush_quic_control(server *s) { |
|
size_t sent = 0; |
|
while (sent < MAX_TX_PER_TICK) { |
|
ngtcp2_tstamp ts = timestamp_now(s); |
|
ngtcp2_ssize pdatalen = -1; |
|
ngtcp2_ssize nwrite = ngtcp2_conn_writev_stream( |
|
s->qconn, &s->ps.path, NULL, s->txbuf, sizeof(s->txbuf), &pdatalen, |
|
NGTCP2_WRITE_STREAM_FLAG_NONE, -1, NULL, 0, ts); |
|
if (nwrite < 0) { |
|
fprintf(stderr, "ngtcp2_conn_writev_stream(ctrl): %s\n", ngtcp2_strerror((int)nwrite)); |
|
log_ssl_errors("writev_stream(ctrl)"); |
|
return -1; |
|
} |
|
if (nwrite == 0) { |
|
return 0; |
|
} |
|
|
|
if (send_quic_packet(s, nwrite, ts) != 0) { |
|
return -1; |
|
} |
|
++sent; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int drive_tx(server *s) { |
|
if (!s->qconn) { |
|
return 0; |
|
} |
|
|
|
if (flush_h3_to_quic(s) != 0) { |
|
return -1; |
|
} |
|
if (flush_quic_control(s) != 0) { |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
static int read_udp_once(server *s) { |
|
struct sockaddr_storage peer; |
|
socklen_t peerlen = sizeof(peer); |
|
ssize_t nread = recvfrom(s->fd, s->rxbuf, sizeof(s->rxbuf), 0, |
|
(struct sockaddr *)&peer, &peerlen); |
|
if (nread < 0) { |
|
if (errno == EAGAIN || errno == EWOULDBLOCK) { |
|
return 0; |
|
} |
|
perror("recvfrom"); |
|
return -1; |
|
} |
|
|
|
if (!s->qconn) { |
|
if (create_server_conn(s, s->rxbuf, (size_t)nread, &peer, peerlen) != 0) { |
|
return 1; |
|
} |
|
} else if (!sockaddr_eq(&peer, &s->peer_addr)) { |
|
return 1; |
|
} |
|
|
|
ngtcp2_path_storage_init(&s->ps, |
|
(struct sockaddr *)&s->local_addr, s->local_addrlen, |
|
(struct sockaddr *)&peer, peerlen, NULL); |
|
|
|
ngtcp2_tstamp ts = timestamp_now(s); |
|
int rv = ngtcp2_conn_read_pkt(s->qconn, &s->ps.path, NULL, |
|
s->rxbuf, (size_t)nread, ts); |
|
if (rv != 0) { |
|
fprintf(stderr, "ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv)); |
|
log_ssl_errors("read_pkt"); |
|
return -1; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
static int run(server *s) { |
|
struct pollfd pfd; |
|
pfd.fd = s->fd; |
|
pfd.events = POLLIN; |
|
|
|
while (!s->had_error && !s->done) { |
|
ngtcp2_tstamp now = timestamp_now(s); |
|
int timeout_ms = 50; |
|
|
|
if (s->qconn) { |
|
ngtcp2_tstamp expiry = ngtcp2_conn_get_expiry(s->qconn); |
|
if (expiry <= now) { |
|
timeout_ms = 0; |
|
} else { |
|
uint64_t diff_ns = expiry - now; |
|
timeout_ms = (int)(diff_ns / NGTCP2_MILLISECONDS); |
|
if (timeout_ms > 50) { |
|
timeout_ms = 50; |
|
} |
|
} |
|
} |
|
|
|
int n = poll(&pfd, 1, timeout_ms); |
|
if (n < 0) { |
|
if (errno == EINTR) { |
|
continue; |
|
} |
|
perror("poll"); |
|
return -1; |
|
} |
|
|
|
if (n > 0 && (pfd.revents & POLLIN)) { |
|
for (;;) { |
|
int rr = read_udp_once(s); |
|
if (rr < 0) { |
|
return -1; |
|
} |
|
if (rr == 0) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (s->qconn) { |
|
now = timestamp_now(s); |
|
if (ngtcp2_conn_handle_expiry(s->qconn, now) != 0) { |
|
fprintf(stderr, "ngtcp2_conn_handle_expiry failed\n"); |
|
return -1; |
|
} |
|
|
|
if (s->handshake_completed) { |
|
if (bind_h3_unidirectional_streams(s) != 0) { |
|
return -1; |
|
} |
|
if (s->h3_streams_bound && submit_pending_responses(s) != 0) { |
|
return -1; |
|
} |
|
} |
|
|
|
if (drive_tx(s) != 0) { |
|
return -1; |
|
} |
|
|
|
if (ngtcp2_conn_in_closing_period(s->qconn) || |
|
ngtcp2_conn_in_draining_period(s->qconn)) { |
|
fprintf(stderr, "connection entered closing/draining\n"); |
|
return -1; |
|
} |
|
} |
|
} |
|
|
|
return s->had_error ? -1 : 0; |
|
} |
|
|
|
static void cleanup(server *s) { |
|
while (s->streams) { |
|
stream_ctx *n = s->streams->next; |
|
free(s->streams); |
|
s->streams = n; |
|
} |
|
|
|
if (s->h3conn) { |
|
nghttp3_conn_del(s->h3conn); |
|
} |
|
if (s->qconn) { |
|
ngtcp2_conn_del(s->qconn); |
|
} |
|
if (s->ossl_ctx) { |
|
ngtcp2_crypto_ossl_ctx_del(s->ossl_ctx); |
|
} |
|
if (s->ssl) { |
|
SSL_set_app_data(s->ssl, NULL); |
|
SSL_free(s->ssl); |
|
} |
|
if (s->ssl_ctx) { |
|
SSL_CTX_free(s->ssl_ctx); |
|
} |
|
if (s->fd >= 0) { |
|
close(s->fd); |
|
} |
|
} |
|
|
|
int main(int argc, char **argv) { |
|
const char *port = DEFAULT_PORT; |
|
const char *cert = "server.crt"; |
|
const char *key = "server.key"; |
|
|
|
if (argc > 1) { |
|
port = argv[1]; |
|
} |
|
if (argc > 2) { |
|
cert = argv[2]; |
|
} |
|
if (argc > 3) { |
|
key = argv[3]; |
|
} |
|
|
|
server s; |
|
memset(&s, 0, sizeof(s)); |
|
s.fd = -1; |
|
|
|
if (setup_socket(&s, port) != 0) { |
|
cleanup(&s); |
|
return 1; |
|
} |
|
if (setup_ssl_ctx(&s, cert, key) != 0) { |
|
cleanup(&s); |
|
return 1; |
|
} |
|
|
|
int rv = run(&s); |
|
cleanup(&s); |
|
return rv == 0 ? 0 : 1; |
|
} |