summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Salzman <daniel.salzman@nic.cz>2024-12-03 11:23:24 +0100
committerDaniel Salzman <daniel.salzman@nic.cz>2024-12-03 11:23:24 +0100
commit04329a6e3128fc32f30f5810b2bc39d005dceb9f (patch)
tree5945043352edd341cc32d536718566ba05bc1c81
parentMerge branch 'keyroll_2active' into 'master' (diff)
parentmod-rrl: increase default limits (diff)
downloadknot-04329a6e3128fc32f30f5810b2bc39d005dceb9f.tar.xz
knot-04329a6e3128fc32f30f5810b2bc39d005dceb9f.zip
Merge branch 'rrl_auth'
fixes #943
-rw-r--r--src/knot/modules/rrl/functions.c29
-rw-r--r--src/knot/modules/rrl/functions.h10
-rw-r--r--src/knot/modules/rrl/rrl.c59
-rw-r--r--src/knot/modules/rrl/rrl.rst19
-rw-r--r--src/knot/nameserver/internet.h1
-rw-r--r--src/knot/server/handler.c12
-rw-r--r--src/knot/server/handler.h25
-rw-r--r--src/knot/server/tcp-handler.c13
-rw-r--r--src/knot/server/xdp-handler.c10
-rw-r--r--src/libknot/quic/quic_conn.h1
-rw-r--r--src/libknot/quic/tls.h1
-rw-r--r--src/libknot/xdp/tcp.c1
-rw-r--r--src/libknot/xdp/tcp.h5
13 files changed, 133 insertions, 53 deletions
diff --git a/src/knot/modules/rrl/functions.c b/src/knot/modules/rrl/functions.c
index 01d89cb66..5534b4437 100644
--- a/src/knot/modules/rrl/functions.c
+++ b/src/knot/modules/rrl/functions.c
@@ -68,18 +68,35 @@ static void addr_tostr(char *dst, size_t maxlen, const struct sockaddr_storage *
}
}
-static void rrl_log_limited(knotd_mod_t *mod, const struct sockaddr_storage *ss,
+static void rrl_log_limited(rrl_log_params_t *params, const struct sockaddr_storage *ss,
const uint8_t prefix, bool rate)
{
- if (mod == NULL) {
+ if (params == NULL) {
return;
}
char addr_str[SOCKADDR_STRLEN];
addr_tostr(addr_str, sizeof(addr_str), ss);
- knotd_mod_log(mod, LOG_NOTICE, "address %s limited on /%d by %s",
- addr_str, prefix, rate ? "rate" : "time");
+ const char *proto_str = "UDP";
+ const char *qname_str = NULL;
+ knot_dname_txt_storage_t buf;
+ if (params->qdata != NULL) {
+ qname_str = knot_dname_to_str(buf, knot_pkt_qname(params->qdata->query),
+ sizeof(buf));
+ } else {
+ switch (params->proto) {
+ case KNOTD_QUERY_PROTO_TCP: proto_str = "TCP"; break;
+ case KNOTD_QUERY_PROTO_QUIC: proto_str = "QUIC"; break;
+ case KNOTD_QUERY_PROTO_TLS: proto_str = "TLS"; break;
+ default: break;
+ }
+ }
+
+ knotd_mod_log(params->mod, LOG_NOTICE, "address %s %s limited on /%d by %s%s%s",
+ addr_str, proto_str, prefix, rate ? "rate" : "time",
+ (qname_str != NULL ? ", qname " : ""),
+ (qname_str != NULL ? qname_str : ""));
}
rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit,
@@ -131,7 +148,7 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit
return rrl;
}
-int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod_t *mod)
+int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, rrl_log_params_t *log)
{
assert(rrl);
assert(remote);
@@ -186,7 +203,7 @@ int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod
do {
if (atomic_compare_exchange_weak_explicit(&rrl->log_time, &log_time_orig, now,
memory_order_relaxed, memory_order_relaxed)) {
- rrl_log_limited(mod, remote, prefix, rrl->rw_mode);
+ rrl_log_limited(log, remote, prefix, rrl->rw_mode);
break;
}
} while (now - log_time_orig + 1024 >= rrl->log_period + 1024);
diff --git a/src/knot/modules/rrl/functions.h b/src/knot/modules/rrl/functions.h
index 0941c8378..8fe1383c5 100644
--- a/src/knot/modules/rrl/functions.h
+++ b/src/knot/modules/rrl/functions.h
@@ -37,6 +37,12 @@ typedef struct rrl_table rrl_table_t;
rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit,
bool rw_mode, uint32_t log_period);
+typedef struct {
+ knotd_mod_t *mod;
+ knotd_qdata_t *qdata; // For rate limiting.
+ knotd_query_proto_t proto; // For time limiting.
+} rrl_log_params_t;
+
/*!
* \brief Query the RRL table for accept or deny, when the rate limit is reached.
*
@@ -44,12 +50,12 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit
*
* \param rrl RRL table.
* \param remote Source address.
- * \param mod Query module (needed for logging).
+ * \param log Logging parameters (can be NULL).
*
* \retval KNOT_EOK if passed.
* \retval KNOT_ELIMIT when the limit is reached.
*/
-int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod_t *mod);
+int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, rrl_log_params_t *log);
/*!
* \brief Update the RRL table.
diff --git a/src/knot/modules/rrl/rrl.c b/src/knot/modules/rrl/rrl.c
index 17e773812..162eb8875 100644
--- a/src/knot/modules/rrl/rrl.c
+++ b/src/knot/modules/rrl/rrl.c
@@ -30,10 +30,10 @@
#define MOD_DRY_RUN "\x07""dry-run"
const yp_item_t rrl_conf[] = {
- { MOD_INST_LIMIT, YP_TINT, YP_VINT = { 1, (1ll << 32) / 768 - 1, 50 } },
- { MOD_RATE_LIMIT, YP_TINT, YP_VINT = { 0, ((1ll << 32) / 768 - 1) * 1000, 20 } },
+ { MOD_INST_LIMIT, YP_TINT, YP_VINT = { 1, (1ll << 32) / 768 - 1, 125 } },
+ { MOD_RATE_LIMIT, YP_TINT, YP_VINT = { 0, ((1ll << 32) / 768 - 1) * 1000, 50 } },
{ MOD_T_INST_LIMIT, YP_TINT, YP_VINT = { 1, 1000000, 5000 } },
- { MOD_T_RATE_LIMIT, YP_TINT, YP_VINT = { 0, 1000000000, 4000 } },
+ { MOD_T_RATE_LIMIT, YP_TINT, YP_VINT = { 0, 1000000000, 5000 } },
{ MOD_SLIP, YP_TINT, YP_VINT = { 0, 100, 1 } },
{ MOD_TBL_SIZE, YP_TINT, YP_VINT = { 1, INT32_MAX, 524288 } },
{ MOD_WHITELIST, YP_TNET, YP_VNONE, YP_FMULTI },
@@ -64,8 +64,7 @@ int rrl_conf_check(knotd_conf_check_args_t *args)
typedef struct {
ALIGNED_CPU_CACHE // Ensures that one thread context occupies one cache line.
struct timespec start_time; // Start time of the measurement.
- bool whitelist_checked; // Indication whether whitelist check took place.
- bool skip; // Skip the rest of the module callbacks.
+ bool skip; // Skip the time table update.
} thrd_ctx_t;
typedef struct {
@@ -87,24 +86,26 @@ static knotd_proto_state_t protolimit_start(knotd_proto_state_t state,
knotd_qdata_params_t *params,
knotd_mod_t *mod)
{
+ assert(params && mod);
+
rrl_ctx_t *ctx = knotd_mod_ctx(mod);
thrd_ctx_t *thrd = &ctx->thrd_ctx[params->thread_id];
thrd->skip = false;
- // Check if a whitelisted client.
- thrd->whitelist_checked = true;
- if (knotd_conf_addr_range_match(&ctx->whitelist, params->remote)) {
- thrd->skip = true;
+ // Time limiting not supported for UDP (source address can be forged).
+ if (params->proto == KNOTD_QUERY_PROTO_UDP) {
return state;
}
- // UDP time limiting not implemented (source address can be forged).
- if (params->proto == KNOTD_QUERY_PROTO_UDP) {
+ // Check if a whitelisted client.
+ if (knotd_conf_addr_range_match(&ctx->whitelist, params->remote)) {
+ thrd->skip = true;
return state;
}
// Check if the packet is limited.
- if (rrl_query(ctx->time_table, params->remote, mod) != KNOT_EOK) {
+ rrl_log_params_t log = { .mod = mod, .proto = params->proto };
+ if (rrl_query(ctx->time_table, params->remote, &log) != KNOT_EOK) {
thrd->skip = true;
knotd_mod_stats_incr(mod, params->thread_id, 2, 0, 1);
return ctx->dry_run ? state : KNOTD_PROTO_STATE_BLOCK;
@@ -118,13 +119,21 @@ static knotd_proto_state_t protolimit_end(knotd_proto_state_t state,
knotd_qdata_params_t *params,
knotd_mod_t *mod)
{
+ assert(params && mod);
+
rrl_ctx_t *ctx = knotd_mod_ctx(mod);
thrd_ctx_t *thrd = &ctx->thrd_ctx[params->thread_id];
+ // Time rate limit is applied to non-UDP.
if (thrd->skip || params->proto == KNOTD_QUERY_PROTO_UDP) {
return state;
}
+ // Don't limit authorized operations.
+ if (params->flags & KNOTD_QUERY_FLAG_AUTHORIZED) {
+ return state;
+ }
+
// Update the time table.
struct timespec end_time;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end_time);
@@ -142,36 +151,26 @@ static knotd_state_t ratelimit_apply(knotd_state_t state, knot_pkt_t *pkt,
assert(pkt && qdata && mod);
rrl_ctx_t *ctx = knotd_mod_ctx(mod);
- thrd_ctx_t *thrd = &ctx->thrd_ctx[qdata->params->thread_id];
-
- if (thrd->skip) {
- return state;
- }
-
- // Don't limit authorized operations.
- if (qdata->params->flags & KNOTD_QUERY_FLAG_AUTHORIZED) {
- thrd->skip = true;
- return state;
- }
- // Rate limit is applied to UDP only.
+ // Rate limiting is applied only to UDP.
if (qdata->params->proto != KNOTD_QUERY_PROTO_UDP) {
return state;
}
- // Check for whitelisted client IF PER-ZONE module (no proto callbacks).
- if (!thrd->whitelist_checked &&
- knotd_conf_addr_range_match(&ctx->whitelist, qdata->params->remote)) {
- thrd->skip = true;
+ // NOTE: (qdata->params->flags & KNOTD_QUERY_FLAG_AUTHORIZED) can't be true here.
+
+ // Check for whitelisted client.
+ if (knotd_conf_addr_range_match(&ctx->whitelist, qdata->params->remote)) {
return state;
}
- // Rate limit is not applied to responses with a valid cookie.
+ // Rate limiting is not applied to responses with a valid cookie.
if (qdata->params->flags & KNOTD_QUERY_FLAG_COOKIE) {
return state;
}
- if (rrl_query(ctx->rate_table, knotd_qdata_remote_addr(qdata), mod) == KNOT_EOK) {
+ rrl_log_params_t log = { .mod = mod, .qdata = qdata };
+ if (rrl_query(ctx->rate_table, knotd_qdata_remote_addr(qdata), &log) == KNOT_EOK) {
// Rate limiting not applied.
return state;
}
diff --git a/src/knot/modules/rrl/rrl.rst b/src/knot/modules/rrl/rrl.rst
index 2b558d6c7..04c37edad 100644
--- a/src/knot/modules/rrl/rrl.rst
+++ b/src/knot/modules/rrl/rrl.rst
@@ -15,7 +15,8 @@ responses as truncated or by dropping them altogether.
This module can also help protect the server from excessive utilization by
limiting incoming packets (including handshakes) based on consumed time.
If a packet is time rate limited, it's dropped. This function works with
-all supported non-UDP transport protocols and cannot be configured per zone.
+all supported non-UDP transport protocols (TCP, TLS, and QUIC) and cannot
+be configured per zone.
.. NOTE::
This module introduces three statistics counters:
@@ -28,6 +29,12 @@ all supported non-UDP transport protocols and cannot be configured per zone.
If the :ref:`Cookies<mod-cookies>` module is active, RRL is not applied
to UDP responses with a valid DNS cookie.
+.. NOTE::
+ The time limiting applies even to handshakes of incoming authorized requests
+ (e.g. NOTIFY, AXFR). In such cases, setting :ref:`mod-rrl_whitelist` or reusing
+ already established connections (e.g. :ref:`server_remote-pool-timeout` on
+ the remote server) can mitigate this issue.
+
Example
-------
@@ -96,7 +103,9 @@ i.e. they are lowered by a constant fraction of their value each millisecond.
The specified rate limit is reached, when the number of queries is the same every millisecond;
sending many queries once a second or even a larger timespan leads to a more strict limiting.
-*Default:* ``20``
+Set to 0 to disable the rate limiting.
+
+*Default:* ``50``
.. _mod-rrl_instant-limit:
@@ -115,7 +124,7 @@ is periodically lowered.
The :ref:`mod-rrl_instant-limit` may be at least :ref:`mod-rrl_rate-limit` **/ 1000**, at which point the
counters are zeroed each millisecond.
-*Default:* ``50``
+*Default:* ``125``
.. _mod-rrl_slip:
@@ -161,7 +170,9 @@ time-rate-limit
This limit works similarly to :ref:`mod-rrl_rate-limit` but considers the time
consumed (in microseconds) by the remote over non-UDP transport protocols.
-*Default:* ``4000`` (microseconds)
+Set to 0 to disable the time limiting.
+
+*Default:* ``5000`` (microseconds)
.. _mod-rrl_time-instant-limit:
diff --git a/src/knot/nameserver/internet.h b/src/knot/nameserver/internet.h
index bb6927ddc..594631ed7 100644
--- a/src/knot/nameserver/internet.h
+++ b/src/knot/nameserver/internet.h
@@ -64,6 +64,7 @@ knot_layer_state_t internet_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata)
#define NS_NEED_AUTH(qdata, action) \
if (!process_query_acl_check(conf(), (action), (qdata)) || \
process_query_verify(qdata) != KNOT_EOK) { \
+ qdata->params->flags &= ~KNOTD_QUERY_FLAG_AUTHORIZED; \
return KNOT_STATE_FAIL; \
} else { \
qdata->params->flags |= KNOTD_QUERY_FLAG_AUTHORIZED; \
diff --git a/src/knot/server/handler.c b/src/knot/server/handler.c
index bcd0ec560..2bc27da81 100644
--- a/src/knot/server/handler.c
+++ b/src/knot/server/handler.c
@@ -94,6 +94,13 @@ static void handle_quic_stream(knot_quic_conn_t *conn, int64_t stream_id, struct
}
handle_finish(layer);
+
+ // Store the qdata params AUTH flag to the connection.
+ if (params->flags & KNOTD_QUERY_FLAG_AUTHORIZED) {
+ conn->flags |= KNOT_QUIC_CONN_AUTHORIZED;
+ } else {
+ conn->flags &= ~KNOT_QUIC_CONN_AUTHORIZED;
+ }
}
void handle_quic_streams(knot_quic_conn_t *conn, knotd_qdata_params_t *params,
@@ -101,14 +108,15 @@ void handle_quic_streams(knot_quic_conn_t *conn, knotd_qdata_params_t *params,
{
uint8_t ans_buf[KNOT_WIRE_MAX_PKTSIZE];
+ params_update_quic(params, conn);
+
int64_t stream_id;
knot_quic_stream_t *stream;
-
while (conn != NULL && (stream = knot_quic_stream_get_process(conn, &stream_id)) != NULL) {
assert(stream->inbufs != NULL);
assert(stream->inbufs->n_inbufs > 0);
struct iovec *inbufs = stream->inbufs->inbufs;
- params_update_quic(params, knot_quic_conn_rtt(conn), conn, stream_id);
+ params_update_quic_stream(params, stream_id);
// NOTE: only the first msg in the stream is used, the rest is dropped.
handle_quic_stream(conn, stream_id, &inbufs[0], layer, params,
ans_buf, sizeof(ans_buf));
diff --git a/src/knot/server/handler.h b/src/knot/server/handler.h
index 4b63a0c16..23dbcf7e8 100644
--- a/src/knot/server/handler.h
+++ b/src/knot/server/handler.h
@@ -53,18 +53,30 @@ inline static knotd_qdata_params_t params_init(knotd_query_proto_t proto,
return params;
}
-inline static void params_update_tcp(knotd_qdata_params_t *params, uint32_t rtt)
+inline static void params_update_tcp(knotd_qdata_params_t *params,
+ knot_tcp_conn_t *conn)
{
- params->measured_rtt = rtt;
+ params->measured_rtt = conn->establish_rtt;
+ if (conn->flags & KNOT_TCP_CONN_AUTHORIZED) {
+ params->flags |= KNOTD_QUERY_FLAG_AUTHORIZED;
+ }
}
#ifdef ENABLE_QUIC
-inline static void params_update_quic(knotd_qdata_params_t *params, uint32_t rtt,
- knot_quic_conn_t *conn, int64_t stream_id)
+inline static void params_update_quic(knotd_qdata_params_t *params,
+ knot_quic_conn_t *conn)
{
params->quic_conn = conn;
+ if (conn->flags & KNOT_QUIC_CONN_AUTHORIZED) {
+ params->flags |= KNOTD_QUERY_FLAG_AUTHORIZED;
+ }
+}
+
+inline static void params_update_quic_stream(knotd_qdata_params_t *params,
+ int64_t stream_id)
+{
params->quic_stream = stream_id;
- params->measured_rtt = rtt;
+ params->measured_rtt = knot_quic_conn_rtt(params->quic_conn);
}
#endif // ENABLE_QUIC
@@ -72,6 +84,9 @@ inline static void params_update_tls(knotd_qdata_params_t *params,
knot_tls_conn_t *conn)
{
params->tls_conn = conn;
+ if (params->tls_conn->flags & KNOT_TLS_CONN_AUTHORIZED) {
+ params->flags |= KNOTD_QUERY_FLAG_AUTHORIZED;
+ }
}
#ifdef ENABLE_XDP
diff --git a/src/knot/server/tcp-handler.c b/src/knot/server/tcp-handler.c
index 54bc3a27a..455dafc45 100644
--- a/src/knot/server/tcp-handler.c
+++ b/src/knot/server/tcp-handler.c
@@ -175,7 +175,9 @@ static int tcp_handle(tcp_context_t *tcp, knotd_qdata_params_t *params,
recv = knot_tls_recv_dns(params->tls_conn, rx->iov_base, rx->iov_len);
break;
default: // E.g. handshake timeout.
- return ret;
+ assert(ret < 0);
+ recv = ret;
+ break;
}
} else {
recv = net_dns_tcp_recv(params->socket, rx->iov_base, rx->iov_len, tcp->io_timeout);
@@ -212,6 +214,15 @@ static int tcp_handle(tcp_context_t *tcp, knotd_qdata_params_t *params,
handle_finish(&tcp->layer);
+ if (params->tls_conn != NULL) {
+ // Store the qdata params AUTH flag to the connection.
+ if (params->flags & KNOTD_QUERY_FLAG_AUTHORIZED) {
+ params->tls_conn->flags |= KNOT_TLS_CONN_AUTHORIZED;
+ } else {
+ params->tls_conn->flags &= ~KNOT_TLS_CONN_AUTHORIZED;
+ }
+ }
+
return KNOT_EOK;
}
diff --git a/src/knot/server/xdp-handler.c b/src/knot/server/xdp-handler.c
index 403bb70ca..8e83d1936 100644
--- a/src/knot/server/xdp-handler.c
+++ b/src/knot/server/xdp-handler.c
@@ -223,7 +223,6 @@ static void handle_udp(xdp_handle_ctx_t *ctx, knot_layer_t *layer,
static void handle_tcp(xdp_handle_ctx_t *ctx, knot_layer_t *layer,
knotd_qdata_params_t *params)
{
-
uint8_t ans_buf[KNOT_WIRE_MAX_PKTSIZE];
for (uint32_t i = 0; i < ctx->msg_recv_count; i++) {
@@ -251,8 +250,7 @@ static void handle_tcp(xdp_handle_ctx_t *ctx, knot_layer_t *layer,
} else if (knot_tcp_relay_empty(rl)) {
continue;
}
-
- params_update_tcp(params, rl->conn->establish_rtt);
+ params_update_tcp(params, rl->conn);
// Process all complete DNS queries in one TCP stream.
for (size_t j = 0; rl->inbf != NULL && j < rl->inbf->n_inbufs; j++) {
@@ -273,6 +271,12 @@ static void handle_tcp(xdp_handle_ctx_t *ctx, knot_layer_t *layer,
}
handle_finish(layer);
+
+ if (params->flags & KNOTD_QUERY_FLAG_AUTHORIZED) {
+ rl->conn->flags |= KNOT_TCP_CONN_AUTHORIZED;
+ } else {
+ rl->conn->flags &= ~KNOT_TCP_CONN_AUTHORIZED;
+ }
}
(void)process_query_proto(params, KNOTD_STAGE_PROTO_END);
diff --git a/src/libknot/quic/quic_conn.h b/src/libknot/quic/quic_conn.h
index b19d50e0b..bab15c2fa 100644
--- a/src/libknot/quic/quic_conn.h
+++ b/src/libknot/quic/quic_conn.h
@@ -74,6 +74,7 @@ typedef enum {
KNOT_QUIC_CONN_HANDSHAKE_DONE = (1 << 0),
KNOT_QUIC_CONN_SESSION_TAKEN = (1 << 1),
KNOT_QUIC_CONN_BLOCKED = (1 << 2),
+ KNOT_QUIC_CONN_AUTHORIZED = (1 << 3),
} knot_quic_conn_flag_t;
typedef struct knot_quic_conn {
diff --git a/src/libknot/quic/tls.h b/src/libknot/quic/tls.h
index 2acced1a9..db7182ed0 100644
--- a/src/libknot/quic/tls.h
+++ b/src/libknot/quic/tls.h
@@ -35,6 +35,7 @@ typedef enum {
KNOT_TLS_CONN_HANDSHAKE_DONE = (1 << 0),
KNOT_TLS_CONN_SESSION_TAKEN = (1 << 1), // unused, to be implemeted later
KNOT_TLS_CONN_BLOCKED = (1 << 2),
+ KNOT_TLS_CONN_AUTHORIZED = (1 << 3),
} knot_tls_conn_flag_t;
typedef struct knot_tls_ctx {
diff --git a/src/libknot/xdp/tcp.c b/src/libknot/xdp/tcp.c
index d219db977..2374953a4 100644
--- a/src/libknot/xdp/tcp.c
+++ b/src/libknot/xdp/tcp.c
@@ -227,6 +227,7 @@ static void conn_init_from_msg(knot_tcp_conn_t *conn, knot_xdp_msg_t *msg)
conn->last_active = get_timestamp();
conn->state = XDP_TCP_NORMAL;
conn->establish_rtt = 0;
+ conn->flags = 0;
memset(&conn->inbuf, 0, sizeof(conn->inbuf));
memset(&conn->outbufs, 0, sizeof(conn->outbufs));
diff --git a/src/libknot/xdp/tcp.h b/src/libknot/xdp/tcp.h
index 39a30fd41..bad28498e 100644
--- a/src/libknot/xdp/tcp.h
+++ b/src/libknot/xdp/tcp.h
@@ -61,6 +61,10 @@ typedef enum {
XDP_TCP_IGNORE_FIN = (1 << 2),
} knot_tcp_ignore_t;
+typedef enum {
+ KNOT_TCP_CONN_AUTHORIZED = (1 << 0),
+} knot_tcp_conn_flag_t;
+
typedef struct knot_tcp_conn {
struct {
struct knot_tcp_conn *list_node_next;
@@ -79,6 +83,7 @@ typedef struct knot_tcp_conn {
uint32_t last_active;
uint32_t establish_rtt; // in microseconds
knot_tcp_state_t state;
+ knot_tcp_conn_flag_t flags;
struct iovec inbuf;
struct knot_tcp_outbuf *outbufs;
struct knot_tcp_conn *next;