-/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+#include "contrib/macros.h"
#include "libdnssec/error.h"
#include "libdnssec/random.h"
#include "libknot/xdp/tcp_iobuf.h"
#define quic_get_encryption_level(level) ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(level)
#define quic_send(ctx, sockfd, family) quic_send_data(ctx, sockfd, family, NULL, 0)
-#define quic_timeout(ts, wait) (((ts) + NGTCP2_SECONDS * (wait)) <= quic_timestamp())
-
-const gnutls_datum_t doq_alpn = {
- (unsigned char *)"doq", 3
-};
-
#define set_application_error(ctx, error_code, reason, reason_len) \
ngtcp2_ccerr_set_application_error(&(ctx)->last_err, \
error_code, reason, reason_len)
-
#define set_transport_error(ctx, error_code, reason, reason_len) \
ngtcp2_ccerr_set_transport_error(&(ctx)->last_err, \
error_code, reason, reason_len)
+const gnutls_datum_t doq_alpn = {
+ (unsigned char *)"doq", 3
+};
+
static int 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)
+ int64_t stream_id, uint64_t offset, const uint8_t *data,
+ size_t datalen, void *user_data, void *stream_user_data)
{
(void)conn;
(void)flags;
quic_ctx_t *ctx = (quic_ctx_t *)user_data;
if (stream_id != ctx->stream.id) {
- return 0;
+ const uint8_t msg[] = "Unknown stream";
+ set_application_error(ctx, DOQ_PROTOCOL_ERROR, msg, sizeof(msg) - 1);
+ return NGTCP2_ERR_CALLBACK_FAILURE;
}
struct iovec in = {
};
int ret = knot_tcp_inbufs_upd(&ctx->stream.in_buffer, in, true,
- &ctx->stream.in_parsed, &ctx->stream.in_parsed_total);
+ &ctx->stream.in_parsed,
+ &ctx->stream.in_parsed_total);
if (ret != KNOT_EOK) {
+ const uint8_t msg[] = "Malformed payload";
+ set_application_error(ctx, DOQ_PROTOCOL_ERROR, msg, sizeof(msg) - 1);
return NGTCP2_ERR_CALLBACK_FAILURE;
}
- ctx->idle_ts = quic_timestamp();
ctx->stream.in_parsed_it = 0;
return 0;
}
static int stream_open_cb(ngtcp2_conn *conn, int64_t stream_id,
- void *user_data)
+ void *user_data)
{
(void)conn;
}
static int 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)
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data)
{
(void)conn;
(void)offset;
return KNOT_EOK;
}
-static int 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)
+static int 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;
static int quic_open_bidi_stream(quic_ctx_t *ctx)
{
- if (ctx->stream.id != -1) {
+ if (ctx->stream.id >= 0) {
return KNOT_EOK;
}
if (ret) {
return KNOT_ERROR;
}
-
- ctx->stream.resets = 3;
-
return KNOT_EOK;
}
-static int extend_max_bidi_streams_cb(ngtcp2_conn *conn, uint64_t max_streams,
- void *user_data)
-{
- (void)conn;
-
- quic_ctx_t *ctx = (quic_ctx_t *)user_data;
- if(max_streams > 0) {
- int ret = quic_open_bidi_stream(ctx);
- if (ret != KNOT_EOK) {
- return NGTCP2_ERR_CALLBACK_FAILURE;
- }
- }
- return 0;
-}
-
static void rand_cb(uint8_t *dest, size_t destlen,
- const ngtcp2_rand_ctx *rand_ctx)
+ const ngtcp2_rand_ctx *rand_ctx)
{
(void)rand_ctx;
}
static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid,
- uint8_t *token, size_t cidlen, void *user_data)
+ uint8_t *token, size_t cidlen, void *user_data)
{
(void)conn;
cid->datalen = cidlen;
if (ngtcp2_crypto_generate_stateless_reset_token(token, ctx->secret,
- sizeof(ctx->secret), cid) != 0) {
+ sizeof(ctx->secret), cid) != 0)
+ {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
}
static int stream_reset_cb(ngtcp2_conn *conn, int64_t stream_id,
- uint64_t final_size, uint64_t app_error_code, void *user_data,
- void *stream_user_data)
+ uint64_t final_size, uint64_t app_error_code, void *user_data,
+ void *stream_user_data)
{
quic_ctx_t *ctx = (quic_ctx_t *)user_data;
if (ctx->stream.id == stream_id) {
- if (--ctx->stream.resets <= 0) {
- //TODO test
- set_transport_error(ctx, NGTCP2_PROTOCOL_VIOLATION, NULL, 0);
- quic_ctx_close(ctx);
- }
+ set_transport_error(ctx, NGTCP2_PROTOCOL_VIOLATION, NULL, 0);
+ quic_ctx_close(ctx);
+ return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
return 0;
}
+static int recv_rx_key_cb(ngtcp2_conn *conn, ngtcp2_encryption_level level,
+ void *user_data)
+{
+ quic_ctx_t *ctx = user_data;
+ if (level == NGTCP2_ENCRYPTION_LEVEL_1RTT) {
+ ctx->state = CONNECTED;
+ }
+
+ return 0;
+}
+
static const ngtcp2_callbacks quic_client_callbacks = {
ngtcp2_crypto_client_initial_cb,
NULL, /* recv_client_initial */
stream_close_cb,
NULL, /* recv_stateless_reset */
ngtcp2_crypto_recv_retry_cb,
- extend_max_bidi_streams_cb,
+ NULL, /* extend_max_bidi_streams */
NULL, /* extend_max_local_streams_uni */
rand_cb,
get_new_connection_id_cb,
ngtcp2_crypto_get_path_challenge_data_cb,
NULL, /* stream_stop_sending */
ngtcp2_crypto_version_negotiation_cb,
- NULL, /* recv_rx_key */
+ recv_rx_key_cb,
NULL /* recv_tx_key */
};
static int hook_func(gnutls_session_t session, unsigned int htype,
- unsigned when, unsigned int incoming,
- const gnutls_datum_t *msg)
+ unsigned when, unsigned int incoming, const gnutls_datum_t *msg)
{
(void)session;
(void)htype;
}
static int quic_send_data(quic_ctx_t *ctx, int sockfd, int family,
- ngtcp2_vec *datav, size_t datavlen)
+ ngtcp2_vec *datav, size_t datavlen)
{
uint8_t enc_buf[MAX_PACKET_SIZE];
struct iovec msg_iov = {
.msg_iovlen = 1
};
uint64_t ts = quic_timestamp();
- size_t tb_send = 0;
- for (int i = 0; i < datavlen; ++i) {
- tb_send += datav[i].len;
- }
- while(1) {
- int64_t stream = -1;
- uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_NONE;
- if (datavlen != 0) {
- flags = NGTCP2_WRITE_STREAM_FLAG_FIN;
- stream = ctx->stream.id;
- }
- ngtcp2_ssize send_datalen = 0;
- ngtcp2_ssize nwrite = ngtcp2_conn_writev_stream(ctx->conn,
- (ngtcp2_path *)ngtcp2_conn_get_path(ctx->conn),
- &ctx->pi, enc_buf, sizeof(enc_buf),
- &send_datalen, flags, stream, datav, datavlen,
- ts);
- if (nwrite <= 0) {
- switch(nwrite) {
- case 0:
- ngtcp2_conn_update_pkt_tx_time(ctx->conn, ts);
- return KNOT_EOK;
- case NGTCP2_ERR_WRITE_MORE:
- assert(0);
- return KNOT_NET_ESEND;
- case NGTCP2_ERR_STREAM_SHUT_WR:
- ctx->stream.id = -1;
- // [[ fallthrough ]]
- default:
- set_transport_error(ctx,
- ngtcp2_err_infer_quic_transport_error_code(nwrite),
- NULL, 0);
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_NONE;
+ int64_t stream_id = -1;
+ if (datavlen > 0) {
+ flags = NGTCP2_WRITE_STREAM_FLAG_FIN;
+ stream_id = ctx->stream.id;
+ }
+ ngtcp2_ssize send_datalen = 0;
+ ngtcp2_ssize nwrite = ngtcp2_conn_writev_stream(ctx->conn,
+ (ngtcp2_path *)ngtcp2_conn_get_path(ctx->conn), &ctx->pi,
+ enc_buf, sizeof(enc_buf), &send_datalen, flags, stream_id,
+ datav, datavlen, ts);
+ if (nwrite <= 0) {
+ switch(nwrite) {
+ case 0:
+ ngtcp2_conn_update_pkt_tx_time(ctx->conn, ts);
+ return KNOT_EOK;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(0);
+ return KNOT_NET_ESEND;
+ default:
+ set_transport_error(ctx,
+ ngtcp2_err_infer_quic_transport_error_code(nwrite),
+ NULL, 0);
+ if (ngtcp2_err_is_fatal(nwrite)) {
return KNOT_NET_ESEND;
+ } else {
+ return KNOT_EOK;
}
}
- if (send_datalen > 0) {
- tb_send -= send_datalen;
- }
+ }
- msg_iov.iov_len = (size_t)nwrite;
+ msg_iov.iov_len = (size_t)nwrite;
- int ret = net_ecn_set(sockfd, family, ctx->pi.ecn);
- if (ret != KNOT_EOK && ret != KNOT_ENOTSUP) {
- return ret;
- }
+ int ret = net_ecn_set(sockfd, family, ctx->pi.ecn);
+ if (ret != KNOT_EOK && ret != KNOT_ENOTSUP) {
+ return ret;
+ }
- if (sendmsg(sockfd, &msg, 0) == -1) {
- set_transport_error(ctx, NGTCP2_INTERNAL_ERROR, NULL,
- 0);
- return KNOT_NET_ESEND;
- }
+ if (sendmsg(sockfd, &msg, 0) == -1) {
+ set_transport_error(ctx, NGTCP2_INTERNAL_ERROR, NULL, 0);
+ return KNOT_NET_ESEND;
+ }
- if (tb_send == 0) {
- break;
- }
+ if (send_datalen > 0) {
+ return send_datalen;
}
+
return KNOT_EOK;
}
ngtcp2_conn_get_path(ctx->conn),
pi, enc_buf, nwrite,
quic_timestamp());
- if (ret != 0) {
- if (ret == NGTCP2_ERR_DROP_CONN) {
- ctx->state = CLOSED;
- } else if (ngtcp2_err_is_fatal(ret)) {
- set_transport_error(ctx,
- ngtcp2_err_infer_quic_transport_error_code(ret),
- NULL, 0);
- }
+ if (ngtcp2_err_is_fatal(ret)) {
+ set_transport_error(ctx,
+ ngtcp2_err_infer_quic_transport_error_code(ret),
+ NULL, 0);
return KNOT_NET_ERECV;
}
return KNOT_EOK;
};
ctx->params = *params;
ctx->tls = tls_ctx;
- ctx->state = OPENING;
+ ctx->state = CLOSED;
ctx->stream.id = -1;
set_application_error(ctx, DOQ_NO_ERROR, NULL, 0);
if (quic_generate_secret(ctx->secret, sizeof(ctx->secret)) != KNOT_EOK) {
return KNOT_ENOMEM;
}
- gnutls_certificate_set_verify_function(tls_ctx->credentials,
- verify_certificate);
+ gnutls_certificate_set_verify_function(
+ tls_ctx->credentials,
+ verify_certificate);
return KNOT_EOK;
}
+static int get_expiry(ngtcp2_conn *ctx)
+{
+ ngtcp2_tstamp now = quic_timestamp();
+ ngtcp2_tstamp expiry = ngtcp2_conn_get_expiry(ctx);
+ if (expiry == UINT64_MAX) {
+ return -1;
+ } else if (expiry < now) {
+ return 0;
+ }
+ /* ceil((expiry - now) / NGTCP2_MILLISECONDS) */
+ return (expiry - now + NGTCP2_MILLISECONDS - 1) / NGTCP2_MILLISECONDS;
+}
+
int quic_ctx_connect(quic_ctx_t *ctx, int sockfd, struct addrinfo *dst_addr)
{
if (connect(sockfd, (const struct sockaddr *)(dst_addr->ai_addr),
- dst_addr->ai_addrlen) != 0) {
+ dst_addr->ai_addrlen) != 0)
+ {
return knot_map_errno();
}
return ret;
}
- ctx->idle_ts = quic_timestamp();
-
ngtcp2_settings settings;
ngtcp2_settings_default(&settings);
- settings.initial_ts = ctx->idle_ts;
+ settings.initial_ts = quic_timestamp();
settings.handshake_timeout = ctx->tls->wait * NGTCP2_SECONDS;
ngtcp2_transport_params params;
params.initial_max_streams_bidi = 0;
params.initial_max_stream_data_bidi_local = NGTCP2_MAX_VARINT;
params.initial_max_data = NGTCP2_MAX_VARINT;
+ params.max_ack_delay = 1 * NGTCP2_SECONDS;
+ params.max_idle_timeout = ctx->tls->wait * NGTCP2_SECONDS;
struct sockaddr_in6 src_addr;
socklen_t src_addr_len = sizeof(src_addr);
&settings, ¶ms, NULL, ctx) != 0) {
return KNOT_NET_ECONNECT;
}
-
gnutls_handshake_set_hook_function(ctx->tls->session,
- GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, hook_func);
+ GNUTLS_HANDSHAKE_ANY,
+ GNUTLS_HOOK_POST, hook_func);
ret = ngtcp2_crypto_gnutls_configure_client_session(ctx->tls->session);
if (ret != KNOT_EOK) {
return KNOT_NET_ECONNECT;
gnutls_session_set_ptr(ctx->tls->session, ctx);
ngtcp2_conn_set_tls_native_handle(ctx->conn, ctx->tls->session);
- // Initialize poll descriptor structure.
struct pollfd pfd = {
.fd = sockfd,
.events = POLLIN,
};
ctx->tls->sockfd = sockfd;
- ret = quic_send(ctx, sockfd, dst_addr->ai_family);
- if (ret != KNOT_EOK) {
- return ret;
- }
+ while (ctx->state != CONNECTED) {
+ ret = quic_send(ctx, sockfd, dst_addr->ai_family);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
- ret = poll(&pfd, 1, ctx->tls->wait * 1000);
- if (ret == 0) {
- WARN("QUIC, peer took too long to respond");
- return KNOT_NET_ECONNECT;
- } else if (ret < 0) {
- return knot_map_errno();
+ int timeout = get_expiry(ctx->conn);
+ ret = poll(&pfd, 1, timeout);
+ if (ret == 0) {
+ ret = ngtcp2_conn_handle_expiry(ctx->conn, quic_timestamp());
+ } else if (ret < 0) {
+ return knot_map_errno();
+ }
+
+ ret = quic_recv(ctx, sockfd);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
}
- ret = quic_recv(ctx, sockfd);
- if (ret != KNOT_EOK) {
- return ret;
+ return KNOT_EOK;
+}
+
+static int offset_span(ngtcp2_vec **vec, size_t *veclen, size_t sub)
+{
+ ngtcp2_vec *new_vec = *vec;
+ size_t new_veclen = *veclen;
+
+ while (sub) {
+ if (new_veclen == 0) {
+ return KNOT_EINVAL;
+ }
+ size_t part = MIN(sub, new_vec->len);
+ new_vec->base += part;
+ new_vec->len -= part;
+ sub -= part;
+ const int empty = ((new_vec->len == 0) ? 1 : 0);
+ new_vec += empty;
+ new_veclen -= empty;
}
+ *vec = new_vec;
+ *veclen = new_veclen;
return KNOT_EOK;
}
int quic_send_dns_query(quic_ctx_t *ctx, int sockfd, struct addrinfo *srv,
- const uint8_t *buf, const size_t buf_len)
+ const uint8_t *buf, const size_t buf_len)
{
if (ctx == NULL || buf == NULL) {
return KNOT_EINVAL;
}
+ if (ctx->state < CONNECTED) {
+ return KNOT_ECONN;
+ }
+
uint16_t query_length = htons(buf_len);
ngtcp2_vec datav[] = {
- {
- .base = (uint8_t *)&query_length,
- .len = sizeof(uint16_t)
- },{
- .base = (uint8_t *)buf,
- .len = buf_len
- }
+ {(uint8_t *)&query_length, sizeof(uint16_t)},
+ {(uint8_t *)buf, buf_len}
};
- size_t datavlen = sizeof(datav)/sizeof(*datav);
+ size_t datavlen = sizeof(datav) / sizeof(*datav);
ngtcp2_vec *pdatav = datav;
struct pollfd pfd = {
.revents = 0,
};
- // Open stream when connection keep-opened
- if (ctx->stream.id == -1) {
- quic_open_bidi_stream(ctx);
- quic_send(ctx, sockfd, srv->ai_family);
+ assert(ctx->stream.id < 0);
+ int ret = quic_open_bidi_stream(ctx);
+ if (ret != KNOT_EOK) {
+ return ret;
}
- while (ctx->stream.out_ack == 0) {
- if (quic_timeout(ctx->idle_ts, ctx->tls->wait)) {
- WARN("QUIC, failed to send");
- set_application_error(ctx, DOQ_REQUEST_CANCELLED,
- (uint8_t *)"Connection timeout",
- sizeof("Connection timeout") - 1);
- return KNOT_NET_ETIMEOUT;
- }
- int ret = quic_send_data(ctx, sockfd, srv->ai_family, pdatav,
- datavlen);
- if (ret != KNOT_EOK) {
+ ctx->stream.out_ack = 0;
+ for (ngtcp2_vec *it = datav; it < datav + datavlen; ++it) {
+ ctx->stream.out_ack += it->len;
+ }
+
+ while (ctx->stream.out_ack > 0) {
+ ret = quic_send_data(ctx, sockfd, srv->ai_family, pdatav, datavlen);
+ if (ret < 0) {
WARN("QUIC, failed to send");
return ret;
- }
- if (ctx->stream.out_ack > 0) {
- pdatav = NULL;
- datavlen = 0;
+ } else if (ret > 0) {
+ ret = offset_span(&pdatav, &datavlen, ret);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
}
- ret = poll(&pfd, 1, ctx->tls->wait * 1000);
+ int timeout = get_expiry(ctx->conn);
+ if (timeout > 0 && datavlen > 0) {
+ continue;
+ }
+ ret = poll(&pfd, 1, timeout);
if (ret < 0) {
WARN("QUIC, failed to send");
return knot_map_errno();
} else if (ret == 0) {
+ ret = ngtcp2_conn_handle_expiry(ctx->conn, quic_timestamp());
continue;
}
ret = quic_recv(ctx, sockfd);
WARN("QUIC, failed to send");
return ret;
}
- if (ctx->stream.in_parsed != NULL) {
- return KNOT_EOK;
- }
}
return KNOT_EOK;
}
int quic_recv_dns_response(quic_ctx_t *ctx, uint8_t *buf, const size_t buf_len,
- struct addrinfo *srv)
+ struct addrinfo *srv)
{
if (ctx == NULL || ctx->tls == NULL || buf == NULL) {
return KNOT_EINVAL;
.revents = 0,
};
- while (!quic_timeout(ctx->idle_ts, ctx->tls->wait)) {
- ret = poll(&pfd, 1, ctx->tls->wait * 1000);
+ while (1) {
+ int timeout = get_expiry(ctx->conn);
+ ret = poll(&pfd, 1, timeout);
if (ret < 0) {
WARN("QUIC, failed to receive reply (%s)",
knot_strerror(errno));
return knot_map_errno();
} else if (ret == 0) {
+ ret = ngtcp2_conn_handle_expiry(ctx->conn, quic_timestamp());
+ WARN("QUIC, peer took too long to respond");
goto send;
}
return KNOT_NET_ERECV;
}
-
- send: ret = quic_send(ctx, sockfd, srv->ai_family);
+ send:
+ ret = quic_send(ctx, sockfd, srv->ai_family);
if (ret != KNOT_EOK) {
WARN("QUIC, failed to receive reply (%s)",
knot_strerror(ret));
}
WARN("QUIC, peer took too long to respond");
- set_application_error(ctx, DOQ_REQUEST_CANCELLED,
- (uint8_t *)"Connection timeout",
- sizeof("Connection timeout") - 1);
+ const uint8_t msg[] = "Connection timeout";
+ set_application_error(ctx, DOQ_REQUEST_CANCELLED, msg, sizeof(msg) - 1);
+
return KNOT_NET_ETIMEOUT;
}
#define quic_ctx_write_close(ctx, dest, dest_len, ts) \
ngtcp2_conn_write_connection_close((ctx)->conn, (ngtcp2_path *)ngtcp2_conn_get_path((ctx)->conn), \
- &(ctx)->pi, dest, dest_len, &(ctx)->last_err, ts)
+ &(ctx)->pi, dest, dest_len, &(ctx)->last_err, ts)
void quic_ctx_close(quic_ctx_t *ctx)
{
.msg_iovlen = 1
};
- ngtcp2_ssize nwrite = quic_ctx_write_close(ctx, enc_buf,
- sizeof(enc_buf), quic_timestamp());
+ ngtcp2_ssize nwrite = quic_ctx_write_close(ctx, enc_buf, sizeof(enc_buf),
+ quic_timestamp());
if (nwrite <= 0) {
return;
}