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();
}
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;
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) {
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);
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)
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)
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;
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));
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);
}
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);
}
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;
}
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");
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
);
#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
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
#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),
};
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 | <the tls alert> (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
#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"
#include <libknot/wire.h>
#include <netinet/in.h>
#include <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
#include <sched.h>
+#include <stdlib.h>
#include <uv.h>
-/* 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
}
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
* 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;
}
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;
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,
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;
.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);
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;
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,
{
(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,
{
(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];
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,
.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
.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
// .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
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;
}
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;
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);
}
}
}
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);
}
}
- 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;
}
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) {
.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);
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;
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(
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();
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,
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 };
);
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);
}
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;
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());
}
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 };
}
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());
}
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;
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;
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();
}
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);
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) {
}
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(
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;
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 */
(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)
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,
((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,
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)
{
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--;
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);
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);
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 };
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 {
/* 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());
}
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());
}
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)
//
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)
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;
}
|| 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;
{
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));
}
}
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);
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,
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) {
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());
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;
}
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());
}
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;
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 {
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);
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);
stream->stream_id,
SIZE_MAX,
false);
-
- stream->conn = NULL;
- stream->conn_ref = NULL;
}
-
return PROTOLAYER_EVENT_PROPAGATE;
}
{
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;
}
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;
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);
static const enum protolayer_type protolayer_grp_doq_demux[] = {
PROTOLAYER_TYPE_UDP,
+ PROTOLAYER_TYPE_PROXYV2_STREAM,
PROTOLAYER_TYPE_QUIC_DEMUX,
PROTOLAYER_TYPE_NULL,
};
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);
}
{
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);
"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": {
---
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)