Skip to content

Commit

Permalink
Merge pull request #561 from h2o/kazuho/ecn
Browse files Browse the repository at this point in the history
support ECN
  • Loading branch information
kazuho authored Oct 18, 2023
2 parents c55d375 + 4186233 commit c5b954b
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 97 deletions.
30 changes: 30 additions & 0 deletions include/quicly.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@ struct st_quicly_context_t {
* expand client hello so that it does not fit into one datagram
*/
unsigned expand_client_hello : 1;
/**
* whether to use ECN on the send side; ECN is always on on the receive side
*/
unsigned enable_ecn : 1;
/**
*
*/
Expand Down Expand Up @@ -454,6 +458,14 @@ struct st_quicly_conn_streamgroup_state_t {
* Total number of packets received out of order. \
*/ \
uint64_t received_out_of_order; \
/** \
* connection-wide counters for ECT(0), ECT(1), CE \
*/ \
uint64_t received_ecn_counts[3]; \
/** \
* connection-wide ack-received counters for ECT(0), ECT(1), CE \
*/ \
uint64_t acked_ecn_counts[3]; \
} num_packets; \
struct { \
/** \
Expand Down Expand Up @@ -481,6 +493,16 @@ struct st_quicly_conn_streamgroup_state_t {
*/ \
uint64_t stream_data_resent; \
} num_bytes; \
struct { \
/** \
* number of paths that were ECN-capable \
*/ \
uint64_t ecn_validated; \
/** \
* number of paths that were deemed as ECN black holes \
*/ \
uint64_t ecn_failed; \
} num_paths; \
/** \
* Total number of each frame being sent / received. \
*/ \
Expand Down Expand Up @@ -818,6 +840,10 @@ typedef struct st_quicly_decoded_packet_t {
uint64_t pn;
uint64_t key_phase;
} decrypted;
/**
* ECN bits
*/
uint8_t ecn : 2;
/**
*
*/
Expand Down Expand Up @@ -1035,6 +1061,10 @@ size_t quicly_send_retry(quicly_context_t *ctx, ptls_aead_context_t *token_encry
*/
int quicly_send(quicly_conn_t *conn, quicly_address_t *dest, quicly_address_t *src, struct iovec *datagrams, size_t *num_datagrams,
void *buf, size_t bufsize);
/**
* returns ECN bits to be set for the packets built by the last invocation of `quicly_send`
*/
uint8_t quicly_send_get_ecn_bits(quicly_conn_t *conn);
/**
*
*/
Expand Down
36 changes: 33 additions & 3 deletions include/quicly/cc.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ typedef struct st_quicly_cc_t {
* Packet number indicating end of recovery period, if in recovery.
*/
uint64_t recovery_end;
/**
* If the most recent loss episode was signalled by ECN only (i.e., no packet loss).
*/
unsigned episode_by_ecn : 1;
/**
* State information specific to the congestion controller implementation.
*/
Expand Down Expand Up @@ -130,9 +134,13 @@ typedef struct st_quicly_cc_t {
*/
uint32_t cwnd_maximum;
/**
* Total number of number of loss episodes (congestion window reductions).
* Total number of loss episodes (congestion window reductions).
*/
uint32_t num_loss_episodes;
/**
* Total number of loss episodes that was reported only by ECN (hence no packet loss).
*/
uint32_t num_ecn_loss_episodes;
} quicly_cc_t;

struct st_quicly_cc_type_t {
Expand All @@ -150,8 +158,9 @@ struct st_quicly_cc_type_t {
void (*cc_on_acked)(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t largest_acked, uint32_t inflight,
uint64_t next_pn, int64_t now, uint32_t max_udp_payload_size);
/**
* Called when a packet is detected as lost. |next_pn| is the next unsent packet number,
* used for setting the recovery window.
* Called when a packet is detected as lost.
* @param bytes bytes declared lost, or zero iff ECN_CE is observed
* @param next_pn the next unsent packet number, used for setting the recovery window
*/
void (*cc_on_lost)(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t lost_pn, uint64_t next_pn, int64_t now,
uint32_t max_udp_payload_size);
Expand Down Expand Up @@ -192,6 +201,27 @@ void quicly_cc_reno_on_lost(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t
int64_t now, uint32_t max_udp_payload_size);
void quicly_cc_reno_on_persistent_congestion(quicly_cc_t *cc, const quicly_loss_t *loss, int64_t now);
void quicly_cc_reno_on_sent(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, int64_t now);
/**
* Updates ECN counter when loss is observed.
*/
static void quicly_cc__update_ecn_episodes(quicly_cc_t *cc, uint32_t lost_bytes, uint64_t lost_pn);

/* inline definitions */

inline void quicly_cc__update_ecn_episodes(quicly_cc_t *cc, uint32_t lost_bytes, uint64_t lost_pn)
{
/* when it is a new loss episode, initially assume that all losses are due to ECN signalling ... */
if (lost_pn >= cc->recovery_end) {
++cc->num_ecn_loss_episodes;
cc->episode_by_ecn = 1;
}

/* ... but if a loss is observed, decrement the ECN loss episode counter */
if (lost_bytes != 0 && cc->episode_by_ecn) {
--cc->num_ecn_loss_episodes;
cc->episode_by_ecn = 0;
}
}

#ifdef __cplusplus
}
Expand Down
3 changes: 2 additions & 1 deletion include/quicly/frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ typedef struct st_quicly_stop_sending_frame_t {

static int quicly_decode_stop_sending_frame(const uint8_t **src, const uint8_t *end, quicly_stop_sending_frame_t *frame);

uint8_t *quicly_encode_ack_frame(uint8_t *dst, uint8_t *dst_end, quicly_ranges_t *ranges, uint64_t ack_delay);
uint8_t *quicly_encode_ack_frame(uint8_t *dst, uint8_t *dst_end, quicly_ranges_t *ranges, uint64_t *ecn_counts, uint64_t ack_delay);

typedef struct st_quicly_ack_frame_t {
uint64_t largest_acknowledged;
Expand All @@ -243,6 +243,7 @@ typedef struct st_quicly_ack_frame_t {
uint64_t num_gaps;
uint64_t ack_block_lengths[QUICLY_ACK_MAX_GAPS + 1];
uint64_t gaps[QUICLY_ACK_MAX_GAPS];
uint64_t ecn_counts[3];
} quicly_ack_frame_t;

int quicly_decode_ack_frame(const uint8_t **src, const uint8_t *end, quicly_ack_frame_t *frame, int is_ack_ecn);
Expand Down
2 changes: 2 additions & 0 deletions lib/cc-cubic.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ static void cubic_on_acked(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t
static void cubic_on_lost(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t lost_pn, uint64_t next_pn,
int64_t now, uint32_t max_udp_payload_size)
{
quicly_cc__update_ecn_episodes(cc, bytes, lost_pn);

/* Nothing to do if loss is in recovery window. */
if (lost_pn < cc->recovery_end)
return;
Expand Down
2 changes: 2 additions & 0 deletions lib/cc-pico.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ static void pico_on_acked(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t b
static void pico_on_lost(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t lost_pn, uint64_t next_pn,
int64_t now, uint32_t max_udp_payload_size)
{
quicly_cc__update_ecn_episodes(cc, bytes, lost_pn);

/* Nothing to do if loss is in recovery window. */
if (lost_pn < cc->recovery_end)
return;
Expand Down
2 changes: 2 additions & 0 deletions lib/cc-reno.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ static void reno_on_acked(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t b
void quicly_cc_reno_on_lost(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t lost_pn, uint64_t next_pn,
int64_t now, uint32_t max_udp_payload_size)
{
quicly_cc__update_ecn_episodes(cc, bytes, lost_pn);

/* Nothing to do if loss is in recovery window. */
if (lost_pn < cc->recovery_end)
return;
Expand Down
2 changes: 2 additions & 0 deletions lib/defaults.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const quicly_context_t quicly_spec_context = {NULL,
DEFAULT_HANDSHAKE_TIMEOUT_RTT_MULTIPLIER,
DEFAULT_MAX_INITIAL_HANDSHAKE_PACKETS,
0, /* enlarge_client_hello */
1, /* enable_ecn */
NULL,
NULL, /* on_stream_open */
&quicly_default_stream_scheduler,
Expand Down Expand Up @@ -80,6 +81,7 @@ const quicly_context_t quicly_performant_context = {NULL,
DEFAULT_HANDSHAKE_TIMEOUT_RTT_MULTIPLIER,
DEFAULT_MAX_INITIAL_HANDSHAKE_PACKETS,
0, /* enlarge_client_hello */
1, /* enable_ecn */
NULL,
NULL, /* on_stream_open */
&quicly_default_stream_scheduler,
Expand Down
26 changes: 21 additions & 5 deletions lib/frame.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ uint8_t *quicly_encode_path_challenge_frame(uint8_t *dst, int is_response, const
return dst;
}

uint8_t *quicly_encode_ack_frame(uint8_t *dst, uint8_t *dst_end, quicly_ranges_t *ranges, uint64_t ack_delay)
uint8_t *quicly_encode_ack_frame(uint8_t *dst, uint8_t *dst_end, quicly_ranges_t *ranges, uint64_t *ecn_counts, uint64_t ack_delay)
{
#define WRITE_BLOCK(start, end) \
do { \
Expand All @@ -42,12 +42,14 @@ uint8_t *quicly_encode_ack_frame(uint8_t *dst, uint8_t *dst_end, quicly_ranges_t
dst = quicly_encodev(dst, _end - _start - 1); \
} while (0)

/* emit ACK_ECN frame if any of the three ECN counts are non-zero */
uint8_t frame_type = (ecn_counts[0] | ecn_counts[1] | ecn_counts[2]) != 0 ? QUICLY_FRAME_TYPE_ACK_ECN : QUICLY_FRAME_TYPE_ACK;
size_t range_index = ranges->num_ranges - 1;

assert(ranges->num_ranges != 0);

/* number of bytes being emitted without space check are 1 + 8 + 8 + 1 bytes (as defined in QUICLY_ACK_FRAME_CAPACITY) */
*dst++ = QUICLY_FRAME_TYPE_ACK;
*dst++ = frame_type;
dst = quicly_encodev(dst, ranges->ranges[range_index].end - 1); /* largest acknowledged */
dst = quicly_encodev(dst, ack_delay); /* ack delay */
PTLS_BUILD_ASSERT(QUICLY_MAX_ACK_BLOCKS - 1 <= 63);
Expand All @@ -60,6 +62,17 @@ uint8_t *quicly_encode_ack_frame(uint8_t *dst, uint8_t *dst_end, quicly_ranges_t
WRITE_BLOCK(ranges->ranges[range_index].end, ranges->ranges[range_index + 1].start);
}

if (frame_type == QUICLY_FRAME_TYPE_ACK_ECN) {
uint8_t buf[24], *p = buf;
for (size_t i = 0; i < 3; ++i)
p = quicly_encodev(p, ecn_counts[i]);
size_t len = p - buf;
if (dst_end - dst < len)
return NULL;
memcpy(dst, buf, len);
dst += len;
}

return dst;

#undef WRITE_BLOCK
Expand Down Expand Up @@ -100,11 +113,14 @@ int quicly_decode_ack_frame(const uint8_t **src, const uint8_t *end, quicly_ack_
}

if (is_ack_ecn) {
/* just skip ECT(0), ECT(1), ECT-CE counters for the time being */
for (i = 0; i != 3; ++i)
if (quicly_decodev(src, end) == UINT64_MAX)
for (i = 0; i < PTLS_ELEMENTSOF(frame->ecn_counts); ++i)
if ((frame->ecn_counts[i] = quicly_decodev(src, end)) == UINT64_MAX)
goto Error;
} else {
for (i = 0; i < PTLS_ELEMENTSOF(frame->ecn_counts); ++i)
frame->ecn_counts[i] = 0;
}

return 0;
Error:
return QUICLY_TRANSPORT_ERROR_FRAME_ENCODING;
Expand Down
Loading

0 comments on commit c5b954b

Please sign in to comment.