diff options
-rw-r--r-- | Knot.files | 2 | ||||
-rw-r--r-- | src/knot/Makefile.inc | 11 | ||||
-rw-r--r-- | src/knot/server/xdp-handler.c | 59 | ||||
-rwxr-xr-x[-rw-r--r--] | src/libknot/Makefile.inc | 11 | ||||
-rw-r--r-- | src/libknot/xdp/quic.c | 799 | ||||
-rw-r--r-- | src/libknot/xdp/quic.h | 95 |
6 files changed, 972 insertions, 5 deletions
diff --git a/Knot.files b/Knot.files index 3484fc8d5..ee988b476 100644 --- a/Knot.files +++ b/Knot.files @@ -536,6 +536,8 @@ src/libknot/xdp/eth.h src/libknot/xdp/msg.h src/libknot/xdp/msg_init.h src/libknot/xdp/protocols.h +src/libknot/xdp/quic.c +src/libknot/xdp/quic.h src/libknot/xdp/tcp.c src/libknot/xdp/tcp.h src/libknot/xdp/tcp_iobuf.c diff --git a/src/knot/Makefile.inc b/src/knot/Makefile.inc index 531de466e..c28e6a8ab 100644 --- a/src/knot/Makefile.inc +++ b/src/knot/Makefile.inc @@ -1,11 +1,16 @@ libknotd_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) $(libkqueue_CFLAGS) \ $(liburcu_CFLAGS) $(lmdb_CFLAGS) $(systemd_CFLAGS) \ - -DKNOTD_MOD_STATIC + $(gnutls_CFLAGS) $(libngtcp2_CFLAGS) -DKNOTD_MOD_STATIC libknotd_la_LDFLAGS = $(AM_LDFLAGS) -export-symbols-regex '^knotd_' -libknotd_la_LIBADD = $(dlopen_LIBS) $(libkqueue_LIBS) $(pthread_LIBS) +libknotd_la_LIBADD = $(dlopen_LIBS) $(libkqueue_LIBS) $(pthread_LIBS) \ + $(libngtcp2_LIBS) libknotd_LIBS = libknotd.la libknot.la libdnssec.la libzscanner.la \ $(libcontrib_LIBS) $(liburcu_LIBS) $(lmdb_LIBS) \ - $(systemd_LIBS) + $(systemd_LIBS) $(gnutls_LIBS) + +if EMBEDDED_LIBNGTCP2 +libknotd_la_LIBADD += $(libembngtcp2_LIBS) +endif EMBEDDED_LIBNGTCP2 include_libknotddir = $(includedir)/knot include_libknotd_HEADERS = \ diff --git a/src/knot/server/xdp-handler.c b/src/knot/server/xdp-handler.c index 29e085373..0621fb16f 100644 --- a/src/knot/server/xdp-handler.c +++ b/src/knot/server/xdp-handler.c @@ -28,6 +28,7 @@ #include "contrib/time.h" #include "contrib/ucw/mempool.h" #include "libknot/error.h" +#include "libknot/xdp/quic.h" #include "libknot/xdp/tcp.h" typedef struct xdp_handle_ctx { @@ -35,10 +36,12 @@ typedef struct xdp_handle_ctx { knot_xdp_msg_t msg_recv[XDP_BATCHLEN]; knot_xdp_msg_t msg_send_udp[XDP_BATCHLEN]; knot_tcp_relay_t relays[XDP_BATCHLEN]; + knot_xquic_conn_t *quic_relays[XDP_BATCHLEN]; uint32_t msg_recv_count; uint32_t msg_udp_count; knot_tcp_table_t *tcp_table; knot_tcp_table_t *syn_table; + knot_xquic_table_t *quic_table; bool udp; // TODO: use this bool tcp; @@ -90,6 +93,7 @@ void xdp_handle_free(xdp_handle_ctx_t *ctx) { knot_tcp_table_free(ctx->tcp_table); knot_tcp_table_free(ctx->syn_table); + knot_xquic_table_free(ctx->quic_table); free(ctx); } @@ -117,6 +121,12 @@ xdp_handle_ctx_t *xdp_handle_init(knot_xdp_socket_t *xdp_sock) } } + ctx->quic_table = knot_xquic_table_new(1000); // FIXME + if (ctx->quic_table == NULL) { + xdp_handle_free(ctx); + return NULL; + } + return ctx; } @@ -245,6 +255,48 @@ static void handle_tcp(xdp_handle_ctx_t *ctx, knot_layer_t *layer, } } +static void handle_quic(xdp_handle_ctx_t *ctx, knot_layer_t *layer, + knotd_qdata_params_t *params) +{ + int ret = knot_xquic_recv(ctx->quic_relays, ctx->msg_recv, ctx->msg_recv_count, ctx->quic_table); + if (ret != KNOT_EOK) { + log_notice("QUIC, failed to process some packets (%s)", knot_strerror(ret)); + return; + } + + uint8_t ans_buf[KNOT_WIRE_MAX_PKTSIZE]; + + for (uint32_t i = 0; i < ctx->msg_recv_count; i++) { + knot_xquic_conn_t *rl = ctx->quic_relays[i]; + if (rl == NULL) { + continue; + } + printf("rl[%u] rx %zu conn %p\n", i, rl->rx_query.iov_len, rl->conn); + if (rl->rx_query.iov_len == 0) { + rl->tx_query.iov_len = 0; + continue; + } + + // Consume the query. + handle_init(params, layer, &ctx->msg_recv[i], &rl->rx_query); + + // Process the reply. + knot_pkt_t *ans = knot_pkt_new(ans_buf, sizeof(ans_buf), layer->mm); + while (tcp_active_state(layer->state)) { + knot_layer_produce(layer, ans); + if (!tcp_send_state(layer->state)) { + continue; + } + + rl->tx_query.iov_len = ans->size; + rl->tx_query.iov_base = malloc(ans->size); // TODO... + memcpy(rl->tx_query.iov_base, ans->wire, ans->size); + } + + handle_finish(layer); + } +} + void xdp_handle_msgs(xdp_handle_ctx_t *ctx, knot_layer_t *layer, server_t *server, unsigned thread_id) { @@ -262,6 +314,7 @@ void xdp_handle_msgs(xdp_handle_ctx_t *ctx, knot_layer_t *layer, if (ctx->tcp) { handle_tcp(ctx, layer, ¶ms); } + handle_quic(ctx, layer, ¶ms); knot_xdp_recv_finish(ctx->sock, ctx->msg_recv, ctx->msg_recv_count); } @@ -277,6 +330,12 @@ void xdp_handle_send(xdp_handle_ctx_t *ctx) log_notice("TCP, failed to send some packets"); } } + for (uint32_t i = 0; i < ctx->msg_recv_count; i++) { + int ret = knot_xquic_send(ctx->sock, ctx->quic_relays[i]); + if (ret != KNOT_EOK) { + log_notice("QUIC, failed to send some packets"); + } + } (void)knot_xdp_send_finish(ctx->sock); diff --git a/src/libknot/Makefile.inc b/src/libknot/Makefile.inc index 9e01b5597..a509b9acb 100644..100755 --- a/src/libknot/Makefile.inc +++ b/src/libknot/Makefile.inc @@ -2,10 +2,15 @@ lib_LTLIBRARIES += libknot.la pkgconfig_DATA += libknot.pc libknot_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) $(lmdb_CFLAGS) \ - ${fuzzer_CFLAGS} + ${fuzzer_CFLAGS} $(libngtcp2_CFLAGS) libknot_la_LDFLAGS = $(AM_LDFLAGS) $(libknot_VERSION_INFO) $(LDFLAG_EXCLUDE_LIBS) \ ${fuzzer_LDFLAGS} -libknot_la_LIBADD = libdnssec.la $(libcontrib_LIBS) $(lmdb_LIBS) $(math_LIBS) +libknot_la_LIBADD = libdnssec.la $(libcontrib_LIBS) $(lmdb_LIBS) $(math_LIBS) \ + $(libngtcp2_LIBS) + +if EMBEDDED_LIBNGTCP2 +libknot_la_LIBADD += $(libembngtcp2_LIBS) +endif EMBEDDED_LIBNGTCP2 include_libknotdir = $(includedir) nobase_include_libknot_HEADERS = \ @@ -99,6 +104,7 @@ nobase_include_libknot_HEADERS += \ libknot/xdp/bpf-consts.h \ libknot/xdp/eth.h \ libknot/xdp/msg.h \ + libknot/xdp/quic.h \ libknot/xdp/tcp.h \ libknot/xdp/xdp.h @@ -110,6 +116,7 @@ libknot_la_SOURCES += \ libknot/xdp/eth.c \ libknot/xdp/msg_init.h \ libknot/xdp/protocols.h \ + libknot/xdp/quic.c \ libknot/xdp/tcp.c \ libknot/xdp/xdp.c endif ENABLE_XDP diff --git a/src/libknot/xdp/quic.c b/src/libknot/xdp/quic.c new file mode 100644 index 000000000..d8f0ab0bd --- /dev/null +++ b/src/libknot/xdp/quic.c @@ -0,0 +1,799 @@ +/* Copyright (C) 2022 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <gnutls/gnutls.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "contrib/macros.h" +#include "contrib/sockaddr.h" +#include "libknot/endian.h" +#include "libdnssec/error.h" +#include "libdnssec/random.h" +#include "libknot/attribute.h" +#include "libknot/error.h" +#include "libknot/xdp/quic.h" + +#define SERVER_DEFAULT_SCIDLEN 18 + +#define QUIC_DEFAULT_VERSION "-VERS-ALL:+VERS-TLS1.3" +#define QUIC_DEFAULT_CIPHERS "-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:+CHACHA20-POLY1305:+AES-128-CCM" +#define QUIC_DEFAULT_GROUPS "-GROUP-ALL:+GROUP-SECP256R1:+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1" +#define QUIC_PRIORITIES "%DISABLE_TLS13_COMPAT_MODE:NORMAL:"QUIC_DEFAULT_VERSION":"QUIC_DEFAULT_CIPHERS":"QUIC_DEFAULT_GROUPS + +static int tls_anti_replay_db_add_func(void *dbf, time_t exp_time, + const gnutls_datum_t *key, + const gnutls_datum_t *data) +{ + return 0; +} + + +static void tls_session_ticket_key_free(gnutls_datum_t *ticket) { + gnutls_memset(ticket->data, 0, ticket->size); + gnutls_free(ticket->data); +} + +_public_ +knot_xquic_table_t *knot_xquic_table_new(size_t table_size) +{ + knot_xquic_table_t *res = calloc(1, sizeof(*res) + table_size * sizeof(res->conns[0])); + if (res == NULL) { + return NULL; + } + + res->size = table_size; + + if (dnssec_random_buffer(res->creds.static_secret, sizeof(res->creds.static_secret)) != DNSSEC_EOK) { + free(res); + return NULL; + } + + int ret = gnutls_anti_replay_init(&res->creds.tls_anti_replay); + if (ret != GNUTLS_E_SUCCESS) { + free(res); + return NULL; + } + gnutls_anti_replay_set_add_function(res->creds.tls_anti_replay, + tls_anti_replay_db_add_func); + gnutls_anti_replay_set_ptr(res->creds.tls_anti_replay, NULL); + + ret = gnutls_certificate_allocate_credentials(&res->creds.tls_cert); + if (ret != GNUTLS_E_SUCCESS) { + free(res); + return NULL; + } + ret = gnutls_certificate_set_x509_system_trust(res->creds.tls_cert); + if (ret < 0) { + knot_xquic_table_free(res); + return NULL; + } + const char *cert_file = "/home/peltan/mnt/MyCertificate.crt"; + const char *key_file = "/home/peltan/mnt/MyKey.key"; // FIXME :) + ret = gnutls_certificate_set_x509_key_file(res->creds.tls_cert, + cert_file, key_file, GNUTLS_X509_FMT_PEM); + if (ret != GNUTLS_E_SUCCESS) { + knot_xquic_table_free(res); + return NULL; + } + + ret = gnutls_session_ticket_key_generate(&res->creds.tls_ticket_key); + if (ret != GNUTLS_E_SUCCESS) { + knot_xquic_table_free(res); + return NULL; + } + + return res; +} + +_public_ +void knot_xquic_table_free(knot_xquic_table_t *table) +{ + if (table != NULL) { + // FIXME free all connections + + gnutls_certificate_free_credentials(table->creds.tls_cert); + if (table->creds.tls_ticket_key.data != NULL) { + tls_session_ticket_key_free(&table->creds.tls_ticket_key); + } + + free(table); + } +} + +static void print_cid(const ngtcp2_cid *cid, const char *name) +{ + if (cid->datalen == 0) { + printf("%s 0 [zero cid] ", name); + } else { + printf("%s %zu [ %02x %02x %02x %02x ... ] ", name, cid->datalen, cid->data[0], cid->data[1], cid->data[2], cid->data[3]); + } +} + +static int tls_secret_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, + const void *rx_secret, const void *tx_secret, + size_t secretlen) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)gnutls_session_get_ptr(session); + int level = ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(gtls_level); + if (rx_secret != NULL) { + int ret = ngtcp2_crypto_derive_and_install_rx_key(ctx->conn, NULL, NULL, NULL, level, rx_secret, secretlen); + printf("RX secret lev %d ret %d (%s)\n", level, ret, ngtcp2_strerror(ret)); + if (ret != 0) { + return -2; + } + } + + if (tx_secret != NULL) { + int ret = ngtcp2_crypto_derive_and_install_tx_key(ctx->conn, NULL, NULL, NULL, level, tx_secret, secretlen); + printf("TX secret lev %d ret %d (%s)\n", level, ret, ngtcp2_strerror(ret)); + if (ret != 0) { + return -4; + } + // TODO uncomment when `call_application_tx_key_cb != NULL` or remove + if (level == NGTCP2_CRYPTO_LEVEL_APPLICATION) { + // && call_application_tx_key_cb(ctx) != 0) { + // return -1; + } + } + return 0; +} + +static int tls_read_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, + gnutls_handshake_description_t htype, const void *data, + size_t data_size) +{ + printf("TLS read htype %d\n", htype); + if (htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC) { + return 0; + } + + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)gnutls_session_get_ptr(session); + int level = ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(gtls_level); + ngtcp2_conn_submit_crypto_data(ctx->conn, level, (const uint8_t *)data, data_size); + return 1; +} + +static int tls_alert_read_func(gnutls_session_t session, + gnutls_record_encryption_level_t level, + gnutls_alert_level_t alert_level, + gnutls_alert_description_t alert_desc) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)gnutls_session_get_ptr(session); + (void)ctx; + printf("TLS alert!\n"); + // ctx->error = NGTCP2_CRYPTO_ERROR | alert_desc; + return 0; +} + +#define ALPN "\03""doq" + +static int tls_client_hello_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) +{ + assert(htype == GNUTLS_HANDSHAKE_CLIENT_HELLO); + assert(when == GNUTLS_HOOK_POST); + assert(incoming == 1); + + // check if ALPN extension is present and properly selected h3 + gnutls_datum_t alpn; + int ret = gnutls_alpn_get_selected_protocol(session, &alpn); + printf("alpn set prot %d (%s)\n", ret, gnutls_strerror(ret)); + if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + return 0; // FIXME: return this workaround + } + if (ret != 0) { + return ret; + } + + const char *dq = (const char *)&ALPN[1]; + printf("dq '%.*s' alpn '%.*s'\n", (int)ALPN[0], dq, alpn.size, alpn.data); + if ((unsigned int)ALPN[0] != alpn.size || + memcmp(dq, alpn.data, alpn.size) != 0) { + return -5; + } + + return 0; +} + +static int tls_tp_recv_func(gnutls_session_t session, const uint8_t *data, + size_t datalen) +{ + ngtcp2_transport_params params; + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)gnutls_session_get_ptr(session); + ngtcp2_conn *conn = ctx->conn; + + int ret = ngtcp2_decode_transport_params(¶ms, + NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, data, + datalen); + printf("decode transport params %d (%s)\n", ret, ngtcp2_strerror(ret)); + if (ret != 0) { + + return -250; + } + + ret = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + printf("set transport params %d (%s)\n", ret, ngtcp2_strerror(ret)); + + printf("conn id limit %lu default %d\n", params.active_connection_id_limit, NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT); + print_cid(ngtcp2_conn_get_dcid(conn), "conn_dcid:"); + print_cid(ngtcp2_conn_get_client_initial_dcid(conn), "conn_init_client_dcid:"); + print_cid(¶ms.initial_scid, "params_initial_scid:"); + print_cid(¶ms.original_dcid, "params_original_dcid:"); + print_cid(¶ms.retry_scid, "retry_scid:"); + printf("\npayload size %lu default %d\n", params.max_udp_payload_size, NGTCP2_MAX_UDP_PAYLOAD_SIZE); + if (ret != 0) { + + return -251; + } + + return 0; +} + +static int tls_tp_send_func(gnutls_session_t session, gnutls_buffer_t extdata) +{ + ngtcp2_transport_params params; + uint8_t buf[256]; + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)gnutls_session_get_ptr(session); + ngtcp2_conn *conn = ctx->conn; + + ngtcp2_conn_get_local_transport_params(conn, ¶ms); + ssize_t nwrite = ngtcp2_encode_transport_params(buf, sizeof(buf), + NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, + ¶ms); + printf("encode transport params %zd (%s)\n", nwrite, ngtcp2_strerror(nwrite)); + if (nwrite < 0) { + return -252; + } + + int ret = gnutls_buffer_append_data(extdata, buf, nwrite); + printf("buffer append data %d (%s)\n", ret, ngtcp2_strerror(ret)); + if (ret != 0) { + return -253; + } + + return 0; +} + +static int tls_keylog_callback(gnutls_session_t session, const char *label, + const gnutls_datum_t *secret) +{ + return 0; +} + +static int tls_init_conn_session(knot_xquic_conn_t *conn) +{ + if (gnutls_init(&conn->tls_session, GNUTLS_SERVER | + GNUTLS_ENABLE_EARLY_DATA | GNUTLS_NO_AUTO_SEND_TICKET | + GNUTLS_NO_END_OF_EARLY_DATA) != GNUTLS_E_SUCCESS) { + return KNOT_ERROR; + } + + if (gnutls_priority_set_direct(conn->tls_session, QUIC_PRIORITIES, + NULL) != GNUTLS_E_SUCCESS) { + return KNOT_ERROR; + } + + if (gnutls_session_ticket_enable_server(conn->tls_session, + &conn->xquic_table->creds.tls_ticket_key) != GNUTLS_E_SUCCESS) { + return KNOT_ERROR; + } + + gnutls_handshake_set_secret_function(conn->tls_session, tls_secret_func); + gnutls_handshake_set_read_function(conn->tls_session, tls_read_func); + gnutls_alert_set_read_function(conn->tls_session, tls_alert_read_func); + gnutls_handshake_set_hook_function(conn->tls_session, + GNUTLS_HANDSHAKE_CLIENT_HELLO, + GNUTLS_HOOK_POST, tls_client_hello_cb); + if (gnutls_session_ext_register(conn->tls_session, + "QUIC Transport Parameters", + NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1, + GNUTLS_EXT_TLS, tls_tp_recv_func, tls_tp_send_func, NULL, NULL, + NULL, GNUTLS_EXT_FLAG_TLS | + GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_EE + ) != 0) { +// std::cerr << "gnutls_session_ext_register failed: " << gnutls_strerror(rv) +// << std::endl; + assert(0); + return -1; + } + + gnutls_anti_replay_enable(conn->tls_session, conn->xquic_table->creds.tls_anti_replay); + gnutls_record_set_max_early_data_size(conn->tls_session, 0xffffffffu); + + gnutls_session_set_ptr(conn->tls_session, conn); + + if (gnutls_credentials_set(conn->tls_session, GNUTLS_CRD_CERTIFICATE, + conn->xquic_table->creds.tls_cert) != GNUTLS_E_SUCCESS) { + // std::cerr << "gnutls_credentials_set failed: " << gnutls_strerror(rv) + // << std::endl; + return -1; + } + + gnutls_datum_t alpn = { + .data = (uint8_t *)(&ALPN[1]), + .size = ALPN[0], + }; + gnutls_alpn_set_protocols(conn->tls_session, &alpn, 1, 0); + + gnutls_session_set_keylog_function(conn->tls_session, tls_keylog_callback); + ngtcp2_conn_set_tls_native_handle(conn->conn, conn->tls_session); + + return KNOT_EOK; +} + +static uint64_t get_timestamp(void) +{ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + uint64_t res = (uint64_t)t.tv_sec * 1000000; + res += (uint64_t)t.tv_nsec / 1000; + return res; // overflow does not matter since we are working with differences +} + +static void knot_quic_rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) +{ + (void)rand_ctx; + dnssec_random_buffer(dest, destlen); +} + +static bool cid_eq(const ngtcp2_cid *a, const ngtcp2_cid *b) // TODO ngtcp2_cid_eq +{ + return a->datalen == b->datalen && + memcmp(a->data, b->data, a->datalen) == 0; +} + +static uint64_t cid2hash(const ngtcp2_cid *cid) +{ + uint64_t hash = 0; + memcpy(&hash, cid->data, MIN(sizeof(hash), cid->datalen)); + hash &= 0xffLU * 0; // FIXME remove !! + return hash; +} + +static knot_xquic_conn_t **xquic_table_add(ngtcp2_conn *conn, const ngtcp2_cid *cid, knot_xquic_table_t *table) +{ + knot_xquic_conn_t *xconn = calloc(1, sizeof(*xconn)); + if (xconn == NULL) { + return NULL; + } + + xconn->conn = conn; + xconn->cid.datalen = cid->datalen; + memcpy(xconn->cid.data, cid->data, cid->datalen); + + uint64_t hash = cid2hash(cid); + + knot_xquic_conn_t **addto = table->conns + (hash % table->size); + xconn->next = *addto; + *addto = xconn; + printf("TABLE addto %p conn %p\n", addto, xconn); + table->usage++; + + return addto; +} + +static knot_xquic_conn_t **xquic_table_lookup(const ngtcp2_cid *cid, knot_xquic_table_t *table) +{ + uint64_t hash = cid2hash(cid); + + knot_xquic_conn_t **res = table->conns + (hash % table->size); + while (*res != NULL) { + if (cid_eq(&(*res)->cid, cid) || true /* FIXME !! */) { + break; + } + res = &(*res)->next; + } + printf("TABLE lookup hash %lu: %p at %p\n", hash, *res, res); + return res; +} + +static void init_random_cid(ngtcp2_cid *cid, size_t len) +{ + if (len == 0) { + len = SERVER_DEFAULT_SCIDLEN; + } + + if (dnssec_random_buffer(cid->data, len) != DNSSEC_EOK) { + cid->datalen = 0; + } else { + cid->datalen = len; + } +} + +static int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, + void *user_data) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + + if (init_random_cid(cid, cidlen), cid->datalen == 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (ngtcp2_crypto_generate_stateless_reset_token(token, (uint8_t *)ctx->xquic_table->hash_secret, sizeof(ctx->xquic_table->hash_secret), cid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int knot_handshake_completed_cb(ngtcp2_conn *conn, void *user_data) { + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + (void)ctx; + printf("Negotiated cipher suite is %s\n", gnutls_cipher_get_name(gnutls_cipher_get(ctx->tls_session))); + gnutls_datum_t alpn; + if (gnutls_alpn_get_selected_protocol(ctx->tls_session, &alpn) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + char alpn_str[alpn.size + 1]; + alpn_str[alpn.size] = '\0'; + memcpy(alpn_str, alpn.data, alpn.size); + printf("Negotiated ALPN is %s\n", alpn_str); + + if (gnutls_session_ticket_send(ctx->tls_session, 1, 0) != GNUTLS_E_SUCCESS) { + printf("Unable to send session ticket\n"); + } + + uint8_t token[NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN]; + ngtcp2_path path = *ngtcp2_conn_get_path(ctx->conn); + uint64_t ts = get_timestamp(); + ngtcp2_ssize tokenlen = ngtcp2_crypto_generate_regular_token(token, + (uint8_t *)ctx->xquic_table->hash_secret, + sizeof(ctx->xquic_table->hash_secret), + path.remote.addr, path.remote.addrlen, ts); + if (tokenlen < 0) { + // if (!config.quiet) { + // std::cerr << "Unable to generate token" << std::endl; + // } + assert(0); + return 0; + } + + if (ngtcp2_conn_submit_new_token(ctx->conn, token, tokenlen) != 0) { +// if (!config.quiet) { +// std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv) +// << std::endl; +// } + assert(0); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int recv_stream_data(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) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + + printf("RECV stream data %zu [ %02x %02x ... ] (size %d) flags %u\n", datalen, datalen > 0 ? data[0] : 0, datalen > 1 ? data[1] : 0, (int)be16toh(*(uint16_t *)data), flags); + + /*if (!(flags & NGTCP2_STREAM_DATA_FLAG_FIN)) { + ctx->rx_query.iov_len = 0; + return 0; // TODO handle fragmented DNS queries? + } + FIXME some clients don't send FIN + */ + + + /* + if (datalen < sizeof(uint16_t) || be16toh(*(uint16_t *)data) != datalen - sizeof(uint16_t)) { + ctx->rx_query.iov_len = 0; + return 0; // TODO handle weirdly fragmented queries? + } + ctx->rx_query.iov_base = (void *)data + sizeof(uint16_t); + ctx->rx_query.iov_len = datalen - sizeof(uint16_t); + */ + + ctx->stream_id = stream_id; + ctx->rx_query.iov_base = (uint8_t *)data; + ctx->rx_query.iov_len = datalen; + return 0; +} + +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) +{ + knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data; + (void)ctx; + return 0; +} + +static int stream_opened(ngtcp2_conn *conn, int64_t stream_id, void *user_data) +{ + printf("STREAM %ld opened...\n", stream_id); + return 0; +} + +static void user_printf(void *user_data, const char *format, ...) +{ + printf("--- "); + (void)user_data; + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); +} + +static void user_qlog(void *user_data, uint32_t flags, const void *data, size_t datalen) +{ + (void)user_data; + FILE *qlog = fopen("/home/peltan/mnt/knot0.qlog", "a"); + if (qlog != NULL) { + //fprintf(qlog, "\n%u: ", flags); + for (size_t i = 0; i < datalen; i++) { + fputc(*(uint8_t *)(data + i), qlog); + } + fclose(qlog); + } +} + +static int conn_server_new(ngtcp2_conn **pconn, const ngtcp2_path *path, const ngtcp2_cid *scid, + const ngtcp2_cid *dcid, const ngtcp2_cid *odcid, uint32_t version, + uint64_t now, void *user_data) +{ + // I. CALLBACKS + const ngtcp2_callbacks callbacks = { + NULL, // client_initial + ngtcp2_crypto_recv_client_initial_cb, + ngtcp2_crypto_recv_crypto_data_cb, + knot_handshake_completed_cb, + NULL, // recv_version_negotiation + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, + recv_stream_data, + acked_stream_data_offset_cb, + stream_opened, + NULL, // TODO stream_close, + NULL, // recv_stateless_reset + NULL, // recv_retry + NULL, // extend_max_streams_bidi + NULL, // extend_max_streams_uni + knot_quic_rand_cb, + get_new_connection_id, + NULL, // TODO remove_connection_id, + ngtcp2_crypto_update_key_cb, + NULL, // TODO path_validation, + NULL, // select_preferred_addr + NULL, // TODO ::stream_reset, + NULL, // TODO ::extend_max_remote_streams_bidi, + NULL, // extend_max_remote_streams_uni + NULL, // TODO ::extend_max_stream_data, + NULL, // dcid_status + NULL, // handshake_confirmed + 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 // TODO stream_stop_sending, + }; + + // II. SETTINGS + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + settings.initial_ts = now; + settings.log_printf = user_printf; + //TODO pass token + settings.token.base = NULL; + settings.token.len = 0; + //TODO UDP payload configuration + if (0 /*configured max UDP payload*/) { + //settings.max_udp_payload_size = 0; //TODO from configuration + settings.no_udp_payload_size_shaping = 1; + } else { + settings.max_udp_payload_size = 1472; + settings.assume_symmetric_path = 1; + } + settings.qlog.odcid = *odcid; + settings.qlog.write = user_qlog; + + // III. PARAMS + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + + params.initial_max_data = 786432; + params.initial_max_stream_data_bidi_local = 524288; + params.initial_max_stream_data_bidi_remote = 524288; + params.initial_max_stream_data_uni = 524288; + + // params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; + // params.initial_max_stream_data_bidi_remote = config.max_stream_data_bidi_remote; + // params.initial_max_stream_data_uni = config.max_stream_data_uni; + // params.initial_max_data = config.max_data; + params.initial_max_streams_bidi = 100; + params.initial_max_streams_uni = 3; + // params.max_idle_timeout = config.timeout; + // params.stateless_reset_token_present = 1; + // params.active_connection_id_limit = 7; + if (odcid) { + params.original_dcid = *odcid; + // params.retry_scid = *scid; + // params.retry_scid_present = 1; // NO! + } else { + params.original_dcid = *scid; + } + if (dnssec_random_buffer(params.stateless_reset_token, NGTCP2_STATELESS_RESET_TOKENLEN) != DNSSEC_EOK) { + // TODO std::cerr << "Could not generate stateless reset token" << std::endl; + return KNOT_ERROR; + } + + return ngtcp2_conn_server_new(pconn, dcid, scid, path, version, &callbacks, &settings, ¶ms, NULL, user_data); +} + +static int handle_packet(knot_xdp_msg_t *msg, knot_xquic_table_t *table, knot_xquic_conn_t **out_conn) +{ + uint32_t pversion = 0; + ngtcp2_cid scid = { 0 }, dcid = { 0 }; + const uint8_t *scid_data, *dcid_data; + uint64_t now = get_timestamp(); + int ret = ngtcp2_pkt_decode_version_cid(&pversion, &dcid_data, &dcid.datalen, &scid_data, &scid.datalen, + msg->payload.iov_base, msg->payload.iov_len, 8 /* FIXME this is only suitable for AIOquic! SERVER_DEFAULT_SCIDLEN */); + if (ret == NGTCP2_NO_ERROR) { + memcpy(dcid.data, dcid_data, dcid.datalen); + memcpy(scid.data, scid_data, scid.datalen); + } + printf("dcid data %p scid data %p msg payload %p\n", dcid.data, scid.data, msg->payload.iov_base); + print_cid(&scid, "packet_scid:"); + print_cid(&dcid, "packet_dcid:"); + printf("(%s)\n", ngtcp2_strerror(ret)); + if (ret == NGTCP2_ERR_VERSION_NEGOTIATION) { + // TODO + assert(0); + return KNOT_EOK; + } else if (ret < 0) { + return ret; + } + + knot_xquic_conn_t *xconn = *xquic_table_lookup(&dcid, table); + + if (pversion == 0 /* short header */ && xconn == NULL) { + // TODO + assert(0); + return KNOT_EOK; + } + + ngtcp2_path path; + path.remote.addr = (struct sockaddr *)&msg->ip_from; + path.remote.addrlen = sockaddr_len((struct sockaddr_storage *)&msg->ip_from); + path.local.addr = (struct sockaddr *)&msg->ip_to; + path.local.addrlen = sockaddr_len((struct sockaddr_storage *)&msg->ip_to); + + if (xconn == NULL) { + // new conn + + ret = ngtcp2_accept(NULL, msg->payload.iov_base, msg->payload.iov_len); // FIXME + printf("accept (%s)\n", ngtcp2_strerror(ret)); + + xconn = *xquic_table_add(NULL, &dcid, table); + if (xconn == NULL) { + return ENOMEM; + } + xconn->xquic_table = table; // FIXME ? + + ret = conn_server_new(&xconn->conn, &path, &dcid, &scid, &dcid /* FIXME: ocid == dcid ? */, pversion, now, xconn); + printf("csn (%s)\n", knot_strerror(ret)); + + if (ret < 0) { + // TODO delete xconn and fail + assert(0); + return KNOT_EOK; + } + + ret = tls_init_conn_session(xconn); + printf("TLS (%s)\n", knot_strerror(ret)); + + if (ret != KNOT_EOK) { + printf("conn TLS error (%s)\n", knot_strerror(ret)); + } + } + + ngtcp2_pkt_info pi = { .ecn = NGTCP2_ECN_NOT_ECT, }; // TODO: explicit congestion notification + + ret = ngtcp2_conn_read_pkt(xconn->conn, &path, &pi, msg->payload.iov_base, msg->payload.iov_len, now); + printf("read pkt %p %p %p len %zu (%s)\n", xconn, xconn->conn, xconn->tls_session, msg->payload.iov_len, ngtcp2_strerror(ret)); + + if (ret == KNOT_EOK) { + *out_conn = xconn; + memcpy(xconn->last_eth_rem, msg->eth_from, sizeof(msg->eth_from)); + memcpy(xconn->last_eth_loc, msg->eth_to, sizeof(msg->eth_to)); + } + + return ret; +} + +_public_ +int knot_xquic_recv(knot_xquic_conn_t **relays, knot_xdp_msg_t *msgs, + uint32_t count, knot_xquic_table_t *quic_table) +{ + memset(relays, 0, count * sizeof(*relays)); + + for (uint32_t i = 0; i < count; i++) { + knot_xdp_msg_t *msg = &msgs[i]; + const uint8_t *payl = msg->payload.iov_base; + if ((msg->flags & KNOT_XDP_MSG_TCP) || + msg->payload.iov_len < 4 || + (payl[2] != 0 && payl[3] == 0)) { // not QUIC + continue; + } + + int ret = handle_packet(msg, quic_table, &relays[i]); + (void)ret; + } + + return KNOT_EOK; +} + +_public_ +int knot_xquic_send(knot_xdp_socket_t *sock, knot_xquic_conn_t *relay) +{ + if (relay == NULL || relay->conn == NULL) { + return KNOT_EOK; + } + + bool ipv6 = false; // FIXME + + knot_xdp_msg_t msg = { 0 }; + int ret = knot_xdp_send_alloc(sock, ipv6 ? KNOT_XDP_MSG_IPV6 : 0, &msg); + if (ret != KNOT_EOK) { + return ret; + } + + ngtcp2_path path = { 0 }; + path.local.addr = (struct sockaddr *)&msg.ip_from; + path.remote.addr = (struct sockaddr *)&msg.ip_to; + + msg.payload.iov_len = MAX(msg.payload.iov_len, 1400); // TODO do something + + if (relay->tx_query.iov_len > 0) { + ret = ngtcp2_conn_writev_stream(relay->conn, &path, NULL, msg.payload.iov_base, msg.payload.iov_len, + NULL, NGTCP2_WRITE_STREAM_FLAG_FIN, relay->stream_id, + (const ngtcp2_vec *)&relay->tx_query, 1, get_timestamp()); + } else { + ret = ngtcp2_conn_writev_stream(relay->conn, &path, NULL, msg.payload.iov_base, msg.payload.iov_len, + NULL, NGTCP2_WRITE_STREAM_FLAG_NONE, -1, NULL, 0, get_timestamp()); + } + + if (ret <= 0) { + printf("writev stream %d (%s)\n", ret, ngtcp2_strerror(ret)); + knot_xdp_send_free(sock, &msg, 1); + return ret; + } else { + msg.payload.iov_len = ret; + ret = KNOT_EOK; + } + printf("writev stream [%zu -> %zu] %d (%s)\n", relay->tx_query.iov_len, msg.payload.iov_len, ret, ngtcp2_strerror(ret)); + + + memcpy(msg.eth_from, relay->last_eth_loc, sizeof(msg.eth_from)); + memcpy(msg.eth_to, relay->last_eth_rem, sizeof(msg.eth_to)); + + uint32_t sent = 0; + return knot_xdp_send(sock, &msg, 1, &sent); +} diff --git a/src/libknot/xdp/quic.h b/src/libknot/xdp/quic.h new file mode 100644 index 000000000..71aa21957 --- /dev/null +++ b/src/libknot/xdp/quic.h @@ -0,0 +1,95 @@ +/* Copyright (C) 2022 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stdint.h> + +#include "contrib/libngtcp2/ngtcp2/ngtcp2.h" +#include "contrib/libngtcp2/ngtcp2/ngtcp2_crypto.h" +#include "contrib/libngtcp2/ngtcp2/ngtcp2_crypto_gnutls.h" + +#include "libknot/xdp/xdp.h" + +typedef struct knot_xquic_conn { +/* struct { + struct knot_xquic_conn *list_node_next; + struct knot_xquic_conn *list_node_prev; + } list_node_placeholder; +*/ + uint8_t last_eth_rem[ETH_ALEN]; + uint8_t last_eth_loc[ETH_ALEN]; + + ngtcp2_conn *conn; + ngtcp2_cid cid; + + gnutls_session_t tls_session; + + struct iovec rx_query; // TODO ? + struct iovec tx_query; // TODO ? + int64_t stream_id; // TODO per-stream buffers. + + struct knot_xquic_conn *next; + + struct knot_xquic_table *xquic_table; // TODO ? +} knot_xquic_conn_t; + +typedef struct { + gnutls_certificate_credentials_t tls_cert; + gnutls_anti_replay_t tls_anti_replay; + gnutls_datum_t tls_ticket_key; + uint8_t static_secret[32]; +} knot_quic_creds_t; + +typedef struct knot_xquic_table { + size_t size; + size_t usage; + uint64_t hash_secret[4]; + knot_quic_creds_t creds; + knot_xquic_conn_t *conns[]; +} knot_xquic_table_t; + +/*! + * \brief Allocate QUIC connections hash table. + * + * \param table_size Number of hash buckets. + * + * \return Allocated table or NULL; + */ +knot_xquic_table_t *knot_xquic_table_new(size_t table_size); + +/*! + * \brief Free QUIC table including its contents. + * + * \param table Table to be freed. + */ +void knot_xquic_table_free(knot_xquic_table_t *table); + +/*! + * \brief Process received packets, pic incomming DNS data. + * + * \param relays Out: affected QUIC connections. + * \param msgs Incomming packets. + * \param count Number of incomming packets. + * \param quic_table Connection table. + * + * \return KNOT_E* + */ +int knot_xquic_recv(knot_xquic_conn_t **relays, knot_xdp_msg_t *msgs, + uint32_t count, knot_xquic_table_t *quic_table); + + +int knot_xquic_send(knot_xdp_socket_t *sock, knot_xquic_conn_t *relay); |