From 21c5263d491741a3ae982bc7874eb4f2f9724d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=A1k?= <jan.hak@nic.cz> Date: Tue, 3 May 2022 09:45:45 +0200 Subject: [PATCH] kdig: quic connect --- src/utils/common/https.c | 20 +- src/utils/common/https.h | 5 +- src/utils/common/netio.c | 110 +++++-- src/utils/common/quic.c | 640 ++++++++++++++++++++++++++++++++++++++- src/utils/common/quic.h | 36 ++- src/utils/common/tls.c | 47 +-- src/utils/common/tls.h | 7 +- 7 files changed, 794 insertions(+), 71 deletions(-) diff --git a/src/utils/common/https.c b/src/utils/common/https.c index e432b0524f..d3910b9be4 100644 --- a/src/utils/common/https.c +++ b/src/utils/common/https.c @@ -25,6 +25,7 @@ #include "contrib/openbsd/strlcat.h" #include "contrib/openbsd/strlcpy.h" #include "contrib/url-parser/url_parser.h" +#include "libknot/dname.h" #include "libknot/errcode.h" #include "utils/common/https.h" #include "utils/common/msg.h" @@ -319,15 +320,15 @@ static int sockaddr_to_authority(char *buf, const size_t buf_len, const struct s return KNOT_EOK; } -int https_ctx_connect(https_ctx_t *ctx, int sockfd, const char *remote, - bool fastopen, struct sockaddr_storage *addr) +int https_ctx_connect(https_ctx_t *ctx, int sockfd, bool fastopen, + struct sockaddr_storage *addr) { if (ctx == NULL || addr == NULL) { return KNOT_EINVAL; } // Create TLS connection - int ret = tls_ctx_connect(ctx->tls, sockfd, remote, fastopen, addr); + int ret = tls_ctx_connect(ctx->tls, sockfd, fastopen, addr); if (ret != KNOT_EOK) { return ret; } @@ -345,11 +346,14 @@ int https_ctx_connect(https_ctx_t *ctx, int sockfd, const char *remote, // Save authority server if (ctx->authority == NULL) { - if (remote != NULL) { - ctx->authority = strdup(remote); - } else { - ctx->authority = (char*)calloc(HTTPS_AUTHORITY_LEN, sizeof(char)); - ret = sockaddr_to_authority(ctx->authority, HTTPS_AUTHORITY_LEN, addr); + //TODO test + ctx->authority = (char*)calloc(KNOT_DNAME_TXT_MAXLEN + 1, sizeof(char)); + unsigned int type = GNUTLS_NAME_DNS; + size_t len = KNOT_DNAME_TXT_MAXLEN + 1; + ret = gnutls_server_name_get(ctx->tls->session, ctx->authority, &len, + &type, 0); + if (ret == GNUTLS_E_SUCCESS && ctx->authority[0] == '\0') { + ret = sockaddr_to_authority(ctx->authority, KNOT_DNAME_TXT_MAXLEN + 1, addr); if (ret != KNOT_EOK) { free(ctx->authority); ctx->authority = NULL; diff --git a/src/utils/common/https.h b/src/utils/common/https.h index 6c54baee15..aed1cd5dd6 100644 --- a/src/utils/common/https.h +++ b/src/utils/common/https.h @@ -96,7 +96,6 @@ int https_ctx_init(https_ctx_t *ctx, tls_ctx_t *tls_ctx, const https_params_t *p * * \param ctx HTTPS context. * \param sockfd Socket descriptor. - * \param remote [optional] Remote name. * \param fastopen Use TCP Fast Open indication. * \param addr Socket address storage with address to server side. * @@ -106,8 +105,8 @@ int https_ctx_init(https_ctx_t *ctx, tls_ctx_t *tls_ctx, const https_params_t *p * \retval KNOT_NET_ETIMEOUT When server respond takes too long. * \retval KNOT_NET_ECONNECT When unnable to connect to the server. */ -int https_ctx_connect(https_ctx_t *ctx, int sockfd, const char *remote, - bool fastopen, struct sockaddr_storage *addr); +int https_ctx_connect(https_ctx_t *ctx, int sockfd, bool fastopen, + struct sockaddr_storage *addr); /*! * \brief Send buffer as DNS message over HTTPS. diff --git a/src/utils/common/netio.c b/src/utils/common/netio.c index de0729b907..28db3c9804 100644 --- a/src/utils/common/netio.c +++ b/src/utils/common/netio.c @@ -224,8 +224,7 @@ int net_init(const srv_info_t *local, // Prepare for HTTPS. if (https_params != NULL && https_params->enable) { ret = tls_ctx_init(&net->tls, tls_params, - GNUTLS_NONBLOCK, net->wait, - &doh_alpn, 1, NULL); + GNUTLS_NONBLOCK, net->wait); if (ret != KNOT_EOK) { net_clean(net); return ret; @@ -241,8 +240,7 @@ int net_init(const srv_info_t *local, if (quic_params != NULL && quic_params->enable) { ret = tls_ctx_init(&net->tls, tls_params, GNUTLS_NONBLOCK | GNUTLS_ENABLE_EARLY_DATA | - GNUTLS_NO_END_OF_EARLY_DATA, net->wait, - quic_alpn, 4, QUIC_PRIORITY); // TODO will be 1 on release + GNUTLS_NO_END_OF_EARLY_DATA, net->wait); if (ret != KNOT_EOK) { net_clean(net); return ret; @@ -256,8 +254,7 @@ int net_init(const srv_info_t *local, #endif //LIBNGTCP2 { ret = tls_ctx_init(&net->tls, tls_params, - GNUTLS_NONBLOCK, net->wait, - &dot_alpn, 1, NULL); + GNUTLS_NONBLOCK, net->wait); if (ret != KNOT_EOK) { net_clean(net); return ret; @@ -321,6 +318,45 @@ static int fastopen_send(int sockfd, const struct msghdr *msg, int timeout) #endif } +static char *net_get_remote(const net_t *net) +{ + if (net->tls.params->sni != NULL) { + return net->tls.params->sni; + } else if (net->tls.params->hostname != NULL) { + return net->tls.params->hostname; + } else if (strchr(net->remote_str, ':') == NULL) { + char *at = strchr(net->remote_str, '@'); + if (at != NULL && strncmp(net->remote->name, net->remote_str, + at - net->remote_str)) { + return net->remote->name; + } + } + return NULL; +} + +#ifdef LIBNGTCP2 +static int fd_set_recv_ecn(int fd, int family) +{ + unsigned int tos = 1; + switch (family) { + case AF_INET: + if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &tos, sizeof(tos)) == -1) { + return knot_map_errno(); + } + break; + case AF_INET6: + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &tos, sizeof(tos)) == -1) { + return knot_map_errno(); + } + break; + default: + return KNOT_EINVAL; + } + return KNOT_EOK; +} +#endif + + int net_connect(net_t *net) { if (net == NULL || net->srv == NULL) { @@ -365,8 +401,9 @@ int net_connect(net_t *net) (void)bind(sockfd, (struct sockaddr *)&local, sockaddr_len(&local)); } + int ret = 0; if (net->socktype == SOCK_STREAM) { - int cs, err, ret = 0; + int cs, err; socklen_t err_len = sizeof(err); bool fastopen = net->flags & NET_FLAGS_FASTOPEN; @@ -403,34 +440,57 @@ int net_connect(net_t *net) #ifdef LIBNGHTTP2 if (net->https.params.enable) { // Establish HTTPS connection. - char *remote = NULL; - if (net->tls.params->sni != NULL) { - remote = net->tls.params->sni; - } else if (net->tls.params->hostname != NULL) { - remote = net->tls.params->hostname; - } else if (strchr(net->remote_str, ':') == NULL) { - char *at = strchr(net->remote_str, '@'); - if (at != NULL && strncmp(net->remote->name, net->remote_str, - at - net->remote_str)) { - remote = net->remote->name; - } + ret = tls_ctx_setup_remote_endpoint(&net->tls, &doh_alpn, 1, NULL, + net_get_remote(net)); + if (ret != 0) { + close(sockfd); + return ret; } - ret = https_ctx_connect(&net->https, sockfd, remote, fastopen, - (struct sockaddr_storage *)net->srv->ai_addr); - } else { + ret = https_ctx_connect(&net->https, sockfd, fastopen, + (struct sockaddr_storage *)net->srv->ai_addr); + } else #endif //LIBNGHTTP2 + { // Establish TLS connection. - ret = tls_ctx_connect(&net->tls, sockfd, net->tls.params->sni, fastopen, - (struct sockaddr_storage *)net->srv->ai_addr); -#ifdef LIBNGHTTP2 + ret = tls_ctx_setup_remote_endpoint(&net->tls, &dot_alpn, 1, NULL, + net_get_remote(net)); + if (ret != 0) { + close(sockfd); + return ret; + } + ret = tls_ctx_connect(&net->tls, sockfd, fastopen, + (struct sockaddr_storage *)net->srv->ai_addr); } -#endif //LIBNGHTTP2 if (ret != KNOT_EOK) { close(sockfd); return ret; } } } +#ifdef LIBNGTCP2 + else if (net->socktype == SOCK_DGRAM) { + if (net->quic.params.enable) { + // Establish QUIC connection. + ret = fd_set_recv_ecn(sockfd, net->srv->ai_family); + if (ret != KNOT_EOK) { + close(sockfd); + return ret; + } + ret = tls_ctx_setup_remote_endpoint(&net->tls, + doq_alpn, 4, QUIC_PRIORITY, net_get_remote(net)); + if (ret != 0) { + close(sockfd); + return ret; + } + ret = quic_ctx_connect(&net->quic, sockfd, + (struct addrinfo *)net->srv); + if (ret != KNOT_EOK) { + close(sockfd); + return ret; + } + } + } +#endif // Store socket descriptor. net->sockfd = sockfd; diff --git a/src/utils/common/quic.c b/src/utils/common/quic.c index 730aa8b63f..ffa52c869e 100644 --- a/src/utils/common/quic.c +++ b/src/utils/common/quic.c @@ -14,15 +14,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -#include <assert.h> #include <stddef.h> -#include <gnutls/crypto.h> - -#include "libdnssec/error.h" -#include "libdnssec/random.h" #include "libknot/errcode.h" #include "utils/common/quic.h" +#include "utils/common/msg.h" int quic_params_copy(quic_params_t *dst, const quic_params_t *src) { @@ -46,7 +42,24 @@ void quic_params_clean(quic_params_t *params) #ifdef LIBNGTCP2 -const gnutls_datum_t quic_alpn[] = { +#include <assert.h> +#include <poll.h> +#include <gnutls/crypto.h> + +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_gnutls.h> + +#include "libdnssec/error.h" +#include "libdnssec/random.h" +#include "libknot/xdp/tcp_iobuf.h" +#include "utils/common/params.h" + +#define quic_ceil_duration_to_ms(x) (((x) + NGTCP2_MILLISECONDS - 1) / NGTCP2_MILLISECONDS) +#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[] = { { .data = (unsigned char *)"doq", .size = 3 @@ -62,6 +75,453 @@ const gnutls_datum_t quic_alpn[] = { } }; +static void set_application_error(quic_ctx_t *ctx, uint64_t error, uint8_t *reason, size_t reasonlen) +{ + ctx->last_err = (ngtcp2_connection_close_error){ + .type = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION, + .error_code = error, + .reason = reason, + .reasonlen = reasonlen + }; +} + + +static void set_transport_error(quic_ctx_t *ctx, uint64_t error, uint8_t *reason, size_t reasonlen) +{ + ctx->last_err = (ngtcp2_connection_close_error){ + .type = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT, + .error_code = error, + .reason = reason, + .reasonlen = reasonlen + }; +} + +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) +{ + (void)conn; + (void)flags; + (void)offset; + (void)stream_user_data; + + quic_ctx_t *ctx = (quic_ctx_t *)user_data; + + if (stream_id != ctx->stream.id) { + return 0; + } + + struct iovec in = { + .iov_base = (uint8_t *)data, + .iov_len = datalen + }; + + int ret = knot_tcp_inbuf_update(&ctx->stream.in_buffer, in, + &ctx->stream.in_parsed, &ctx->stream.in_parsed_size, + &ctx->stream.in_parsed_total); + if (ret != KNOT_EOK) { + 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)conn; + + quic_ctx_t *ctx = (quic_ctx_t *)user_data; + set_application_error(ctx, DOQ_PROTOCOL_ERROR, NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; +} + +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) +{ + (void)conn; + (void)offset; + (void)stream_user_data; + + quic_ctx_t *ctx = (quic_ctx_t *)user_data; + if (ctx->stream.id == stream_id) { + ctx->stream.out_ack -= datalen; + } + 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) +{ + (void)conn; + (void)flags; + (void)app_error_code; + (void)stream_user_data; + + quic_ctx_t *ctx = (quic_ctx_t *)user_data; + if (ctx && stream_id == ctx->stream.id) { + ctx->stream.id = -1; + } + return KNOT_EOK; +} + +static int quic_open_bidi_stream(quic_ctx_t *ctx) +{ + if (ctx->stream.id != -1) { + return KNOT_EOK; + } + + int ret = ngtcp2_conn_open_bidi_stream(ctx->conn, &ctx->stream.id, NULL); + 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) +{ + (void)rand_ctx; + + dnssec_random_buffer(dest, 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; + + quic_ctx_t *ctx = (quic_ctx_t *)user_data; + + if (dnssec_random_buffer(cid->data, cidlen) != DNSSEC_EOK) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + cid->datalen = cidlen; + + if (ngtcp2_crypto_generate_stateless_reset_token(token, ctx->secret, + sizeof(ctx->secret), cid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +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) +{ + 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); + } + } + + return 0; +} + +static int handshake_confirmed_cb(ngtcp2_conn *conn, void *user_data) +{ + (void)conn; + + quic_ctx_t *ctx = (quic_ctx_t *)user_data; + ctx->state = CONNECTED; + return 0; +} + +static const ngtcp2_callbacks quic_client_callbacks = { + ngtcp2_crypto_client_initial_cb, + NULL, /* recv_client_initial */ + ngtcp2_crypto_recv_crypto_data_cb, + NULL, /* handshake_completed */ + NULL, /* recv_version_negotiation */ + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, + recv_stream_data_cb, + acked_stream_data_offset_cb, + stream_open_cb, + stream_close_cb, + NULL, /* recv_stateless_reset */ + ngtcp2_crypto_recv_retry_cb, + extend_max_bidi_streams_cb, + NULL, /* extend_max_local_streams_uni */ + rand_cb, + get_new_connection_id_cb, + NULL, /* remove_connection_id */ + ngtcp2_crypto_update_key_cb, + NULL, /* path_validation */ + NULL, /* select_preferred_address */ + stream_reset_cb, + NULL, /* extend_max_remote_streams_bidi */ + NULL, /* extend_max_remote_streams_uni */ + NULL, /* extend_max_stream_data */ + NULL, /* dcid_status */ + handshake_confirmed_cb, + NULL, /* recv_new_token */ + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + NULL, /* recv_datagram */ + NULL, /* ack_datagram */ + NULL, /* lost_datagram */ + ngtcp2_crypto_get_path_challenge_data_cb, + NULL, /* stream_stop_sending */ +}; + +static int hook_func(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) +{ + (void)session; + (void)htype; + (void)when; + (void)incoming; + (void)msg; + + return GNUTLS_E_SUCCESS; +} + +static int secret_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, const void *rx_secret, + const void *tx_secret, size_t secretlen) +{ + quic_ctx_t *ctx = (quic_ctx_t *)gnutls_session_get_ptr(session); + ngtcp2_crypto_level level = quic_get_encryption_level(gtls_level); + + if (rx_secret) { + int ret = ngtcp2_crypto_derive_and_install_rx_key(ctx->conn, + NULL, NULL, NULL, level, rx_secret, secretlen); + if (ret != 0) { + return -1; + } + + if (level == NGTCP2_CRYPTO_LEVEL_APPLICATION) { + quic_open_bidi_stream(ctx); + } + } + + if (tx_secret && + ngtcp2_crypto_derive_and_install_tx_key(ctx->conn, NULL, NULL, + NULL, level, tx_secret, secretlen) != 0) { + return -1; + } + + return GNUTLS_E_SUCCESS; +} + +static int read_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, + gnutls_handshake_description_t htype, const void *data, size_t datalen) +{ + if (htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC) { + return GNUTLS_E_SUCCESS; + } + + quic_ctx_t *ctx = (quic_ctx_t *)gnutls_session_get_ptr(session); + if (ngtcp2_conn_submit_crypto_data(ctx->conn, + quic_get_encryption_level(gtls_level), (const uint8_t *)data, + datalen) != 0) { + return -1; + } + + return GNUTLS_E_SUCCESS; +} + +static int alert_read_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, + gnutls_alert_level_t alert_level, gnutls_alert_description_t alert) +{ + (void)gtls_level; + (void)alert_level; + + quic_ctx_t *ctx = (quic_ctx_t *)gnutls_session_get_ptr(session); + set_transport_error(ctx, NGTCP2_CRYPTO_ERROR | alert, NULL, 0); + + return GNUTLS_E_SUCCESS; +} + +static int tp_recv_func(gnutls_session_t session, const uint8_t *data, + size_t datalen) +{ + quic_ctx_t *ctx = (quic_ctx_t *)gnutls_session_get_ptr(session); + ngtcp2_transport_params params; + + int ret = ngtcp2_decode_transport_params(¶ms, + NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, data, + datalen); + if (ret != 0) { + return -1; + } + + ret = ngtcp2_conn_set_remote_transport_params(ctx->conn, ¶ms); + if (ret != 0) { + return -1; + } + + return GNUTLS_E_SUCCESS; +} + +static int tp_send_func(gnutls_session_t session, gnutls_buffer_t extdata) +{ + quic_ctx_t *ctx = (quic_ctx_t *)gnutls_session_get_ptr(session); + uint8_t buf[64]; + ngtcp2_transport_params params; + + ngtcp2_conn_get_local_transport_params(ctx->conn, ¶ms); + ngtcp2_ssize nwrite = ngtcp2_encode_transport_params(buf, sizeof(buf), + NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, ¶ms); + if (nwrite < 0) { + return -1; + } + + return gnutls_buffer_append_data(extdata, buf, (size_t)nwrite); +} + +static int quic_setup_tls(tls_ctx_t *tls_ctx) +{ + gnutls_handshake_set_hook_function(tls_ctx->session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, hook_func); + gnutls_handshake_set_secret_function(tls_ctx->session, secret_func); + gnutls_handshake_set_read_function(tls_ctx->session, read_func); + gnutls_alert_set_read_function(tls_ctx->session, alert_read_func); + return gnutls_session_ext_register(tls_ctx->session, + "QUIC Transport Parameters", + NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1, GNUTLS_EXT_TLS, + tp_recv_func, tp_send_func, NULL, NULL, NULL, + GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | + GNUTLS_EXT_FLAG_EE); +} + +static int quic_send_data(quic_ctx_t *ctx, int sockfd, int family, + ngtcp2_vec *datav, size_t datavlen) +{ + uint8_t enc_buf[MAX_PACKET_SIZE]; + struct iovec msg_iov = { + .iov_base = enc_buf, + .iov_len = 0 + }; + struct msghdr msg = { + .msg_iov = &msg_iov, + .msg_iovlen = 1 + }; + uint64_t ts = quic_timestamp(); + + 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 nwrite = ngtcp2_conn_writev_stream(ctx->conn, + (ngtcp2_path *)ngtcp2_conn_get_path(ctx->conn), + &ctx->pi, enc_buf, sizeof(enc_buf), NULL, + flags, stream, datav, datavlen, ts); + if (nwrite < 0) { + switch(nwrite) { + case NGTCP2_ERR_WRITE_MORE: + assert(0); + continue; + 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); + return KNOT_NET_ESEND; + } + } else if (nwrite == 0) { + ngtcp2_conn_update_pkt_tx_time(ctx->conn, ts); + return KNOT_EOK; + } + datav = NULL; + datavlen = 0; + + msg_iov.iov_len = (size_t)nwrite; + + int ret = quic_set_enc(sockfd, family, ctx->pi.ecn); + if (ret != KNOT_EOK) { + return ret; + } + + if (sendmsg(sockfd, &msg, 0) == -1) { + set_transport_error(ctx, NGTCP2_INTERNAL_ERROR, NULL, + 0); + return KNOT_NET_ESEND; + } + } + return KNOT_EOK; +} + +static int quic_recv(quic_ctx_t *ctx, int sockfd) +{ + uint8_t enc_buf[MAX_PACKET_SIZE]; + uint8_t msg_ctrl[CMSG_SPACE(sizeof(uint8_t))]; + struct sockaddr_in6 from = { 0 }; + struct iovec msg_iov = { + .iov_base = enc_buf, + .iov_len = sizeof(enc_buf) + }; + struct msghdr msg = { + .msg_name = &from, + .msg_namelen = sizeof(from), + .msg_iov = &msg_iov, + .msg_iovlen = 1, + .msg_control = msg_ctrl, + .msg_controllen = sizeof(msg_ctrl), + .msg_flags = 0 + }; + + ssize_t nwrite = recvmsg(sockfd, &msg, 0); + if (nwrite <= 0) { + return knot_map_errno(); + } + ctx->pi.ecn = quic_get_ecn(&msg, from.sin6_family); + if (ctx->pi.ecn == 0 && errno != 0) { + return knot_map_errno(); + } + + int ret = ngtcp2_conn_read_pkt(ctx->conn, + ngtcp2_conn_get_path(ctx->conn), + &ctx->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); + } + return KNOT_NET_ERECV; + } + return KNOT_EOK; +} + uint64_t quic_timestamp(void) { struct timespec ts; @@ -78,16 +538,67 @@ int quic_generate_secret(uint8_t *buf, size_t buflen) uint8_t rand[16], hash[32]; int ret = dnssec_random_buffer(rand, sizeof(rand)); if (ret != DNSSEC_EOK) { - return ret; + return KNOT_ERROR; } ret = gnutls_hash_fast(GNUTLS_DIG_SHA256, rand, sizeof(rand), hash); if (ret != 0) { - return ret; + return KNOT_ERROR; } memcpy(buf, hash, buflen); return KNOT_EOK; } +int quic_set_enc(int sockfd, int family, uint32_t ecn) +{ + switch (family) { + case AF_INET: + if (setsockopt(sockfd, IPPROTO_IP, IP_TOS, &ecn, + (socklen_t)sizeof(ecn)) == -1) { + return knot_map_errno(); + } + break; + case AF_INET6: + if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_TCLASS, &ecn, + (socklen_t)sizeof(ecn)) == -1) { + return knot_map_errno(); + } + break; + default: + return KNOT_ENOTSUP; + } + return KNOT_EOK; +} + +uint32_t quic_get_ecn(struct msghdr *msg, const int family) +{ + errno = 0; + switch (family) { + case AF_INET: + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg; + cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_TOS && cmsg->cmsg_len) { + return *(uint8_t *)CMSG_DATA(cmsg); + } + } + errno = ENOENT; + break; + case AF_INET6: + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg; + cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_TCLASS && cmsg->cmsg_len) { + return *(uint8_t *)CMSG_DATA(cmsg); + } + } + errno = ENOENT; + break; + default: + errno = ENOTSUP; + } + + return 0; +} static int verify_certificate(gnutls_session_t session) { @@ -105,7 +616,7 @@ int quic_ctx_init(quic_ctx_t *ctx, tls_ctx_t *tls_ctx, const quic_params_t *para ctx->tls = tls_ctx; ctx->state = OPENING; ctx->stream.id = -1; - ctx->timestamp = quic_timestamp(); + set_application_error(ctx, DOQ_NO_ERROR, NULL, 0); if (quic_generate_secret(ctx->secret, sizeof(ctx->secret)) != KNOT_EOK) { tls_ctx_deinit(ctx->tls); return KNOT_ENOMEM; @@ -117,5 +628,116 @@ int quic_ctx_init(quic_ctx_t *ctx, tls_ctx_t *tls_ctx, const quic_params_t *para return KNOT_EOK; } +int quic_ctx_connect(quic_ctx_t *ctx, int sockfd, struct addrinfo *dst_addr) +{ + if (connect(sockfd, (const struct sockaddr *)(dst_addr->ai_addr), + sizeof(struct sockaddr_storage)) != 0) { + tls_ctx_deinit(ctx->tls); + return knot_map_errno(); + } + + ngtcp2_cid dcid, scid; + scid.datalen = NGTCP2_MAX_CIDLEN; + int ret = dnssec_random_buffer(scid.data, scid.datalen); + if (ret != DNSSEC_EOK) { + tls_ctx_deinit(ctx->tls); + return ret; + } + dcid.datalen = 18; + ret = dnssec_random_buffer(dcid.data, dcid.datalen); + if (ret != DNSSEC_EOK) { + tls_ctx_deinit(ctx->tls); + return ret; + } + + ctx->idle_ts = quic_timestamp(); + + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + settings.initial_ts = ctx->idle_ts; + settings.handshake_timeout = ctx->tls->wait * NGTCP2_SECONDS; + + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + params.initial_max_streams_uni = 0; + params.initial_max_streams_bidi = 0; + params.initial_max_stream_data_bidi_local = MAX_PACKET_SIZE; + params.initial_max_data = MAX_PACKET_SIZE; + + struct sockaddr_in6 src_addr; + socklen_t src_addr_len = sizeof(src_addr); + ret = getsockname(sockfd, (struct sockaddr *)&src_addr, &src_addr_len); + if (ret < 0) { + tls_ctx_deinit(ctx->tls); + return knot_map_errno(); + } + ngtcp2_path path = { + .local = { + .addrlen = src_addr_len, + .addr = (struct sockaddr *)&src_addr + }, + .remote = { + .addrlen = sizeof(*(dst_addr->ai_addr)), + .addr = (struct sockaddr *)(dst_addr->ai_addr) + }, + .user_data = NULL + }; + + if (ngtcp2_conn_client_new(&ctx->conn, &dcid, &scid, &path, + NGTCP2_PROTO_VER_V1, &quic_client_callbacks, + &settings, ¶ms, NULL, ctx) != 0) { + tls_ctx_deinit(ctx->tls); + return KNOT_NET_ECONNECT; + } + + ret = quic_setup_tls(ctx->tls); + if (ret != KNOT_EOK) { + tls_ctx_deinit(ctx->tls); + 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, + .revents = 0, + }; + ctx->tls->sockfd = sockfd; + + int timeout = ctx->tls->wait * 1000; + while(ctx->state != CONNECTED) { + if (quic_timeout(ctx->idle_ts, ctx->tls->wait)) { + WARN("QUIC, peer took too long to respond\n"); + tls_ctx_deinit(ctx->tls); + return KNOT_NET_ETIMEOUT; + } + ret = quic_send(ctx, sockfd, dst_addr->ai_family); + if (ret != KNOT_EOK) { + tls_ctx_deinit(ctx->tls); + return ret; + } + + ret = poll(&pfd, 1, timeout); + if (ret < 0) { + tls_ctx_deinit(ctx->tls); + return knot_map_errno(); + } else if (ret == 0) { + continue; + } + + ret = quic_recv(ctx, sockfd); + if (ret != KNOT_EOK) { + tls_ctx_deinit(ctx->tls); + return ret; + } + + ngtcp2_conn_get_remote_transport_params(ctx->conn, ¶ms); + timeout = quic_ceil_duration_to_ms(params.max_ack_delay); + } + + return KNOT_EOK; +} #endif diff --git a/src/utils/common/quic.h b/src/utils/common/quic.h index 8b7b537412..1b4ad1e448 100644 --- a/src/utils/common/quic.h +++ b/src/utils/common/quic.h @@ -39,13 +39,36 @@ void quic_params_clean(quic_params_t *params); #define QUIC_DEFAULT_GROUPS "-GROUP-ALL:+GROUP-SECP256R1:+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1" #define QUIC_PRIORITY "%DISABLE_TLS13_COMPAT_MODE:NORMAL:"QUIC_DEFAULT_VERSION":"QUIC_DEFAULT_CIPHERS":"QUIC_DEFAULT_GROUPS - typedef enum { OPENING, CONNECTED, - CLOSING + CLOSING, + CLOSED } quic_state_t; +typedef enum { + /*! No error. This is used when the connection or stream needs to be + closed, but there is no error to signal. */ + DOQ_NO_ERROR = 0x0, + /*! The DoQ implementation encountered an internal error and is + incapable of pursuing the transaction or the connection. */ + DOQ_INTERNAL_ERROR = 0x1, + /*! The DoQ implementation encountered a protocol error and is forcibly + aborting the connection. */ + DOQ_PROTOCOL_ERROR = 0x2, + /*! A DoQ client uses this to signal that it wants to cancel an + outstanding transaction. */ + DOQ_REQUEST_CANCELLED = 0x3, + /*! A DoQ implementation uses this to signal when closing a connection + due to excessive load. */ + DOQ_EXCESSIVE_LOAD = 0x4, + /*! A DoQ implementation uses this in the absence of a more specific + error code. */ + DOQ_UNSPECIFIED_ERROR = 0x5, + /*! Alternative error code used for tests. */ + DOQ_ERROR_RESERVED = 0xd098ea5e +} quic_doq_error_t; + typedef struct { // Parameters quic_params_t params; @@ -60,6 +83,7 @@ typedef struct { size_t in_parsed_size; size_t in_parsed_total; size_t in_parsed_it; + int resets; } stream; ngtcp2_connection_close_error last_err; uint8_t secret[32]; @@ -70,12 +94,18 @@ typedef struct { uint64_t idle_ts; } quic_ctx_t; -extern const gnutls_datum_t quic_alpn[]; +extern const gnutls_datum_t doq_alpn[]; uint64_t quic_timestamp(void); int quic_generate_secret(uint8_t *buf, size_t buflen); +uint32_t quic_get_ecn(struct msghdr *msg, const int family); + +int quic_set_enc(int sockfd, int family, uint32_t ecn); + int quic_ctx_init(quic_ctx_t *ctx, tls_ctx_t *tls_ctx, const quic_params_t *params); +int quic_ctx_connect(quic_ctx_t *ctx, int sockfd, struct addrinfo *dst_addr); + #endif //LIBNGTCP2 diff --git a/src/utils/common/tls.c b/src/utils/common/tls.c index e66fd3fc65..311c63b9b3 100644 --- a/src/utils/common/tls.c +++ b/src/utils/common/tls.c @@ -427,8 +427,7 @@ static int verify_certificate(gnutls_session_t session) } int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params, - unsigned int flags, int wait, const gnutls_datum_t *alpn, - size_t alpn_size, const char *priority) + unsigned int flags, int wait) { if (ctx == NULL || params == NULL || !params->enable) { @@ -507,6 +506,23 @@ int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params, return KNOT_ENOMEM; } + ret = gnutls_credentials_set(ctx->session, GNUTLS_CRD_CERTIFICATE, + ctx->credentials); + if (ret != GNUTLS_E_SUCCESS) { + gnutls_deinit(ctx->session); + return KNOT_ERROR; + } + + return KNOT_EOK; +} + +int tls_ctx_setup_remote_endpoint(tls_ctx_t *ctx, const gnutls_datum_t *alpn, + size_t alpn_size, const char *priority, const char *remote) +{ + if (ctx == NULL || ctx->session == NULL || ctx->credentials == NULL) { + return KNOT_EINVAL; + } + int ret = 0; if (alpn != NULL) { ret = gnutls_alpn_set_protocols(ctx->session, alpn, alpn_size, 0); if (ret != GNUTLS_E_SUCCESS) { @@ -525,34 +541,25 @@ int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params, return KNOT_EINVAL; } - ret = gnutls_credentials_set(ctx->session, GNUTLS_CRD_CERTIFICATE, - ctx->credentials); - if (ret != GNUTLS_E_SUCCESS) { - gnutls_deinit(ctx->session); - return KNOT_ERROR; + if (remote != NULL) { + ret = gnutls_server_name_set(ctx->session, GNUTLS_NAME_DNS, remote, + strlen(remote)); + if (ret != GNUTLS_E_SUCCESS) { + gnutls_deinit(ctx->session); + return KNOT_EINVAL; + } } - return KNOT_EOK; } -int tls_ctx_connect(tls_ctx_t *ctx, int sockfd, const char *remote, bool fastopen, - struct sockaddr_storage *addr) +int tls_ctx_connect(tls_ctx_t *ctx, int sockfd, bool fastopen, + struct sockaddr_storage *addr) { if (ctx == NULL) { return KNOT_EINVAL; } int ret = 0; - // TODO mayble also move to `tls_ctx_init` - if (remote != NULL) { - ret = gnutls_server_name_set(ctx->session, GNUTLS_NAME_DNS, remote, - strlen(remote)); - if (ret != GNUTLS_E_SUCCESS) { - gnutls_deinit(ctx->session); - return KNOT_NET_ECONNECT; - } - } - gnutls_session_set_ptr(ctx->session, ctx); if (fastopen) { diff --git a/src/utils/common/tls.h b/src/utils/common/tls.h index 25201e261f..7c25ab76dc 100644 --- a/src/utils/common/tls.h +++ b/src/utils/common/tls.h @@ -68,9 +68,10 @@ void tls_params_clean(tls_params_t *params); int tls_certificate_verification(tls_ctx_t *ctx); int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params, - unsigned int flags, int wait, const gnutls_datum_t *alpn, - size_t alpn_size, const char *priority); -int tls_ctx_connect(tls_ctx_t *ctx, int sockfd, const char *remote, + unsigned int flags, int wait); +int tls_ctx_setup_remote_endpoint(tls_ctx_t *ctx, const gnutls_datum_t *alpn, + size_t alpn_size, const char *priority, const char *remote); +int tls_ctx_connect(tls_ctx_t *ctx, int sockfd, bool fastopen, struct sockaddr_storage *addr); int tls_ctx_send(tls_ctx_t *ctx, const uint8_t *buf, const size_t buf_len); -- GitLab