diff --git a/include/quicly.h b/include/quicly.h index a4b9a6cd..22c10649 100644 --- a/include/quicly.h +++ b/include/quicly.h @@ -326,6 +326,15 @@ struct st_quicly_context_t { * the connection. */ uint64_t max_initial_handshake_packets; + /** + * maximum number of probe packets (i.e., packets carrying PATH_CHALLENGE frames) to be sent before calling a path unreachable + */ + uint64_t max_probe_packets; + /** + * Once path validation fails for the specified number of paths, packets arriving on new tuples will be dropped. Setting this + * value to zero effectively disables the endpoint responding to path migration attempts. + */ + uint64_t max_path_validation_failures; /** * Jumpstart CWND to be used when there is no previous information. If set to zero, slow start is used. Note jumpstart is * possible only when the use_pacing flag is set. @@ -484,6 +493,14 @@ struct st_quicly_conn_streamgroup_state_t { * connection-wide ack-received counters for ECT(0), ECT(1), CE \ */ \ uint64_t acked_ecn_counts[3]; \ + /** \ + * Total number of packets sent on promoted paths. \ + */ \ + uint64_t sent_promoted_paths; \ + /** \ + * Total number of acked packets that were sent on promoted. \ + */ \ + uint64_t ack_received_promoted_paths; \ } num_packets; \ struct { \ /** \ @@ -512,6 +529,30 @@ struct st_quicly_conn_streamgroup_state_t { uint64_t stream_data_resent; \ } num_bytes; \ struct { \ + /** \ + * number of alternate paths created \ + */ \ + uint64_t created; \ + /** \ + * number alternate paths validated \ + */ \ + uint64_t validated; \ + /** \ + * number of alternate paths that were created but failed to validate \ + */ \ + uint64_t validation_failed; \ + /** \ + * number of paths on which migration has been elicited (i.e., received non-probing packets) \ + */ \ + uint64_t migration_elicited; \ + /** \ + * number of migrations \ + */ \ + uint64_t promoted; \ + /** \ + * number of alternate paths that were closed due to Connection ID being unavailable \ + */ \ + uint64_t closed_no_dcid; \ /** \ * number of paths that were ECN-capable \ */ \ @@ -625,10 +666,6 @@ struct _st_quicly_conn_public_t { * on that assumption. */ quicly_local_cid_set_t cid_set; - /** - * the local address (may be AF_UNSPEC) - */ - quicly_address_t address; /** * the SCID used in long header packets. Equivalent to local_cid[seq=0]. Retaining the value separately is the easiest way * of staying away from the complexity caused by remote peer sending RCID frames before the handshake concludes. @@ -644,20 +681,12 @@ struct _st_quicly_conn_public_t { * CIDs received from the remote peer */ quicly_remote_cid_set_t cid_set; - /** - * the remote address (cannot be AF_UNSPEC) - */ - quicly_address_t address; struct st_quicly_conn_streamgroup_state_t bidi, uni; quicly_transport_parameters_t transport_params; struct { unsigned validated : 1; unsigned send_probe : 1; } address_validation; - /** - * largest value of Retire Prior To field observed so far - */ - uint64_t largest_retire_prior_to; } remote; /** * Retains the original DCID used by the client. Servers use this to route packets incoming packets. Clients use this when @@ -998,11 +1027,11 @@ static quicly_stream_id_t quicly_get_remote_next_stream_id(quicly_conn_t *conn, /** * Returns the local address of the connection. This may be AF_UNSPEC, indicating that the operating system is choosing the address. */ -static struct sockaddr *quicly_get_sockname(quicly_conn_t *conn); +struct sockaddr *quicly_get_sockname(quicly_conn_t *conn); /** * Returns the remote address of the connection. This would never be AF_UNSPEC. */ -static struct sockaddr *quicly_get_peername(quicly_conn_t *conn); +struct sockaddr *quicly_get_peername(quicly_conn_t *conn); /** * */ @@ -1429,18 +1458,6 @@ inline quicly_stream_id_t quicly_get_remote_next_stream_id(quicly_conn_t *conn, return uni ? c->remote.uni.next_stream_id : c->remote.bidi.next_stream_id; } -inline struct sockaddr *quicly_get_sockname(quicly_conn_t *conn) -{ - struct _st_quicly_conn_public_t *c = (struct _st_quicly_conn_public_t *)conn; - return &c->local.address.sa; -} - -inline struct sockaddr *quicly_get_peername(quicly_conn_t *conn) -{ - struct _st_quicly_conn_public_t *c = (struct _st_quicly_conn_public_t *)conn; - return &c->remote.address.sa; -} - inline uint32_t quicly_get_protocol_version(quicly_conn_t *conn) { struct _st_quicly_conn_public_t *c = (struct _st_quicly_conn_public_t *)conn; diff --git a/include/quicly/remote_cid.h b/include/quicly/remote_cid.h index 8f3f8a16..3be8c180 100644 --- a/include/quicly/remote_cid.h +++ b/include/quicly/remote_cid.h @@ -28,24 +28,40 @@ extern "C" { #endif +/** + * state of `quicly_remote_cid_t` + */ +typedef enum en_quicly_remote_cid_state_t { + /** + * cid and stateless reset token have not been received for the sequence number + */ + QUICLY_REMOTE_CID_UNAVAILABLE, + /** + * cid is in use + */ + QUICLY_REMOTE_CID_IN_USE, + /** + * cid has been receive but has not been used yet + */ + QUICLY_REMOTE_CID_AVAILABLE +} quicly_remote_cid_state_t; + /** * records a CID given by the remote peer */ typedef struct st_quicly_remote_cid_t { /** - * indicates whether this record holds an active (given by remote peer and not retired) CID + * state */ - int is_active; + quicly_remote_cid_state_t state; /** - * sequence number of the CID - * - * If is_active, this represents the sequence number associated with the CID. - * If !is_active, this represents a "reserved" slot, meaning that we are expecting to receive a NEW_CONNECTION_ID frame - * with this sequence number. This helps determine if a received frame is carrying a CID that is already retired. + * sequence number of the CID; if `state` is UNAVAILABLE, this is a reserved slot meaning that we are expecting to receive a + * NEW_CONNECTION_ID frame with this sequence number. This helps determine if a received frame is carrying a CID that is already + * retired. */ uint64_t sequence; /** - * CID; only usable if `is_active` is true + * CID; available unless `state` is UNAVAILABLE */ struct st_quicly_cid_t cid; /** @@ -59,8 +75,9 @@ typedef struct st_quicly_remote_cid_t { */ typedef struct st_quicly_remote_cid_set_t { /** - * we retain QUICLY_LOCAL_ACTIVE_CONNECTION_ID_LIMIT active connection IDs - * cids[0] holds the current (in use) CID which is used when emitting packets + * We retain QUICLY_LOCAL_ACTIVE_CONNECTION_ID_LIMIT active connection IDs. `cids[0]` used to retain the current DCID, but it is + * no longer the case. DCID of the non-probing path should now be obtained via `get_dcid(conn->paths[0])` where `paths[0]` is + * the non-probing path. */ quicly_remote_cid_t cids[QUICLY_LOCAL_ACTIVE_CONNECTION_ID_LIMIT]; /** @@ -84,9 +101,8 @@ int quicly_remote_cid_register(quicly_remote_cid_set_t *set, uint64_t sequence, uint64_t unregistered_seqs[QUICLY_LOCAL_ACTIVE_CONNECTION_ID_LIMIT], size_t *num_unregistered_seqs); /** * unregisters specified CID from the store - * returns 0 if success, 1 if failure */ -int quicly_remote_cid_unregister(quicly_remote_cid_set_t *set, uint64_t sequence); +void quicly_remote_cid_unregister(quicly_remote_cid_set_t *set, uint64_t sequence); #ifdef __cplusplus } diff --git a/include/quicly/sentmap.h b/include/quicly/sentmap.h index 8a3ba288..c12e2c2a 100644 --- a/include/quicly/sentmap.h +++ b/include/quicly/sentmap.h @@ -57,9 +57,13 @@ typedef struct st_quicly_sent_packet_t { */ uint8_t frames_in_flight : 1; /** - * + * */ uint8_t cc_limited : 1; + /** + * if sent on a promoted path + */ + uint8_t promoted_path : 1; /** * number of bytes in-flight for the packet, from the context of CC (becomes zero when deemed lost, but not when PTO fires) */ @@ -256,7 +260,7 @@ int quicly_sentmap_prepare(quicly_sentmap_t *map, uint64_t packet_number, int64_ /** * commits a write */ -static void quicly_sentmap_commit(quicly_sentmap_t *map, uint16_t bytes_in_flight, int cc_limited); +static void quicly_sentmap_commit(quicly_sentmap_t *map, uint16_t bytes_in_flight, int cc_limited, int promoted_path); /** * Allocates a slot to contain a callback for a frame. The function MUST be called after _prepare but before _commit. */ @@ -294,7 +298,7 @@ inline int quicly_sentmap_is_open(quicly_sentmap_t *map) return map->_pending_packet != NULL; } -inline void quicly_sentmap_commit(quicly_sentmap_t *map, uint16_t bytes_in_flight, int cc_limited) +inline void quicly_sentmap_commit(quicly_sentmap_t *map, uint16_t bytes_in_flight, int cc_limited, int promoted_path) { assert(quicly_sentmap_is_open(map)); @@ -305,6 +309,8 @@ inline void quicly_sentmap_commit(quicly_sentmap_t *map, uint16_t bytes_in_fligh map->bytes_in_flight += bytes_in_flight; } map->_pending_packet->data.packet.frames_in_flight = 1; + if (promoted_path) + map->_pending_packet->data.packet.promoted_path = 1; map->_pending_packet = NULL; ++map->num_packets; diff --git a/lib/defaults.c b/lib/defaults.c index faee332f..cdf8d8a2 100644 --- a/lib/defaults.c +++ b/lib/defaults.c @@ -30,6 +30,8 @@ #define DEFAULT_PRE_VALIDATION_AMPLIFICATION_LIMIT 3 #define DEFAULT_HANDSHAKE_TIMEOUT_RTT_MULTIPLIER 400 #define DEFAULT_MAX_INITIAL_HANDSHAKE_PACKETS 1000 +#define DEFAULT_MAX_PROBE_PACKETS 5 +#define DEFAULT_MAX_PATH_VALIDATION_FAILURES 100 /* profile that employs IETF specified values */ const quicly_context_t quicly_spec_context = {NULL, /* tls */ @@ -49,6 +51,8 @@ const quicly_context_t quicly_spec_context = {NULL, 0, /* ack_frequency */ DEFAULT_HANDSHAKE_TIMEOUT_RTT_MULTIPLIER, DEFAULT_MAX_INITIAL_HANDSHAKE_PACKETS, + DEFAULT_MAX_PROBE_PACKETS, + DEFAULT_MAX_PATH_VALIDATION_FAILURES, 0, /* default_jumpstart_cwnd_bytes */ 0, /* max_jumpstart_cwnd_bytes */ 0, /* enlarge_client_hello */ @@ -84,6 +88,8 @@ const quicly_context_t quicly_performant_context = {NULL, 0, /* ack_frequency */ DEFAULT_HANDSHAKE_TIMEOUT_RTT_MULTIPLIER, DEFAULT_MAX_INITIAL_HANDSHAKE_PACKETS, + DEFAULT_MAX_PROBE_PACKETS, + DEFAULT_MAX_PATH_VALIDATION_FAILURES, 0, /* default_jumpstart_cwnd_bytes */ 0, /* max_jumpstart_cwnd_bytes */ 0, /* enlarge_client_hello */ diff --git a/lib/quicly.c b/lib/quicly.c index db9aaa2a..4c0cfa08 100644 --- a/lib/quicly.c +++ b/lib/quicly.c @@ -21,6 +21,7 @@ */ #include #include +#include #include #include #include @@ -91,8 +92,10 @@ KHASH_MAP_INIT_INT64(quicly_stream_t, quicly_stream_t *) QUICLY_##label(_conn, __VA_ARGS__); \ QUICLY_TRACER(label, _conn, __VA_ARGS__); \ } while (0) +#define QUICLY_PROBE_ENABLED(label) QUICLY_##label##_ENABLED() #else #define QUICLY_PROBE(label, conn, ...) QUICLY_TRACER(label, conn, __VA_ARGS__) +#define QUICLY_PROBE_ENABLED(label) 0 #endif #define QUICLY_PROBE_HEXDUMP(s, l) \ ({ \ @@ -183,8 +186,66 @@ struct st_quicly_application_space_t { int one_rtt_writable; }; +struct st_quicly_conn_path_t { + struct { + /** + * remote address (must not be AF_UNSPEC) + */ + quicly_address_t remote; + /** + * local address (may be AF_UNSPEC) + */ + quicly_address_t local; + } address; + /** + * DCID being used for the path indicated by the sequence number; or UINT64_MAX if yet to be assigned. Multile paths will share + * the same value of zero if peer CID is zero-length. + */ + uint64_t dcid; + /** + * Maximum number of packets being received by the connection when a packet was last received on this path. This value is used + * to determine the least-recently-used path which will be recycled. + */ + uint64_t packet_last_received; + /** + * `send_at` indicates when a PATH_CHALLENGE frame carrying `data` should be sent, or if the value is INT64_MAX the path is + * validated + */ + struct { + int64_t send_at; + uint64_t num_sent; + uint8_t data[QUICLY_PATH_CHALLENGE_DATA_LEN]; + } path_challenge; + /** + * path response to be sent, if `send_` is set + */ + struct { + uint8_t send_; + uint8_t data[QUICLY_PATH_CHALLENGE_DATA_LEN]; + } path_response; + /** + * if this path is the initial path (i.e., the one on which handshake is done) + */ + uint8_t initial : 1; + /** + * if only probe packets have been received (and hence have been sent) on the path + */ + uint8_t probe_only : 1; + /** + * number of packets being sent / received on the path + */ + struct { + uint64_t sent; + uint64_t received; + } num_packets; +}; + struct st_quicly_conn_t { struct _st_quicly_conn_public_t super; + /** + * `paths[0]` is the non-probing path that is guaranteed to exist, others are backups that may be NULL + */ + struct st_quicly_conn_path_t *paths[QUICLY_LOCAL_ACTIVE_CONNECTION_ID_LIMIT]; /** * the initial context */ @@ -270,13 +331,6 @@ struct st_quicly_conn_t { quicly_maxsender_t blocked_sender; } uni, bidi; } max_streams; - /** - * - */ - struct { - uint8_t send_; - uint8_t data[QUICLY_PATH_CHALLENGE_DATA_LEN]; - } path_response; /** * */ @@ -297,13 +351,22 @@ struct st_quicly_conn_t { */ int64_t last_retransmittable_sent_at; /** - * when to send an ACK, or other frames used for managing the connection + * when to send an ACK, connection close frames or to destroy the connection */ int64_t send_ack_at; + /** + * when a PATH_CHALLENGE or PATH_RESPONSE frame is to be sent on any path + */ + int64_t send_probe_at; /** * congestion control */ quicly_cc_t cc; + /** + * Next PN to be used when the path is initialized or promoted. As loss recovery / CC is reset upon path promotion, ACKs for + * for packets with PN below this property are ignored. + */ + uint64_t pn_path_start; /** * pacer */ @@ -342,9 +405,9 @@ struct st_quicly_conn_t { /* The flags below indicate if the respective frames have to be sent or not. There are no false positives. */ #define QUICLY_PENDING_FLOW_NEW_TOKEN_BIT (1 << 4) #define QUICLY_PENDING_FLOW_HANDSHAKE_DONE_BIT (1 << 5) -/* Indicates that PATH_CHALLENGE, PATH_RESPONSE, MAX_STREAMS, MAX_DATA, DATA_BLOCKED, STREAMS_BLOCKED, NEW_CONNECTION_ID _might_ - * have to be sent. There could be false positives; logic for sending each of these frames have the capability of detecting such - * false positives. The purpose of this bit is to consolidate information as an optimization. */ +/* Indicates that MAX_STREAMS, MAX_DATA, DATA_BLOCKED, STREAMS_BLOCKED, NEW_CONNECTION_ID _might_ have to be sent. There could be + * false positives; logic for sending each of these frames have the capability of detecting such false positives. The purpose of + * this bit is to consolidate information as an optimization. */ #define QUICLY_PENDING_FLOW_OTHERS_BIT (1 << 6) /** * @@ -441,6 +504,7 @@ struct st_quicly_conn_t { struct st_quicly_handle_payload_state_t { const uint8_t *src, *const end; size_t epoch; + size_t path_index; uint64_t frame_type; }; @@ -966,6 +1030,21 @@ static void schedule_retire_connection_id_frame(quicly_conn_t *conn, uint64_t se conn->egress.pending_flows |= QUICLY_PENDING_FLOW_OTHERS_BIT; } +static void retire_connection_id(quicly_conn_t *conn, uint64_t sequence) +{ + /* Reset path CIDs that are being retired. To maximize the chance of having enough number of CIDs to run all paths when new CIDs + * are provided through multiple NCID frames possibly scattered over multiple packets, CIDs are reassigned to the paths lazily. + */ + for (size_t i = 0; i < PTLS_ELEMENTSOF(conn->paths); ++i) { + struct st_quicly_conn_path_t *path = conn->paths[i]; + if (path != NULL && path->dcid == sequence) + path->dcid = UINT64_MAX; + } + + quicly_remote_cid_unregister(&conn->super.remote.cid_set, sequence); + schedule_retire_connection_id_frame(conn, sequence); +} + static int write_crypto_data(quicly_conn_t *conn, ptls_buffer_t *tlsbuf, size_t epoch_offsets[5]) { size_t epoch; @@ -1270,6 +1349,16 @@ uint32_t quicly_num_streams_by_group(quicly_conn_t *conn, int uni, int locally_i return state->num_streams; } +struct sockaddr *quicly_get_sockname(quicly_conn_t *conn) +{ + return &conn->paths[0]->address.local.sa; +} + +struct sockaddr *quicly_get_peername(quicly_conn_t *conn) +{ + return &conn->paths[0]->address.remote.sa; +} + int quicly_get_stats(quicly_conn_t *conn, quicly_stats_t *stats) { /* copy the pre-built stats fields */ @@ -1676,12 +1765,221 @@ static int received_key_update(quicly_conn_t *conn, uint64_t newly_decrypted_key } } +static void calc_resume_sendrate(quicly_conn_t *conn, uint64_t *rate, uint32_t *rtt) +{ + quicly_rate_t reported; + + quicly_ratemeter_report(&conn->egress.ratemeter, &reported); + + if (reported.smoothed != 0 || reported.latest != 0) { + *rate = reported.smoothed > reported.latest ? reported.smoothed : reported.latest; + *rtt = conn->egress.loss.rtt.minimum; + } else { + *rate = 0; + *rtt = 0; + } +} + static inline void update_open_count(quicly_context_t *ctx, ssize_t delta) { if (ctx->update_open_count != NULL) ctx->update_open_count->cb(ctx->update_open_count, delta); } +#define LONGEST_ADDRESS_STR "[0000:1111:2222:3333:4444:5555:6666:7777]:12345" +static void stringify_address(char *buf, struct sockaddr *sa) +{ + char *p = buf; + uint16_t port = 0; + + p = buf; + switch (sa->sa_family) { + case AF_INET: + inet_ntop(AF_INET, &((struct sockaddr_in *)sa)->sin_addr, p, sizeof(LONGEST_ADDRESS_STR)); + p += strlen(p); + port = ntohs(((struct sockaddr_in *)sa)->sin_port); + break; + case AF_INET6: + *p++ = '['; + inet_ntop(AF_INET6, &((struct sockaddr_in6 *)sa)->sin6_addr, p, sizeof(LONGEST_ADDRESS_STR)); + *p++ = ']'; + port = ntohs(((struct sockaddr_in *)sa)->sin_port); + break; + default: + assert("unexpected addres family"); + break; + } + + *p++ = ':'; + sprintf(p, "%" PRIu16, port); +} + +static int new_path(quicly_conn_t *conn, size_t path_index, struct sockaddr *remote_addr, struct sockaddr *local_addr) +{ + struct st_quicly_conn_path_t *path; + + assert(conn->paths[path_index] == NULL); + + if ((path = malloc(sizeof(*conn->paths[path_index]))) == NULL) + return PTLS_ERROR_NO_MEMORY; + + if (path_index == 0) { + /* default path used for handshake */ + *path = (struct st_quicly_conn_path_t){ + .dcid = 0, + .path_challenge.send_at = INT64_MAX, + .initial = 1, + .probe_only = 0, + }; + } else { + *path = (struct st_quicly_conn_path_t){ + .dcid = UINT64_MAX, + .path_challenge.send_at = 0, + .probe_only = 1, + }; + conn->super.ctx->tls->random_bytes(path->path_challenge.data, sizeof(path->path_challenge.data)); + conn->super.stats.num_paths.created += 1; + } + set_address(&path->address.remote, remote_addr); + set_address(&path->address.local, local_addr); + + conn->paths[path_index] = path; + + if (QUICLY_PROBE_ENABLED(NEW_PATH) || ptls_log.is_active) { + char remote[sizeof(LONGEST_ADDRESS_STR)]; + stringify_address(remote, &path->address.remote.sa); + QUICLY_PROBE(NEW_PATH, conn, conn->stash.now, path_index, remote); + QUICLY_LOG_CONN(new_path, conn, { + PTLS_LOG_ELEMENT_UNSIGNED(path_index, path_index); + PTLS_LOG_ELEMENT_SAFESTR(remote, remote); + }); + } + + return 0; +} + +static void do_delete_path(quicly_conn_t *conn, struct st_quicly_conn_path_t *path) +{ + if (path->dcid != UINT64_MAX && conn->super.remote.cid_set.cids[0].cid.len != 0) + retire_connection_id(conn, path->dcid); + free(path); +} + +static void delete_path(quicly_conn_t *conn, size_t path_index) +{ + QUICLY_PROBE(DELETE_PATH, conn, conn->stash.now, path_index); + QUICLY_LOG_CONN(delete_path, conn, { PTLS_LOG_ELEMENT_UNSIGNED(path_index, path_index); }); + + struct st_quicly_conn_path_t *path = conn->paths[path_index]; + conn->paths[path_index] = NULL; + if (path->path_challenge.send_at != INT64_MAX) + conn->super.stats.num_paths.validation_failed += 1; + + do_delete_path(conn, path); +} + +/** + * paths[0] (the default path) is freed and the path specified by `path_index` is promoted + */ +static int promote_path(quicly_conn_t *conn, size_t path_index) +{ + QUICLY_PROBE(PROMOTE_PATH, conn, conn->stash.now, path_index); + QUICLY_LOG_CONN(promote_path, conn, { PTLS_LOG_ELEMENT_UNSIGNED(path_index, path_index); }); + + { /* mark all packets as lost, as it is unlikely that packets sent on the old path wound be acknowledged */ + quicly_sentmap_iter_t iter; + int ret; + if ((ret = quicly_loss_init_sentmap_iter(&conn->egress.loss, &iter, conn->stash.now, + conn->super.remote.transport_params.max_ack_delay, 0)) != 0) + return ret; + const quicly_sent_packet_t *sent; + while ((sent = quicly_sentmap_get(&iter))->packet_number != UINT64_MAX) { + if ((ret = quicly_sentmap_update(&conn->egress.loss.sentmap, &iter, QUICLY_SENTMAP_EVENT_PTO)) != 0) + return ret; + } + } + + /* reset CC (FIXME flush sentmap and reset loss recovery) */ + conn->egress.cc.type->cc_init->cb( + conn->egress.cc.type->cc_init, &conn->egress.cc, + quicly_cc_calc_initial_cwnd(conn->super.ctx->initcwnd_packets, conn->egress.max_udp_payload_size), conn->stash.now); + + /* set jumpstart target */ + calc_resume_sendrate(conn, &conn->super.stats.jumpstart.prev_rate, &conn->super.stats.jumpstart.prev_rtt); + + /* reset RTT estimate, adopting SRTT of the original path as initial RTT (TODO calculate RTT based on path challenge RT) */ + quicly_rtt_init(&conn->egress.loss.rtt, &conn->super.ctx->loss, + conn->egress.loss.rtt.smoothed < conn->super.ctx->loss.default_initial_rtt + ? conn->egress.loss.rtt.smoothed + : conn->super.ctx->loss.default_initial_rtt); + + /* reset ratemeter */ + quicly_ratemeter_init(&conn->egress.ratemeter); + + /* remember PN when the path was promoted */ + conn->egress.pn_path_start = conn->egress.packet_number; + + /* update path mapping */ + struct st_quicly_conn_path_t *path = conn->paths[0]; + conn->paths[0] = conn->paths[path_index]; + conn->paths[path_index] = NULL; + conn->super.stats.num_paths.promoted += 1; + + do_delete_path(conn, path); + + return 0; +} + +static int open_path(quicly_conn_t *conn, size_t *path_index, struct sockaddr *remote_addr, struct sockaddr *local_addr) +{ + int ret; + + /* choose a slot that in unused or the least-recently-used one that has completed validation */ + *path_index = SIZE_MAX; + for (size_t i = 1; i < PTLS_ELEMENTSOF(conn->paths); ++i) { + struct st_quicly_conn_path_t *p = conn->paths[i]; + if (p == NULL) { + *path_index = i; + break; + } + if (p->path_challenge.send_at != INT64_MAX) + continue; + if (*path_index == SIZE_MAX || p->packet_last_received < conn->paths[*path_index]->packet_last_received) + *path_index = i; + } + if (*path_index == SIZE_MAX) + return QUICLY_ERROR_PACKET_IGNORED; + + /* free existing path info */ + if (conn->paths[*path_index] != NULL) + delete_path(conn, *path_index); + + /* initialize new path info */ + if ((ret = new_path(conn, *path_index, remote_addr, local_addr)) != 0) + return ret; + + /* schedule emission of PATH_CHALLENGE */ + conn->egress.send_probe_at = 0; + + return 0; +} + +static void recalc_send_probe_at(quicly_conn_t *conn) +{ + conn->egress.send_probe_at = INT64_MAX; + + for (size_t i = 0; i < PTLS_ELEMENTSOF(conn->paths); ++i) { + if (conn->paths[i] == NULL) + continue; + if (conn->egress.send_probe_at > conn->paths[i]->path_challenge.send_at) + conn->egress.send_probe_at = conn->paths[i]->path_challenge.send_at; + if (conn->paths[i]->path_response.send_) { + conn->egress.send_probe_at = 0; + break; + } + } +} + void quicly_free(quicly_conn_t *conn) { lock_now(conn, 0); @@ -1689,14 +1987,13 @@ void quicly_free(quicly_conn_t *conn) QUICLY_PROBE(FREE, conn, conn->stash.now); QUICLY_LOG_CONN(free, conn, {}); -#if QUICLY_USE_DTRACE - if (QUICLY_CONN_STATS_ENABLED()) { + if (QUICLY_PROBE_ENABLED(CONN_STATS)) { quicly_stats_t stats; quicly_get_stats(conn, &stats); QUICLY_PROBE(CONN_STATS, conn, conn->stash.now, &stats, sizeof(stats)); // TODO: emit stats with QUICLY_LOG_CONN() } -#endif + destroy_all_streams(conn, 0, 1); update_open_count(conn->super.ctx, -1); clear_datagram_frame_payloads(conn); @@ -1719,6 +2016,13 @@ void quicly_free(quicly_conn_t *conn) free_application_space(&conn->application); ptls_buffer_dispose(&conn->crypto.transport_params.buf); + + for (size_t i = 0; i < PTLS_ELEMENTSOF(conn->paths); ++i) { + if (conn->paths[i] != NULL) + delete_path(conn, i); + } + + /* `crytpo.tls` is disposed late, because logging relies on `ptls_skip_tracing` */ if (conn->crypto.async_in_progress) { /* When async signature generation is inflight, `ptls_free` will be called from `quicly_resume_handshake` laterwards. */ *ptls_get_data_ptr(conn->crypto.tls) = NULL; @@ -2222,11 +2526,18 @@ static quicly_conn_t *create_connection(quicly_context_t *ctx, uint32_t protocol lock_now(conn, 0); conn->created_at = conn->stash.now; conn->super.stats.handshake_confirmed_msec = UINT64_MAX; - set_address(&conn->super.local.address, local_addr); - set_address(&conn->super.remote.address, remote_addr); + conn->crypto.tls = tls; + if (new_path(conn, 0, remote_addr, local_addr) != 0) { + unlock_now(conn); + ptls_free(tls); + free(conn); + return NULL; + } quicly_local_cid_init_set(&conn->super.local.cid_set, ctx->cid_encryptor, local_cid); conn->super.local.long_header_src_cid = conn->super.local.cid_set.cids[0].cid; quicly_remote_cid_init_set(&conn->super.remote.cid_set, remote_cid, ctx->tls->random_bytes); + assert(conn->paths[0]->dcid == 0 && conn->super.remote.cid_set.cids[0].sequence == 0 && + conn->super.remote.cid_set.cids[0].state == QUICLY_REMOTE_CID_IN_USE && "paths[0].dcid uses cids[0]"); conn->super.state = QUICLY_STATE_FIRSTFLIGHT; if (server_name != NULL) { conn->super.local.bidi.next_stream_id = 0; @@ -2241,7 +2552,6 @@ static quicly_conn_t *create_connection(quicly_context_t *ctx, uint32_t protocol } conn->super.remote.transport_params = default_transport_params; conn->super.version = protocol_version; - conn->super.remote.largest_retire_prior_to = 0; quicly_linklist_init(&conn->super._default_scheduler.active); quicly_linklist_init(&conn->super._default_scheduler.blocked); conn->streams = kh_init(quicly_stream_t); @@ -2258,6 +2568,7 @@ static quicly_conn_t *create_connection(quicly_context_t *ctx, uint32_t protocol init_max_streams(&conn->egress.max_streams.bidi); conn->egress.ack_frequency.update_at = INT64_MAX; conn->egress.send_ack_at = INT64_MAX; + conn->egress.send_probe_at = INT64_MAX; conn->super.ctx->init_cc->cb(conn->super.ctx->init_cc, &conn->egress.cc, initcwnd, conn->stash.now); if (pacer != NULL) { conn->egress.pacer = pacer; @@ -2269,10 +2580,7 @@ static quicly_conn_t *create_connection(quicly_context_t *ctx, uint32_t protocol quicly_linklist_init(&conn->egress.pending_streams.blocked.bidi); quicly_linklist_init(&conn->egress.pending_streams.control); quicly_ratemeter_init(&conn->egress.ratemeter); - if (server_name == NULL && conn->super.ctx->use_pacing && conn->egress.cc.type->cc_jumpstart != NULL && - (conn->super.ctx->default_jumpstart_cwnd_packets != 0 || conn->super.ctx->max_jumpstart_cwnd_packets != 0)) - conn->egress.try_jumpstart = 1; - conn->crypto.tls = tls; + conn->egress.try_jumpstart = 1; if (handshake_properties != NULL) { assert(handshake_properties->additional_extensions == NULL); assert(handshake_properties->collect_extension == NULL); @@ -3158,6 +3466,8 @@ int64_t quicly_get_first_timeout(quicly_conn_t *conn) if (conn->egress.send_ack_at < at) at = conn->egress.send_ack_at; } + if (at > conn->egress.send_probe_at) + at = conn->egress.send_probe_at; return at; } @@ -3170,6 +3480,50 @@ uint64_t quicly_get_next_expected_packet_number(quicly_conn_t *conn) return conn->application->super.next_expected_packet_number; } +static int setup_path_dcid(quicly_conn_t *conn, size_t path_index) +{ + struct st_quicly_conn_path_t *path = conn->paths[path_index]; + quicly_remote_cid_set_t *set = &conn->super.remote.cid_set; + size_t found = SIZE_MAX; + + assert(path->dcid == UINT64_MAX); + + if (set->cids[0].cid.len == 0) { + /* if peer CID is zero-length, we can send packets to whatever address without the fear of corelation */ + found = 0; + } else { + /* find the unused entry with a smallest sequence number */ + for (size_t i = 0; i < PTLS_ELEMENTSOF(set->cids); ++i) { + if (set->cids[i].state == QUICLY_REMOTE_CID_AVAILABLE && + (found == SIZE_MAX || set->cids[i].sequence < set->cids[found].sequence)) + found = i; + } + if (found == SIZE_MAX) + return 0; + } + + /* associate */ + set->cids[found].state = QUICLY_REMOTE_CID_IN_USE; + path->dcid = set->cids[found].sequence; + + return 1; +} + +static quicly_cid_t *get_dcid(quicly_conn_t *conn, size_t path_index) +{ + struct st_quicly_conn_path_t *path = conn->paths[path_index]; + + assert(path->dcid != UINT64_MAX); + + /* lookup DCID and return */ + for (size_t i = 0; i < PTLS_ELEMENTSOF(conn->super.remote.cid_set.cids); ++i) { + if (conn->super.remote.cid_set.cids[i].sequence == path->dcid) + return &conn->super.remote.cid_set.cids[i].cid; + } + assert(!"CID lookup failure"); + return NULL; +} + /** * data structure that is used during one call through quicly_send() */ @@ -3246,6 +3600,18 @@ struct st_quicly_send_context_t { * first packet number to be used within the lifetime of this send context */ uint64_t first_packet_number; + /** + * index of `conn->paths[]` to which we are sending + */ + size_t path_index; + /** + * DCID to be used for the path + */ + quicly_cid_t *dcid; + /** + * if `conn->egress.send_probe_at` should be recalculated + */ + unsigned recalc_send_probe_at : 1; }; static int commit_send_packet(quicly_conn_t *conn, quicly_send_context_t *s, int coalesced) @@ -3302,6 +3668,7 @@ static int commit_send_packet(quicly_conn_t *conn, quicly_send_context_t *s, int s->dst_payload_from - s->payload_buf.datagram, conn->egress.packet_number, coalesced); /* update CC, commit sentmap */ + int on_promoted_path = s->path_index == 0 && !conn->paths[0]->initial; if (s->target.ack_eliciting) { packet_bytes_in_flight = s->dst - s->target.first_byte_at; s->send_window -= packet_bytes_in_flight; @@ -3311,12 +3678,15 @@ static int commit_send_packet(quicly_conn_t *conn, quicly_send_context_t *s, int if (quicly_sentmap_is_open(&conn->egress.loss.sentmap)) { int cc_limited = conn->egress.loss.sentmap.bytes_in_flight + packet_bytes_in_flight >= conn->egress.cc.cwnd / 2; /* for the rationale behind this formula, see handle_ack_frame */ - quicly_sentmap_commit(&conn->egress.loss.sentmap, (uint16_t)packet_bytes_in_flight, cc_limited); + quicly_sentmap_commit(&conn->egress.loss.sentmap, (uint16_t)packet_bytes_in_flight, cc_limited, on_promoted_path); } - conn->egress.cc.type->cc_on_sent(&conn->egress.cc, &conn->egress.loss, (uint32_t)packet_bytes_in_flight, conn->stash.now); - if (conn->egress.pacer != NULL) - quicly_pacer_consume_window(conn->egress.pacer, packet_bytes_in_flight); + if (packet_bytes_in_flight != 0) { + assert(s->path_index == 0 && "CC governs path 0 and data is sent only on that path"); + conn->egress.cc.type->cc_on_sent(&conn->egress.cc, &conn->egress.loss, (uint32_t)packet_bytes_in_flight, conn->stash.now); + if (conn->egress.pacer != NULL) + quicly_pacer_consume_window(conn->egress.pacer, packet_bytes_in_flight); + } QUICLY_PROBE(PACKET_SENT, conn, conn->stash.now, conn->egress.packet_number, s->dst - s->target.first_byte_at, get_epoch(*s->target.first_byte_at), !s->target.ack_eliciting); @@ -3329,6 +3699,9 @@ static int commit_send_packet(quicly_conn_t *conn, quicly_send_context_t *s, int ++conn->egress.packet_number; ++conn->super.stats.num_packets.sent; + ++conn->paths[s->path_index]->num_packets.sent; + if (on_promoted_path) + ++conn->super.stats.num_packets.sent_promoted_paths; if (!coalesced) { conn->super.stats.num_bytes.sent += datagram_size; @@ -3348,7 +3721,7 @@ static int commit_send_packet(quicly_conn_t *conn, quicly_send_context_t *s, int return ret; if (quicly_sentmap_allocate(&conn->egress.loss.sentmap, on_invalid_ack) == NULL) return PTLS_ERROR_NO_MEMORY; - quicly_sentmap_commit(&conn->egress.loss.sentmap, 0, 0); + quicly_sentmap_commit(&conn->egress.loss.sentmap, 0, 0, 0); ++conn->egress.packet_number; conn->egress.next_pn_to_skip = calc_next_pn_to_skip(conn->super.ctx->tls, conn->egress.packet_number, conn->egress.cc.cwnd, conn->egress.max_udp_payload_size); @@ -3393,11 +3766,9 @@ static int do_allocate_frame(quicly_conn_t *conn, quicly_send_context_t *s, size /* commit at the same time determining if we will coalesce the packets */ if (s->target.first_byte_at != NULL) { if (coalescible) { - size_t overhead = 1 /* type */ + conn->super.remote.cid_set.cids[0].cid.len + QUICLY_SEND_PN_SIZE + - s->current.cipher->aead->algo->tag_size; + size_t overhead = 1 /* type */ + s->dcid->len + QUICLY_SEND_PN_SIZE + s->current.cipher->aead->algo->tag_size; if (QUICLY_PACKET_IS_LONG_HEADER(s->current.first_byte)) - overhead += 4 /* version */ + 1 /* cidl */ + conn->super.remote.cid_set.cids[0].cid.len + - conn->super.local.long_header_src_cid.len + + overhead += 4 /* version */ + 1 /* cidl */ + s->dcid->len + conn->super.local.long_header_src_cid.len + (s->current.first_byte == QUICLY_PACKET_TYPE_INITIAL) /* token_length == 0 */ + 2 /* length */; size_t packet_min_space = QUICLY_MAX_PN_SIZE - QUICLY_SEND_PN_SIZE; if (packet_min_space < min_space) @@ -3434,11 +3805,10 @@ static int do_allocate_frame(quicly_conn_t *conn, quicly_send_context_t *s, size } s->target.ack_eliciting = 0; - QUICLY_PROBE(PACKET_PREPARE, conn, conn->stash.now, s->current.first_byte, - QUICLY_PROBE_HEXDUMP(conn->super.remote.cid_set.cids[0].cid.cid, conn->super.remote.cid_set.cids[0].cid.len)); + QUICLY_PROBE(PACKET_PREPARE, conn, conn->stash.now, s->current.first_byte, QUICLY_PROBE_HEXDUMP(s->dcid->cid, s->dcid->len)); QUICLY_LOG_CONN(packet_prepare, conn, { PTLS_LOG_ELEMENT_UNSIGNED(first_octet, s->current.first_byte); - PTLS_LOG_ELEMENT_HEXDUMP(dcid, conn->super.remote.cid_set.cids[0].cid.cid, conn->super.remote.cid_set.cids[0].cid.len); + PTLS_LOG_ELEMENT_HEXDUMP(dcid, s->dcid->cid, s->dcid->len); }); /* emit header */ @@ -3446,8 +3816,8 @@ static int do_allocate_frame(quicly_conn_t *conn, quicly_send_context_t *s, size *s->dst++ = s->current.first_byte | 0x1 /* pnlen == 2 */; if (QUICLY_PACKET_IS_LONG_HEADER(s->current.first_byte)) { s->dst = quicly_encode32(s->dst, conn->super.version); - *s->dst++ = conn->super.remote.cid_set.cids[0].cid.len; - s->dst = emit_cid(s->dst, &conn->super.remote.cid_set.cids[0].cid); + *s->dst++ = s->dcid->len; + s->dst = emit_cid(s->dst, s->dcid); *s->dst++ = conn->super.local.long_header_src_cid.len; s->dst = emit_cid(s->dst, &conn->super.local.long_header_src_cid); /* token */ @@ -3463,7 +3833,7 @@ static int do_allocate_frame(quicly_conn_t *conn, quicly_send_context_t *s, size *s->dst++ = 0; *s->dst++ = 0; } else { - s->dst = emit_cid(s->dst, &conn->super.remote.cid_set.cids[0].cid); + s->dst = emit_cid(s->dst, s->dcid); } s->dst += QUICLY_SEND_PN_SIZE; /* space for PN bits, filled in at commit time */ s->dst_payload_from = s->dst; @@ -4016,15 +4386,17 @@ static int mark_frames_on_pto(quicly_conn_t *conn, uint8_t ack_epoch, size_t *by static void notify_congestion_to_cc(quicly_conn_t *conn, uint16_t lost_bytes, uint64_t lost_pn) { - conn->egress.cc.type->cc_on_lost(&conn->egress.cc, &conn->egress.loss, lost_bytes, lost_pn, conn->egress.packet_number, - conn->stash.now, conn->egress.max_udp_payload_size); - QUICLY_PROBE(CC_CONGESTION, conn, conn->stash.now, lost_pn + 1, conn->egress.loss.sentmap.bytes_in_flight, - conn->egress.cc.cwnd); - QUICLY_LOG_CONN(cc_congestion, conn, { - PTLS_LOG_ELEMENT_UNSIGNED(max_lost_pn, lost_pn + 1); - PTLS_LOG_ELEMENT_UNSIGNED(flight, conn->egress.loss.sentmap.bytes_in_flight); - PTLS_LOG_ELEMENT_UNSIGNED(cwnd, conn->egress.cc.cwnd); - }); + if (conn->egress.pn_path_start <= lost_pn) { + conn->egress.cc.type->cc_on_lost(&conn->egress.cc, &conn->egress.loss, lost_bytes, lost_pn, conn->egress.packet_number, + conn->stash.now, conn->egress.max_udp_payload_size); + QUICLY_PROBE(CC_CONGESTION, conn, conn->stash.now, lost_pn + 1, conn->egress.loss.sentmap.bytes_in_flight, + conn->egress.cc.cwnd); + QUICLY_LOG_CONN(cc_congestion, conn, { + PTLS_LOG_ELEMENT_UNSIGNED(max_lost_pn, lost_pn + 1); + PTLS_LOG_ELEMENT_UNSIGNED(flight, conn->egress.loss.sentmap.bytes_in_flight); + PTLS_LOG_ELEMENT_UNSIGNED(cwnd, conn->egress.cc.cwnd); + }); + } } static void on_loss_detected(quicly_loss_t *loss, const quicly_sent_packet_t *lost_packet, int is_time_threshold) @@ -4275,18 +4647,8 @@ static size_t encode_resumption_info(quicly_conn_t *conn, uint8_t *dst, size_t c static int send_resumption_token(quicly_conn_t *conn, quicly_send_context_t *s) { - { /* fill conn->super.stats.token_sent the information we are sending now */ - quicly_rate_t rate; - conn->super.stats.token_sent.at = conn->stash.now - conn->created_at; - if (conn->egress.loss.rtt.minimum != 0 && - (quicly_ratemeter_report(&conn->egress.ratemeter, &rate), rate.smoothed != 0 || rate.latest != 0)) { - conn->super.stats.token_sent.rate = rate.smoothed > rate.latest ? rate.smoothed : rate.latest; - conn->super.stats.token_sent.rtt = conn->egress.loss.rtt.minimum; - } else { - conn->super.stats.token_sent.rate = 0; - conn->super.stats.token_sent.rtt = 0; - } - } + /* fill conn->super.stats.token_sent the information we are sending now */ + calc_resume_sendrate(conn, &conn->super.stats.token_sent.rate, &conn->super.stats.token_sent.rtt); quicly_address_token_plaintext_t token; ptls_buffer_t tokenbuf; @@ -4299,7 +4661,7 @@ static int send_resumption_token(quicly_conn_t *conn, quicly_send_context_t *s) /* build token */ token = (quicly_address_token_plaintext_t){QUICLY_ADDRESS_TOKEN_TYPE_RESUMPTION, conn->super.ctx->now->cb(conn->super.ctx->now)}; - token.remote = conn->super.remote.address; + token.remote = conn->paths[0]->address.remote; token.resumption.len = encode_resumption_info(conn, token.resumption.bytes, sizeof(token.resumption.bytes)); /* encrypt */ @@ -4638,7 +5000,7 @@ static int send_path_challenge(quicly_conn_t *conn, quicly_send_context_t *s, in { int ret; - if ((ret = do_allocate_frame(conn, s, QUICLY_PATH_CHALLENGE_FRAME_CAPACITY, ALLOCATE_FRAME_TYPE_ACK_ELICITING_NO_CC)) != 0) + if ((ret = do_allocate_frame(conn, s, QUICLY_PATH_CHALLENGE_FRAME_CAPACITY, ALLOCATE_FRAME_TYPE_NON_ACK_ELICITING)) != 0) return ret; s->dst = quicly_encode_path_challenge_frame(s->dst, is_response, data); @@ -4646,8 +5008,12 @@ static int send_path_challenge(quicly_conn_t *conn, quicly_send_context_t *s, in if (!is_response) { ++conn->super.stats.num_frames_sent.path_challenge; + QUICLY_PROBE(PATH_CHALLENGE_SEND, conn, conn->stash.now, data, QUICLY_PATH_CHALLENGE_DATA_LEN); + QUICLY_LOG_CONN(path_challenge_send, conn, { PTLS_LOG_ELEMENT_HEXDUMP(data, data, QUICLY_PATH_CHALLENGE_DATA_LEN); }); } else { ++conn->super.stats.num_frames_sent.path_response; + QUICLY_PROBE(PATH_RESPONSE_SEND, conn, conn->stash.now, data, QUICLY_PATH_CHALLENGE_DATA_LEN); + QUICLY_LOG_CONN(path_response_send, conn, { PTLS_LOG_ELEMENT_HEXDUMP(data, data, QUICLY_PATH_CHALLENGE_DATA_LEN); }); } return 0; @@ -4738,6 +5104,9 @@ static int update_traffic_key_cb(ptls_update_traffic_key_t *self, ptls_t *tls, i conn->application->one_rtt_writable = 1; open_blocked_streams(conn, 1); open_blocked_streams(conn, 0); + if (quicly_linklist_is_linked(&conn->egress.pending_streams.blocked.bidi) || + quicly_linklist_is_linked(&conn->egress.pending_streams.blocked.uni)) + conn->egress.pending_flows |= QUICLY_PENDING_FLOW_OTHERS_BIT; /* send the first resumption token using the 0.5 RTT window */ if (!quicly_is_client(conn) && conn->super.ctx->generate_resumption_token != NULL) { ret = quicly_send_resumption_token(conn); @@ -4757,13 +5126,6 @@ static int send_other_control_frames(quicly_conn_t *conn, quicly_send_context_t { int ret; - /* respond to all pending received PATH_CHALLENGE frames */ - if (conn->egress.path_response.send_) { - if ((ret = send_path_challenge(conn, s, 1, conn->egress.path_response.data)) != 0) - return ret; - conn->egress.path_response.send_ = 0; - } - /* MAX_STREAMS */ if ((ret = send_max_streams(conn, 1, s)) != 0) return ret; @@ -4908,6 +5270,8 @@ static int do_send(quicly_conn_t *conn, quicly_send_context_t *s) if (s->send_window == 0) ack_only = 1; + s->dcid = get_dcid(conn, s->path_index); + /* send handshake flows; when PTO fires... * * quicly running as a client sends either a Handshake probe (or data) if the handshake keys are available, or else an * Initial probe (or data). @@ -4921,82 +5285,104 @@ static int do_send(quicly_conn_t *conn, quicly_send_context_t *s) /* setup 0-RTT or 1-RTT send context (as the availability of the two epochs are mutually exclusive, we can try 1-RTT first as an * optimization), then send application data if that succeeds */ if (setup_send_space(conn, QUICLY_EPOCH_1RTT, s) != NULL || setup_send_space(conn, QUICLY_EPOCH_0RTT, s) != NULL) { - /* acks */ - if (conn->application->one_rtt_writable && conn->egress.send_ack_at <= conn->stash.now && - conn->application->super.unacked_count != 0) { - if ((ret = send_ack(conn, &conn->application->super, s)) != 0) - goto Exit; - } - /* DATAGRAM frame. Notes regarding current implementation: - * * Not limited by CC, nor the bytes counted by CC. - * * When given payload is too large and does not fit into a QUIC packet, a packet containing only PADDING frames is sent. - * This is because we do not have a way to retract the generation of a QUIC packet. - * * Does not notify the application that the frame was dropped internally. */ - if (should_send_datagram_frame(conn)) { - for (size_t i = 0; i != conn->egress.datagram_frame_payloads.count; ++i) { - ptls_iovec_t *payload = conn->egress.datagram_frame_payloads.payloads + i; - size_t required_space = quicly_datagram_frame_capacity(*payload); - if ((ret = do_allocate_frame(conn, s, required_space, ALLOCATE_FRAME_TYPE_ACK_ELICITING_NO_CC)) != 0) + { /* path_challenge / response */ + struct st_quicly_conn_path_t *path = conn->paths[s->path_index]; + assert(path != NULL); + if (path->path_challenge.send_at <= conn->stash.now) { + /* emit path challenge frame, doing exponential back off using PTO(initial_rtt) */ + if ((ret = send_path_challenge(conn, s, 0, path->path_challenge.data)) != 0) goto Exit; - if (s->dst_end - s->dst >= required_space) { - s->dst = quicly_encode_datagram_frame(s->dst, *payload); - QUICLY_PROBE(DATAGRAM_SEND, conn, conn->stash.now, payload->base, payload->len); - QUICLY_LOG_CONN(datagram_send, conn, - { PTLS_LOG_APPDATA_ELEMENT_HEXDUMP(payload, payload->base, payload->len); }); - } else { - /* FIXME: At the moment, we add a padding because we do not have a way to reclaim allocated space, and because - * it is forbidden to send an empty QUIC packet. */ - *s->dst++ = QUICLY_FRAME_TYPE_PADDING; - } + path->path_challenge.num_sent += 1; + path->path_challenge.send_at = + conn->stash.now + ((3 * conn->super.ctx->loss.default_initial_rtt) << (path->path_challenge.num_sent - 1)); + s->recalc_send_probe_at = 1; } - } - if (!ack_only) { - /* PTO or loss detection timeout, always send PING. This is the easiest thing to do in terms of timer control. */ - if (min_packets_to_send != 0) { - if ((ret = do_allocate_frame(conn, s, 1, ALLOCATE_FRAME_TYPE_ACK_ELICITING)) != 0) + if (path->path_response.send_) { + if ((ret = send_path_challenge(conn, s, 1, path->path_response.data)) != 0) goto Exit; - *s->dst++ = QUICLY_FRAME_TYPE_PING; - ++conn->super.stats.num_frames_sent.ping; - QUICLY_PROBE(PING_SEND, conn, conn->stash.now); - QUICLY_LOG_CONN(ping_send, conn, {}); + path->path_response.send_ = 0; + s->recalc_send_probe_at = 1; } - /* take actions only permitted for short header packets */ - if (conn->application->one_rtt_writable) { - /* send HANDSHAKE_DONE */ - if ((conn->egress.pending_flows & QUICLY_PENDING_FLOW_HANDSHAKE_DONE_BIT) != 0 && - (ret = send_handshake_done(conn, s)) != 0) + } + /* non probing frames are sent only on path zero */ + if (s->path_index == 0) { + /* acks */ + if (conn->application->one_rtt_writable && conn->egress.send_ack_at <= conn->stash.now && + conn->application->super.unacked_count != 0) { + if ((ret = send_ack(conn, &conn->application->super, s)) != 0) goto Exit; - /* post-handshake messages */ - if ((conn->egress.pending_flows & (uint8_t)(1 << QUICLY_EPOCH_1RTT)) != 0) { - quicly_stream_t *stream = quicly_get_stream(conn, -(1 + QUICLY_EPOCH_1RTT)); - assert(stream != NULL); - if ((ret = quicly_send_stream(stream, s)) != 0) + } + /* DATAGRAM frame. Notes regarding current implementation: + * * Not limited by CC, nor the bytes counted by CC. + * * When given payload is too large and does not fit into a QUIC packet, a packet containing only PADDING frames is + * sent. This is because we do not have a way to retract the generation of a QUIC packet. + * * Does not notify the application that the frame was dropped internally. */ + if (should_send_datagram_frame(conn)) { + for (size_t i = 0; i != conn->egress.datagram_frame_payloads.count; ++i) { + ptls_iovec_t *payload = conn->egress.datagram_frame_payloads.payloads + i; + size_t required_space = quicly_datagram_frame_capacity(*payload); + if ((ret = do_allocate_frame(conn, s, required_space, ALLOCATE_FRAME_TYPE_ACK_ELICITING_NO_CC)) != 0) goto Exit; - resched_stream_data(stream); + if (s->dst_end - s->dst >= required_space) { + s->dst = quicly_encode_datagram_frame(s->dst, *payload); + QUICLY_PROBE(DATAGRAM_SEND, conn, conn->stash.now, payload->base, payload->len); + QUICLY_LOG_CONN(datagram_send, conn, + { PTLS_LOG_APPDATA_ELEMENT_HEXDUMP(payload, payload->base, payload->len); }); + } else { + /* FIXME: At the moment, we add a padding because we do not have a way to reclaim allocated space, and + * because it is forbidden to send an empty QUIC packet. */ + *s->dst++ = QUICLY_FRAME_TYPE_PADDING; + } } - /* send other connection-level control frames, and iff we succeed in sending all of them, clear OTHERS_BIT to - * disable `quicly_send` being called right again to send more control frames */ - if ((ret = send_other_control_frames(conn, s)) != 0) + } + if (!ack_only) { + /* PTO or loss detection timeout, always send PING. This is the easiest thing to do in terms of timer control. */ + if (min_packets_to_send != 0) { + if ((ret = do_allocate_frame(conn, s, 1, ALLOCATE_FRAME_TYPE_ACK_ELICITING)) != 0) + goto Exit; + *s->dst++ = QUICLY_FRAME_TYPE_PING; + ++conn->super.stats.num_frames_sent.ping; + QUICLY_PROBE(PING_SEND, conn, conn->stash.now); + QUICLY_LOG_CONN(ping_send, conn, {}); + } + /* take actions only permitted for short header packets */ + if (conn->application->one_rtt_writable) { + /* send HANDSHAKE_DONE */ + if ((conn->egress.pending_flows & QUICLY_PENDING_FLOW_HANDSHAKE_DONE_BIT) != 0 && + (ret = send_handshake_done(conn, s)) != 0) + goto Exit; + /* post-handshake messages */ + if ((conn->egress.pending_flows & (uint8_t)(1 << QUICLY_EPOCH_1RTT)) != 0) { + quicly_stream_t *stream = quicly_get_stream(conn, -(1 + QUICLY_EPOCH_1RTT)); + assert(stream != NULL); + if ((ret = quicly_send_stream(stream, s)) != 0) + goto Exit; + resched_stream_data(stream); + } + /* send other connection-level control frames, and iff we succeed in sending all of them, clear OTHERS_BIT to + * disable `quicly_send` being called right again to send more control frames */ + if ((ret = send_other_control_frames(conn, s)) != 0) + goto Exit; + conn->egress.pending_flows &= ~QUICLY_PENDING_FLOW_OTHERS_BIT; + /* send NEW_TOKEN */ + if ((conn->egress.pending_flows & QUICLY_PENDING_FLOW_NEW_TOKEN_BIT) != 0 && + (ret = send_resumption_token(conn, s)) != 0) + goto Exit; + } + /* send stream-level control frames */ + if ((ret = send_stream_control_frames(conn, s)) != 0) goto Exit; - conn->egress.pending_flows &= ~QUICLY_PENDING_FLOW_OTHERS_BIT; - /* send NEW_TOKEN */ - if ((conn->egress.pending_flows & QUICLY_PENDING_FLOW_NEW_TOKEN_BIT) != 0 && - (ret = send_resumption_token(conn, s)) != 0) + /* send STREAM frames */ + if ((ret = conn->super.ctx->stream_scheduler->do_send(conn->super.ctx->stream_scheduler, conn, s)) != 0) goto Exit; - } - /* send stream-level control frames */ - if ((ret = send_stream_control_frames(conn, s)) != 0) - goto Exit; - /* send STREAM frames */ - if ((ret = conn->super.ctx->stream_scheduler->do_send(conn->super.ctx->stream_scheduler, conn, s)) != 0) - goto Exit; - /* once more, send control frames related to streams, as the state might have changed */ - if ((ret = send_stream_control_frames(conn, s)) != 0) - goto Exit; - if ((conn->egress.pending_flows & QUICLY_PENDING_FLOW_OTHERS_BIT) != 0) { - if ((ret = send_other_control_frames(conn, s)) != 0) + /* once more, send control frames related to streams, as the state might have changed */ + if ((ret = send_stream_control_frames(conn, s)) != 0) goto Exit; - conn->egress.pending_flows &= ~QUICLY_PENDING_FLOW_OTHERS_BIT; + if ((conn->egress.pending_flows & QUICLY_PENDING_FLOW_OTHERS_BIT) != 0) { + if ((ret = send_other_control_frames(conn, s)) != 0) + goto Exit; + conn->egress.pending_flows &= ~QUICLY_PENDING_FLOW_OTHERS_BIT; + } } /* stream operations might have requested emission of NEW_TOKEN at the tail; if so, try to bundle it */ if ((conn->egress.pending_flows & QUICLY_PENDING_FLOW_NEW_TOKEN_BIT) != 0) { @@ -5015,28 +5401,31 @@ static int do_send(quicly_conn_t *conn, quicly_send_context_t *s) * size of the pacer (10 packets) */ if (conn->egress.try_jumpstart && conn->egress.loss.rtt.minimum != UINT32_MAX) { conn->egress.try_jumpstart = 0; - if (conn->egress.cc.num_loss_episodes == 0) { - uint32_t jumpstart_cwnd = 0; + conn->super.stats.jumpstart.new_rtt = 0; + conn->super.stats.jumpstart.cwnd = 0; + if (conn->super.ctx->use_pacing && conn->egress.cc.type->cc_jumpstart != NULL && + (conn->super.ctx->default_jumpstart_cwnd_packets != 0 || conn->super.ctx->max_jumpstart_cwnd_packets != 0) && + conn->egress.cc.num_loss_episodes == 0) { conn->super.stats.jumpstart.new_rtt = conn->egress.loss.rtt.minimum; if (conn->super.ctx->max_jumpstart_cwnd_packets != 0 && conn->super.stats.jumpstart.prev_rate != 0 && conn->super.stats.jumpstart.prev_rtt != 0) { /* Careful Resume */ - jumpstart_cwnd = + conn->super.stats.jumpstart.cwnd = derive_jumpstart_cwnd(conn->super.ctx, conn->super.stats.jumpstart.new_rtt, conn->super.stats.jumpstart.prev_rate, conn->super.stats.jumpstart.prev_rtt); } else if (conn->super.ctx->default_jumpstart_cwnd_packets != 0) { /* jumpstart without previous information */ - jumpstart_cwnd = quicly_cc_calc_initial_cwnd(conn->super.ctx->default_jumpstart_cwnd_packets, - conn->super.ctx->transport_params.max_udp_payload_size); + conn->super.stats.jumpstart.cwnd = quicly_cc_calc_initial_cwnd( + conn->super.ctx->default_jumpstart_cwnd_packets, conn->super.ctx->transport_params.max_udp_payload_size); } - /* Jumpstart if the amount that can be sent in 1 RTT would be higher than without. Comparison target is CWND + + /* Jumpstart only if the amount that can be sent in 1 RTT would be higher than without. Comparison target is CWND + * inflight, as that is the amount that can be sent at most. Note the flow rate can become smaller due to packets * paced across the entire RTT during jumpstart. */ - if (jumpstart_cwnd > conn->egress.cc.cwnd + orig_bytes_inflight) { - conn->super.stats.jumpstart.cwnd = (uint32_t)jumpstart_cwnd; - conn->egress.cc.type->cc_jumpstart(&conn->egress.cc, jumpstart_cwnd, conn->egress.packet_number); - } + if (conn->super.stats.jumpstart.cwnd <= conn->egress.cc.cwnd + orig_bytes_inflight) + conn->super.stats.jumpstart.cwnd = 0; } + if (conn->super.stats.jumpstart.cwnd > 0) + conn->egress.cc.type->cc_jumpstart(&conn->egress.cc, conn->super.stats.jumpstart.cwnd, conn->egress.packet_number); } } if (ret == 0 && s->target.first_byte_at != NULL) { @@ -5104,12 +5493,21 @@ int quicly_send(quicly_conn_t *conn, quicly_address_t *dest, quicly_address_t *s goto Exit; } - QUICLY_PROBE(SEND, conn, conn->stash.now, conn->super.state, - QUICLY_PROBE_HEXDUMP(conn->super.remote.cid_set.cids[0].cid.cid, conn->super.remote.cid_set.cids[0].cid.len)); - QUICLY_LOG_CONN(send, conn, { - PTLS_LOG_ELEMENT_SIGNED(state, conn->super.state); - PTLS_LOG_ELEMENT_HEXDUMP(dcid, conn->super.remote.cid_set.cids[0].cid.cid, conn->super.remote.cid_set.cids[0].cid.len); - }); + /* determine DCID of active path; doing so is guaranteed to succeed as the protocol guarantees that there will always be at + * least one non-retired CID available */ + if (conn->paths[0]->dcid == UINT64_MAX) { + int success = setup_path_dcid(conn, 0); + assert(success); + } + + if ((QUICLY_PROBE_ENABLED(SEND) || ptls_log.is_active) && !ptls_skip_tracing(conn->crypto.tls)) { + const quicly_cid_t *dcid = get_dcid(conn, 0); + QUICLY_PROBE(SEND, conn, conn->stash.now, conn->super.state, QUICLY_PROBE_HEXDUMP(dcid->cid, dcid->len)); + QUICLY_LOG_CONN(send, conn, { + PTLS_LOG_ELEMENT_SIGNED(state, conn->super.state); + PTLS_LOG_ELEMENT_HEXDUMP(dcid, dcid->cid, dcid->len); + }); + } if (conn->super.state >= QUICLY_STATE_CLOSING) { quicly_sentmap_iter_t iter; @@ -5129,6 +5527,7 @@ int quicly_send(quicly_conn_t *conn, quicly_address_t *dest, quicly_address_t *s * called from a stream handler */ destroy_all_streams(conn, 0, 0); /* send CONNECTION_CLOSE in all possible epochs */ + s.dcid = get_dcid(conn, 0); for (size_t epoch = 0; epoch < QUICLY_NUM_EPOCHS; ++epoch) { if ((ret = send_connection_close(conn, epoch, &s)) != 0) goto Exit; @@ -5143,17 +5542,49 @@ int quicly_send(quicly_conn_t *conn, quicly_address_t *dest, quicly_address_t *s goto Exit; } - /* emit packets */ - if ((ret = do_send(conn, &s)) != 0) - goto Exit; + /* try emitting one probe packet on one of the backup paths, or ... (note: API of `quicly_send` allows us to send packets on no + * more than one path at a time) */ + if (conn->egress.send_probe_at <= conn->stash.now) { + for (s.path_index = 1; s.path_index < PTLS_ELEMENTSOF(conn->paths); ++s.path_index) { + if (conn->paths[s.path_index] == NULL || conn->stash.now < conn->paths[s.path_index]->path_challenge.send_at) + continue; + if (conn->paths[s.path_index]->path_challenge.num_sent > conn->super.ctx->max_probe_packets) { + delete_path(conn, s.path_index); + s.recalc_send_probe_at = 1; + continue; + } + /* determine DCID to be used, if not yet been done; upon failure, this path (being secondary) is discarded */ + if (conn->paths[s.path_index]->dcid == UINT64_MAX && !setup_path_dcid(conn, s.path_index)) { + delete_path(conn, s.path_index); + s.recalc_send_probe_at = 1; + conn->super.stats.num_paths.closed_no_dcid += 1; + continue; + } + if ((ret = do_send(conn, &s)) != 0) + goto Exit; + if (s.num_datagrams != 0) + break; + } + } + /* otherwise, emit non-probing packets */ + if (s.num_datagrams == 0) { + s.path_index = 0; + if ((ret = do_send(conn, &s)) != 0) + goto Exit; + } else { + ret = 0; + } assert_consistency(conn, 1); Exit: - clear_datagram_frame_payloads(conn); + if (s.path_index == 0) + clear_datagram_frame_payloads(conn); + if (s.recalc_send_probe_at) + recalc_send_probe_at(conn); if (s.num_datagrams != 0) { - *dest = conn->super.remote.address; - *src = conn->super.local.address; + *dest = conn->paths[s.path_index]->address.remote; + *src = conn->paths[s.path_index]->address.local; } *num_datagrams = s.num_datagrams; unlock_now(conn); @@ -5260,7 +5691,7 @@ static int enter_close(quicly_conn_t *conn, int local_is_initiating, int wait_dr return ret; if (quicly_sentmap_allocate(&conn->egress.loss.sentmap, on_end_closing) == NULL) return PTLS_ERROR_NO_MEMORY; - quicly_sentmap_commit(&conn->egress.loss.sentmap, 0, 0); + quicly_sentmap_commit(&conn->egress.loss.sentmap, 0, 0, 0); ++conn->egress.packet_number; if (local_is_initiating) { @@ -5508,18 +5939,24 @@ static int handle_ack_frame(quicly_conn_t *conn, struct st_quicly_handle_payload } } ++conn->super.stats.num_packets.ack_received; - largest_newly_acked.pn = pn_acked; - largest_newly_acked.sent_at = sent->sent_at; + if (sent->promoted_path) + ++conn->super.stats.num_packets.ack_received_promoted_paths; + if (conn->egress.pn_path_start <= pn_acked) { + largest_newly_acked.pn = pn_acked; + largest_newly_acked.sent_at = sent->sent_at; + } QUICLY_PROBE(PACKET_ACKED, conn, conn->stash.now, pn_acked, is_late_ack); QUICLY_LOG_CONN(packet_acked, conn, { PTLS_LOG_ELEMENT_UNSIGNED(pn, pn_acked); PTLS_LOG_ELEMENT_BOOL(is_late_ack, is_late_ack); }); if (sent->cc_bytes_in_flight != 0) { - bytes_acked += sent->cc_bytes_in_flight; + if (conn->egress.pn_path_start <= pn_acked) { + bytes_acked += sent->cc_bytes_in_flight; + if (sent->cc_limited) + cc_limited = 1; + } conn->super.stats.num_bytes.ack_received += sent->cc_bytes_in_flight; - if (sent->cc_limited) - cc_limited = 1; } if ((ret = quicly_sentmap_update(&conn->egress.loss.sentmap, &iter, QUICLY_SENTMAP_EVENT_ACKED)) != 0) return ret; @@ -5759,17 +6196,38 @@ static int handle_path_challenge_frame(quicly_conn_t *conn, struct st_quicly_han if ((ret = quicly_decode_path_challenge_frame(&state->src, state->end, &frame)) != 0) return ret; + QUICLY_PROBE(PATH_CHALLENGE_RECEIVE, conn, conn->stash.now, frame.data, sizeof(frame.data)); + QUICLY_LOG_CONN(path_challenge_receive, conn, { PTLS_LOG_ELEMENT_HEXDUMP(data, frame.data, sizeof(frame.data)); }); + /* schedule the emission of PATH_RESPONSE frame */ - memcpy(conn->egress.path_response.data, frame.data, QUICLY_PATH_CHALLENGE_DATA_LEN); - conn->egress.path_response.send_ = 1; - conn->egress.pending_flows |= QUICLY_PENDING_FLOW_OTHERS_BIT; + struct st_quicly_conn_path_t *path = conn->paths[state->path_index]; + memcpy(path->path_response.data, frame.data, QUICLY_PATH_CHALLENGE_DATA_LEN); + path->path_response.send_ = 1; + conn->egress.send_probe_at = 0; return 0; } static int handle_path_response_frame(quicly_conn_t *conn, struct st_quicly_handle_payload_state_t *state) { - return QUICLY_TRANSPORT_ERROR_PROTOCOL_VIOLATION; + quicly_path_challenge_frame_t frame; + int ret; + + if ((ret = quicly_decode_path_challenge_frame(&state->src, state->end, &frame)) != 0) + return ret; + + QUICLY_PROBE(PATH_RESPONSE_RECEIVE, conn, conn->stash.now, frame.data, sizeof(frame.data)); + QUICLY_LOG_CONN(path_response_receive, conn, { PTLS_LOG_ELEMENT_HEXDUMP(data, frame.data, sizeof(frame.data)); }); + + struct st_quicly_conn_path_t *path = conn->paths[state->path_index]; + + if (ptls_mem_equal(path->path_challenge.data, frame.data, QUICLY_PATH_CHALLENGE_DATA_LEN)) { + /* Path validation succeeded, stop sending PATH_CHALLENGEs. Active path might become changed in `quicly_receive`. */ + path->path_challenge.send_at = INT64_MAX; + conn->super.stats.num_paths.validated += 1; + } + + return 0; } static int handle_new_token_frame(quicly_conn_t *conn, struct st_quicly_handle_payload_state_t *state) @@ -5932,15 +6390,18 @@ static int is_stateless_reset(quicly_conn_t *conn, quicly_decoded_packet_t *deco break; } - if (!conn->super.remote.cid_set.cids[0].is_active) - return 0; if (decoded->octets.len < QUICLY_STATELESS_RESET_PACKET_MIN_LEN) return 0; - if (memcmp(decoded->octets.base + decoded->octets.len - QUICLY_STATELESS_RESET_TOKEN_LEN, - conn->super.remote.cid_set.cids[0].stateless_reset_token, QUICLY_STATELESS_RESET_TOKEN_LEN) != 0) - return 0; - return 1; + for (size_t i = 0; i < PTLS_ELEMENTSOF(conn->super.remote.cid_set.cids); ++i) { + if (conn->super.remote.cid_set.cids[0].state == QUICLY_REMOTE_CID_UNAVAILABLE) + continue; + if (memcmp(decoded->octets.base + decoded->octets.len - QUICLY_STATELESS_RESET_TOKEN_LEN, + conn->super.remote.cid_set.cids[i].stateless_reset_token, QUICLY_STATELESS_RESET_TOKEN_LEN) == 0) + return 1; + } + + return 0; } int quicly_is_destination(quicly_conn_t *conn, struct sockaddr *dest_addr, struct sockaddr *src_addr, @@ -5948,10 +6409,10 @@ int quicly_is_destination(quicly_conn_t *conn, struct sockaddr *dest_addr, struc { if (QUICLY_PACKET_IS_LONG_HEADER(decoded->octets.base[0])) { /* long header: validate address, then consult the CID */ - if (compare_socket_address(&conn->super.remote.address.sa, src_addr) != 0) + if (compare_socket_address(&conn->paths[0]->address.remote.sa, src_addr) != 0) return 0; - if (conn->super.local.address.sa.sa_family != AF_UNSPEC && - compare_socket_address(&conn->super.local.address.sa, dest_addr) != 0) + if (conn->paths[0]->address.local.sa.sa_family != AF_UNSPEC && + compare_socket_address(&conn->paths[0]->address.local.sa, dest_addr) != 0) return 0; /* server may see the CID generated by the client for Initial and 0-RTT packets */ if (!quicly_is_client(conn) && decoded->cid.dest.might_be_client_generated) { @@ -5974,10 +6435,10 @@ int quicly_is_destination(quicly_conn_t *conn, struct sockaddr *dest_addr, struc if (is_stateless_reset(conn, decoded)) goto Found_StatelessReset; } else { - if (compare_socket_address(&conn->super.remote.address.sa, src_addr) == 0) + if (compare_socket_address(&conn->paths[0]->address.remote.sa, src_addr) == 0) goto Found; - if (conn->super.local.address.sa.sa_family != AF_UNSPEC && - compare_socket_address(&conn->super.local.address.sa, dest_addr) != 0) + if (conn->paths[0]->address.local.sa.sa_family != AF_UNSPEC && + compare_socket_address(&conn->paths[0]->address.local.sa, dest_addr) != 0) return 0; } @@ -6080,17 +6541,6 @@ static int handle_new_connection_id_frame(quicly_conn_t *conn, struct st_quicly_ PTLS_LOG_ELEMENT_HEXDUMP(stateless_reset_token, frame.stateless_reset_token, QUICLY_STATELESS_RESET_TOKEN_LEN); }); - if (frame.sequence < conn->super.remote.largest_retire_prior_to) { - /* An endpoint that receives a NEW_CONNECTION_ID frame with a sequence number smaller than the Retire Prior To - * field of a previously received NEW_CONNECTION_ID frame MUST send a corresponding RETIRE_CONNECTION_ID frame - * that retires the newly received connection ID, unless it has already done so for that sequence number. (19.15) - * TODO: "unless ..." part may not be properly addressed here (we may already have sent the RCID frame for this - * sequence) */ - schedule_retire_connection_id_frame(conn, frame.sequence); - /* do not install this CID */ - return 0; - } - uint64_t unregistered_seqs[QUICLY_LOCAL_ACTIVE_CONNECTION_ID_LIMIT]; size_t num_unregistered_seqs; if ((ret = quicly_remote_cid_register(&conn->super.remote.cid_set, frame.sequence, frame.cid.base, frame.cid.len, @@ -6099,10 +6549,7 @@ static int handle_new_connection_id_frame(quicly_conn_t *conn, struct st_quicly_ return ret; for (size_t i = 0; i < num_unregistered_seqs; i++) - schedule_retire_connection_id_frame(conn, unregistered_seqs[i]); - - if (frame.retire_prior_to > conn->super.remote.largest_retire_prior_to) - conn->super.remote.largest_retire_prior_to = frame.retire_prior_to; + retire_connection_id(conn, unregistered_seqs[i]); return 0; } @@ -6210,8 +6657,8 @@ static int handle_ack_frequency_frame(quicly_conn_t *conn, struct st_quicly_hand return 0; } -static int handle_payload(quicly_conn_t *conn, size_t epoch, const uint8_t *_src, size_t _len, uint64_t *offending_frame_type, - int *is_ack_only) +static int handle_payload(quicly_conn_t *conn, size_t epoch, size_t path_index, const uint8_t *_src, size_t _len, + uint64_t *offending_frame_type, int *is_ack_only, int *is_probe_only) { /* clang-format off */ @@ -6220,84 +6667,87 @@ static int handle_payload(quicly_conn_t *conn, size_t epoch, const uint8_t *_src int (*cb)(quicly_conn_t *, struct st_quicly_handle_payload_state_t *); /* callback function that handles the frame */ uint8_t permitted_epochs; /* the epochs the frame can appear, calculated as bitwise-or of `1 << epoch` */ uint8_t ack_eliciting; /* boolean indicating if the frame is ack-eliciting */ + uint8_t probing; /* boolean indicating if the frame is a "probing frame" */ size_t counter_offset; /* offset of corresponding `conn->super.stats.num_frames_received.type` within quicly_conn_t */ } frame_handlers[] = { -#define FRAME(n, i, z, h, o, ae) \ +#define FRAME(n, i, z, h, o, ae, p) \ { \ handle_##n##_frame, \ (i << QUICLY_EPOCH_INITIAL) | (z << QUICLY_EPOCH_0RTT) | (h << QUICLY_EPOCH_HANDSHAKE) | (o << QUICLY_EPOCH_1RTT), \ ae, \ + p, \ offsetof(quicly_conn_t, super.stats.num_frames_received.n) \ } - /* +----------------------+-------------------+---------------+ - * | | permitted epochs | | - * | frame +----+----+----+----+ ack-eliciting | - * | | IN | 0R | HS | 1R | | - * +----------------------+----+----+----+----+---------------+ */ - FRAME( padding , 1 , 1 , 1 , 1 , 0 ), /* 0 */ - FRAME( ping , 1 , 1 , 1 , 1 , 1 ), - FRAME( ack , 1 , 0 , 1 , 1 , 0 ), - FRAME( ack , 1 , 0 , 1 , 1 , 0 ), - FRAME( reset_stream , 0 , 1 , 0 , 1 , 1 ), - FRAME( stop_sending , 0 , 1 , 0 , 1 , 1 ), - FRAME( crypto , 1 , 0 , 1 , 1 , 1 ), - FRAME( new_token , 0 , 0 , 0 , 1 , 1 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 ), /* 8 */ - FRAME( stream , 0 , 1 , 0 , 1 , 1 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 ), - FRAME( stream , 0 , 1 , 0 , 1 , 1 ), - FRAME( max_data , 0 , 1 , 0 , 1 , 1 ), /* 16 */ - FRAME( max_stream_data , 0 , 1 , 0 , 1 , 1 ), - FRAME( max_streams_bidi , 0 , 1 , 0 , 1 , 1 ), - FRAME( max_streams_uni , 0 , 1 , 0 , 1 , 1 ), - FRAME( data_blocked , 0 , 1 , 0 , 1 , 1 ), - FRAME( stream_data_blocked , 0 , 1 , 0 , 1 , 1 ), - FRAME( streams_blocked , 0 , 1 , 0 , 1 , 1 ), - FRAME( streams_blocked , 0 , 1 , 0 , 1 , 1 ), - FRAME( new_connection_id , 0 , 1 , 0 , 1 , 1 ), /* 24 */ - FRAME( retire_connection_id , 0 , 0 , 0 , 1 , 1 ), - FRAME( path_challenge , 0 , 1 , 0 , 1 , 1 ), - FRAME( path_response , 0 , 0 , 0 , 1 , 1 ), - FRAME( transport_close , 1 , 1 , 1 , 1 , 0 ), - FRAME( application_close , 0 , 1 , 0 , 1 , 0 ), - FRAME( handshake_done , 0, 0 , 0 , 1 , 1 ), - /* +----------------------+----+----+----+----+---------------+ */ + /* +----------------------+-------------------+---------------+---------+ + * | | permitted epochs | | | + * | frame +----+----+----+----+ ack-eliciting | probing | + * | | IN | 0R | HS | 1R | | | + * +----------------------+----+----+----+----+---------------+---------+ */ + FRAME( padding , 1 , 1 , 1 , 1 , 0 , 1 ), /* 0 */ + FRAME( ping , 1 , 1 , 1 , 1 , 1 , 0 ), + FRAME( ack , 1 , 0 , 1 , 1 , 0 , 0 ), + FRAME( ack , 1 , 0 , 1 , 1 , 0 , 0 ), + FRAME( reset_stream , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stop_sending , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( crypto , 1 , 0 , 1 , 1 , 1 , 0 ), + FRAME( new_token , 0 , 0 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), /* 8 */ + FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( max_data , 0 , 1 , 0 , 1 , 1 , 0 ), /* 16 */ + FRAME( max_stream_data , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( max_streams_bidi , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( max_streams_uni , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( data_blocked , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( stream_data_blocked , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( streams_blocked , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( streams_blocked , 0 , 1 , 0 , 1 , 1 , 0 ), + FRAME( new_connection_id , 0 , 1 , 0 , 1 , 1 , 1 ), /* 24 */ + FRAME( retire_connection_id , 0 , 0 , 0 , 1 , 1 , 0 ), + FRAME( path_challenge , 0 , 1 , 0 , 1 , 1 , 1 ), + FRAME( path_response , 0 , 0 , 0 , 1 , 1 , 1 ), + FRAME( transport_close , 1 , 1 , 1 , 1 , 0 , 0 ), + FRAME( application_close , 0 , 1 , 0 , 1 , 0 , 0 ), + FRAME( handshake_done , 0, 0 , 0 , 1 , 1 , 0 ), + /* +----------------------+----+----+----+----+---------------+---------+ */ #undef FRAME }; static const struct { uint64_t type; struct st_quicly_frame_handler_t _; } ex_frame_handlers[] = { -#define FRAME(uc, lc, i, z, h, o, ae) \ +#define FRAME(uc, lc, i, z, h, o, ae, p) \ { \ QUICLY_FRAME_TYPE_##uc, \ { \ handle_##lc##_frame, \ (i << QUICLY_EPOCH_INITIAL) | (z << QUICLY_EPOCH_0RTT) | (h << QUICLY_EPOCH_HANDSHAKE) | (o << QUICLY_EPOCH_1RTT), \ ae, \ - offsetof(quicly_conn_t, super.stats.num_frames_received.lc) \ + p, \ + offsetof(quicly_conn_t, super.stats.num_frames_received.lc) \ }, \ } - /* +----------------------------------+-------------------+---------------+ - * | frame | permitted epochs | | - * |------------------+---------------+----+----+----+----+ ack-eliciting | - * | upper-case | lower-case | IN | 0R | HS | 1R | | - * +------------------+---------------+----+----+----+----+---------------+ */ - FRAME( DATAGRAM_NOLEN , datagram , 0 , 1, 0, 1 , 1 ), - FRAME( DATAGRAM_WITHLEN , datagram , 0 , 1, 0, 1 , 1 ), - FRAME( ACK_FREQUENCY , ack_frequency , 0 , 0 , 0 , 1 , 1 ), - /* +------------------+---------------+-------------------+---------------+ */ + /* +----------------------------------+-------------------+---------------+---------+ + * | frame | permitted epochs | | | + * |------------------+---------------+----+----+----+----+ ack-eliciting | probing | + * | upper-case | lower-case | IN | 0R | HS | 1R | | | + * +------------------+---------------+----+----+----+----+---------------+---------+ */ + FRAME( DATAGRAM_NOLEN , datagram , 0 , 1, 0, 1 , 1 , 0 ), + FRAME( DATAGRAM_WITHLEN , datagram , 0 , 1, 0, 1 , 1 , 0 ), + FRAME( ACK_FREQUENCY , ack_frequency , 0 , 0 , 0 , 1 , 1 , 0 ), + /* +------------------+---------------+-------------------+---------------+---------+ */ #undef FRAME {UINT64_MAX}, }; /* clang-format on */ - struct st_quicly_handle_payload_state_t state = {_src, _src + _len, epoch}; - size_t num_frames_ack_eliciting = 0; + struct st_quicly_handle_payload_state_t state = {.epoch = epoch, .path_index = path_index, .src = _src, .end = _src + _len}; + size_t num_frames_ack_eliciting = 0, num_frames_non_probing = 0; int ret; do { @@ -6330,12 +6780,16 @@ static int handle_payload(quicly_conn_t *conn, size_t epoch, const uint8_t *_src break; } ++*(uint64_t *)((uint8_t *)conn + frame_handler->counter_offset); - num_frames_ack_eliciting += frame_handler->ack_eliciting; + if (frame_handler->ack_eliciting) + ++num_frames_ack_eliciting; + if (!frame_handler->probing) + ++num_frames_non_probing; if ((ret = frame_handler->cb(conn, &state)) != 0) break; } while (state.src != state.end); *is_ack_only = num_frames_ack_eliciting == 0; + *is_probe_only = num_frames_non_probing == 0; if (ret != 0) *offending_frame_type = state.frame_type; return ret; @@ -6370,7 +6824,7 @@ int quicly_accept(quicly_conn_t **conn, quicly_context_t *ctx, struct sockaddr * } cipher = {}; ptls_iovec_t payload; uint64_t next_expected_pn, pn, offending_frame_type = QUICLY_FRAME_TYPE_PADDING; - int is_ack_only, ret; + int is_ack_only, is_probe_only, ret; *conn = NULL; @@ -6460,7 +6914,8 @@ int quicly_accept(quicly_conn_t **conn, quicly_context_t *ctx, struct sockaddr * if (packet->ecn != 0) (*conn)->super.stats.num_packets.received_ecn_counts[get_ecn_index_from_bits(packet->ecn)] += 1; (*conn)->super.stats.num_bytes.received += packet->datagram_size; - if ((ret = handle_payload(*conn, QUICLY_EPOCH_INITIAL, payload.base, payload.len, &offending_frame_type, &is_ack_only)) != 0) + if ((ret = handle_payload(*conn, QUICLY_EPOCH_INITIAL, 0, payload.base, payload.len, &offending_frame_type, &is_ack_only, + &is_probe_only)) != 0) goto Exit; if ((ret = record_receipt(&(*conn)->initial->super, pn, packet->ecn, 0, (*conn)->stash.now, &(*conn)->egress.send_ack_at, &(*conn)->super.stats.num_packets.received_out_of_order)) != 0) @@ -6491,10 +6946,10 @@ int quicly_receive(quicly_conn_t *conn, struct sockaddr *dest_addr, struct socka void *ctx; } aead; struct st_quicly_pn_space_t **space; - size_t epoch; + size_t epoch, path_index; ptls_iovec_t payload; uint64_t pn, offending_frame_type = QUICLY_FRAME_TYPE_PADDING; - int is_ack_only, ret; + int is_ack_only, is_probe_only, ret; assert(src_addr->sa_family == AF_INET || src_addr->sa_family == AF_INET6); @@ -6508,12 +6963,34 @@ int quicly_receive(quicly_conn_t *conn, struct sockaddr *dest_addr, struct socka PTLS_LOG_ELEMENT_HEXDUMP(bytes, packet->octets.base, packet->octets.len); }); + /* drop packets with invalid server tuple (note: when running as a server, `dest_addr` may not be available depending on the + * socket option being used */ + if (quicly_is_client(conn)) { + if (compare_socket_address(src_addr, &conn->paths[0]->address.remote.sa) != 0) { + ret = QUICLY_ERROR_PACKET_IGNORED; + goto Exit; + } + } else if (dest_addr != NULL && dest_addr->sa_family != AF_UNSPEC) { + assert(conn->paths[0]->address.local.sa.sa_family != AF_UNSPEC); + if (compare_socket_address(dest_addr, &conn->paths[0]->address.local.sa) != 0) { + ret = QUICLY_ERROR_PACKET_IGNORED; + goto Exit; + } + } + if (is_stateless_reset(conn, packet)) { ret = handle_stateless_reset(conn); goto Exit; } - /* FIXME check peer address */ + /* Determine the incoming path. path_index may be set to PTLS_ELEMENTSOF(conn->paths), which indicates that a new path needs to + * be created once packet decryption succeeds. */ + for (path_index = 0; path_index < PTLS_ELEMENTSOF(conn->paths); ++path_index) + if (conn->paths[path_index] != NULL && compare_socket_address(src_addr, &conn->paths[path_index]->address.remote.sa) == 0) + break; + if (path_index == PTLS_ELEMENTSOF(conn->paths) && + conn->super.stats.num_paths.validation_failed >= conn->super.ctx->max_path_validation_failures) + return QUICLY_ERROR_PACKET_IGNORED; /* add unconditionally, as packet->datagram_size is set only for the first packet within the UDP datagram */ conn->super.stats.num_bytes.received += packet->datagram_size; @@ -6670,10 +7147,16 @@ int quicly_receive(quicly_conn_t *conn, struct sockaddr *dest_addr, struct socka PTLS_LOG_ELEMENT_UNSIGNED(packet_type, get_epoch(packet->octets.base[0])); }); + /* open a new path if necessary, now that decryption succeeded */ + if (path_index == PTLS_ELEMENTSOF(conn->paths) && (ret = open_path(conn, &path_index, src_addr, dest_addr)) != 0) + goto Exit; + /* update states */ if (conn->super.state == QUICLY_STATE_FIRSTFLIGHT) conn->super.state = QUICLY_STATE_CONNECTED; conn->super.stats.num_packets.received += 1; + conn->paths[path_index]->packet_last_received = conn->super.stats.num_packets.received; + conn->paths[path_index]->num_packets.received += 1; if (packet->ecn != 0) conn->super.stats.num_packets.received_ecn_counts[get_ecn_index_from_bits(packet->ecn)] += 1; @@ -6699,8 +7182,16 @@ int quicly_receive(quicly_conn_t *conn, struct sockaddr *dest_addr, struct socka } /* handle the payload */ - if ((ret = handle_payload(conn, epoch, payload.base, payload.len, &offending_frame_type, &is_ack_only)) != 0) + if ((ret = handle_payload(conn, epoch, path_index, payload.base, payload.len, &offending_frame_type, &is_ack_only, + &is_probe_only)) != 0) goto Exit; + if (!is_probe_only && conn->paths[path_index]->probe_only) { + assert(path_index != 0); + conn->paths[path_index]->probe_only = 0; + ++conn->super.stats.num_paths.migration_elicited; + QUICLY_PROBE(ELICIT_PATH_MIGRATION, conn, conn->stash.now, path_index); + QUICLY_LOG_CONN(elicit_path_migration, conn, { PTLS_LOG_ELEMENT_UNSIGNED(path_index, path_index); }); + } if (*space != NULL && conn->super.state < QUICLY_STATE_CLOSING) { if ((ret = record_receipt(*space, pn, packet->ecn, is_ack_only, conn->stash.now, &conn->egress.send_ack_at, &conn->super.stats.num_packets.received_out_of_order)) != 0) @@ -6721,9 +7212,9 @@ int quicly_receive(quicly_conn_t *conn, struct sockaddr *dest_addr, struct socka if (quicly_is_client(conn)) { /* Running as a client. * Respect "disable_migration" TP sent by the remote peer at the end of the TLS handshake. */ - if (conn->super.local.address.sa.sa_family == AF_UNSPEC && dest_addr != NULL && dest_addr->sa_family != AF_UNSPEC && + if (conn->paths[0]->address.local.sa.sa_family == AF_UNSPEC && dest_addr != NULL && dest_addr->sa_family != AF_UNSPEC && ptls_handshake_is_complete(conn->crypto.tls) && conn->super.remote.transport_params.disable_active_migration) - set_address(&conn->super.local.address, dest_addr); + set_address(&conn->paths[0]->address.local, dest_addr); } else { /* Running as a server. * If handshake was just completed, drop handshake context, schedule the first emission of HANDSHAKE_DONE frame. */ @@ -6739,6 +7230,13 @@ int quicly_receive(quicly_conn_t *conn, struct sockaddr *dest_addr, struct socka case QUICLY_EPOCH_1RTT: if (!is_ack_only && should_send_max_data(conn)) conn->egress.pending_flows |= QUICLY_PENDING_FLOW_OTHERS_BIT; + /* switch active path to current path, if current path is validated and not probe-only */ + if (path_index != 0 && conn->paths[path_index]->path_challenge.send_at == INT64_MAX && + !conn->paths[path_index]->probe_only) { + if ((ret = promote_path(conn, path_index)) != 0) + goto Exit; + recalc_send_probe_at(conn); + } break; default: break; @@ -6803,7 +7301,10 @@ int quicly_open_stream(quicly_conn_t *conn, quicly_stream_t **_stream, int uni) stream->streams_blocked = 1; quicly_linklist_insert((uni ? &conn->egress.pending_streams.blocked.uni : &conn->egress.pending_streams.blocked.bidi)->prev, &stream->_send_aux.pending_link.control); - conn->egress.pending_flows |= QUICLY_PENDING_FLOW_OTHERS_BIT; + /* schedule the emission of STREAMS_BLOCKED if application write key is available (otherwise the scheduling is done when + * the key becomes available) */ + if (stream->conn->application != NULL && stream->conn->application->cipher.egress.key.aead != NULL) + conn->egress.pending_flows |= QUICLY_PENDING_FLOW_OTHERS_BIT; } /* application-layer initialization */ @@ -7201,19 +7702,21 @@ const quicly_stream_callbacks_t quicly_stream_noop_callbacks = { void quicly__debug_printf(quicly_conn_t *conn, const char *function, int line, const char *fmt, ...) { -#if QUICLY_USE_DTRACE - char buf[1024]; - va_list args; - - if (!QUICLY_DEBUG_MESSAGE_ENABLED()) - return; + if (QUICLY_PROBE_ENABLED(DEBUG_MESSAGE) || ptls_log.is_active) { + char buf[1024]; + va_list args; - va_start(args, fmt); - vsnprintf(buf, sizeof(buf), fmt, args); - va_end(args); + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); - QUICLY_DEBUG_MESSAGE(conn, function, line, buf); -#endif + QUICLY_PROBE(DEBUG_MESSAGE, conn, function, line, buf); + QUICLY_LOG_CONN(debug_message, conn, { + PTLS_LOG_ELEMENT_UNSAFESTR(function, function, strlen(function)); + PTLS_LOG_ELEMENT_SIGNED(line, line); + PTLS_LOG_ELEMENT_UNSAFESTR(message, buf, strlen(buf)); + }); + } } const uint32_t quicly_supported_versions[] = {QUICLY_PROTOCOL_VERSION_1, QUICLY_PROTOCOL_VERSION_DRAFT29, diff --git a/lib/remote_cid.c b/lib/remote_cid.c index 31317d47..ff7f57c3 100644 --- a/lib/remote_cid.c +++ b/lib/remote_cid.c @@ -26,7 +26,7 @@ void quicly_remote_cid_init_set(quicly_remote_cid_set_t *set, ptls_iovec_t *initial_cid, void (*random_bytes)(void *, size_t)) { set->cids[0] = (quicly_remote_cid_t){ - .is_active = 1, + .state = QUICLY_REMOTE_CID_IN_USE, .sequence = 0, }; if (initial_cid != NULL) { @@ -39,29 +39,13 @@ void quicly_remote_cid_init_set(quicly_remote_cid_set_t *set, ptls_iovec_t *init for (size_t i = 1; i < PTLS_ELEMENTSOF(set->cids); i++) set->cids[i] = (quicly_remote_cid_t){ - .is_active = 0, + .state = QUICLY_REMOTE_CID_UNAVAILABLE, .sequence = i, }; set->_largest_sequence_expected = PTLS_ELEMENTSOF(set->cids) - 1; } -/** - * promote CID at idx_to_promote as the current CID for communication - * i.e. swap cids[idx_to_promote] and cids[0] - */ -static void promote_cid(quicly_remote_cid_set_t *set, size_t idx_to_promote) -{ - uint64_t seq_tmp = set->cids[0].sequence; - - assert(idx_to_promote > 0); - assert(!set->cids[0].is_active); - - set->cids[0] = set->cids[idx_to_promote]; - set->cids[idx_to_promote].is_active = 0; - set->cids[idx_to_promote].sequence = seq_tmp; -} - static int do_register(quicly_remote_cid_set_t *set, uint64_t sequence, const uint8_t *cid, size_t cid_len, const uint8_t srt[QUICLY_STATELESS_RESET_TOKEN_LEN]) { @@ -71,7 +55,7 @@ static int do_register(quicly_remote_cid_set_t *set, uint64_t sequence, const ui return QUICLY_TRANSPORT_ERROR_CONNECTION_ID_LIMIT; for (size_t i = 0; i < PTLS_ELEMENTSOF(set->cids); i++) { - if (set->cids[i].is_active) { + if (set->cids[i].state != QUICLY_REMOTE_CID_UNAVAILABLE) { /* compare newly received CID against what we already have, to see if there is duplication/conflicts */ /* If an endpoint receives a NEW_CONNECTION_ID frame that repeats a previously issued connection ID with @@ -97,12 +81,8 @@ static int do_register(quicly_remote_cid_set_t *set, uint64_t sequence, const ui set->cids[i].sequence = sequence; quicly_set_cid(&set->cids[i].cid, ptls_iovec_init(cid, cid_len)); memcpy(set->cids[i].stateless_reset_token, srt, QUICLY_STATELESS_RESET_TOKEN_LEN); - set->cids[i].is_active = 1; + set->cids[i].state = QUICLY_REMOTE_CID_AVAILABLE; was_stored = 1; - if (i > 0 && !set->cids[0].is_active) { - /* promote this CID for communication */ - promote_cid(set, i); - } } } @@ -113,66 +93,33 @@ static int do_register(quicly_remote_cid_set_t *set, uint64_t sequence, const ui static void do_unregister(quicly_remote_cid_set_t *set, size_t idx_to_unreg) { - assert(set->cids[idx_to_unreg].is_active); - - set->cids[idx_to_unreg].is_active = 0; + set->cids[idx_to_unreg].state = QUICLY_REMOTE_CID_UNAVAILABLE; set->cids[idx_to_unreg].sequence = ++set->_largest_sequence_expected; } -int quicly_remote_cid_unregister(quicly_remote_cid_set_t *set, uint64_t sequence) +void quicly_remote_cid_unregister(quicly_remote_cid_set_t *set, uint64_t sequence) { - uint64_t min_seq = UINT64_MAX; - size_t min_seq_idx = SIZE_MAX; for (size_t i = 0; i < PTLS_ELEMENTSOF(set->cids); i++) { if (sequence == set->cids[i].sequence) { do_unregister(set, i); - if (i != 0) - return 0; /* if not retiring idx=0 (current in-use CID), simply return */ - } - if (set->cids[i].is_active && min_seq > set->cids[i].sequence) { - /* find a CID with minimum sequence number, while iterating over the array */ - min_seq = set->cids[i].sequence; - min_seq_idx = i; + return; } } - - if (!set->cids[0].is_active) { - /* we have retired the current CID (idx=0) */ - if (min_seq_idx != SIZE_MAX) - promote_cid(set, min_seq_idx); - return 0; - } else { - /* we did not unregister any slot */ - return 1; - } + assert(!"invalid CID sequence number"); } static size_t unregister_prior_to(quicly_remote_cid_set_t *set, uint64_t seq_unreg_prior_to, uint64_t unregistered_seqs[QUICLY_LOCAL_ACTIVE_CONNECTION_ID_LIMIT]) { - uint64_t min_seq = UINT64_MAX, min_seq_idx = UINT64_MAX; size_t num_unregistered = 0; + for (size_t i = 0; i < PTLS_ELEMENTSOF(set->cids); i++) { - if (set->cids[i].is_active) { - if (set->cids[i].sequence < seq_unreg_prior_to) { - unregistered_seqs[num_unregistered++] = set->cids[i].sequence; - do_unregister(set, i); - continue; - } - if (min_seq > set->cids[i].sequence) { - /* find a CID with minimum sequence number, while iterating over the array */ - min_seq = set->cids[i].sequence; - min_seq_idx = i; - } + if (set->cids[i].sequence < seq_unreg_prior_to) { + unregistered_seqs[num_unregistered++] = set->cids[i].sequence; + do_unregister(set, i); } } - if (!set->cids[0].is_active) { - /* we have retired the current CID (idx=0) */ - if (min_seq_idx != UINT64_MAX) - promote_cid(set, min_seq_idx); - } - return num_unregistered; } @@ -180,7 +127,7 @@ int quicly_remote_cid_register(quicly_remote_cid_set_t *set, uint64_t sequence, const uint8_t srt[QUICLY_STATELESS_RESET_TOKEN_LEN], uint64_t retire_prior_to, uint64_t unregistered_seqs[QUICLY_LOCAL_ACTIVE_CONNECTION_ID_LIMIT], size_t *num_unregistered_seqs) { - quicly_remote_cid_t backup_cid = set->cids[0]; // preserve one valid entry in cids[0] to handle protocol violation + quicly_remote_cid_set_t backup = *set; /* preserve current state so that it can be restored to notify protocol violation */ int ret; assert(sequence >= retire_prior_to); @@ -189,11 +136,9 @@ int quicly_remote_cid_register(quicly_remote_cid_set_t *set, uint64_t sequence, * retires active_connection_id_limit CIDs and then installs one new CID. */ *num_unregistered_seqs = unregister_prior_to(set, retire_prior_to, unregistered_seqs); - /* Then, register given value. */ + /* Then, register given value. If an error occurs, restore the backup and send the error. */ if ((ret = do_register(set, sequence, cid, cid_len, srt)) != 0) { - /* restore the backup and send the error */ - if (!set->cids[0].is_active) - set->cids[0] = backup_cid; + *set = backup; return ret; } diff --git a/quicly-probes.d b/quicly-probes.d index f81eeeb6..a3967adc 100644 --- a/quicly-probes.d +++ b/quicly-probes.d @@ -50,6 +50,11 @@ provider quicly { probe initial_handshake_packet_exceed(struct st_quicly_conn_t *conn, int64_t at, uint64_t num_packets); probe stateless_reset_receive(struct st_quicly_conn_t *conn, int64_t at); + probe new_path(struct st_quicly_conn_t *conn, int64_t at, size_t path_index, const char *remote); + probe delete_path(struct st_quicly_conn_t *conn, int64_t at, size_t path_index); + probe promote_path(struct st_quicly_conn_t *conn, int64_t at, size_t path_index); + probe elicit_path_migration(struct st_quicly_conn_t *conn, int64_t at, size_t path_index); + probe crypto_handshake(struct st_quicly_conn_t *conn, int64_t at, int ret); probe crypto_update_secret(struct st_quicly_conn_t *conn, int64_t at, int is_enc, uint8_t epoch, const char *label, const char *secret); probe crypto_send_key_update(struct st_quicly_conn_t *conn, int64_t at, uint64_t phase, const char *secret); @@ -129,6 +134,12 @@ provider quicly { probe ecn_validation(struct st_quicly_conn_t *conn, int64_t at, int ecn_state); probe ecn_congestion(struct st_quicly_conn_t *conn, int64_t at, uint64_t ce_count); + probe path_challenge_send(struct st_quicly_conn_t *conn, int64_t at, const void *bytes, size_t bytes_len); + probe path_challenge_receive(struct st_quicly_conn_t *conn, int64_t at, const void *bytes, size_t bytes_len); + + probe path_response_send(struct st_quicly_conn_t *conn, int64_t at, const void *bytes, size_t bytes_len); + probe path_response_receive(struct st_quicly_conn_t *conn, int64_t at, const void *bytes, size_t bytes_len); + probe datagram_send(struct st_quicly_conn_t *conn, int64_t at, const void *payload, size_t payload_len); probe datagram_receive(struct st_quicly_conn_t *conn, int64_t at, const void *payload, size_t payload_len); diff --git a/quicly.xcodeproj/project.pbxproj b/quicly.xcodeproj/project.pbxproj index 5548b58e..2b63dfed 100644 --- a/quicly.xcodeproj/project.pbxproj +++ b/quicly.xcodeproj/project.pbxproj @@ -985,7 +985,7 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; @@ -1049,7 +1049,7 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; diff --git a/src/cli.c b/src/cli.c index 8cd3a554..5463095f 100644 --- a/src/cli.c +++ b/src/cli.c @@ -19,12 +19,16 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ +#ifdef __APPLE__ +#define __APPLE_USE_RFC_3542 /* to use IPV6_PKTINFO */ +#endif #include #include #include #include #include #include +#include #include #include #include @@ -147,18 +151,20 @@ static void dump_stats(FILE *fp, quicly_conn_t *conn) ", received-ecn-ce: %" PRIu64 ", packets-decryption-failed: %" PRIu64 ", packets-sent: %" PRIu64 ", packets-lost: %" PRIu64 ", ack-received: %" PRIu64 ", ack-ecn-ect0: %" PRIu64 ", ack-ecn-ect1: %" PRIu64 ", ack-ecn-ce: %" PRIu64 ", late-acked: %" PRIu64 ", bytes-received: %" PRIu64 ", bytes-sent: %" PRIu64 - ", srtt: %" PRIu32 ", num-loss-episodes: %" PRIu32 ", num-ecn-loss-episodes: %" PRIu32 ", delivery-rate: %" PRIu64 - ", cwnd: %" PRIu32 ", cwnd-exiting-slow-start: %" PRIu32 ", slow-start-exit-at: %" PRId64 ", jumpstart-cwnd: %" PRIu32 + ", paths-created %" PRIu64 ", paths-validated %" PRIu64 ", paths-promoted: %" PRIu64 ", srtt: %" PRIu32 + ", num-loss-episodes: %" PRIu32 ", num-ecn-loss-episodes: %" PRIu32 ", delivery-rate: %" PRIu64 ", cwnd: %" PRIu32 + ", cwnd-exiting-slow-start: %" PRIu32 ", slow-start-exit-at: %" PRId64 ", jumpstart-cwnd: %" PRIu32 ", jumpstart-exit: %" PRIu32 ", jumpstart-prev-rate: %" PRIu64 ", jumpstart-prev-rtt: %" PRIu32 ", token-sent-rate: %" PRIu64 ", token-sent-rtt: %" PRIu32 "\n", stats.num_packets.received, stats.num_packets.received_ecn_counts[0], stats.num_packets.received_ecn_counts[1], stats.num_packets.received_ecn_counts[2], stats.num_packets.decryption_failed, stats.num_packets.sent, stats.num_packets.lost, stats.num_packets.ack_received, stats.num_packets.acked_ecn_counts[0], stats.num_packets.acked_ecn_counts[1], stats.num_packets.acked_ecn_counts[2], stats.num_packets.late_acked, - stats.num_bytes.received, stats.num_bytes.sent, stats.rtt.smoothed, stats.cc.num_loss_episodes, - stats.cc.num_ecn_loss_episodes, stats.delivery_rate.smoothed, stats.cc.cwnd, stats.cc.cwnd_exiting_slow_start, - stats.cc.exit_slow_start_at, stats.jumpstart.cwnd, stats.cc.cwnd_exiting_jumpstart, stats.jumpstart.prev_rate, - stats.jumpstart.prev_rtt, stats.token_sent.rate, stats.token_sent.rtt); + stats.num_bytes.received, stats.num_bytes.sent, stats.num_paths.created, stats.num_paths.validated, + stats.num_paths.promoted, stats.rtt.smoothed, stats.cc.num_loss_episodes, stats.cc.num_ecn_loss_episodes, + stats.delivery_rate.smoothed, stats.cc.cwnd, stats.cc.cwnd_exiting_slow_start, stats.cc.exit_slow_start_at, + stats.jumpstart.cwnd, stats.cc.cwnd_exiting_jumpstart, stats.jumpstart.prev_rate, stats.jumpstart.prev_rtt, + stats.token_sent.rate, stats.token_sent.rtt); } static int validate_path(const char *path) @@ -419,10 +425,10 @@ static int on_generate_resumption_token(quicly_generate_resumption_token_t *self static quicly_generate_resumption_token_t generate_resumption_token = {&on_generate_resumption_token}; /* buf should be ctx.transport_params.max_udp_payload_size bytes long */ -static ssize_t receive_datagram(int fd, void *buf, quicly_address_t *src, uint8_t *ecn) +static ssize_t receive_datagram(int fd, void *buf, quicly_address_t *dest, quicly_address_t *src, uint8_t *ecn) { struct iovec vec = {.iov_base = buf, .iov_len = ctx.transport_params.max_udp_payload_size}; - char cmsgbuf[CMSG_SPACE(sizeof(int) /* == max(V4_TOS, V6_TCLASS) */)] = {}; + char cmsgbuf[CMSG_SPACE(sizeof(struct in6_pktinfo) /* == max(V4_TOS, V6_TCLASS) */) + CMSG_SPACE(1 /* TOS */)] = {}; struct msghdr mess = { .msg_name = &src->sa, .msg_namelen = sizeof(*src), @@ -442,8 +448,31 @@ static ssize_t receive_datagram(int fd, void *buf, quicly_address_t *src, uint8_ ; if (rret >= 0) { + dest->sa.sa_family = AF_UNSPEC; *ecn = 0; for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&mess); cmsg != NULL; cmsg = CMSG_NXTHDR(&mess, cmsg)) { +#ifdef IP_PKTINFO + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { + dest->sin.sin_family = AF_INET; + memcpy(&dest->sin.sin_addr, CMSG_DATA(cmsg) + offsetof(struct in_pktinfo, ipi_addr), sizeof(dest->sin.sin_addr)); + dest->sin.sin_port = localaddr.sin.sin_port; + } +#endif +#ifdef IP_RECVDSTADDR + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR) { + dest->sin.sin_family = AF_INET; + memcpy(&dest->sin.sin_addr, CMSG_DATA(cmsg), sizeof(dest->sin.sin_addr)); + dest->sin.sin_port = localaddr.sin.sin_port; + } +#endif +#ifdef IPV6_PKTINFO + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IPV6_PKTINFO) { + dest->sin6.sin6_family = AF_INET6; + memcpy(&dest->sin6.sin6_addr, CMSG_DATA(cmsg) + offsetof(struct in6_pktinfo, ipi6_addr), + sizeof(dest->sin6.sin6_addr)); + dest->sin6.sin6_port = localaddr.sin6.sin6_port; + } +#endif #ifdef IP_RECVTOS if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == #ifdef __APPLE__ @@ -462,6 +491,44 @@ static ssize_t receive_datagram(int fd, void *buf, quicly_address_t *src, uint8_ return rret; } +/* in6_pktinfo would be the largest structure among the ones that might be stored */ +static void set_srcaddr(struct msghdr *mess, quicly_address_t *addr) +{ + struct cmsghdr *cmsg = (struct cmsghdr *)((char *)mess->msg_control + mess->msg_controllen); + + switch (addr->sa.sa_family) { + case AF_INET: { +#ifdef IP_PKTINFO + struct in_pktinfo info = {.ipi_spec_dst = addr->sin.sin_addr}; + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(info)); + memcpy(CMSG_DATA(cmsg), &info, sizeof(info)); + mess->msg_controllen += CMSG_SPACE(sizeof(info)); +#elif defined(IP_SENDSRCADDR) + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_SENDSRCADDR; + cmsg->cmsg_len = CMSG_LEN(sizeof(addr->sin)); + memcpy(CMSG_DATA(cmsg), &addr->sin, sizeof(addr->sin)); + mess->msg_controllen += CMSG_SPACE(sizeof(addr->sin)); +#else + assert(!"FIXME"); +#endif + } break; + case AF_INET6: { + struct in6_pktinfo info = {.ipi6_addr = addr->sin6.sin6_addr}; + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(info)); + memcpy(CMSG_DATA(cmsg), &info, sizeof(info)); + mess->msg_controllen += CMSG_SPACE(sizeof(info)); + } break; + default: + assert(!"FIXME"); + break; + } +} + static void set_ecn(struct msghdr *mess, int ecn) { if (ecn == 0) @@ -477,17 +544,20 @@ static void set_ecn(struct msghdr *mess, int ecn) mess->msg_controllen += CMSG_SPACE(sizeof(ecn)); } -static void send_packets_default(int fd, struct sockaddr *dest, struct iovec *packets, size_t num_packets, uint8_t ecn) +static void send_packets_default(int fd, quicly_address_t *dest, quicly_address_t *src, struct iovec *packets, size_t num_packets, + uint8_t ecn) { for (size_t i = 0; i != num_packets; ++i) { - char cmsgbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + char cmsgbuf[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))]; struct msghdr mess = { .msg_name = dest, - .msg_namelen = quicly_get_socklen(dest), + .msg_namelen = quicly_get_socklen(&dest->sa), .msg_iov = &packets[i], .msg_iovlen = 1, .msg_control = cmsgbuf, }; + if (src != NULL && src->sa.sa_family != AF_UNSPEC) + set_srcaddr(&mess, src); set_ecn(&mess, ecn); assert(mess.msg_controllen <= sizeof(cmsgbuf)); if (mess.msg_controllen == 0) @@ -508,27 +578,35 @@ static void send_packets_default(int fd, struct sockaddr *dest, struct iovec *pa #define UDP_SEGMENT 103 #endif -static void send_packets_gso(int fd, struct sockaddr *dest, struct iovec *packets, size_t num_packets, uint8_t ecn) +static void send_packets_gso(int fd, quicly_address_t *dest, quicly_address_t *src, struct iovec *packets, size_t num_packets, + uint8_t ecn) { struct iovec vec = {.iov_base = (void *)packets[0].iov_base, .iov_len = packets[num_packets - 1].iov_base + packets[num_packets - 1].iov_len - packets[0].iov_base}; - char cmsgbuf[CMSG_SPACE(sizeof(uint16_t)) + CMSG_SPACE(sizeof(int))]; /* UDP_SEGMENT and IP_TOS */ + char cmsgbuf[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(uint16_t)) /* UDP_SEGMENT */ + + CMSG_SPACE(sizeof(int)) /* IP_TOS */]; struct msghdr mess = { .msg_name = dest, - .msg_namelen = quicly_get_socklen(dest), + .msg_namelen = quicly_get_socklen(&dest->sa), .msg_iov = &vec, .msg_iovlen = 1, .msg_control = cmsgbuf, }; + + if (src != NULL && src->sa.sa_family != AF_UNSPEC) + set_srcaddr(&mess, src); if (num_packets != 1) { - struct cmsghdr *cmsg = mess.msg_control; + struct cmsghdr *cmsg = (struct cmsghdr *)((char *)mess.msg_control + mess.msg_controllen); cmsg->cmsg_level = SOL_UDP; cmsg->cmsg_type = UDP_SEGMENT; cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t)); *(uint16_t *)CMSG_DATA(cmsg) = packets[0].iov_len; - mess.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); + mess.msg_controllen += CMSG_SPACE(sizeof(uint16_t)); } set_ecn(&mess, ecn); + assert(mess.msg_controllen <= sizeof(cmsgbuf)); + if (mess.msg_controllen == 0) + mess.msg_control = NULL; int ret; while ((ret = sendmsg(fd, &mess, 0)) == -1 && errno == EINTR) @@ -539,12 +617,12 @@ static void send_packets_gso(int fd, struct sockaddr *dest, struct iovec *packet #endif -static void (*send_packets)(int, struct sockaddr *, struct iovec *, size_t, uint8_t ecn) = send_packets_default; +static void (*send_packets)(int, quicly_address_t *, quicly_address_t *, struct iovec *, size_t, uint8_t) = send_packets_default; -static void send_one_packet(int fd, struct sockaddr *dest, const void *payload, size_t payload_len) +static void send_one_packet(int fd, quicly_address_t *dest, quicly_address_t *src, const void *payload, size_t payload_len) { struct iovec vec = {.iov_base = (void *)payload, .iov_len = payload_len}; - send_packets(fd, dest, &vec, 1, 0); + send_packets(fd, dest, src, &vec, 1, 0); } static int send_pending(int fd, quicly_conn_t *conn) @@ -556,7 +634,7 @@ static int send_pending(int fd, quicly_conn_t *conn) int ret; if ((ret = quicly_send(conn, &dest, &src, packets, &num_packets, buf, sizeof(buf))) == 0 && num_packets != 0) - send_packets(fd, &dest.sa, packets, num_packets, quicly_send_get_ecn_bits(conn)); + send_packets(fd, &dest, &src, packets, num_packets, quicly_send_get_ecn_bits(conn)); return ret; } @@ -596,15 +674,24 @@ static void enqueue_requests(quicly_conn_t *conn) enqueue_requests_at = INT64_MAX; } +static volatile int client_gotsig = 0; + +static void on_client_signal(int signo) +{ + client_gotsig = signo; +} + static int run_client(int fd, struct sockaddr *sa, const char *host) { - struct sockaddr_in local; + quicly_address_t local; int ret; quicly_conn_t *conn = NULL; + signal(SIGTERM, on_client_signal); + memset(&local, 0, sizeof(local)); - local.sin_family = AF_INET; - if (bind(fd, (void *)&local, sizeof(local)) != 0) { + local.sa.sa_family = sa->sa_family; + if (bind(fd, &local.sa, local.sa.sa_family == AF_INET ? sizeof(local.sin) : sizeof(local.sin6)) != 0) { perror("bind(2) failed"); return 1; } @@ -643,8 +730,8 @@ static int run_client(int fd, struct sockaddr *sa, const char *host) if (FD_ISSET(fd, &readfds)) { while (1) { uint8_t buf[ctx.transport_params.max_udp_payload_size], ecn; - quicly_address_t src; - ssize_t rret = receive_datagram(fd, buf, &src, &ecn); + quicly_address_t dest, src; + ssize_t rret = receive_datagram(fd, buf, &dest, &src, &ecn); if (rret <= 0) break; if (verbosity >= 2) @@ -655,7 +742,7 @@ static int run_client(int fd, struct sockaddr *sa, const char *host) if (quicly_decode_packet(&ctx, &packet, buf, rret, &off) == SIZE_MAX) break; packet.ecn = ecn; - quicly_receive(conn, NULL, &src.sa, &packet); + quicly_receive(conn, &dest.sa, &src.sa, &packet); if (send_datagram_frame && quicly_connection_is_ready(conn)) { const char *message = "hello datagram!"; ptls_iovec_t datagram = ptls_iovec_init(message, strlen(message)); @@ -663,7 +750,7 @@ static int run_client(int fd, struct sockaddr *sa, const char *host) send_datagram_frame = 0; } if (quicly_num_streams(conn) == 0) { - if (request_interval != 0) { + if (request_interval != 0 && client_gotsig != SIGTERM) { if (enqueue_requests_at == INT64_MAX) enqueue_requests_at = ctx.now->cb(ctx.now) + request_interval; } else { @@ -698,7 +785,7 @@ static int run_client(int fd, struct sockaddr *sa, const char *host) static quicly_conn_t **conns; static size_t num_conns = 0; -static void on_signal(int signo) +static void on_server_signal(int signo) { size_t i; for (i = 0; i != num_conns; ++i) { @@ -784,8 +871,8 @@ static int validate_token(struct sockaddr *remote, ptls_iovec_t client_cid, ptls static int run_server(int fd, struct sockaddr *sa, socklen_t salen) { - signal(SIGINT, on_signal); - signal(SIGHUP, on_signal); + signal(SIGINT, on_server_signal); + signal(SIGHUP, on_server_signal); if (bind(fd, sa, salen) != 0) { perror("bind(2) failed"); @@ -821,9 +908,9 @@ static int run_server(int fd, struct sockaddr *sa, socklen_t salen) } while (select(fd + 1, &readfds, NULL, NULL, tv) == -1 && errno == EINTR); if (FD_ISSET(fd, &readfds)) { while (1) { + quicly_address_t local, remote; uint8_t buf[ctx.transport_params.max_udp_payload_size], ecn; - quicly_address_t remote; - ssize_t rret = receive_datagram(fd, buf, &remote, &ecn); + ssize_t rret = receive_datagram(fd, buf, &local, &remote, &ecn); if (rret == -1) break; if (verbosity >= 2) @@ -840,7 +927,7 @@ static int run_server(int fd, struct sockaddr *sa, socklen_t salen) size_t payload_len = quicly_send_version_negotiation(&ctx, packet.cid.src, packet.cid.dest.encrypted, quicly_supported_versions, payload); assert(payload_len != SIZE_MAX); - send_one_packet(fd, &remote.sa, payload, payload_len); + send_one_packet(fd, &remote, &local, payload, payload_len); break; } /* there is no way to send response to these v1 packets */ @@ -851,14 +938,14 @@ static int run_server(int fd, struct sockaddr *sa, socklen_t salen) quicly_conn_t *conn = NULL; size_t i; for (i = 0; i != num_conns; ++i) { - if (quicly_is_destination(conns[i], NULL, &remote.sa, &packet)) { + if (quicly_is_destination(conns[i], &local.sa, &remote.sa, &packet)) { conn = conns[i]; break; } } if (conn != NULL) { /* existing connection */ - quicly_receive(conn, NULL, &remote.sa, &packet); + quicly_receive(conn, &local.sa, &remote.sa, &packet); } else if (QUICLY_PACKET_IS_INITIAL(packet.octets.base[0])) { /* long header packet; potentially a new connection */ quicly_address_token_plaintext_t *token = NULL, token_buf; @@ -877,7 +964,7 @@ static int run_server(int fd, struct sockaddr *sa, socklen_t salen) size_t payload_len = quicly_send_close_invalid_token(&ctx, packet.version, packet.cid.src, packet.cid.dest.encrypted, err_desc, payload); assert(payload_len != SIZE_MAX); - send_one_packet(fd, &remote.sa, payload, payload_len); + send_one_packet(fd, &remote, NULL, payload, payload_len); } } if (enforce_retry && (token == NULL || token->address_mismatch) && packet.cid.dest.encrypted.len >= 8) { @@ -892,11 +979,11 @@ static int run_server(int fd, struct sockaddr *sa, socklen_t salen) ptls_iovec_init(new_server_cid, sizeof(new_server_cid)), packet.cid.dest.encrypted, ptls_iovec_init(NULL, 0), ptls_iovec_init(NULL, 0), NULL, payload); assert(payload_len != SIZE_MAX); - send_one_packet(fd, &remote.sa, payload, payload_len); + send_one_packet(fd, &remote, NULL, payload, payload_len); break; } else { /* new connection */ - int ret = quicly_accept(&conn, &ctx, NULL, &remote.sa, &packet, token, &next_cid, NULL, NULL); + int ret = quicly_accept(&conn, &ctx, &local.sa, &remote.sa, &packet, token, &next_cid, NULL, NULL); if (ret == 0) { assert(conn != NULL); ++next_cid.master_id; @@ -915,7 +1002,7 @@ static int run_server(int fd, struct sockaddr *sa, socklen_t salen) uint8_t payload[ctx.transport_params.max_udp_payload_size]; size_t payload_len = quicly_send_stateless_reset(&ctx, packet.cid.dest.encrypted.base, payload); assert(payload_len != SIZE_MAX); - send_one_packet(fd, &remote.sa, payload, payload_len); + send_one_packet(fd, &remote, NULL, payload, payload_len); } } } @@ -1520,6 +1607,7 @@ int main(int argc, char **argv) ctx.transport_params.max_datagram_frame_size = ctx.transport_params.max_udp_payload_size; } + int use_cid_encryptor = 0; if (cert_file != NULL || ctx.tls->sign_certificate != NULL) { /* server */ if (cert_file == NULL || ctx.tls->sign_certificate == NULL) { @@ -1534,13 +1622,7 @@ int main(int argc, char **argv) } else { load_certificate_chain(ctx.tls, cert_file); } - if (cid_key == NULL) { - static char random_key[17]; - tlsctx.random_bytes(random_key, sizeof(random_key) - 1); - cid_key = random_key; - } - ctx.cid_encryptor = quicly_new_default_cid_encryptor(&ptls_openssl_bfecb, &ptls_openssl_aes128ecb, &ptls_openssl_sha256, - ptls_iovec_init(cid_key, strlen(cid_key))); + use_cid_encryptor = 1; } else { /* client */ if (raw_pubkey_file != NULL) { @@ -1562,6 +1644,16 @@ int main(int argc, char **argv) load_session(); hs_properties.client.ech.configs = ech.config_list; hs_properties.client.ech.retry_configs = &ech.retry.configs; + use_cid_encryptor = cid_key != NULL; + } + if (use_cid_encryptor) { + if (cid_key == NULL) { + static char random_key[17]; + tlsctx.random_bytes(random_key, sizeof(random_key) - 1); + cid_key = random_key; + } + ctx.cid_encryptor = quicly_new_default_cid_encryptor(&ptls_openssl_bfecb, &ptls_openssl_aes128ecb, &ptls_openssl_sha256, + ptls_iovec_init(cid_key, strlen(cid_key))); } if (argc != 2) { fprintf(stderr, "missing host and port\n"); @@ -1617,6 +1709,32 @@ int main(int argc, char **argv) perror("Warning: setsockopt(IP_RECVTOS) failed"); } #endif + switch (sa.ss_family) { + case AF_INET: { +#ifdef IP_PKTINFO + int on = 1; + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)) != 0) { + perror("setsockopt(IP_PKTINFO) failed"); + return 1; + } +#elif defined(IP_RECVDSTADDR) + int on = 1; + if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)) != 0) { + perror("setsockopt(IP_RECVDSTADDR) failed"); + return 1; + } +#endif + } break; + case AF_INET6: { + int on = 1; + if (setsockopt(fd, IPPROTO_IP, IPV6_RECVPKTINFO, &on, sizeof(on)) != 0) { + perror("setsockopt(IPV6_RECVPKTINNFO) failed"); + return 1; + } + } break; + default: + break; + } return ctx.tls->certificates.count != 0 ? run_server(fd, (void *)&sa, salen) : run_client(fd, (void *)&sa, host); } diff --git a/t/e2e.t b/t/e2e.t index 4fec2e07..88c8be62 100755 --- a/t/e2e.t +++ b/t/e2e.t @@ -9,7 +9,6 @@ use IO::Socket::INET; use JSON; use Net::EmptyPort qw(empty_port); use POSIX ":sys_wait_h"; -use Scope::Guard qw(scope_guard); use Test::More; use Time::HiRes qw(sleep time); @@ -353,6 +352,67 @@ subtest "raw-certificates-ec" => sub { is $resp, "hello world\n"; }; +subtest "path-migration" => sub { + my $doit = sub { + my @client_opts = @_; + my $server_guard = spawn_server("-e", "$tempdir/events"); + my $udpfw_guard = undef; + my $respawn_udpfw = sub { + $udpfw_guard = undef; # terminate existing process + $udpfw_guard = spawn_process( + ["sh", "-c", "exec $udpfw -b 100 -i 1 -p 0 -B 100 -I 1 -P 10000 -l $udpfw_port 127.0.0.1 $port > /dev/null 2>&1"], + $udpfw_port, + ); + }; + $respawn_udpfw->(); + # spawn client that sends one request every second, recording events to file + my $pid = fork; + die "fork failed:$!" + unless defined $pid; + if ($pid == 0) { + exec $cli, @client_opts, qw(-O -i 1000 -p /10000 127.0.0.1), $udpfw_port; + die "exec $cli failed:$!"; + } + # send two USR1 signals, each of them causing path migration between requests + sleep .5; + $respawn_udpfw->(); + sleep 2; + $respawn_udpfw->(); + sleep 2; + # kill the peers + kill 'TERM', $pid; + while (waitpid($pid, 0) != $pid) {} + sleep 0.5; # wait for server-side to close and emit stats + my $server_output = $server_guard->finalize; + # read the log + my $log = slurp_file("$tempdir/events"); + # check that the path has migrated twice + like $log, qr{"type":"promote_path".*\n.*"type":"promote_path"}s; + subtest "CID seq 1 is used for 1st path probe" => sub { + plan skip_all => "zero-length CID" + unless @client_opts; + complex $log, sub { + /"type":"new_connection_id_receive",[^\n]*"sequence":1,[^\n]*"cid":"(.*?)"/s; + my $cid1 = $1; + /"type":"packet_prepare",[^\n]*"dcid":"([^\"]*)"[^\n]*\n[^\n]*"type":"path_challenge_send",/s; + my $cid_probe = $1; + $cid1 eq $cid_probe; + }; + }; + # check that packets are lost (or deemed lost), but that CC is in slow start + complex $server_output, sub { + /packets-lost:\s*(\d+).*num-loss-episodes:\s*(\d+)/ and $1 >= 2 and $2 == 0; + }, "packets-lost-but-cc-in-slow-start"; + + }; + subtest "without-cid" => sub { + $doit->(); + }; + subtest "with-cid" => sub { + $doit->(qw(-B 01234567)); + }; +}; + subtest "slow-start" => sub { # spawn udpfw that applies 100ms RTT but otherwise nothing my $udpfw_guard = spawn_process( @@ -481,29 +541,71 @@ sub spawn_server { spawn_process(\@cmd, $port); } -sub spawn_process { - my ($cmd, $listen_port) = @_; - - my $pid = fork; - die "fork failed:$!" - unless defined $pid; - if ($pid == 0) { - exec @$cmd; - die "failed to exec @{[$cmd->[0]]}:$?"; - } - for (1..10) { - if (`netstat -na` =~ /^udp.*\s(127\.0\.0\.1|0\.0\.0\.0|\*)[\.:]$listen_port\s/m) { - last; +package SpawnedProcess { + use POSIX ":sys_wait_h"; + + sub new { + my ($klass, $cmd, $listen_port) = @_; + + my $self = bless { + logfh => scalar File::Temp::tempfile(), + pid => fork(), + }, $klass; + + die "fork failed:$!" + unless defined $self->{pid}; + if ($self->{pid} == 0) { + close STDOUT; + open STDOUT, ">&", $self->{logfh} + or die "failed to dup(2) log file to STDOUT:$!"; + open STDERR, ">&", $self->{logfh} + or die "failed to dup(2) log file to STDERR:$!"; + exec @$cmd; + die "failed to exec @{[$cmd->[0]]}:$?"; } - if (waitpid($pid, WNOHANG) == $pid) { - die "failed to launch @{[$cmd->[0]]}:$?"; + for (1..10) { + if (`netstat -na` =~ /^udp.*\s(127\.0\.0\.1|0\.0\.0\.0|\*)[\.:]$listen_port\s/m) { + last; + } + if (waitpid($self->{pid}, WNOHANG) == $self->{pid}) { + die "failed to launch @{[$cmd->[0]]}:$?"; + } + sleep 0.1; } - sleep 0.1; + + $self; } - return scope_guard(sub { - kill 9, $pid; - while (waitpid($pid, 0) != $pid) {} - }); + + sub DESTROY { + goto \&finalize; + } + + sub finalize { + my $self = shift; + + return unless $self->{pid}; + + # kill the process + kill 9, $self->{pid}; + while (waitpid($self->{pid}, 0) != $self->{pid}) {} + undef $self->{pid}; + + # fetch and close the log file + seek $self->{logfh}, 0, 0; + my $log = do { + local $/; + readline $self->{logfh}; + }; + close $self->{logfh}; + + print STDERR $log; + + return $log; + } +} + +sub spawn_process { + SpawnedProcess->new(@_); } sub slurp_file { diff --git a/t/loss.c b/t/loss.c index 20cced1d..0bae3e7b 100644 --- a/t/loss.c +++ b/t/loss.c @@ -60,11 +60,11 @@ static void test_time_detection(void) /* commit 3 packets (pn=0..2); check that loss timer is not active */ ok(quicly_sentmap_prepare(&loss.sentmap, 0, now, QUICLY_EPOCH_INITIAL) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); ok(quicly_sentmap_prepare(&loss.sentmap, 1, now, QUICLY_EPOCH_INITIAL) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); ok(quicly_sentmap_prepare(&loss.sentmap, 2, now, QUICLY_EPOCH_INITIAL) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); ok(quicly_loss_detect_loss(&loss, now, quicly_spec_context.transport_params.max_ack_delay, 0, on_loss_detected) == 0); ok(loss.loss_time == INT64_MAX); @@ -104,13 +104,13 @@ static void test_pn_detection(void) /* commit 4 packets (pn=0..3); check that loss timer is not active */ ok(quicly_sentmap_prepare(&loss.sentmap, 0, now, QUICLY_EPOCH_INITIAL) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); ok(quicly_sentmap_prepare(&loss.sentmap, 1, now, QUICLY_EPOCH_INITIAL) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); ok(quicly_sentmap_prepare(&loss.sentmap, 2, now, QUICLY_EPOCH_INITIAL) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); ok(quicly_sentmap_prepare(&loss.sentmap, 3, now, QUICLY_EPOCH_INITIAL) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); ok(quicly_loss_detect_loss(&loss, now, quicly_spec_context.transport_params.max_ack_delay, 0, on_loss_detected) == 0); ok(loss.loss_time == INT64_MAX); @@ -145,9 +145,9 @@ static void test_slow_cert_verify(void) /* sent Handshake+1RTT packet */ ok(quicly_sentmap_prepare(&loss.sentmap, 1, now, QUICLY_EPOCH_HANDSHAKE) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); ok(quicly_sentmap_prepare(&loss.sentmap, 2, now, QUICLY_EPOCH_1RTT) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); last_retransmittable_sent_at = now; quicly_loss_update_alarm(&loss, now, last_retransmittable_sent_at, 1, 0, 1, 0, 1); @@ -169,9 +169,9 @@ static void test_slow_cert_verify(void) /* therefore send probes */ ok(quicly_sentmap_prepare(&loss.sentmap, 3, now, QUICLY_EPOCH_HANDSHAKE) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); ok(quicly_sentmap_prepare(&loss.sentmap, 4, now, QUICLY_EPOCH_1RTT) == 0); - quicly_sentmap_commit(&loss.sentmap, 10, 0); + quicly_sentmap_commit(&loss.sentmap, 10, 0, 0); now += 10; diff --git a/t/remote_cid.c b/t/remote_cid.c index 7fb0d251..fbe37ea4 100644 --- a/t/remote_cid.c +++ b/t/remote_cid.c @@ -27,27 +27,33 @@ /* clang-format off */ static uint8_t cids[][CID_LEN] = { - {0, 1, 2, 3, 4, 5, 6, 7}, /* 0 */ - {1, 2, 3, 4, 5, 6, 7, 0}, - {2, 3, 4, 5, 6, 7, 0, 1}, - {3, 4, 5, 6, 7, 0, 1, 2}, - {4, 5, 6, 7, 0, 1, 2, 3}, - {5, 6, 7, 0, 1, 2, 3, 4}, - {6, 7, 0, 1, 2, 3, 4, 5}, - {7, 0, 1, 2, 3, 4, 5, 6}, - {8, 9, 10, 11, 12, 13, 14, 15}, /* 8 */ + {0, 1, 2, 3, 4, 5, 6, 7}, /* 0 */ + {1, 2, 3, 4, 5, 6, 7, 0}, + {2, 3, 4, 5, 6, 7, 0, 1}, + {3, 4, 5, 6, 7, 0, 1, 2}, + {4, 5, 6, 7, 0, 1, 2, 3}, + {5, 6, 7, 0, 1, 2, 3, 4}, + {6, 7, 0, 1, 2, 3, 4, 5}, + {7, 0, 1, 2, 3, 4, 5, 6}, + {8, 9, 10, 11, 12, 13, 14, 15}, /* 8 */ + {9, 10, 11, 12, 13, 14, 15, 16}, + {10, 11, 12, 13, 14, 15, 16, 17}, + {11, 12, 13, 14, 15, 16, 17, 18}, }; static uint8_t srts[][QUICLY_STATELESS_RESET_TOKEN_LEN] = { - {0}, - {1}, - {2}, - {3}, - {4}, - {5}, - {6}, - {7}, - {8}, + {0}, + {1}, + {2}, + {3}, + {4}, + {5}, + {6}, + {7}, + {8}, + {9}, + {10}, + {11}, }; /* clang-format on */ @@ -57,13 +63,33 @@ void test_received_cid(void) uint64_t unregistered_seqs[QUICLY_LOCAL_ACTIVE_CONNECTION_ID_LIMIT]; size_t num_unregistered; +#define TEST_SET(...) \ + do { \ + static const struct { \ + uint64_t seq; \ + quicly_remote_cid_state_t state; \ + } expected[] = {__VA_ARGS__}; \ + PTLS_BUILD_ASSERT(PTLS_ELEMENTSOF(expected) == QUICLY_LOCAL_ACTIVE_CONNECTION_ID_LIMIT); \ + for (size_t i = 0; i < PTLS_ELEMENTSOF(expected); ++i) { \ + ok(set.cids[i].state == expected[i].state); \ + ok(set.cids[i].sequence == expected[i].seq); \ + if (expected[i].state != QUICLY_REMOTE_CID_UNAVAILABLE) { \ + ok(set.cids[i].cid.len == CID_LEN); \ + ok(expected[i].seq == 0 /* cid is random for seq 0 */ || \ + memcmp(set.cids[i].cid.cid, cids[expected[i].seq], CID_LEN) == 0); \ + ok(expected[i].seq == 0 /* so is srt */ || \ + memcmp(set.cids[i].stateless_reset_token, srts[expected[i].seq], QUICLY_STATELESS_RESET_TOKEN_LEN) == 0); \ + } \ + } \ + } while (0) + quicly_remote_cid_init_set(&set, NULL, quic_ctx.tls->random_bytes); /* fill CIDs */ for (int i = 1; i < 4; i++) { ok(quicly_remote_cid_register(&set, i, cids[i], CID_LEN, srts[i], 0, unregistered_seqs, &num_unregistered) == 0); ok(num_unregistered == 0); } - /* active CIDs = {*0, 1, 2, 3} (*0 is the current one) */ + /* CIDs = {0, 1, 2, 3} */ /* dup */ ok(quicly_remote_cid_register(&set, 1, cids[1], CID_LEN, srts[1], 0, unregistered_seqs, &num_unregistered) == 0); @@ -74,23 +100,18 @@ void test_received_cid(void) /* already full */ ok(quicly_remote_cid_register(&set, 4, cids[4], CID_LEN, srts[4], 0, unregistered_seqs, &num_unregistered) == QUICLY_TRANSPORT_ERROR_CONNECTION_ID_LIMIT); - ok(set.cids[0].is_active && "we have CID to send error"); + TEST_SET({0, QUICLY_REMOTE_CID_IN_USE}, /* we have CID to send error */ + {1, QUICLY_REMOTE_CID_AVAILABLE}, {2, QUICLY_REMOTE_CID_AVAILABLE}, {3, QUICLY_REMOTE_CID_AVAILABLE}); - /* try to unregister something doesn't exist */ - ok(quicly_remote_cid_unregister(&set, 255) != 0); /* retire seq=0 */ - ok(quicly_remote_cid_unregister(&set, 0) == 0); - /* active CIDs = {*1, 2, 3} */ - ok(set.cids[0].is_active); - ok(set.cids[0].sequence == 1); - ok(memcmp(set.cids[0].cid.cid, cids[1], CID_LEN) == 0); - ok(memcmp(set.cids[0].stateless_reset_token, srts[1], QUICLY_STATELESS_RESET_TOKEN_LEN) == 0); - /* try to unregister sequence which is already unregistered */ - ok(quicly_remote_cid_unregister(&set, 0) != 0); + quicly_remote_cid_unregister(&set, 0); + /* CIDs = {(4), 1, 2, 3} */ + TEST_SET({4, QUICLY_REMOTE_CID_UNAVAILABLE}, {1, QUICLY_REMOTE_CID_AVAILABLE}, {2, QUICLY_REMOTE_CID_AVAILABLE}, + {3, QUICLY_REMOTE_CID_AVAILABLE}); /* sequence number out of current acceptable window */ ok(quicly_remote_cid_register(&set, 255, cids[4], CID_LEN, srts[4], 0, unregistered_seqs, &num_unregistered) == QUICLY_TRANSPORT_ERROR_CONNECTION_ID_LIMIT); - ok(set.cids[0].is_active && "we have CID to send error"); + ok(set.cids[1].state == QUICLY_REMOTE_CID_AVAILABLE && "we have CID to send error"); /* ignore already retired CID */ ok(quicly_remote_cid_register(&set, 0, cids[0], CID_LEN, srts[0], 0, unregistered_seqs, &num_unregistered) == 0); @@ -99,57 +120,54 @@ void test_received_cid(void) /* register 5th CID */ ok(quicly_remote_cid_register(&set, 4, cids[4], CID_LEN, srts[4], 0, unregistered_seqs, &num_unregistered) == 0); ok(num_unregistered == 0); - /* active CIDs = {*1, 2, 3, 4} */ + /* active CIDs = {4, 1, 2, 3} */ + TEST_SET({4, QUICLY_REMOTE_CID_AVAILABLE}, {1, QUICLY_REMOTE_CID_AVAILABLE}, {2, QUICLY_REMOTE_CID_AVAILABLE}, + {3, QUICLY_REMOTE_CID_AVAILABLE}); /* unregister seq=2 */ - ok(quicly_remote_cid_unregister(&set, 2) == 0); - /* active CIDs = {*1, 3, 4} */ - ok(set.cids[0].is_active); - ok(set.cids[0].sequence == 1); + quicly_remote_cid_unregister(&set, 2); + /* active CIDs = {4, 1, (5), 3} */ + TEST_SET({4, QUICLY_REMOTE_CID_AVAILABLE}, {1, QUICLY_REMOTE_CID_AVAILABLE}, {5, QUICLY_REMOTE_CID_UNAVAILABLE}, + {3, QUICLY_REMOTE_CID_AVAILABLE}); /* register 5, unregister prior to 5 -- seq=1,3,4 should be unregistered at this moment */ ok(quicly_remote_cid_register(&set, 5, cids[5], CID_LEN, srts[5], 5, unregistered_seqs, &num_unregistered) == 0); - /* active CIDs = {} */ ok(num_unregistered == 3); - { - /* order in unregistered_seqs is not defined, so use a set to determine equivalence */ - char expected[5] = {0, 1, 0, 1, 1}; /* expect seq=1,3,4 */ - char seqset[5] = {0}; - for (size_t i = 0; i < num_unregistered; i++) { - if (unregistered_seqs[i] < sizeof(seqset)) - seqset[unregistered_seqs[i]] = 1; - } - ok(memcmp(seqset, expected, sizeof(seqset)) == 0); - } - /* active CIDs = {*5} */ - ok(set.cids[0].is_active); - ok(set.cids[0].sequence == 5); - ok(memcmp(set.cids[0].cid.cid, cids[5], CID_LEN) == 0); - ok(memcmp(set.cids[0].stateless_reset_token, srts[5], QUICLY_STATELESS_RESET_TOKEN_LEN) == 0); + /* check unregistered_seqs */ + ok(unregistered_seqs[0] == 4); + ok(unregistered_seqs[1] == 1); + ok(unregistered_seqs[2] == 3); + /* active CIDs = {(6), (7), 5, (8)} */ + TEST_SET({6, QUICLY_REMOTE_CID_UNAVAILABLE}, {7, QUICLY_REMOTE_CID_UNAVAILABLE}, {5, QUICLY_REMOTE_CID_AVAILABLE}, + {8, QUICLY_REMOTE_CID_UNAVAILABLE}); /* install CID with out-of-order sequence */ ok(quicly_remote_cid_register(&set, 8, cids[8], CID_LEN, srts[8], 5, unregistered_seqs, &num_unregistered) == 0); ok(num_unregistered == 0); - /* active CIDs = {*5, 8} */ + /* active CIDs = {(6), (7), 5, 8} */ + TEST_SET({6, QUICLY_REMOTE_CID_UNAVAILABLE}, {7, QUICLY_REMOTE_CID_UNAVAILABLE}, {5, QUICLY_REMOTE_CID_AVAILABLE}, + {8, QUICLY_REMOTE_CID_AVAILABLE}); ok(quicly_remote_cid_register(&set, 7, cids[7], CID_LEN, srts[7], 5, unregistered_seqs, &num_unregistered) == 0); - /* active CIDs = {*5, 7, 8} */ - ok(set.cids[0].is_active); - ok(set.cids[0].sequence == 5); + /* active CIDs = {(6), 7, 5, 8} */ + TEST_SET({6, QUICLY_REMOTE_CID_UNAVAILABLE}, {7, QUICLY_REMOTE_CID_AVAILABLE}, {5, QUICLY_REMOTE_CID_AVAILABLE}, + {8, QUICLY_REMOTE_CID_AVAILABLE}); - /* unregister prior to 8 -- seq=5,7 should be unregistered at this moment */ + /* unregister prior to 8 -- seq=5-7 should be unregistered at this moment */ ok(quicly_remote_cid_register(&set, 8, cids[8], CID_LEN, srts[8], 8, unregistered_seqs, &num_unregistered) == 0); /* active CIDs = {*8} */ - ok(num_unregistered == 2); - { - /* order in unregistered_seqs is not defined, so use a set to determine equivalence */ - char expected[8] = {0, 0, 0, 0, 0, 1, 0, 1}; /* expect seq=5,7 */ - char seqset[8] = {0}; - for (size_t i = 0; i < num_unregistered; i++) { - if (unregistered_seqs[i] < sizeof(seqset)) - seqset[unregistered_seqs[i]] = 1; - } - ok(memcmp(seqset, expected, sizeof(seqset)) == 0); - } - ok(set.cids[0].is_active); - ok(set.cids[0].sequence == 8); + ok(num_unregistered == 3); + /* check unregistered_seqs */ + ok(unregistered_seqs[0] == 6); + ok(unregistered_seqs[1] == 7); + ok(unregistered_seqs[2] == 5); + /* active CIDs = {(9), (10), (11), 8} */ + TEST_SET({9, QUICLY_REMOTE_CID_UNAVAILABLE}, {10, QUICLY_REMOTE_CID_UNAVAILABLE}, {11, QUICLY_REMOTE_CID_UNAVAILABLE}, + {8, QUICLY_REMOTE_CID_AVAILABLE}); + + /* register 11 */ + ok(quicly_remote_cid_register(&set, 11, cids[11], CID_LEN, srts[11], 8, unregistered_seqs, &num_unregistered) == 0); + ok(num_unregistered == 0); + /* active CIDs = {(9), (10), (11), 8} */ + TEST_SET({9, QUICLY_REMOTE_CID_UNAVAILABLE}, {10, QUICLY_REMOTE_CID_UNAVAILABLE}, {11, QUICLY_REMOTE_CID_AVAILABLE}, + {8, QUICLY_REMOTE_CID_AVAILABLE}); } diff --git a/t/sentmap.c b/t/sentmap.c index ace8ce57..908b0592 100644 --- a/t/sentmap.c +++ b/t/sentmap.c @@ -59,7 +59,7 @@ static void test_basic(void) quicly_sentmap_prepare(&map, at * 5 + i, at, QUICLY_EPOCH_INITIAL); quicly_sentmap_allocate(&map, on_acked); quicly_sentmap_allocate(&map, on_acked); - quicly_sentmap_commit(&map, 1, 0); + quicly_sentmap_commit(&map, 1, 0, 0); } } @@ -115,10 +115,10 @@ static void test_late_ack(void) /* commit pn 1, 2 */ quicly_sentmap_prepare(&map, 1, 0, QUICLY_EPOCH_INITIAL); quicly_sentmap_allocate(&map, on_acked); - quicly_sentmap_commit(&map, 10, 0); + quicly_sentmap_commit(&map, 10, 0, 0); quicly_sentmap_prepare(&map, 2, 0, QUICLY_EPOCH_INITIAL); quicly_sentmap_allocate(&map, on_acked); - quicly_sentmap_commit(&map, 20, 0); + quicly_sentmap_commit(&map, 20, 0, 0); ok(map.bytes_in_flight == 30); /* mark pn 1 as lost */ @@ -159,10 +159,10 @@ static void test_pto(void) /* commit pn 1, 2 */ quicly_sentmap_prepare(&map, 1, 0, QUICLY_EPOCH_INITIAL); quicly_sentmap_allocate(&map, on_acked); - quicly_sentmap_commit(&map, 10, 0); + quicly_sentmap_commit(&map, 10, 0, 0); quicly_sentmap_prepare(&map, 2, 0, QUICLY_EPOCH_INITIAL); quicly_sentmap_allocate(&map, on_acked); - quicly_sentmap_commit(&map, 20, 0); + quicly_sentmap_commit(&map, 20, 0, 0); ok(map.bytes_in_flight == 30); /* mark pn 1 for PTO */