QUIC_CFQ_ITEM *ossl_quic_cfq_item_get_priority_next(const QUIC_CFQ_ITEM *item,
uint32_t pn_space);
+int ossl_quic_cfq_discard_unreliable(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item);
#endif
#endif
void ossl_quic_channel_set_tcause(QUIC_CHANNEL *ch, uint64_t app_error_code,
const char *app_reason);
+
+void ossl_ch_reset_rx_state(QUIC_CHANNEL *ch);
#endif
#endif
void ossl_quic_fifd_set_qlog_cb(QUIC_FIFD *fifd, QLOG *(*get_qlog_cb)(void *arg),
void *arg);
+void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *tpkt);
#endif
#endif
* https://www.openssl.org/source/license.html
*/
+#include "internal/quic_channel.h"
#include "internal/quic_cfq.h"
#include "internal/numbers.h"
}
}
+int ossl_quic_cfq_discard_unreliable(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item)
+{
+ int discarded;
+
+ if (ossl_quic_cfq_item_is_unreliable(item)) {
+ ossl_quic_cfq_release(cfq, item);
+ discarded = 1;
+ } else {
+ discarded = 0;
+ }
+
+ return discarded;
+}
+
/*
* Releases a CFQ item. The item may be in either state (NEW or TX) prior to the
* call. The QUIC_CFQ_ITEM pointer must not be used following this call.
"forgery limit");
}
+void ossl_ch_reset_rx_state(QUIC_CHANNEL *ch)
+{
+ ch->did_crypto_frame = 0;
+ ch->seen_path_challenge = 0;
+}
+
/* Process queued incoming packets and handle frames, if any. */
static int ch_rx(QUIC_CHANNEL *ch, int channel_only, int *notify_other_threads)
{
#include "internal/quic_stream_map.h"
#include "internal/quic_tls.h"
+/*
+ * This is a part of PATH_CHALLENGE flood [1] mitigation. This limits the
+ * number of PATH_CHALLENGE frames QUIC stack is willing to process for
+ * connection. Local QUIC stack creates PATH_RESPONSE frame for PATH_CHALLENGE
+ * frame it receives from remote peer. The response frame is put Control Frame
+ * Queue waiting to be dispatched. The PATH_RESPONSE frame is removed from CFQ
+ * after it is dispatched. The QUIC_PATH_RESPONSE_QLEN limits the number of
+ * PATH_RESPONSE frames waiting to be dispatched. No new PATH_RESPONSE frames
+ * are inserted into CFQ if queue limit is exceeded.
+ *
+ * QUIC implementations use different limits for PATH_RESPONSE queue lengths:
+ * quic-go defines maxPathResponses as 256
+ * quiche from cloadflare sets DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN to 3
+ * t-quic from tencent chooses MAX_PATH_CHALS_RECV to be 8
+ *
+ * OpenSSL here introduces QUIC_PATH_RESPONSE_QLEN as 32.
+ *
+ * [1] https://www.ietf.org/archive/id/draft-chen-quic-logical-vuln-mitigations-00.txt
+ * (section 4.2)
+ */
+#define QUIC_PATH_RESPONSE_QLEN 32
+
/*
* QUIC Channel Structure
* ======================
/* Has qlog been requested? */
unsigned int is_tserver_ch : 1;
+ /*
+ * RFC 9000 Section 9.2.1 says:
+ * However, an endpoint SHOULD NOT send multiple
+ * PATH_CHALLENGE frames in a single packet.
+ * The counter here allows us to detect multiple presence
+ * of PATH_CHALLENGE frame in packet. We process only the
+ * first PATH_CHALLENGE frame found in packet. Remaining PATH_CHALLENGE
+ * frames are ignored.
+ * seen_path_challenge flag is always reset before
+ * ossl_quic_handle_frames() gets called.
+ */
+ unsigned int seen_path_challenge : 1;
/* Saved error stack in case permanent error was encountered */
ERR_STATE *err_state;
/* Title for qlog purposes. We own this copy. */
char *qlog_title;
+ /*
+ * number of path responses waiting to be dispatched
+ * from control frame queue (CFQ)
+ */
+ unsigned int path_response_limit;
};
#endif
fifd->get_qlog_cb = get_qlog_cb;
fifd->get_qlog_cb_arg = get_qlog_cb_arg;
}
+
+static void txpim_pkt_remove_cfq_item(QUIC_TXPIM_PKT *pkt, QUIC_CFQ_ITEM *cfq_item)
+{
+ QUIC_CFQ_ITEM *prev = cfq_item->pkt_prev;
+
+ if (prev != NULL) {
+ prev->pkt_next = cfq_item->pkt_next;
+ } else {
+ pkt->retx_head = cfq_item->pkt_next;
+ }
+
+ if (cfq_item->pkt_next != NULL)
+ cfq_item->pkt_next->pkt_prev = prev;
+
+ cfq_item->pkt_prev = NULL;
+ cfq_item->pkt_next = NULL;
+}
+
+void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt)
+{
+ QUIC_CFQ_ITEM *cfq_item, *cfq_next;
+
+ /*
+ * The packet has been written to network. We can discard frames we don't
+ * retransmit when loss is detected.
+ */
+ cfq_item = pkt->retx_head;
+ while (cfq_item != NULL) {
+ /*
+ * Discarded items are moved to free list. If item
+ * got moved to free list we must also remove it from
+ * cfq list kept in pkt, so ACKM does not find it when
+ * receives an ACK for pkt.
+ */
+ if (ossl_quic_cfq_discard_unreliable(fifd->cfq, cfq_item)) {
+ cfq_next = cfq_item->pkt_next;
+ txpim_pkt_remove_cfq_item(pkt, cfq_item);
+ cfq_item = cfq_next;
+ } else {
+ cfq_item = cfq_item->pkt_next;
+ }
+ }
+}
static void free_path_response(unsigned char *buf, size_t buf_len, void *arg)
{
+ QUIC_CHANNEL *ch = (QUIC_CHANNEL *)arg;
+
+ assert(ch->path_response_limit > 0);
+
+ ch->path_response_limit--;
+
OPENSSL_free(buf);
}
return 0;
}
- /*
- * RFC 9000 s. 8.2.2: On receiving a PATH_CHALLENGE frame, an endpoint MUST
- * respond by echoing the data contained in the PATH_CHALLENGE frame in a
- * PATH_RESPONSE frame.
- *
- * TODO(QUIC FUTURE): We should try to avoid allocation here in the future.
- */
- encoded_len = sizeof(uint64_t) + 1;
- if ((encoded = OPENSSL_malloc(encoded_len)) == NULL)
- goto err;
+ if (ch->seen_path_challenge == 0
+ && ch->path_response_limit < QUIC_PATH_RESPONSE_QLEN) {
+ /*
+ * RFC 9000 s. 8.2.2: On receiving a PATH_CHALLENGE frame, an endpoint
+ * MUST respond by echoing the data contained in the PATH_CHALLENGE
+ * frame in a PATH_RESPONSE frame.
+ *
+ * TODO(QUIC FUTURE): We should try to avoid allocation here in the
+ * future.
+ */
+ encoded_len = sizeof(uint64_t) + 1;
+ if ((encoded = OPENSSL_malloc(encoded_len)) == NULL)
+ goto err;
- if (!WPACKET_init_static_len(&wpkt, encoded, encoded_len, 0))
- goto err;
+ if (!WPACKET_init_static_len(&wpkt, encoded, encoded_len, 0))
+ goto err;
- if (!ossl_quic_wire_encode_frame_path_response(&wpkt, frame_data)) {
- WPACKET_cleanup(&wpkt);
- goto err;
- }
+ if (!ossl_quic_wire_encode_frame_path_response(&wpkt, frame_data)) {
+ WPACKET_cleanup(&wpkt);
+ goto err;
+ }
- WPACKET_finish(&wpkt);
+ WPACKET_finish(&wpkt);
- if (!ossl_quic_cfq_add_frame(ch->cfq, 0, QUIC_PN_SPACE_APP,
- OSSL_QUIC_FRAME_TYPE_PATH_RESPONSE,
- QUIC_CFQ_ITEM_FLAG_UNRELIABLE,
- encoded, encoded_len,
- free_path_response, NULL))
- goto err;
+ if (!ossl_quic_cfq_add_frame(ch->cfq, 0, QUIC_PN_SPACE_APP,
+ OSSL_QUIC_FRAME_TYPE_PATH_RESPONSE,
+ QUIC_CFQ_ITEM_FLAG_UNRELIABLE,
+ encoded, encoded_len,
+ free_path_response, ch))
+ goto err;
+ ch->seen_path_challenge = 1;
+ ch->path_response_limit++;
+ }
return 1;
if (ch == NULL)
return 0;
- ch->did_crypto_frame = 0;
+ ossl_ch_reset_rx_state(ch);
/* Initialize |ackm_data| (and reinitialize |ok|)*/
memset(&ackm_data, 0, sizeof(ackm_data));
--probe_info->pto[pn_space];
}
+ ossl_quic_fifd_pkt_discard_unreliable(&txp->fifd, tpkt);
+
return rc;
}