Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Last active February 27, 2026 22:25
Show Gist options
  • Select an option

  • Save masakielastic/3652faad19425eda02b4bdd359e98c0f to your computer and use it in GitHub Desktop.

Select an option

Save masakielastic/3652faad19425eda02b4bdd359e98c0f to your computer and use it in GitHub Desktop.
 ngtcp2 と nghttp3 で HTTP/3 サーバー (OpenSSL)

ngtcp2 と nghttp3 で HTTP/3 サーバー (OpenSSL)

export PKG_CONFIG_PATH="$PREFIX/lib64/pkgconfig:$PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH"
export LD_LIBRARY_PATH="$PREFIX/lib64:$PREFIX/lib:$LD_LIBRARY_PATH"
pkg-config --modversion libngtcp2 libngtcp2_crypto_ossl libnghttp3 openssl
cc -Wall -Wextra -O2 h3_server.c -o h3_server \
    $(pkg-config --cflags --libs libngtcp2 libngtcp2_crypto_ossl libnghttp3 openssl)
#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(&params);
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, &params,
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment