From: Frantisek Tobias Date: Tue, 23 Jun 2026 13:05:26 +0000 (+0200) Subject: daemon/quic: various smaller changes, improvements and fixes aggregated into a single... X-Git-Tag: v6.4.1^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=beab42290f5ff85237c1f52622be95bc6435e44d;p=thirdparty%2Fknot-resolver.git daemon/quic: various smaller changes, improvements and fixes aggregated into a single commit list of changes: - lower default concurrent stream limit 1024 to 16 - rename quic_idle_timeout to quic_handle_timeout - use PTO for closing/draining state timeouts - move kr_quic_table_rem2 to quic_common.c - set_tls_error for tls alerts (in conn close messages with error type TRANSPORT) - add some function documentation to quic_*.h - improve/fix error messages in conn close packets - fix and simplify tls alert transport when conn close is sent - fix cid handling - call uv_close on the timers in sub-sessions - add QUIC_SEND_NONE for read errors which forbid/do not need a reply - quic_bye for closing conns which are in states <= CLOSING (i.e. conn that can still send) - unite handshake timer with ngtcp2 driver timer loop - add a custom state for failed handshake - QUIC_STATE_HS_ABORT - handle cid removal from the table for conns which fail before their ngtcp2_conn is created - kr_quic_table_sweep is no longer used, termination handled complately by timers - failed attempt to create ngtcp2_conn now causes the conn session to be destroyed immediately --- diff --git a/daemon/quic_common.c b/daemon/quic_common.c index c5d15ee45..549ce80a5 100644 --- a/daemon/quic_common.c +++ b/daemon/quic_common.c @@ -30,19 +30,16 @@ int quic_configuration_set(void) the_network->quic_params = quic_params; /* Default values */ the_network->quic_params->require_retry = false; - the_network->quic_params->max_streams = 1024; + the_network->quic_params->max_streams = 16; the_network->quic_params->max_conns = 1024; return kr_ok(); } int quic_configuration_free(struct net_quic_params *quic_params) { - if (quic_params == NULL){ - return kr_ok(); + if (quic_params) { + free(quic_params); } - - free(quic_params); - return kr_ok(); } @@ -66,6 +63,17 @@ bool kr_quic_conn_timeout(struct pl_quic_conn_sess_data *conn, uint64_t *now) return *now > ngtcp2_conn_get_expiry(conn->conn); } +inline uint64_t quic_ns_to_ms_ceil(uint64_t ns) +{ + return (ns + NGTCP2_MILLISECONDS - 1) / NGTCP2_MILLISECONDS; +} + +inline uint64_t quic_get_closing_timeout(uint64_t pto_ns) +{ + /* see: https://nghttp2.org/ngtcp2/programmers-guide.html#the-closing-and-draining-state */ + return 3 * quic_ns_to_ms_ceil(pto_ns); +} + void quic_hs_timeout(uv_timer_t *timer) { struct session2 *s = timer->data; @@ -76,11 +84,20 @@ void quic_hs_timeout(uv_timer_t *timer) session2_event(conn->h.session, PROTOLAYER_EVENT_CONNECT_TIMEOUT, NULL); } -void quic_idle_timeout(uv_timer_t *timer) +void quic_handle_timeout(uv_timer_t *timer) { struct session2 *s = timer->data; struct pl_quic_conn_sess_data *conn = protolayer_sess_data_get_proto(s, PROTOLAYER_TYPE_QUIC_CONN); + + if (conn->state & QUIC_STATE_CLOSING) { + session2_close(conn->h.session); + return; + } else if (conn->state & QUIC_STATE_DRAINING) { + session2_force_close(conn->h.session); + return; + } + uint64_t now = quic_timestamp(); int ret = ngtcp2_conn_handle_expiry(conn->conn, now); if (ret != 0) { @@ -88,9 +105,9 @@ void quic_idle_timeout(uv_timer_t *timer) ngtcp2_strerror(ret), ret); } if (ret == NGTCP2_ERR_IDLE_CLOSE) { - /* idle equal max_idle_timeout, don't send CONNECTION_CLOSE */ session2_force_close(s); } else if (ret < 0) { + QUIC_SET_CLOSING(conn); session2_close(s); } else { quic_flush_streams(conn); @@ -113,7 +130,7 @@ int quic_set_hs_timeout(struct session2 *s, uint64_t ms) int quic_set_idle_timeout(struct session2 *s, uint64_t ms) { - return quic_set_timeout(s, ms, quic_idle_timeout); + return quic_set_timeout(s, ms, quic_handle_timeout); } void quic_reset_expiry(struct pl_quic_conn_sess_data *conn) @@ -124,27 +141,35 @@ void quic_reset_expiry(struct pl_quic_conn_sess_data *conn) uv_timer_stop(&s->timer); if (expiry == UINT64_MAX) { - uv_timer_start(&s->timer, quic_idle_timeout, + uv_timer_start(&s->timer, quic_handle_timeout, QUIC_MAX_IDLE_TIMEOUT, 0); return; } uint64_t now = quic_timestamp(); if (expiry > now) { - uint64_t ceil_delay_ns = expiry - now + NGTCP2_MILLISECONDS - 1; - uint64_t delay_ms = ceil_delay_ns / NGTCP2_MILLISECONDS; - uv_timer_start(&s->timer, quic_idle_timeout, delay_ms, 0); + uv_timer_start(&s->timer, quic_handle_timeout, + quic_ns_to_ms_ceil(expiry - now), 0); } else { - uv_timer_start(&s->timer, quic_idle_timeout, 0, 0); + uv_timer_start(&s->timer, quic_handle_timeout, 0, 0); } } -void init_random_cid(ngtcp2_cid *cid, size_t len) +int init_random_cid(ngtcp2_cid *cid, size_t len) { if (len == 0) len = SERVER_DEFAULT_SCIDLEN; - cid->datalen = dnssec_random_buffer(cid->data, len) == - /* DNSSEC_EOK */0 ? len : 0; + uint8_t buf[32]; + if (len > sizeof(buf)) { + len = sizeof(buf); + } + + int ret; + if ((ret = dnssec_random_buffer(buf, len)) == 0) { + ngtcp2_cid_init(cid, buf, len); + } + + return ret; } uint64_t cid2hash(const ngtcp2_cid *cid, kr_quic_table_t *table) @@ -157,19 +182,22 @@ uint64_t cid2hash(const ngtcp2_cid *cid, kr_quic_table_t *table) return ret; } -kr_quic_cid_t **kr_quic_table_lookup2(const ngtcp2_cid *cid, kr_quic_table_t *table) +kr_quic_cid_t **kr_quic_table_lookup2(const ngtcp2_cid *cid, + kr_quic_table_t *table) { uint64_t hash = cid2hash(cid, table); kr_quic_cid_t **res = table->conns + (hash % table->size); - while (*res != NULL && !ngtcp2_cid_eq(cid, (const ngtcp2_cid *)(*res)->cid_placeholder)) { + while (*res != NULL && !ngtcp2_cid_eq(cid, + (const ngtcp2_cid *)(*res)->cid_placeholder)) { res = &(*res)->next; } return res; } -struct pl_quic_conn_sess_data *kr_quic_table_lookup(const ngtcp2_cid *cid, kr_quic_table_t *table) +struct pl_quic_conn_sess_data *kr_quic_table_lookup(const ngtcp2_cid *cid, + kr_quic_table_t *table) { kr_quic_cid_t **pcid = kr_quic_table_lookup2(cid, table); return *pcid == NULL ? NULL : (*pcid)->conn_sess; @@ -178,6 +206,10 @@ struct pl_quic_conn_sess_data *kr_quic_table_lookup(const ngtcp2_cid *cid, kr_qu kr_quic_cid_t **kr_quic_table_insert(struct pl_quic_conn_sess_data *conn, const ngtcp2_cid *cid, kr_quic_table_t *table) { + if (kr_fails_assert(conn && cid && table)) { + return NULL; + } + uint64_t hash = cid2hash(cid, table); kr_quic_cid_t *cidobj = malloc(sizeof(*cidobj)); @@ -199,7 +231,7 @@ kr_quic_cid_t **kr_quic_table_insert(struct pl_quic_conn_sess_data *conn, int kr_quic_table_add(struct pl_quic_conn_sess_data *conn_sess, const ngtcp2_cid *cid, kr_quic_table_t *table) { - if (!conn_sess || !cid || !table) { + if (kr_fails_assert(conn_sess && cid && table)) { return kr_error(EINVAL); } @@ -210,7 +242,8 @@ int kr_quic_table_add(struct pl_quic_conn_sess_data *conn_sess, kr_quic_cid_t **addto = kr_quic_table_insert(conn_sess, cid, table); if (addto == NULL) { - heap_delete(table->expiry_heap, heap_find(table->expiry_heap, (heap_val_t *)conn_sess)); + heap_delete(table->expiry_heap, heap_find(table->expiry_heap, + (heap_val_t *)conn_sess)); return kr_error(ENOMEM); } @@ -218,46 +251,82 @@ int kr_quic_table_add(struct pl_quic_conn_sess_data *conn_sess, return kr_ok(); } +int kr_quic_table_rem2(kr_quic_cid_t **pcid, kr_quic_table_t *table) +{ + kr_quic_cid_t *cid = *pcid; + *pcid = cid->next; + cid->conn_sess->cid_pointers--; + free(cid); + table->pointers--; + + return kr_ok(); +} + +int set_tls_error(struct pl_quic_conn_sess_data *conn, + quic_doq_error_t *error_code, + const uint8_t *msg, size_t msglen) +{ + if (kr_fails_assert(conn && msglen < 128)) + return kr_error(EINVAL); + + /* Not redundant! ngtcp2 doesn't memcpy the message. + * msgs on the stack could therefore cause stack use after return + * once ngtcp2_conn_writev_stream is called. */ + if (msg && msglen > 0) { + memcpy(&conn->err_msg_buffer, msg, msglen); + } + + *error_code = 0x100 | ngtcp2_conn_get_tls_alert(conn->conn); + + ngtcp2_ccerr_set_tls_alert(&conn->ccerr, + ngtcp2_conn_get_tls_alert(conn->conn), + conn->err_msg_buffer, msglen); + + return kr_ok(); +} + int set_application_error(struct pl_quic_conn_sess_data *conn, quic_doq_error_t error_code, const uint8_t *msg, size_t msglen) { if (kr_fails_assert(conn && msglen < 128)) return kr_error(EINVAL); + /* Not redundant! ngtcp2 doesn't memcpy the message. + * msgs on the stack could therefore cause stack use after return + * once ngtcp2_conn_writev_stream is called. */ if (msg && msglen > 0) { memcpy(&conn->err_msg_buffer, msg, msglen); } + ngtcp2_ccerr_set_application_error(&conn->ccerr, error_code, conn->err_msg_buffer, msglen); return kr_ok(); } -bool init_unique_cid(ngtcp2_cid *cid, size_t len, kr_quic_table_t *table) +int init_unique_cid(ngtcp2_cid *cid, size_t len, kr_quic_table_t *table) { do { - if (init_random_cid(cid, len), cid->datalen == 0) - return false; + if (init_random_cid(cid, len) != 0) + return -1; } while (kr_quic_table_lookup(cid, table) != NULL); - return true; + return 0; } int write_retry_packet(struct wire_buf *dest, kr_quic_table_t *table, ngtcp2_version_cid *dec_cids, - const struct sockaddr *src_addr, - uint8_t *secret, size_t secret_len) + const struct sockaddr *src_addr) { - ngtcp2_cid dcid; - ngtcp2_cid scid; - ngtcp2_cid new_dcid; - - ngtcp2_cid_init(&dcid, dec_cids->dcid, dec_cids->dcidlen); - ngtcp2_cid_init(&scid, dec_cids->scid, dec_cids->scidlen); - - init_random_cid(&new_dcid, 0); - if (!init_unique_cid(&new_dcid, 0, table)) { + ngtcp2_cid odcid; + ngtcp2_cid oscid; + ngtcp2_cid retry_scid; + + ngtcp2_cid_init(&odcid, dec_cids->dcid, dec_cids->dcidlen); + ngtcp2_cid_init(&oscid, dec_cids->scid, dec_cids->scidlen); + init_random_cid(&retry_scid, 0); + if (init_unique_cid(&retry_scid, 0, table) != 0) { kr_log_debug(DOQ, "Failed to initialize unique cid for Retry packet\n"); return -1; } @@ -266,10 +335,10 @@ int write_retry_packet(struct wire_buf *dest, kr_quic_table_t *table, uint64_t now = quic_timestamp(); int ret = ngtcp2_crypto_generate_retry_token2( - retry_token, (const uint8_t *)secret, - secret_len, dec_cids->version, + retry_token, (const uint8_t *)table->hash_secret, + sizeof(table->hash_secret), dec_cids->version, src_addr, kr_sockaddr_len(src_addr), - &new_dcid, &dcid, now); + &retry_scid, &odcid, now); if (ret < 0) { kr_log_debug(DOQ, "Failed to generate retry token\n"); @@ -279,8 +348,8 @@ int write_retry_packet(struct wire_buf *dest, kr_quic_table_t *table, ret = ngtcp2_crypto_write_retry( wire_buf_free_space(dest), wire_buf_free_space_length(dest), - dec_cids->version, &scid, - &new_dcid, &dcid, + dec_cids->version, &oscid, + &retry_scid, &odcid, retry_token, ret ); diff --git a/daemon/quic_common.h b/daemon/quic_common.h index 71a5696d4..350c615aa 100644 --- a/daemon/quic_common.h +++ b/daemon/quic_common.h @@ -20,6 +20,9 @@ #include "session2.h" #include "network.h" +/* option to turn of ngtcp2 log which is by default enabled for log_level = debug */ +#define DEBUG_NGTCP2 false + /** RFC 9250 4.3 DoQ Error Codes for use as application protocol error codes */ typedef enum { /*! No error. This is used when the connection or stream needs to be @@ -41,7 +44,7 @@ typedef enum { error code. */ DOQ_UNSPECIFIED_ERROR = 0x5, /*! Alternative error code, can be used for tests. */ - //DOQ_ERROR_RESERVED = 0xd098ea5e + // DOQ_ERROR_RESERVED = 0xd098ea5e } quic_doq_error_t; // Macros from knot quic impl @@ -51,30 +54,30 @@ typedef enum { #define QUIC_SEND_RETRY NGTCP2_ERR_RETRY #define QUIC_SEND_STATELESS_RESET (-NGTCP2_STATELESS_RESET_TOKENLEN) #define QUIC_SEND_CONN_CLOSE (-2000) +/* Returning this value from handle_packet has to be accompanied + * by timer startup (if not already running). Otherwise the connection + * might linger in the table until the DoQ endpoint terminates. + * see NGTCP2_ERR_DROP_CONN switch case in handle_packet */ +#define QUIC_SEND_NONE (-2001) +/* maximum message length for messages used in ccerr */ #define MAX_REASONLEN 128 - #define BUCKETS_PER_CONNS 8 - #define MAX_QUIC_PKT_SIZE 65536 #define MAX_QUIC_FRAME_SIZE 65536 #define QUIC_MAX_SEND_PER_RECV 4 - -/* The connection can reach a state when the internal ngtcp2 state machine is - * not waiting for any event. In such cases the ngtcp2_conn_get_expiry - * returns UINT64_MAX => meaining no timer event. If the peer abruptly - * stops communicating in such a state the connection would be active - * untill a new connection prompts a conn table sweep. Instead of waiting - * for a new connection we set the timer to the QUIC_MAX_TIMEOUT to assure - * that every connection will terminate within a reasonable time period - * regardless of the incoming traffic. This is just a cleanliness measure. */ +/* A connection can reach a state where the internal ngtcp2 state machine is + * not waiting for any event. In such a case the ngtcp2_conn_get_expiry + * returns UINT64_MAX => meaning no timer event. If the peer abruptly + * stops communicating in such a state the connection remain in the table/ + * We set the timer to the QUIC_MAX_TIMEOUT to assure + * that every connection will terminate within a reasonable time period */ #define QUIC_MAX_IDLE_TIMEOUT (15 * NGTCP2_SECONDS) -#define QUIC_CONN_IDLE_TIMEOUT (2 * NGTCP2_SECONDS) -#define QUIC_HS_IDLE_TIMEOUT (3 * NGTCP2_SECONDS) +#define QUIC_CONN_IDLE_TIMEOUT (8 * NGTCP2_SECONDS) +#define QUIC_HS_IDLE_TIMEOUT (5 * NGTCP2_SECONDS) -#define container_of(ptr, type, member) ({ \ - const typeof( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)((char *)__mptr - offsetof(type,member));}) +#define container_of(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) typedef enum { KNOT_QUIC_TABLE_CLIENT_ONLY = (1 << 0), @@ -121,36 +124,82 @@ struct kr_quic_stream_param { }; int quic_configuration_set(void); + int quic_configuration_free(struct net_quic_params *quic_params); + bool kr_quic_conn_timeout(struct pl_quic_conn_sess_data *conn, uint64_t *now); + uint64_t cid2hash(const ngtcp2_cid *cid, kr_quic_table_t *table); + +/* ngtcp2 operates with ns whilst libuv uses ms. Simple inline ceil converter. */ +uint64_t quic_ns_to_ms_ceil(uint64_t ns); + +/* Closing/draining state PTO based timeout recommended by ngtcp2. The returned + * value is the time in ms that the connection state should be available for. + * Combine with quic_set_idle_timeout to take care of conn state + * teardown once the closing/draining state is reached. */ +uint64_t quic_get_closing_timeout(uint64_t pto_ns); + /* start uv_timer with timeout equal to ms, this timer is to be used * only during the handshake, after that the negotiated max_idle_timeout * will be used. Returns 0 or EINVAL */ int quic_set_hs_timeout(struct session2 *s, uint64_t ms); + +/* Only used when the connection entered closing/draining state + * and we have to wait 3 * PTO before freeing the connection (e.g. session). */ int quic_set_idle_timeout(struct session2 *s, uint64_t ms); + +/* Call this every time a bunch of writes are executed. + * The next expiry is retrieved and the connection timer is reset + * so ngtcp2 library can schedule next event */ void quic_reset_expiry(struct pl_quic_conn_sess_data *conn); + +int kr_quic_table_rem2(kr_quic_cid_t **pcid, kr_quic_table_t *table); + +/* Set the connection ccerr to type TRANSPORT with the error code + * containing the tls alert. The error code is calculated as + * 0x100 | (see RFC 9001 4.8. TLS Errors). */ +int set_tls_error(struct pl_quic_conn_sess_data *conn, + quic_doq_error_t *error_code, + const uint8_t *msg, size_t msglen); + +/* This function sets the ccerr type to APPLICATION and the error_code to + * the provided error_code. msg can be NULL, if so set mgslen to 0. + * see RFC 9250 4.3 DoQ Error Codes */ int set_application_error(struct pl_quic_conn_sess_data *conn, quic_doq_error_t error_code, const uint8_t *msg, size_t msglen); + +/* Inserts another cid into the table. This cid will point + * to the connection provided via the conn parameter. Can be called + * multiple times on the same connection (i.e. use when new cid for an + * existing connection is required). */ kr_quic_cid_t **kr_quic_table_insert(struct pl_quic_conn_sess_data *conn, const ngtcp2_cid *cid, kr_quic_table_t *table); + +/* Registeres a new connection in the table and expiry heap. + * Only call once when the connection is created. Calls kr_quic_table_insert + * to add a reference from cid to the connection. */ int kr_quic_table_add(struct pl_quic_conn_sess_data *conn_sess, const ngtcp2_cid *cid, kr_quic_table_t *table); -bool init_unique_cid(ngtcp2_cid *cid, size_t len, kr_quic_table_t *table); -void init_random_cid(ngtcp2_cid *cid, size_t len); + +int init_unique_cid(ngtcp2_cid *cid, size_t len, kr_quic_table_t *table); + +int init_random_cid(ngtcp2_cid *cid, size_t len); + uint64_t quic_timestamp(void); void quic_hs_timeout(uv_timer_t *timer); -void quic_idle_timeout(uv_timer_t *timer); -void quic_reset_expiry(struct pl_quic_conn_sess_data *conn); + +void quic_handle_timeout(uv_timer_t *timer); kr_quic_cid_t **kr_quic_table_lookup2(const ngtcp2_cid *cid, kr_quic_table_t *table); + struct pl_quic_conn_sess_data *kr_quic_table_lookup(const ngtcp2_cid *cid, kr_quic_table_t *table); + int write_retry_packet(struct wire_buf *dest, kr_quic_table_t *table, ngtcp2_version_cid *dec_cids, - const struct sockaddr *src_addr, - uint8_t *secret, size_t secret_len); + const struct sockaddr *src_addr); #endif diff --git a/daemon/quic_conn.c b/daemon/quic_conn.c index 3b69d373d..6e63930d6 100644 --- a/daemon/quic_conn.c +++ b/daemon/quic_conn.c @@ -6,6 +6,7 @@ #include "lib/proto.h" #include "network.h" #include "quic_common.h" +#include "quic_demux.h" #include "quic_stream.h" #include "lib/dnssec.h" #include "session2.h" @@ -14,12 +15,11 @@ #include #include #include +#include #include +#include #include -/* option to turn of ngtcp2 log which is usually enabled for log_level = debug */ -#define DEBUG_NGTCP2 true - #define EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE ((time_t)60*60*24*7) /* QUIC only works with TLSv1.3, auth KnotDNS have experienced issues @@ -51,11 +51,16 @@ static int handle_packet(struct pl_quic_conn_sess_data *conn, } if (ret == 0) { - /* given the 0 return value the pkt has been processed */ wire_buf_reset(ctx->payload.wire_buf); return kr_ok(); } + /* Avoid overwriting any more specific DoQ error codes. */ + if (ret < 0 && conn->ccerr.error_code == DOQ_NO_ERROR) { + ngtcp2_ccerr_set_liberr(&conn->ccerr, ret, NULL, 0); + } + + ngtcp2_duration pto_ns = 0; switch (ret) { case NGTCP2_ERR_RETRY: /* "Server must perform address validation by sending Retry packet @@ -63,21 +68,46 @@ static int handle_packet(struct pl_quic_conn_sess_data *conn, * and discard the connection state. Client application does * not get this error code." */ return QUIC_SEND_RETRY; + case NGTCP2_ERR_DROP_CONN: + *doq_error = DOQ_UNSPECIFIED_ERROR; + QUIC_SET_DRAINING(conn); + kr_log_debug(DOQ, "Connection was dropped, waiting 1 PTO before termination\n"); + pto_ns = ngtcp2_conn_get_pto(conn->conn); + quic_set_idle_timeout(conn->h.session, + quic_ns_to_ms_ceil(pto_ns)); + return QUIC_SEND_NONE; + case NGTCP2_ERR_DRAINING: - case NGTCP2_ERR_CLOSING: *doq_error = DOQ_UNSPECIFIED_ERROR; QUIC_SET_DRAINING(conn); - return QUIC_SEND_CONN_CLOSE; + kr_log_debug(DOQ, "Connection entered draining state, waiting 3 PTO\n"); + pto_ns = ngtcp2_conn_get_pto(conn->conn); + quic_set_idle_timeout(conn->h.session, + quic_get_closing_timeout(pto_ns)); + return QUIC_SEND_NONE; + + case NGTCP2_ERR_CLOSING: + *doq_error = DOQ_UNSPECIFIED_ERROR; + QUIC_SET_CLOSING(conn); + kr_log_debug(DOQ, "Connection entered closing state, waiting 3 PTO\n"); + pto_ns = ngtcp2_conn_get_pto(conn->conn); + quic_set_idle_timeout(conn->h.session, + quic_get_closing_timeout(pto_ns)); + return QUIC_SEND_NONE; + case NGTCP2_ERR_CRYPTO: - *doq_error = DOQ_INTERNAL_ERROR; - kr_log_error(DOQ, "TLS stack error %d\n", + /* see RFC 9001 4.8. TLS Errors */ + set_tls_error(conn, doq_error, NULL, 0); + kr_log_error(DOQ, "TLS stack alert: %d\n", ngtcp2_conn_get_tls_alert(conn->conn)); - QUIC_SET_DRAINING(conn); + return QUIC_SEND_CONN_CLOSE; + default: *doq_error = DOQ_UNSPECIFIED_ERROR; - QUIC_SET_CLOSING(conn); + kr_log_debug(DOQ, "Unspecified error (%d), sending connection close\n", + ret); return QUIC_SEND_CONN_CLOSE; } @@ -99,71 +129,73 @@ static int handshake_completed_cb(ngtcp2_conn *ngconn, void *user_data) struct pl_quic_conn_sess_data *conn = user_data; QUIC_SET_HS_COMPLETED(conn); - /* Notify worker that waiting tasks can be sent out */ if (conn->h.session->outgoing) { // pass } else { quic_reset_expiry(conn); } - return kr_ok(); + return NGTCP2_NO_ERROR; } static int kr_recv_stream_data_cb(ngtcp2_conn *ngconn, uint32_t flags, int64_t stream_id, uint64_t offset, const uint8_t *data, size_t datalen, void *user_data, void *stream_user_data) { + /* Stream session was already terminated because the connection closed */ + if (stream_user_data == NULL) { + return NGTCP2_NO_ERROR; + } + + (void)ngtcp2_conn_extend_max_stream_offset(ngconn, stream_id, datalen); + ngtcp2_conn_extend_max_offset(ngconn, datalen); + + const uint8_t msg[] = "malformed data"; struct pl_quic_conn_sess_data *conn = user_data; struct pl_quic_stream_sess_data *stream = stream_user_data; - if (kr_fails_assert(offset == wire_buf_data_length(&stream->pers_inbuf))) { - session2_force_close(stream->h.session); + /* just to be safe, ngtcp2 guarantees in order data with no gaps */ + if (unlikely(kr_fails_assert(offset == wire_buf_data_length( + &stream->pers_inbuf)))) { return NGTCP2_ERR_CALLBACK_FAILURE; } stream->incflags = flags; stream->sdata_offset = offset; - /* The length prefix hasn't arrived yet */ - if (wire_buf_data_length(&stream->pers_inbuf) < 2) { + if (wire_buf_data_length(&stream->pers_inbuf) < sizeof(uint16_t)) { if (datalen == 0) { - session2_force_close(stream->h.session); - return NGTCP2_ERR_CALLBACK_FAILURE; + goto malformed; } if (datalen == 1 && wire_buf_data_length(&stream->pers_inbuf) == 0) { /* received only the first length prefix octet */ stream->pers_inbuf.buf[0] = data[0]; - kr_require(wire_buf_consume(&stream->pers_inbuf, 1) == 0); + wire_buf_consume(&stream->pers_inbuf, 1); goto finished; } if (wire_buf_data_length(&stream->pers_inbuf) == 1) { /* finish partial length prefix octet * (previous first stream payload had datalen == 1) */ stream->pers_inbuf.buf[1] = data[0]; - kr_require(wire_buf_consume(&stream->pers_inbuf, 1) == 0); + wire_buf_consume(&stream->pers_inbuf, 1); data += 1; datalen--; } else { /* length prefix arrived in one piece */ knot_wire_write_u16(wire_buf_data(&stream->pers_inbuf), knot_wire_read_u16(data)); - kr_require(wire_buf_consume(&stream->pers_inbuf, - sizeof(uint16_t)) == 0); + wire_buf_consume(&stream->pers_inbuf, sizeof(uint16_t)); data += sizeof(uint16_t); datalen -= sizeof(uint16_t); } - uint16_t qsize = knot_wire_read_u16(wire_buf_data(&stream->pers_inbuf)); - uint16_t qsize_prefixed = qsize + sizeof(uint16_t); + uint32_t qsize = + knot_wire_read_u16(wire_buf_data(&stream->pers_inbuf)); + uint32_t qsize_prefixed = qsize + sizeof(uint16_t); if (qsize > wire_buf_free_space_length(&stream->pers_inbuf)) { - char *new_buf = realloc(stream->pers_inbuf.buf, - qsize_prefixed); - if (!new_buf) { - session2_force_close(stream->h.session); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - stream->pers_inbuf.buf = new_buf; - stream->pers_inbuf.size = qsize_prefixed; + uint32_t qsize_prefixed_safe = qsize_prefixed + 16; + wire_buf_reserve(&stream->pers_inbuf, + qsize_prefixed_safe); } if (datalen == 0) { goto finished; @@ -179,35 +211,50 @@ static int kr_recv_stream_data_cb(ngtcp2_conn *ngconn, uint32_t flags, goto finished; } - kr_require(wire_buf_free_space_length(&stream->pers_inbuf) >= datalen); + /* the peer sent more data than declared but the length prefix. + * close the connection with EPROTO and report malformed data */ + if (wire_buf_free_space_length(&stream->pers_inbuf) < datalen) { + uint32_t query_size = sizeof(uint16_t) + + knot_wire_read_u16(wire_buf_data(&stream->pers_inbuf)); + uint32_t payload_size = wire_buf_data_length(&stream->pers_inbuf); + kr_log_debug(DOQ, "invalid payload size, malformed data (%hu != %ju)\n", + query_size, payload_size + datalen); + goto malformed; + + } memcpy(wire_buf_free_space(&stream->pers_inbuf), data, datalen); wire_buf_consume(&stream->pers_inbuf, datalen); - (void)ngtcp2_conn_extend_max_stream_offset(ngconn, stream_id, datalen); - ngtcp2_conn_extend_max_offset(ngconn, datalen); finished: if (flags & NGTCP2_STREAM_DATA_FLAG_FIN) { - uint16_t query_size = sizeof(uint16_t) + + if (wire_buf_data_length(&stream->pers_inbuf) < sizeof(uint16_t)) { + kr_log_debug(DOQ, "invalid payload size, malformed data (payload < 2)\n"); + goto malformed; + } + uint32_t query_size = sizeof(uint16_t) + knot_wire_read_u16(wire_buf_data(&stream->pers_inbuf)); - size_t payload_size = wire_buf_data_length(&stream->pers_inbuf); + uint32_t payload_size = wire_buf_data_length(&stream->pers_inbuf); - /* a remote endpoint might maliciously or accidentally send - * less that advertised by the length prefix => malformed data. - * respond by closing the connection forcefully. - * (see: RFC 9250 4.3.3. Protocol Errors) */ if (query_size != payload_size) { - kr_log_debug(DOQ, "length mismatch between length prefix and actual length of the query (%hu != %zu)\n", + kr_log_debug(DOQ, "invalid payload size, malformed data (%hu != %hu)\n", query_size, payload_size); - const uint8_t msg[] = "Incomplete query"; - set_application_error(conn, DOQ_PROTOCOL_ERROR, - msg, sizeof(msg) - 1); - return NGTCP2_ERR_CALLBACK_FAILURE; + goto malformed; } session2_inc_refs(stream->h.session); queue_push(conn->pending_unwrap, stream); } - return kr_ok(); + return NGTCP2_NO_ERROR; +/* a remote endpoint might maliciously or accidentally send + * less that advertised by the length prefix => malformed data. + * respond by closing the connection forcefully. + * (see: RFC 9250 4.3.3. Protocol Errors) */ +malformed: + QUIC_SET_CLOSING(conn); + set_application_error(conn, DOQ_PROTOCOL_ERROR, + msg, sizeof(msg) - 1); + return NGTCP2_ERR_CALLBACK_FAILURE; + } static int acked_stream_data_offset_cb(ngtcp2_conn *ngconn, @@ -215,6 +262,10 @@ static int acked_stream_data_offset_cb(ngtcp2_conn *ngconn, void *user_data, void *stream_user_data) { (void)ngconn; + /* Stream session was already terminated because the connection closed */ + if (stream_user_data == NULL) { + return NGTCP2_NO_ERROR; + } struct pl_quic_stream_sess_data *stream = stream_user_data; kr_quic_stream_ack_data(stream, stream_id, offset + datalen, false); return NGTCP2_NO_ERROR; @@ -233,21 +284,21 @@ static int stream_open_cb(ngtcp2_conn *ngconn, .protocol = PROTOLAYER_TYPE_QUIC_STREAM, .param = ¶ms }; - struct session2 *new_subsession = - session2_new_child(conn->h.session, - KR_PROTO_DOQ_STREAM, - &data_param, - 1, - false); + struct session2 *new_subsession = + session2_new_child(conn->h.session, KR_PROTO_DOQ_STREAM, + &data_param, 1, false); if (!new_subsession) { kr_log_error(DOQ, "Failed to create new quic stream session\n"); - return kr_error(ENOMEM); + /* TODO could use EXCESSIVE_LOAD */ + set_application_error(conn, DOQ_INTERNAL_ERROR, NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; } struct pl_quic_stream_sess_data *stream = protolayer_sess_data_get_proto(new_subsession, PROTOLAYER_TYPE_QUIC_STREAM); + kr_require(stream); stream->conn_ref = conn; if (conn->streams_count <= 0) { add_head(&conn->streams, &stream->list_node); @@ -267,10 +318,17 @@ static int stream_close_cb(ngtcp2_conn *ngconn, uint32_t flags, int64_t stream_id, uint64_t app_error_code, void *user_data, void *stream_user_data) { + /* Stream session was already terminated because the connection closed */ + if (stream_user_data == NULL) { + return NGTCP2_NO_ERROR; + } + struct pl_quic_conn_sess_data *conn = user_data; struct pl_quic_stream_sess_data *stream = stream_user_data; + + stream->closed = true; + session2_close(stream->h.session); - --conn->streams_count; ++conn->finished_streams; return NGTCP2_NO_ERROR; @@ -280,7 +338,7 @@ static void kr_quic_rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { (void)rand_ctx; - dnssec_random_buffer(dest, destlen); + (void)dnssec_random_buffer(dest, destlen); } static int get_new_connection_id_cb(ngtcp2_conn *ngconn, ngtcp2_cid *cid, @@ -288,16 +346,24 @@ static int get_new_connection_id_cb(ngtcp2_conn *ngconn, ngtcp2_cid *cid, { (void)ngconn; struct pl_quic_conn_sess_data *conn = user_data; - session2_event(conn->h.session->transport.parent, - PROTOLAYER_EVENT_CONNECT_UPDATE, conn); - memcpy(cid, &conn->dcid, sizeof(ngtcp2_cid)); + if (init_unique_cid(cid, cidlen, conn->table_ref) != 0) { + kr_log_error(DOQ, "Failed to create init new cid\n"); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + memcpy(&conn->dcid, cid, sizeof(ngtcp2_cid)); if (ngtcp2_crypto_generate_stateless_reset_token(token, conn->secret, sizeof(conn->secret), cid) != 0) { + kr_log_error(DOQ, "Failed to generate stateless reset token\n"); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (kr_quic_table_insert(conn, cid, conn->table_ref) == NULL) { + kr_log_error(DOQ, "Failed to add new cid to conn map\n"); return NGTCP2_ERR_CALLBACK_FAILURE; } - return 0; + return NGTCP2_NO_ERROR; } int remove_connection_id_cb(ngtcp2_conn *ngconn, @@ -305,15 +371,22 @@ int remove_connection_id_cb(ngtcp2_conn *ngconn, { (void)ngconn; struct pl_quic_conn_sess_data *conn = user_data; - ngtcp2_cid save_cid = conn->dcid; - memcpy(&conn->dcid, cid, sizeof(ngtcp2_cid)); - session2_event(conn->h.session->transport.parent, - PROTOLAYER_EVENT_CONNECT_RETIRE, - conn); - conn->dcid = save_cid; - return 0; + + kr_quic_cid_t **pcid = kr_quic_table_lookup2(cid, conn->table_ref); + if (!pcid || !*pcid) { + kr_log_error(DOQ, "Table doesn't contain cid that is to be removed\n"); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if ((*pcid)->conn_sess->cid_pointers <= 1) { + kr_log_error(DOQ, "Cannot remove all connection ids, protocol error\n"); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return kr_quic_table_rem2(pcid, conn->table_ref); } +#if DEBUG_NGTCP2 static void quic_debug_cb(void *user_data, const char *format, ...) { char buf[256]; @@ -323,6 +396,7 @@ static void quic_debug_cb(void *user_data, const char *format, ...) kr_log_debug(DOQ_LIBNGTCP2, "%s\n", buf); va_end(args); } +#endif /* DEBUG_NGTCP2 */ static int conn_new_handler(ngtcp2_conn **pconn, const ngtcp2_path *path, const ngtcp2_cid *scid, const ngtcp2_cid *dcid, @@ -343,6 +417,8 @@ static int conn_new_handler(ngtcp2_conn **pconn, const ngtcp2_path *path, .acked_stream_data_offset = acked_stream_data_offset_cb, .stream_open = stream_open_cb, .stream_close = stream_close_cb, + // .stream_stop_sending - OPTIONAL + // .stream_reset - OPTIONAL // .recv_stateless_reset, - OPTIONAL // .ngtcp2_crypto_recv_retry_cb, - OPTIONAL // .extend_max_streams_bidi - OPTIONAL @@ -353,7 +429,6 @@ static int conn_new_handler(ngtcp2_conn **pconn, const ngtcp2_path *path, .update_key = ngtcp2_crypto_update_key_cb, // .path_validation, - OPTIONAL // .select_preferred_addr - OPTIONAL - // .stream_rst, - OPTIONAL // .extend_max_remote_streams_bidi - OPTIONAL // .extend_max_remote_streams_uni - OPTIONAL // .extend_max_stream_data, - OPTIONAL @@ -366,7 +441,6 @@ static int conn_new_handler(ngtcp2_conn **pconn, const ngtcp2_path *path, // .ack_datagram - OPTIONAL // .lost_datagram - OPTIONAL .get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb, - // .stream_stop_sending - OPTIONAL .version_negotiation = ngtcp2_crypto_version_negotiation_cb, // .recv_rx_key - OPTIONAL // .recv_tx_key - OPTIONAL @@ -375,42 +449,32 @@ static int conn_new_handler(ngtcp2_conn **pconn, const ngtcp2_path *path, ngtcp2_settings settings; ngtcp2_settings_default(&settings); settings.initial_ts = now; - - if (KR_LOG_LEVEL_IS(LOG_DEBUG)) { - settings.log_printf = quic_debug_cb; - } - - size_t limit = - protolayer_globals[PROTOLAYER_TYPE_QUIC_CONN].wire_buf_max_overhead; - - if (limit != 0) { - settings.max_tx_udp_payload_size = limit; - } - + settings.max_tx_udp_payload_size = NGTCP2_MAX_UDP_PAYLOAD_SIZE; settings.handshake_timeout = QUIC_HS_IDLE_TIMEOUT; +#if DEBUG_NGTCP2 + settings.log_printf = quic_debug_cb; +#endif /* DEBUG_NGTCP2 */ settings.no_pmtud = true; ngtcp2_transport_params params; ngtcp2_transport_params_default(¶ms); - - /** This informs peer that active migration might not be available. - * Peer might still attempt to migrate. see RFC 9000/5.2.3 */ - params.disable_active_migration = true; - - /* We have no use for unidirectional streams */ - params.initial_max_streams_uni = 0; - if (unlikely(kr_fails_assert(the_network && the_network->quic_params))) { kr_log_debug(DOQ, "Missing network struct or network quic_parameters\n"); return kr_error(EINVAL); } params.initial_max_streams_bidi = the_network->quic_params->max_streams; + /* DoQ has no use for unidirectional streams */ + params.initial_max_streams_uni = 0; params.initial_max_stream_data_bidi_local = MAX_QUIC_FRAME_SIZE; params.initial_max_stream_data_bidi_remote = MAX_QUIC_FRAME_SIZE; params.initial_max_data = MAX_QUIC_PKT_SIZE; + /** This informs peer that active migration might not be available. + * Peer might still attempt to migrate. + * see RFC 9000 5.2.3 Considerations for Simple Load Balancers */ + params.disable_active_migration = true; params.max_idle_timeout = QUIC_CONN_IDLE_TIMEOUT; - params.stateless_reset_token_present = 1; - // params.active_connection_id_limit = 8; + params.stateless_reset_token_present = + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; if (odcid != NULL) { params.original_dcid = *odcid; @@ -418,7 +482,7 @@ static int conn_new_handler(ngtcp2_conn **pconn, const ngtcp2_path *path, } if (retry_sent) { - /* retry scid is retrieved from the + /* retry scid is retrieved from * ngtcp2_crypto_verify_retry_roken2 as the odcid * used by the client. */ params.retry_scid = *dcid; @@ -434,13 +498,14 @@ static int conn_new_handler(ngtcp2_conn **pconn, const ngtcp2_path *path, if (server) { const ngtcp2_cid *client_dcid = scid; const ngtcp2_cid *client_scid = dcid; - return ngtcp2_conn_server_new(pconn, client_dcid, client_scid, path, - version, &callbacks, &settings, ¶ms, NULL, conn); + return ngtcp2_conn_server_new(pconn, client_dcid, client_scid, + path, version, &callbacks, &settings, ¶ms, + NULL, conn); } else { kr_log_warning(DOQ, "Client side is not implemented\n"); return kr_error(EINVAL); - // return ngtcp2_conn_client_new(pconn, dcid, scid, path, version, &callbacks, - // &settings, ¶ms, NULL, conn); + // return ngtcp2_conn_client_new(pconn, dcid, scid, path, version, + // &callbacks, &settings, ¶ms, NULL, conn); } } @@ -474,10 +539,12 @@ int kr_tls_server_session(struct pl_quic_conn_sess_data *conn) } time_t now = time(NULL); - if (the_network->tls_credentials->valid_until != GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION) { + if (the_network->tls_credentials->valid_until + != GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION) { if (the_network->tls_credentials->ephemeral_servicename) { /* ephemeral cert: refresh if due to expire within a week */ - if (now >= the_network->tls_credentials->valid_until - EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE) { + if (now >= the_network->tls_credentials->valid_until + - EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE) { struct tls_credentials *newcreds = tls_get_ephemeral_credentials(); if (newcreds) { tls_credentials_release(the_network->tls_credentials); @@ -495,28 +562,40 @@ int kr_tls_server_session(struct pl_quic_conn_sess_data *conn) } } - gnutls_priority_init2(&conn->priority, NULL, NULL, 0); + int ret = gnutls_priority_init2(&conn->priority, NULL, NULL, 0); + if (ret != GNUTLS_E_SUCCESS) { + kr_log_error(TLS, "gnutls_priority_init2(): %s (%d)\n", + gnutls_strerror_name(ret), ret); + return ret; + } int flags = GNUTLS_SERVER | GNUTLS_NONBLOCK; #if GNUTLS_VERSION_NUMBER >= 0x030705 if (gnutls_check_version("3.7.5")) flags |= GNUTLS_NO_TICKETS_TLS12; #endif - int ret = gnutls_init(&conn->tls_session, flags); + ret = gnutls_init(&conn->tls_session, flags); if (ret != GNUTLS_E_SUCCESS) { - kr_log_error(TLS, "gnutls_init(): %s (%d)\n", gnutls_strerror_name(ret), ret); + kr_log_error(TLS, "gnutls_init(): %s (%d)\n", + gnutls_strerror_name(ret), ret); return ret; } gnutls_certificate_send_x509_rdn_sequence(conn->tls_session, 1); gnutls_certificate_server_set_request(conn->tls_session, GNUTLS_CERT_IGNORE); ret = gnutls_priority_set(conn->tls_session, conn->priority); + if (ret != GNUTLS_E_SUCCESS) { + kr_log_error(TLS, "gnutls_priority_set(): %s (%d)\n", + gnutls_strerror_name(ret), ret); + return ret; + } conn->server_credentials = tls_credentials_reserve(the_network->tls_credentials); ret = gnutls_credentials_set(conn->tls_session, GNUTLS_CRD_CERTIFICATE, conn->server_credentials->credentials); if (ret != GNUTLS_E_SUCCESS) { - kr_log_error(TLS, "gnutls_credentials_set(): %s (%d)\n", gnutls_strerror_name(ret), ret); + kr_log_error(TLS, "gnutls_credentials_set(): %s (%d)\n", + gnutls_strerror_name(ret), ret); return ret; } @@ -527,7 +606,7 @@ int kr_tls_server_session(struct pl_quic_conn_sess_data *conn) kr_log_error(TLS, "setting priority '%s' failed at character %zd (...'%s') with %s (%d)\n", tlsv13_priorities, errpos - tlsv13_priorities, errpos, gnutls_strerror_name(err), err); - return kr_error(EINVAL); + return ret; } if (the_network->tls_session_ticket_ctx) { @@ -539,7 +618,7 @@ int kr_tls_server_session(struct pl_quic_conn_sess_data *conn) .data = (void *)"doq", .size = 3 }; - gnutls_alpn_set_protocols(conn->tls_session, &alpn_datum, 1, + ret = gnutls_alpn_set_protocols(conn->tls_session, &alpn_datum, 1, GNUTLS_ALPN_MANDATORY); if (ret != GNUTLS_E_SUCCESS) { kr_log_error(TLS, "gnutls_alpn_set_protocols(): %s (%d)\n", gnutls_strerror_name(ret), ret); @@ -604,9 +683,8 @@ int quic_init_server_conn(struct pl_quic_conn_sess_data *conn, conn->dec_cids.version, now, true, conn->retry_sent, conn); - - if (ret >= 0) { - ret = tls_init_conn_session(conn, true);; + if (ret == 0) { + ret = tls_init_conn_session(conn, true); } return ret; @@ -615,15 +693,18 @@ int quic_init_server_conn(struct pl_quic_conn_sess_data *conn, int quic_flush_streams(struct pl_quic_conn_sess_data *conn) { kr_require(conn); - node_t *s_node; - WALK_LIST(s_node, conn->streams) { + node_t *s_node = 0, *next = 0; + bool sent = false; + WALK_LIST_DELSAFE(s_node, next, conn->streams) { struct pl_quic_stream_sess_data *s = container_of(s_node, struct pl_quic_stream_sess_data, list_node); /* Skip streams that have no data to send */ - if (s->unsent_obuf == NULL) + if (s->write_closed || s->unsent_obuf == NULL) { continue; + } + s->skip_update_time = true; if (session2_wrap_after(s->h.session, PROTOLAYER_TYPE_DNS_SINGLE_STREAM, protolayer_payload_wire_buf( @@ -632,8 +713,14 @@ int quic_flush_streams(struct pl_quic_conn_sess_data *conn) NULL, NULL) < 0) { /* Either closing or invalid */ - session2_close(s->h.session); } + + s->skip_update_time = false; + sent = true; + } + + if (sent) { + ngtcp2_conn_update_pkt_tx_time(conn->conn, quic_timestamp()); } return kr_ok(); @@ -669,9 +756,13 @@ int send_special(ngtcp2_version_cid *dec_cids, kr_quic_table_t *table, struct pl_quic_conn_sess_data *conn, struct session2 *session, quic_doq_error_t *doq_error) { + if (kr_fails_assert(ctx)) { + return kr_error(EINVAL); + } char *err_buf = mm_alloc(&ctx->pool, NGTCP2_MAX_UDP_PAYLOAD_SIZE); if (!err_buf) return kr_error(ENOMEM); + struct wire_buf err_wb = { .buf = err_buf, .end = 0, @@ -688,9 +779,12 @@ int send_special(ngtcp2_version_cid *dec_cids, kr_quic_table_t *table, ngtcp2_pkt_info pi = { 0 }; uint8_t rnd = 0; - int ret = 0; + int ret = kr_error(EINVAL); switch (action) { case QUIC_SEND_VERSION_NEGOTIATION: + if (kr_fails_assert(!conn && dec_cids)) { + break; + } kr_require(!conn); dnssec_random_buffer(&rnd, sizeof(rnd)); uint32_t supported_quic[1] = { NGTCP2_PROTO_VER_V1 }; @@ -703,21 +797,27 @@ int send_special(ngtcp2_version_cid *dec_cids, kr_quic_table_t *table, ); break; case QUIC_SEND_RETRY: - kr_require(dec_cids); + if (kr_fails_assert(dec_cids && table)) { + break; + } ret = write_retry_packet(ctx->payload.wire_buf, table, dec_cids, - ctx->comm->src_addr, - (uint8_t *)table->hash_secret, - sizeof(table->hash_secret)); + ctx->comm->src_addr); + if (conn) { - QUIC_SET_CLOSING(conn); + QUIC_SET_HS_ABORT(conn); } break; case QUIC_SEND_CONN_CLOSE: - if (!conn || !QUIC_CAN_SEND(conn)) { + if (kr_fails_assert(conn && session)) { + break; + } + if (!QUIC_CAN_SEND(conn)) { + ret = kr_ok(); break; } + /* Keep the more specific last error */ if (conn->ccerr.error_code == DOQ_NO_ERROR) { set_application_error(conn, *doq_error, NULL, 0); } @@ -728,39 +828,58 @@ int send_special(ngtcp2_version_cid *dec_cids, kr_quic_table_t *table, wire_buf_free_space_length(ctx->payload.wire_buf), &conn->ccerr, now); + kr_log_debug(DOQ, "Connection entered closing state, waiting 3 PTO\n"); QUIC_SET_CLOSING(conn); + quic_set_idle_timeout(conn->h.session, quic_get_closing_timeout( + ngtcp2_conn_get_pto(conn->conn))); break; /* Unused for now */ // case QUIC_SEND_STATELESS_RESET: default: - ret = kr_error(EINVAL); break; } if (ret > 0) { wire_buf_consume(ctx->payload.wire_buf, ret); - session2_wrap_after(session, - PROTOLAYER_TYPE_QUIC_CONN, - ctx->payload, - ctx->comm, - ctx->finished_cb, - ctx->finished_cb_baton); + if (session->proto == KR_PROTO_DOQ_CONN) { + session2_wrap_after(session, PROTOLAYER_TYPE_QUIC_CONN, + ctx->payload, ctx->comm, + ctx->finished_cb, + ctx->finished_cb_baton); + } else { + /* send special can be called from demux layer + * where the conn session might not exist yet. */ + session2_wrap(session, ctx->payload, ctx->comm, + NULL, ctx->finished_cb, + ctx->finished_cb_baton); + } ret = kr_ok(); } - mm_free(&ctx->pool, ctx->payload.wire_buf); + mm_free(&ctx->pool, ctx->payload.wire_buf->buf); ctx->payload.wire_buf = save_wb; return ret; } +/* If the packet is INITIAL and the connection setup fails simply setting the + * conn->state to DRAINING will result in session teardown. */ static enum protolayer_iter_cb_result pl_quic_conn_unwrap(void *sess_data, void *iter_data, struct protolayer_iter_ctx *ctx) { int ret = kr_ok(); struct pl_quic_conn_sess_data *conn = sess_data; + if (conn->state & (QUIC_STATE_DRAINING | QUIC_STATE_HS_ABORT)) { + return protolayer_break(ctx, kr_ok()); + } + + /* Deferred payload (async processing) switches the buffer type + * (see protolayer_payload_ensure_long_lived) */ if (ctx->payload.type == PROTOLAYER_PAYLOAD_BUFFER) { struct wire_buf *wb = mm_alloc(&ctx->pool, sizeof(struct wire_buf)); + if (!wb) { + return protolayer_break(ctx, kr_error(ENOMEM)); + } wb->size = ctx->payload.buffer.len; wb->buf = ctx->payload.buffer.buf; wb->end = wb->size; @@ -783,9 +902,12 @@ static enum protolayer_iter_cb_result pl_quic_conn_unwrap(void *sess_data, quic_doq_error_t doq_error; ret = handle_packet(conn, ctx, &doq_error); if (ret != kr_ok()) { - (void)send_special(&conn->dec_cids, - conn->table_ref, ctx, ret, conn, - conn->h.session, &doq_error); + if (ret != QUIC_SEND_NONE) { + (void)send_special(&conn->dec_cids, + conn->table_ref, ctx, ret, conn, + conn->h.session, &doq_error); + } + return protolayer_break(ctx, kr_ok()); } @@ -827,6 +949,24 @@ static enum protolayer_iter_cb_result pl_quic_conn_wrap(void *sess_data, kr_quic_set_addrs(ctx, &conn->path); + /* Flush bye message and promote state to DRAINING */ + if (conn->state & QUIC_STATE_CLOSING) { + quic_doq_error_t doq_error = DOQ_NO_ERROR; + int ret = send_special(&conn->dec_cids, + conn->table_ref, + ctx, + QUIC_SEND_CONN_CLOSE, + conn, + conn->h.session, + &doq_error); + if (ret < 0) { + kr_log_debug(DOQ, "sending CONNECTION_CLOSE failed: %d\n", + ret); + } + QUIC_SET_DRAINING(conn); + return protolayer_break(ctx, ret < 0 ? ret : 0); + } + if (ctx->payload.type != PROTOLAYER_PAYLOAD_IOVEC) { ngtcp2_ssize sent = 0; ngtcp2_conn_info info = { 0 }; @@ -838,27 +978,21 @@ static enum protolayer_iter_cb_result pl_quic_conn_wrap(void *sess_data, } int nwrite = ngtcp2_conn_writev_stream(conn->conn, - conn->path, - &pi, wire_buf_free_space(ctx->payload.wire_buf), + conn->path, &pi, + wire_buf_free_space(ctx->payload.wire_buf), wire_buf_free_space_length(ctx->payload.wire_buf), &sent, NGTCP2_WRITE_STREAM_FLAG_NONE, -1, NULL, 0, quic_timestamp()); if (nwrite > 0) ngtcp2_conn_update_pkt_tx_time(conn->conn, quic_timestamp()); - if (nwrite == 0) + if (nwrite == 0) { quic_reset_expiry(conn); + return protolayer_break(ctx, kr_ok()); + } - if (nwrite <= 0) { - if (nwrite == NGTCP2_ERR_NOMEM) { - size_t new_size = ctx->payload.wire_buf->size + 1024; - char *new_buf = realloc(ctx->payload.wire_buf->buf, new_size); - kr_require(new_buf); - ctx->payload.wire_buf->buf = new_buf; - ctx->payload.wire_buf->size = new_size; - ctx->payload.wire_buf->end = new_size; - } - + if (nwrite < 0) { + ngtcp2_ccerr_set_liberr(&conn->ccerr, nwrite, NULL, 0); return protolayer_break(ctx, kr_ok()); } @@ -893,7 +1027,8 @@ int quic_generate_secret(uint8_t *buf, size_t buflen) return kr_ok(); } -static int pl_quic_conn_sess_init(struct session2 *session, void *sess_data, void *param) +static int pl_quic_conn_sess_init(struct session2 *session, void *sess_data, + void *param) { struct pl_quic_conn_sess_data *conn = sess_data; conn->state = 0; @@ -914,21 +1049,6 @@ static int pl_quic_conn_sess_init(struct session2 *session, void *sess_data, voi memcpy(&conn->dec_cids, p->dec_cids, sizeof(ngtcp2_version_cid)); struct comm_info *comm = p->comm_storage; - if (comm->src_addr) { - int len = kr_sockaddr_len(comm->src_addr); - kr_require(len > 0 && len <= sizeof(union kr_sockaddr)); - memcpy(&conn->comm_storage.src_addr, comm->src_addr, len); - } - if (comm->comm_addr) { - int len = kr_sockaddr_len(comm->comm_addr); - kr_require(len > 0 && len <= sizeof(union kr_sockaddr)); - memcpy(&conn->comm_storage.comm_addr, comm->comm_addr, len); - } - if (comm->dst_addr) { - int len = kr_sockaddr_len(comm->dst_addr); - kr_require(len > 0 && len <= sizeof(union kr_sockaddr)); - memcpy(&conn->comm_storage.dst_addr, comm->dst_addr, len); - } conn->comm_storage = *comm; session->comm_storage = conn->comm_storage; @@ -943,13 +1063,10 @@ static int pl_quic_conn_sess_init(struct session2 *session, void *sess_data, voi conn->tls_session = NULL; conn->server_credentials = NULL; if (unlikely(quic_generate_secret(conn->secret, sizeof(conn->secret)) != kr_ok())) { - pl_quic_conn_sess_deinit(conn->h.session, conn); kr_log_error(DOQ, "Failed to init connection session\n"); return kr_error(EINVAL); } - quic_set_hs_timeout(session, QUIC_CONN_IDLE_TIMEOUT / NGTCP2_MILLISECONDS); - return kr_ok(); } @@ -959,11 +1076,20 @@ static int pl_quic_conn_sess_deinit(struct session2 *session, void *sess_data) while (session2_tasklist_del_first(session, false) != NULL); session2_timer_stop(session); - struct pl_quic_stream_sess_data *s_node; - kr_log_debug(DOQ, "Closing connection, %s useful, served %zu streams\n", - conn->finished_streams ? "was" : "wasn't", - conn->finished_streams); + kr_require(session2_is_empty(session)); + kr_require(EMPTY_LIST(conn->streams)); // NOLINT(bugprone-casting-through-void) + kr_require(conn->cid_pointers == 0); + kr_require(conn->streams_count == 0); + + if (conn->state & QUIC_STATE_HANDSHAKE_DONE) { + kr_log_debug(DOQ, "Closing established connection, [%s] useful, served %zu streams\n", + conn->finished_streams ? "was" : "was not", + conn->finished_streams); + } else { + kr_log_debug(DOQ, "Closing connection with unfinished HS, wasn't useful, [%s] send retry\n", + conn->retry_sent ? "did" : "did not"); + } if (conn->priority) { gnutls_priority_deinit(conn->priority); @@ -993,60 +1119,57 @@ static int pl_quic_conn_sess_deinit(struct session2 *session, void *sess_data) return kr_ok(); } +static int quic_bye(struct pl_quic_conn_sess_data *conn) +{ + if (!conn || !conn->conn) { + return kr_error(EINVAL); + } + + struct protolayer_payload payload = + protolayer_payload_wire_buf(&conn->h.session->wire_buf, true); + + return session2_wrap(conn->h.session, + payload, + &conn->comm_storage, + NULL, + /* finished_cb: Free to be used */NULL, + /* finished_cb_baton: Free to be used */NULL); +} + static enum protolayer_event_cb_result pl_quic_conn_event_unwrap( enum protolayer_event_type event, void **baton, struct session2 *session, void *sess_data) { struct pl_quic_conn_sess_data *conn = sess_data; - if (event == PROTOLAYER_EVENT_DISCONNECT - || event == PROTOLAYER_EVENT_CLOSE - || event == PROTOLAYER_EVENT_FORCE_CLOSE - || event == PROTOLAYER_EVENT_GENERAL_TIMEOUT - || event == PROTOLAYER_EVENT_CONNECT_TIMEOUT) { + switch (event) { + case PROTOLAYER_EVENT_CLOSE: + QUIC_SET_CLOSING(conn); + (void)quic_bye(conn); + /* fallthrough */ + case PROTOLAYER_EVENT_FORCE_CLOSE: + case PROTOLAYER_EVENT_CONNECT_TIMEOUT: + case PROTOLAYER_EVENT_GENERAL_TIMEOUT: while (queue_len(conn->pending_unwrap) > 0) { struct session2 *s = queue_head(conn->pending_unwrap)->h.session; + queue_pop(conn->pending_unwrap); session2_dec_refs(s); } node_t *s_node; - uint64_t last_stream_id = -1; - bool forced = false; - WALK_LIST_FIRST(s_node, conn->streams) { + node_t *temp_ptr; + WALK_LIST_DELSAFE(s_node, temp_ptr, conn->streams) { struct pl_quic_stream_sess_data *s = container_of(s_node, struct pl_quic_stream_sess_data, list_node); - /* stream has a pending task to finish. If force - * try force_close, otherwise defer conn termination */ - if (s->stream_id == last_stream_id) { - if (event == PROTOLAYER_EVENT_FORCE_CLOSE - && !forced) { - session2_force_close(s->h.session); - forced = true; - continue; - } - - session2_touch(session); - session2_timer_restart(session); - return PROTOLAYER_EVENT_CONSUME; - } - - last_stream_id = s->stream_id; - - (void)ngtcp2_conn_shutdown_stream(s->conn, 0, s->stream_id, 0); session2_close(s->h.session); - /* These streams die with the connection, stream_close_cb - * will not be called so adjust counters here. */ - --conn->streams_count; - ++conn->finished_streams; + s = NULL; } - if (!EMPTY_LIST(conn->streams)) { - return PROTOLAYER_EVENT_CONSUME; - } + kr_require(EMPTY_LIST(conn->streams)); // NOLINT(bugprone-casting-through-void) if (!conn->disconnected) { if (event == PROTOLAYER_EVENT_CLOSE) { @@ -1056,10 +1179,14 @@ static enum protolayer_event_cb_result pl_quic_conn_event_unwrap( } conn->disconnected = true; } + /* fallthrough */ + default: + /* Propagate into wrap direction of quic_conn session. + * If possible we will unref the session and free it. + * (in the unwrap direction the event propagation works only + * within the session, subsessions were handled manually) */ return PROTOLAYER_EVENT_PROPAGATE; } - - return PROTOLAYER_EVENT_PROPAGATE; } static enum protolayer_event_cb_result pl_quic_conn_event_wrap( @@ -1074,21 +1201,25 @@ static enum protolayer_event_cb_result pl_quic_conn_event_wrap( if (baton && *baton) { struct pl_quic_stream_sess_data *stream = *baton; rem_node(&stream->list_node); - session2_dec_refs(stream->h.session); + stream->conn_ref->streams_count--; + ngtcp2_conn_extend_max_streams_bidi(stream->conn, 1); + ngtcp2_conn_shutdown_stream(stream->conn, 0, + stream->stream_id, 0); + ngtcp2_conn_set_stream_user_data(conn->conn, + stream->stream_id, NULL); + stream->conn = NULL; + stream->conn_ref = NULL; + uv_close((uv_handle_t *)&stream->h.session->timer, + on_session2_timer_close); + return PROTOLAYER_EVENT_CONSUME; } - if (EMPTY_LIST(conn->streams) && conn->disconnected) { + // NOLINTNEXTLINE(bugprone-casting-through-void) + if (baton && EMPTY_LIST(conn->streams) && conn->disconnected) { /* Connection can be terminated */ *baton = conn; return PROTOLAYER_EVENT_PROPAGATE; } - - /* Close terminates the timer. We failed to close - * due to a stream waiting for a task to finish - * so we restart the timer and try again */ - session2_touch(session); - session2_timer_restart(session); - return PROTOLAYER_EVENT_CONSUME; } return PROTOLAYER_EVENT_PROPAGATE; diff --git a/daemon/quic_conn.h b/daemon/quic_conn.h index 83de65aa5..981b6198d 100644 --- a/daemon/quic_conn.h +++ b/daemon/quic_conn.h @@ -25,7 +25,10 @@ typedef enum { QUIC_STATE_AUTHORIZED = (1 << 3), QUIC_STATE_EPROTO = (1 << 4), QUIC_STATE_CLOSING = (1 << 5), + /* Following states prevent connections from writing (retry packet + * is an exception, it doesn't require ngtcp2_conn struct). */ QUIC_STATE_DRAINING = (1 << 6), + QUIC_STATE_HS_ABORT = (1 << 7), } quic_conn_state_t; /* Quic connection state set functions */ @@ -35,6 +38,8 @@ typedef enum { (conn)->state |= QUIC_STATE_CLOSING; #define QUIC_SET_HS_COMPLETED(conn) \ (conn)->state |= QUIC_STATE_HANDSHAKE_DONE; +#define QUIC_SET_HS_ABORT(conn) \ + (conn)->state |= QUIC_STATE_HS_ABORT; #define QUIC_CAN_SEND(conn) \ ((conn)->state < QUIC_STATE_DRAINING) @@ -102,7 +107,38 @@ struct pl_quic_conn_sess_data { kr_quic_table_t *table_ref; }; +/* Iterates over the connections streams attempting to send out + * any data that failed to get sent previously. Also flushes + * any messages the connection itself might want to send. */ int quic_flush_streams(struct pl_quic_conn_sess_data *conn); + +/* This function handles responses to special states from handle_packet(...), + * often followed by CONNECTION CLOSE. This function doesn't overwrite the + * buffer in the provided ctx and takes care of submitting the message + * to the wrap dirrection cascade. + * Currently sends special payload to the + * following QUIC_SEND requests: + * + * QUIC_SEND_VERSION_NEGOTIOATION + * Response to a client using an unsupported QUIC version. + * + * QUIC_SEND_RETRY + * sets the conn->state to QUIC_SET_HS_ABORT. Retry packets are sent + * for address validation and are a response to the INITIAL packet. + * Once the retry packet is sent the connection state is to be destroyed and + * the client is expected to send a new INITIAL containing the address + * validation token (as a response to the retry packet). + * + * QUIC_SEND_CONN_CLOSE + * in case the ccerr for this connection is DOQ_NO_ERROR this function will + * replace the ccerr with the DoQ error code passed to this function as + * the doq_error parameter. If the current ccerr is not DOQ_NO_ERROR the ccerr + * will not be changed to ensure more specific error code remains. + * + * Returns 0 on success, + * EINVAL if the requested state was missing some argument, + * ENOMEM if the function fails to allocate memory for the payload, + * or other negative error code from some ngtcp2 library function. */ int send_special(ngtcp2_version_cid *dec_cids, kr_quic_table_t *table, struct protolayer_iter_ctx *ctx, int action, diff --git a/daemon/quic_demux.c b/daemon/quic_demux.c index efba345b8..92028a8eb 100644 --- a/daemon/quic_demux.c +++ b/daemon/quic_demux.c @@ -24,10 +24,11 @@ static int cmp_expiry_heap_nodes(void *c1, void *c2) ((struct pl_quic_conn_sess_data *)c2)->h.heap_value) return 1; - /* If the heap_values equal stop the swapping subroutine as there is - * no clear hierarchy. This also protects new connections from being - * terminated by kr_quic_table_sweep right after initialisation. */ - return -1; + if (((struct pl_quic_conn_sess_data *)c1)->h.heap_value < + ((struct pl_quic_conn_sess_data *)c2)->h.heap_value) + return -1; + + return 0; } static void conn_heap_reschedule(struct pl_quic_conn_sess_data *conn, @@ -49,16 +50,6 @@ void quic_conn_mark_used(struct pl_quic_conn_sess_data *conn, conn_heap_reschedule(conn, table); } -int kr_quic_table_rem2(kr_quic_cid_t **pcid, kr_quic_table_t *table) -{ - kr_quic_cid_t *cid = *pcid; - *pcid = cid->next; - free(cid); - table->pointers--; - - return kr_ok(); -} - void kr_quic_table_rem(struct pl_quic_conn_sess_data *conn, kr_quic_table_t *table) { @@ -77,18 +68,28 @@ void kr_quic_table_rem(struct pl_quic_conn_sess_data *conn, continue; } kr_quic_table_rem2(pcid, table); - conn->cid_pointers--; } free(scids); + } else { + kr_quic_cid_t **pcid = kr_quic_table_lookup2(&conn->dcid, table); + if (kr_fails_assert(pcid != NULL && (*pcid) != NULL)) { + /* Likely impossible without a significantly corrupt + * state and/or presence of a programming error */ + kr_log_debug(DOQ, "Failed to remove connection cid from table, counters might not match\n"); + } else { + kr_quic_table_rem2(pcid, table); + } + } + + kr_quic_cid_t **podcid = kr_quic_table_lookup2(&conn->odcid, table); + if (kr_fails_assert(podcid != NULL && *podcid != NULL)) { + kr_log_debug(DOQ, "Failed to remove connection cid from table, counters might not match\n"); + } else { + kr_quic_table_rem2(podcid, table); } int pos = heap_find(table->expiry_heap, (heap_val_t *)conn); - /* Since deferred iteration context increases the session ref_count - * it is possible that the session will exist after being removed - * from the expiry heap. In such case no cid is found and the - * the heap_find function returns 0, which is not a valid value - * because the heap index starts at 1. */ if (pos != 0) { heap_delete(table->expiry_heap, pos); table->usage--; @@ -100,13 +101,7 @@ void kr_quic_table_free(kr_quic_table_t *table) if (!table) return; - while (!EMPTY_HEAP(table->expiry_heap)) { - struct pl_quic_conn_sess_data *c = - *(struct pl_quic_conn_sess_data **)HHEAD(table->expiry_heap); - - kr_quic_table_rem(c, table); - } - + kr_require(EMPTY_HEAP(table->expiry_heap)); kr_assert(table->usage == 0); kr_assert(table->pointers == 0); @@ -116,88 +111,20 @@ void kr_quic_table_free(kr_quic_table_t *table) free(table); } -void kr_quic_table_sweep(struct kr_quic_table *table, - struct protolayer_iter_ctx *ctx) -{ - uint64_t now = 0; - while (!EMPTY_HEAP(table->expiry_heap)) { - struct pl_quic_conn_sess_data *c = - *(struct pl_quic_conn_sess_data **) - HHEAD(table->expiry_heap); - - if ((c->state & QUIC_STATE_BLOCKED)) { - break; - /* when we reach the limit of open conns we lookup the most idle - * one but only close it if it received at least one query. - * This is to prevent closing brand new connections which has - * crippling effects on the number of answered queries when - * conn limits are reached. */ - } else if (table->usage >= table->max_conns - && c->finished_streams > 0 - && !c->disconnected) { - quic_doq_error_t doq_error = DOQ_EXCESSIVE_LOAD; - send_special(&c->dec_cids, c->table_ref, ctx, - QUIC_SEND_CONN_CLOSE, c, c->h.session, - &doq_error); - session2_event(c->h.session->transport.parent, - PROTOLAYER_EVENT_DISCONNECT, - c); - } else if (c->state & QUIC_STATE_DRAINING) { - session2_event(c->h.session->transport.parent, - PROTOLAYER_EVENT_DISCONNECT, - c); - // } else if (c->state & QUIC_STATE_CLOSING) { - // quic_doq_error_t doq_error = DOQ_NO_ERROR; - // send_special(&c->dec_cids, c->table_ref, - // ctx, QUIC_SEND_CONN_CLOSE, - // c, c->h.session, &doq_error); - // session2_event(c->h.session->transport.parent, - // PROTOLAYER_EVENT_DISCONNECT, - // c); - } else if (kr_quic_conn_timeout(c, &now)) { - int ret = ngtcp2_conn_handle_expiry(c->conn, now); - if (ret != NGTCP2_NO_ERROR) { - quic_doq_error_t doq_error = DOQ_NO_ERROR; - /* see https://nghttp2.org/ngtcp2/ngtcp2_conn_handle_expiry.html */ - if (ret != NGTCP2_ERR_IDLE_CLOSE) { - send_special(&c->dec_cids, c->table_ref, - ctx, QUIC_SEND_CONN_CLOSE, - c, c->h.session, &doq_error); - } - session2_event(c->h.session->transport.parent, - PROTOLAYER_EVENT_DISCONNECT, c); - } else { - // quic_conn_mark_used(c, table); - } - } - // HHEAD already handled, NOOP, avoid infinite loop - if (*(struct pl_quic_conn_sess_data **) - HHEAD(table->expiry_heap) == c) { - break; - } - } -} - static enum protolayer_iter_cb_result pl_quic_demux_unwrap(void *sess_data, void *iter_data, struct protolayer_iter_ctx *ctx) { - int ret = kr_ok(); struct pl_quic_conn_sess_data *qconn = NULL; struct pl_quic_demux_sess_data *demux = sess_data; - /* Currently we only receive WIRE_BUF payload */ - // if (ctx->payload.type == PROTOLAYER_PAYLOAD_WIRE_BUF) { - // kr_log_warning(DOQ, "Unexpected payload type in quic-demux\n"); - // return protolayer_break(ctx, kr_error(ENOTSUP)); - // } - bool retry_sent = false; ngtcp2_version_cid dec_cids; ngtcp2_cid odcid; ngtcp2_cid dcid; ngtcp2_cid scid; - ret = ngtcp2_pkt_decode_version_cid(&dec_cids, + kr_require(ctx->payload.type == PROTOLAYER_PAYLOAD_WIRE_BUF); + int ret = ngtcp2_pkt_decode_version_cid(&dec_cids, wire_buf_data(ctx->payload.wire_buf), wire_buf_data_length(ctx->payload.wire_buf), SERVER_DEFAULT_SCIDLEN); @@ -220,11 +147,7 @@ static enum protolayer_iter_cb_result pl_quic_demux_unwrap(void *sess_data, qconn = kr_quic_table_lookup(&dcid, demux->conn_table); if (!qconn) { if (demux->conn_table->usage >= demux->conn_table->max_conns) { - kr_quic_table_sweep(demux->conn_table, ctx); - if (demux->conn_table->usage >= demux->conn_table->max_conns) { - /* no luck */ - return protolayer_break(ctx, kr_ok()); - } + return protolayer_break(ctx, kr_ok()); } ngtcp2_pkt_hd header = { 0 }; @@ -274,12 +197,9 @@ static enum protolayer_iter_cb_result pl_quic_demux_unwrap(void *sess_data, QUIC_REGULAR_TOKEN_TIMEOUT, now); } if (ret != 0) { - /* FIXME the generate string might not be correct */ kr_log_debug(DOQ, "Failed to verify retry or regular token: %s (%d)\n", ngtcp2_strerror(ret), ret); return protolayer_break(ctx, kr_ok()); - } else { - kr_log_debug(DOQ, "Retry or regular token successfully verified\n"); } } else { @@ -287,7 +207,7 @@ static enum protolayer_iter_cb_result pl_quic_demux_unwrap(void *sess_data, /* TODO remove 'likely' once outgoing DoQ is supported */ if (likely(!demux->h.session->outgoing)) { - if (!init_unique_cid(&dcid, 0, demux->conn_table)) { + if (init_unique_cid(&dcid, 0, demux->conn_table) != 0) { kr_log_debug(DOQ, "Failed to initialize unique cid (servers choice)\n"); return protolayer_break(ctx, kr_ok()); } @@ -316,23 +236,50 @@ static enum protolayer_iter_cb_result pl_quic_demux_unwrap(void *sess_data, 1, false); + if (!new_conn_sess) { + return protolayer_break(ctx, kr_error(ENOMEM)); + } + struct pl_quic_conn_sess_data *conn_sess_data = protolayer_sess_data_get_proto(new_conn_sess, PROTOLAYER_TYPE_QUIC_CONN); - kr_quic_table_add(conn_sess_data, &dcid, - demux->conn_table); + kr_quic_table_add(conn_sess_data, &dcid, demux->conn_table); + kr_quic_table_insert(conn_sess_data, &odcid, demux->conn_table); qconn = conn_sess_data; - } - ret = session2_unwrap(qconn->h.session, - ctx->payload, - ctx->comm, - // NULL, - ctx->finished_cb, - ctx->finished_cb_baton); + /* We need the handling of the first packet of the connection + * to execute right away. Skip defer to make sure connection + * state contains correct flags when we check below. */ + ret = session2_unwrap_after(qconn->h.session, + PROTOLAYER_TYPE_DEFER, + ctx->payload, + ctx->comm, + ctx->finished_cb, + ctx->finished_cb_baton); + /* The connection failed to initialize. Either a general + * failure or a retry pkt was sent, both cases mean we + * can discard the current conn state (close the session) */ + if (qconn->state >= QUIC_STATE_DRAINING) { + session2_force_close(new_conn_sess); + return protolayer_break(ctx, kr_ok()); + } + } else { + ret = session2_unwrap(qconn->h.session, + ctx->payload, + ctx->comm, + ctx->finished_cb, + ctx->finished_cb_baton); + } - quic_conn_mark_used(qconn, demux->conn_table); - kr_quic_table_sweep(demux->conn_table, ctx); + /* The received message caused the conn to enter closing state, + * terminate session here since the protolayer_iter_ctx needs + * the session, otherwise mark used and sweep. */ + if ((qconn->state & QUIC_STATE_DRAINING) + || (qconn->state & QUIC_STATE_CLOSING)) { + // pass + } else { + quic_conn_mark_used(qconn, demux->conn_table); + } return protolayer_break(ctx, kr_ok()); } @@ -348,17 +295,13 @@ kr_quic_table_t *kr_quic_table_new(size_t max_conns, size_t udp_payload, return NULL; } + new_table->size = table_size; new_table->usage = 0; + new_table->pointers = 0; new_table->max_conns = max_conns; new_table->udp_payload_limit = udp_payload; - // kr_require(new_table->creds); - // ret = gnutls_certificate_allocate_credentials(&new_table->creds->credentials); - // if (ret != GNUTLS_E_SUCCESS) - // goto fail; - // - // NOTE: Taken from tls-proxy.c/96, we might need to use this // to enforce the use of tls1.3 (tls1.3 compat mode might be problematic) // @@ -450,40 +393,6 @@ static int pl_quic_demux_sess_deinit(struct session2 *session, void *data) return kr_ok(); } -static int remove_connection_id(struct pl_quic_demux_sess_data *demux, - ngtcp2_cid *cid, void *user_data) -{ - kr_quic_cid_t **pcid = kr_quic_table_lookup2(cid, demux->conn_table); - if (!pcid || !*pcid) { - kr_log_error(DOQ, "Table doesn't contain cid that is to be removed\n"); - return kr_error(EINVAL); - } - - if ((*pcid)->conn_sess->cid_pointers <= 1) { - kr_log_error(DOQ, "Cannot remove all connection ids, protocol error\n"); - return kr_error(EPROTO); - } - - return kr_quic_table_rem2(pcid, demux->conn_table); -} - -static int update_connection_id_map(struct pl_quic_demux_sess_data *demux, - struct pl_quic_conn_sess_data *conn) -{ - ngtcp2_cid *cid = &conn->dcid; - if (!init_unique_cid(cid, cid->datalen, demux->conn_table)) { - kr_log_error(DOQ, "Failed to create init new cid\n"); - return kr_error(EINVAL); - } - - if (kr_quic_table_insert(conn, cid, demux->conn_table) == NULL) { - kr_log_error(DOQ, "Failed to add new cid to conn map\n"); - return kr_error(EINVAL); - } - - return kr_ok(); -} - static enum protolayer_event_cb_result pl_quic_demux_event_unwrap( enum protolayer_event_type event, void **baton, struct session2 *session, void *sess_data) @@ -494,40 +403,12 @@ static enum protolayer_event_cb_result pl_quic_demux_event_unwrap( struct pl_quic_conn_sess_data *c = *(struct pl_quic_conn_sess_data **)HHEAD( demux->conn_table->expiry_heap); - session2_force_close(c->h.session); - } - - session2_dec_refs(session); - return PROTOLAYER_EVENT_CONSUME; - } - - if (*baton == NULL) { - return PROTOLAYER_EVENT_PROPAGATE; - } - - struct pl_quic_conn_sess_data *conn = *baton; - - /* received NEW_CONNECTION_ID, update mapping to conn_sess_data */ - if (event == PROTOLAYER_EVENT_CONNECT_UPDATE) { - if (update_connection_id_map(demux, conn) != kr_ok()) { - event = PROTOLAYER_EVENT_DISCONNECT; - /* fallthrough */ + ngtcp2_ccerr_set_application_error(&c->ccerr, + DOQ_NO_ERROR, NULL, 0); + session2_event(c->h.session, event, NULL); } } - if (event == PROTOLAYER_EVENT_CONNECT_RETIRE) { - if (remove_connection_id(demux, &conn->dcid, conn) != kr_ok()) { - event = PROTOLAYER_EVENT_DISCONNECT; - /* fallthrough */ - } - } - - if (event == PROTOLAYER_EVENT_DISCONNECT || - event == PROTOLAYER_EVENT_CONNECT_TIMEOUT) { - session2_close(conn->h.session); - return PROTOLAYER_EVENT_CONSUME; - } - return PROTOLAYER_EVENT_PROPAGATE; } @@ -539,15 +420,14 @@ static enum protolayer_event_cb_result pl_quic_demux_event_wrap( || event == PROTOLAYER_EVENT_CLOSE || event == PROTOLAYER_EVENT_GENERAL_TIMEOUT || event == PROTOLAYER_EVENT_CONNECT_TIMEOUT) { - kr_require(baton && *baton); - struct pl_quic_demux_sess_data *demux = sess_data; - struct pl_quic_conn_sess_data *conn = *baton; - kr_quic_table_rem(conn, demux->conn_table); - - kr_require(EMPTY_LIST(conn->streams)); - session2_dec_refs(conn->h.session); - - return PROTOLAYER_EVENT_CONSUME; + /* Terminate subsession */ + if (baton && *baton) { + struct pl_quic_conn_sess_data *conn = *baton; + kr_quic_table_rem(conn, conn->table_ref); + uv_close((uv_handle_t *)&conn->h.session->timer, + on_session2_timer_close); + return PROTOLAYER_EVENT_CONSUME; + } } return PROTOLAYER_EVENT_PROPAGATE; diff --git a/daemon/quic_stream.c b/daemon/quic_stream.c index ac5208b30..3422a8c71 100644 --- a/daemon/quic_stream.c +++ b/daemon/quic_stream.c @@ -19,7 +19,7 @@ static enum protolayer_iter_cb_result pl_quic_stream_unwrap(void *sess_data, { struct pl_quic_stream_sess_data *stream = sess_data; - if (!(stream->incflags & NGTCP2_STREAM_DATA_FLAG_FIN)) { + if (unlikely(!(stream->incflags & NGTCP2_STREAM_DATA_FLAG_FIN))) { return protolayer_break(ctx, kr_error(EINVAL)); } @@ -60,7 +60,7 @@ uint8_t *kr_quic_stream_add_data(struct pl_quic_stream_sess_data *s, } list_t *list = (list_t *)&s->outbufs; - if (EMPTY_LIST(*list)) { + if (EMPTY_LIST(*list)) { // NOLINT(bugprone-casting-through-void) s->unsent_obuf = obuf; } add_tail((list_t *)&s->outbufs, (node_t *)obuf); @@ -79,7 +79,7 @@ static enum protolayer_iter_cb_result pl_quic_stream_wrap(void *sess_data, if (likely(ctx->payload.type == PROTOLAYER_PAYLOAD_IOVEC)) { if (unlikely(kr_quic_stream_add_data(stream, ctx->payload.iovec.iov[1].iov_base, ctx->payload.iovec.iov[1].iov_len) == NULL)) { - return kr_error(ENOMEM); + return protolayer_break(ctx, kr_error(ENOMEM)); } ctx->payload = protolayer_payload_wire_buf(&stream->outbuf, @@ -97,11 +97,7 @@ static enum protolayer_iter_cb_result pl_quic_stream_wrap(void *sess_data, size_t uf = stream->unsent_offset; struct kr_quic_obuf *uo = stream->unsent_obuf; if (uo == NULL) { - if (sent_msgs > 0) { - break; - } - - protolayer_break(ctx, kr_ok()); + break; } if (wire_buf_data_length(ctx->payload.wire_buf) != 0) { @@ -114,6 +110,8 @@ static enum protolayer_iter_cb_result pl_quic_stream_wrap(void *sess_data, if (nwrite < 0) { if (nwrite == NGTCP2_ERR_NOMEM) { kr_log_error(DOQ, "Insufficient memory available\n"); + } else if (nwrite == NGTCP2_ERR_STREAM_SHUT_WR) { + stream->write_closed = true; } return protolayer_break(ctx, kr_ok()); @@ -126,8 +124,8 @@ static enum protolayer_iter_cb_result pl_quic_stream_wrap(void *sess_data, protolayer_finished_cb finished_cb = NULL; void *finished_baton = NULL; - if (sent > 0 && stream->unsent_obuf == NULL && stream->h.session->outgoing - && false) { + if (sent > 0 && stream->unsent_obuf == NULL + && stream->h.session->outgoing) { finished_cb = ctx->finished_cb; finished_baton = ctx->finished_cb_baton; } @@ -140,6 +138,11 @@ static enum protolayer_iter_cb_result pl_quic_stream_wrap(void *sess_data, finished_baton); } while (sent > 0 && sent_msgs < QUIC_MAX_SEND_PER_RECV); + if (!stream->skip_update_time) { + ngtcp2_conn_update_pkt_tx_time(stream->conn_ref->conn, + quic_timestamp()); + } + return protolayer_break(ctx, kr_ok()); } @@ -148,7 +151,7 @@ static int send_stream(struct pl_quic_stream_sess_data *stream, size_t len, bool fin, ngtcp2_ssize *sent) { if (!stream->conn_ref || !QUIC_CAN_SEND(stream->conn_ref)) { - return 0; + return kr_error(ENODATA); } int64_t stream_id = stream->stream_id; @@ -198,9 +201,11 @@ static int pl_quic_stream_sess_init(struct session2 *session, struct kr_quic_stream_param *p = param; stream->conn = p->conn; + stream->closed = false; stream->stream_id = p->stream_id; + stream->write_closed = false; + stream->skip_update_time = false; session->comm_storage = p->comm_storage; - if (stream->obufs_size == 0) { init_list(&stream->outbufs); } else { @@ -216,14 +221,16 @@ void kr_quic_stream_ack_data(struct pl_quic_stream_sess_data *stream, struct list *obs = &stream->outbufs; struct kr_quic_obuf *first; + // NOLINTNEXTLINE(bugprone-casting-through-void) while (!EMPTY_LIST(*obs) && end_acked >= (first = HEAD(*obs))->len + stream->first_offset) { rem_node(&first->node); stream->obufs_size -= first->len; stream->first_offset += first->len; if (stream->unsent_obuf == first) { - stream->unsent_obuf = - EMPTY_LIST(*obs) == 0 ? NULL : HEAD(*obs); + // NOLINTNEXTLINE(bugprone-casting-through-void) + stream->unsent_obuf = EMPTY_LIST(*obs) + ? NULL : HEAD(*obs); stream->unsent_offset = 0; } free(first); @@ -233,19 +240,6 @@ void kr_quic_stream_ack_data(struct pl_quic_stream_sess_data *stream, static int pl_quic_stream_sess_deinit(struct session2 *session, void *sess_data) { struct pl_quic_stream_sess_data *stream = sess_data; - - if (stream->ngtcp2_stream_active) { - if (!stream->conn) { - kr_log_debug(DOQ, "Stream missing connection pointer, cannot update ngtcp2 stream limits\n"); - } else { - stream->ngtcp2_stream_active = false; - ngtcp2_conn_extend_max_streams_bidi(stream->conn, 1); - } - } - - if (!session2_tasklist_is_empty(session)) { - session2_tasklist_finalize_expired(session); - } kr_quic_stream_ack_data(stream, stream->stream_id, SIZE_MAX, false); wire_buf_deinit(&stream->pers_inbuf); wire_buf_deinit(&stream->outbuf); @@ -265,11 +259,7 @@ static enum protolayer_event_cb_result pl_quic_stream_event_unwrap( stream->stream_id, SIZE_MAX, false); - - stream->conn = NULL; - stream->conn_ref = NULL; } - return PROTOLAYER_EVENT_PROPAGATE; } @@ -279,13 +269,9 @@ static enum protolayer_event_cb_result pl_quic_stream_event_wrap( { if (event == PROTOLAYER_EVENT_CLOSE || event == PROTOLAYER_EVENT_FORCE_CLOSE) { - if (session2_is_empty(session)) { - *baton = sess_data; - return PROTOLAYER_EVENT_PROPAGATE; - } - return PROTOLAYER_EVENT_CONSUME; + kr_require(session2_is_empty(session)); + *baton = sess_data; } - return PROTOLAYER_EVENT_PROPAGATE; } diff --git a/daemon/quic_stream.h b/daemon/quic_stream.h index 33808d9b0..0c5bf5fd5 100644 --- a/daemon/quic_stream.h +++ b/daemon/quic_stream.h @@ -32,8 +32,10 @@ struct pl_quic_stream_sess_data { struct kr_quic_obuf *unsent_obuf; size_t first_offset; size_t unsent_offset; - + bool closed; + bool write_closed; uint32_t incflags; + bool skip_update_time; uint64_t sdata_offset; struct pl_quic_conn_sess_data *conn_ref; @@ -41,3 +43,5 @@ struct pl_quic_stream_sess_data { void kr_quic_stream_ack_data(struct pl_quic_stream_sess_data *stream, int64_t stream_id, size_t end_acked, bool keep_stream); +uint8_t *kr_quic_stream_add_data(struct pl_quic_stream_sess_data *s, + uint8_t *data, size_t len); diff --git a/daemon/session2.c b/daemon/session2.c index d07119bfc..e99b7cc5e 100644 --- a/daemon/session2.c +++ b/daemon/session2.c @@ -80,6 +80,7 @@ static const enum protolayer_type protolayer_grp_doq_conn[] = { static const enum protolayer_type protolayer_grp_doq_demux[] = { PROTOLAYER_TYPE_UDP, + PROTOLAYER_TYPE_PROXYV2_STREAM, PROTOLAYER_TYPE_QUIC_DEMUX, PROTOLAYER_TYPE_NULL, }; @@ -1740,7 +1741,7 @@ static void on_session2_handle_close(uv_handle_t *handle) io_free(handle); } -static void on_session2_timer_close(uv_handle_t *handle) +void on_session2_timer_close(uv_handle_t *handle) { session2_dec_refs(handle->data); } diff --git a/daemon/session2.h b/daemon/session2.h index 1ba05ae31..2a5fa9670 100644 --- a/daemon/session2.h +++ b/daemon/session2.h @@ -1197,3 +1197,6 @@ static inline void session2_touch(struct session2 *session) { session->last_activity = kr_now(); } + +/* Decrements the session ref_count, used as a function parameter of uv_close. */ +void on_session2_timer_close(uv_handle_t *handle); diff --git a/doc/_static/config.schema.json b/doc/_static/config.schema.json index 61bf67b02..c49dc0df4 100644 --- a/doc/_static/config.schema.json +++ b/doc/_static/config.schema.json @@ -375,7 +375,7 @@ "type": "integer", "minimum": 1, "maximum": 4096, - "description": "Maximum number of concurrent streams a connection is allowed to open.", + "description": "Maximum number of concurrent streams each connection is allowed to open. Each stream allocates > 64 KB of memory, setting this value too high might quicly consume a lot of memory. We recommend between 4 and 64.", "default": 1024 }, "require-retry": { diff --git a/python/knot_resolver/datamodel/network_schema.py b/python/knot_resolver/datamodel/network_schema.py index 6f84544ee..e017fc70c 100644 --- a/python/knot_resolver/datamodel/network_schema.py +++ b/python/knot_resolver/datamodel/network_schema.py @@ -55,10 +55,10 @@ class QUICSchema(ConfigSchema): --- max_conns: Maximum number of active connections a single worker is allowed to accept. - max_streams: Maximum number of concurrent streams a connection is allowed to open. + max_streams: Maximum number of concurrent streams each connection is allowed to open. Each stream allocates > 64 KB of memory, setting this value too high might quicly consume a lot of memory. We recommend between 4 and 64. require_retry: Require address validation for unknown source addresses. This adds a 1-RTT delay to connection establishment. - """ + """ # noqa: E501 max_conns: Int1_4096 = Int1_4096(1024) max_streams: Int1_4096 = Int1_4096(1024)