Skip to content

Commit 7354439

Browse files
authored
Merge pull request #1836 from private-octopus/improve-create-path-api
Use path allowed API for testing multipath
2 parents 5e79f4a + 52221bc commit 7354439

File tree

7 files changed

+316
-106
lines changed

7 files changed

+316
-106
lines changed

picohttp/democlient.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,9 @@ int picoquic_demo_client_callback(picoquic_cnx_t* cnx,
633633

634634
break;
635635
}
636+
case picoquic_callback_next_path_allowed:
637+
/* This event should be handled according to the application's requirements */
638+
break;
636639
default:
637640
/* unexpected */
638641
break;

picohttp/democlient.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ typedef struct st_picoquic_demo_client_callback_ctx_t {
8888
int no_print;
8989
int connection_ready;
9090
int connection_closed;
91+
92+
/* Context extension for handling asynchronous creation of paths */
93+
void (*handle_path_allowed)(picoquic_cnx_t* cnx, void* ctx);
94+
void* path_allowed_context;
9195
} picoquic_demo_callback_ctx_t;
9296

9397
picoquic_alpn_enum picoquic_parse_alpn(char const * alpn);

picoquic/frames.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -498,10 +498,13 @@ const uint8_t* picoquic_decode_new_connection_id_frame(picoquic_cnx_t* cnx, cons
498498
else {
499499
uint64_t transport_error = picoquic_add_remote_cnxid_to_stash(cnx, remote_cnxid_stash, retire_before,
500500
sequence, cid_length, cnxid_bytes, secret_bytes, NULL);
501-
if (transport_error == 0 && remote_cnxid_stash->retire_cnxid_before < retire_before) {
502-
/* retire the now deprecated CIDs */
503-
remote_cnxid_stash->retire_cnxid_before = retire_before;
504-
transport_error = picoquic_remove_not_before_cid(cnx, unique_path_id, retire_before, current_time);
501+
if (transport_error == 0) {
502+
picoquic_test_and_signal_new_path_allowed(cnx);
503+
if (remote_cnxid_stash->retire_cnxid_before < retire_before) {
504+
/* retire the now deprecated CIDs */
505+
remote_cnxid_stash->retire_cnxid_before = retire_before;
506+
transport_error = picoquic_remove_not_before_cid(cnx, unique_path_id, retire_before, current_time);
507+
}
505508
}
506509
if (transport_error != 0) {
507510
picoquic_connection_error(cnx, transport_error,

picoquic/picoquic.h

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ extern "C" {
104104
#define PICOQUIC_ERROR_PATH_ID_INVALID (PICOQUIC_ERROR_CLASS + 60)
105105
#define PICOQUIC_ERROR_RETRY_NEEDED (PICOQUIC_ERROR_CLASS + 61)
106106
#define PICOQUIC_ERROR_SERVER_BUSY (PICOQUIC_ERROR_CLASS + 62)
107+
#define PICOQUIC_ERROR_PATH_DUPLICATE (PICOQUIC_ERROR_CLASS + 63)
108+
#define PICOQUIC_ERROR_PATH_ID_BLOCKED (PICOQUIC_ERROR_CLASS + 64)
109+
#define PICOQUIC_ERROR_PATH_CID_BLOCKED (PICOQUIC_ERROR_CLASS + 65)
110+
#define PICOQUIC_ERROR_PATH_ADDRESS_FAMILY (PICOQUIC_ERROR_CLASS + 66)
111+
#define PICOQUIC_ERROR_PATH_NOT_READY (PICOQUIC_ERROR_CLASS + 67)
112+
#define PICOQUIC_ERROR_PATH_LIMIT_EXCEEDED (PICOQUIC_ERROR_CLASS + 68)
107113

108114
/*
109115
* Protocol errors defined in the QUIC spec
@@ -274,7 +280,8 @@ typedef enum {
274280
picoquic_callback_path_deleted, /* An existing path has been deleted */
275281
picoquic_callback_path_quality_changed, /* Some path quality parameters have changed */
276282
picoquic_callback_path_address_observed, /* The peer has reported an address for the path */
277-
picoquic_callback_app_wakeup /* wakeup timer set by application has expired */
283+
picoquic_callback_app_wakeup, /* wakeup timer set by application has expired */
284+
picoquic_callback_next_path_allowed /* There are enough path_id and connection ID available for the next path */
278285
} picoquic_call_back_event_t;
279286

280287
typedef struct st_picoquic_tp_prefered_address_t {
@@ -858,6 +865,47 @@ void picoquic_set_rejected_version(picoquic_cnx_t* cnx, uint32_t rejected_versio
858865
* Like all user-level networking API, the "probe new path" API assumes that the
859866
* port numbers in the socket addresses structures are expressed in network order.
860867
*
868+
* If an error occurs during a call to picoquic_probe_new_path_ex,
869+
* the function returns an error code describing the issue:
870+
*
871+
* PICOQUIC_ERROR_PATH_DUPLICATE: there is already an existing path with
872+
* the same 4 tuple. This error only happens if the multipath extensions
873+
* are not negotiated, because the multipath extensions allow creation of
874+
* multiple paths with the same 4 tuple.
875+
*
876+
* PICOQUIC_ERROR_PATH_ID_BLOCKED: when using the multipath extension,
877+
* the peers manage a max_path_id value. The code cannot create a new path
878+
* if the path_id would exceed the limit negotiated with the peer. Applications
879+
* encountering that error code should wait until the peer has increased the limit.
880+
* They may want to signal the issue to the peer by queuing a PATHS_BLOCKED frame.
881+
*
882+
* PICOQUIC_ERROR_PATH_CID_BLOCKED: when using the multipath extension,
883+
* the peers use NEW_PATH_CONNECTION_ID frame to provide CIDs associated with each
884+
* valid path_id. The error occurs when the peer has not yet provided CIDs for the
885+
* next path_id. Applications encountering the error should wait until the peer
886+
* provides CID for the path. They may want to signal the issue to the peer by
887+
* queuing a PATH_CIDS_BLOCKED frame.
888+
*
889+
* PICOQUIC_ERROR_PATH_ADDRESS_FAMILY: API error. The application is trying
890+
* to use a four tuple with different address family for source and destination.
891+
*
892+
* PICOQUIC_ERROR_PATH_NOT_READY: API error. The application is trying to create
893+
* paths before the connection handshake is complete. The application should wait
894+
* until it is notified that the connection is ready.
895+
*
896+
* PICOQUIC_ERROR_PATH_LIMIT_EXCEEDED: The application is trying to create more
897+
* simultaneous paths than allowed. It will need to close one of the existing paths
898+
* before creating a new one.
899+
*
900+
* The errors PICOQUIC_ERROR_PATH_ID_BLOCKED, PICOQUIC_ERROR_PATH_CID_BLOCKED.
901+
* PICOQUIC_ERROR_PATH_NOT_READY and PICOQUIC_ERROR_PATH_LIMIT_EXCEEDED are transient.
902+
* The application can use the `picoquic_check_new_path_allowed` API to check whether
903+
* a new path may be created. This function will return 1 if the path creation
904+
* can be attempted immediately, 0 otherwise. If the return code is 0, the stack
905+
* will issue a callback `picoquic_callback_next_path_ready` when the transient
906+
* issues are resolved and `picoquic_probe_new_path_ex` could be called
907+
* again.
908+
*
861909
* Path event callbacks can be enabled by calling "picoquic_enable_path_callbacks".
862910
* This can be set as the default for new connections by calling
863911
* "picoquic_enable_path_callbacks_default". If enabled, the folling events
@@ -893,10 +941,9 @@ void picoquic_set_rejected_version(picoquic_cnx_t* cnx, uint32_t rejected_versio
893941
* maintain path data in an app specific context, it will use a call to
894942
* "picoquic_set_app_path_ctx" to document it. The path created during
895943
* the connection setup has the unique_path_id 0.
896-
897-
*
944+
*
898945
* If an error occurs, such as reference to an obsolete unique path id,
899-
* all the functions return -1.
946+
* all the path management functions return -1.
900947
*
901948
* The call to "refresh the connection ID" will trigger a renewal of the connection
902949
* ID used for sending packets on that path. This API is mostly used in test
@@ -918,6 +965,8 @@ int picoquic_abandon_path(picoquic_cnx_t* cnx, uint64_t unique_path_id,
918965
int picoquic_refresh_path_connection_id(picoquic_cnx_t* cnx, uint64_t unique_path_id);
919966
int picoquic_set_stream_path_affinity(picoquic_cnx_t* cnx, uint64_t stream_id, uint64_t unique_path_id);
920967
int picoquic_set_path_status(picoquic_cnx_t* cnx, uint64_t unique_path_id, picoquic_path_status_enum status);
968+
int picoquic_subscribe_new_path_allowed(picoquic_cnx_t* cnx, int* is_already_allowed);
969+
921970
/* The get path addr API provides the IP addresses used by a specific path.
922971
* The "local" argument determines whether the APi returns the local address
923972
* (local == 1), the address of the peer (local == 2) or the address observed by the peer (local == 3).

picoquic/picoquic_internal.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,8 @@ typedef struct st_picoquic_cnx_t {
13131313
unsigned int is_forced_probe_up_required : 1; /* application wants "probe up" if CC requests it */
13141314
unsigned int is_address_discovery_provider : 1; /* send the address discovery extension */
13151315
unsigned int is_address_discovery_receiver : 1; /* receive the address discovery extension */
1316+
unsigned int is_subscribed_to_path_allowed : 1; /* application wants to be advised if it is now possible to create a path */
1317+
unsigned int is_notified_that_path_is_allowed : 1; /* application wants to be advised if it is now possible to create a path */
13161318

13171319
/* PMTUD policy */
13181320
picoquic_pmtud_policy_enum pmtud_policy;
@@ -2043,6 +2045,8 @@ const uint8_t* picoquic_skip_path_abandon_frame(const uint8_t* bytes, const uint
20432045
const uint8_t* picoquic_skip_path_available_or_standby_frame(const uint8_t* bytes, const uint8_t* bytes_max);
20442046
int picoquic_queue_path_available_or_standby_frame(
20452047
picoquic_cnx_t* cnx, picoquic_path_t* path_x, picoquic_path_status_enum status);
2048+
/* Internal only API, notify that next path is now allowed. */
2049+
void picoquic_test_and_signal_new_path_allowed(picoquic_cnx_t* cnx);
20462050

20472051
int picoquic_decode_closing_frames(picoquic_cnx_t* cnx, uint8_t* bytes, size_t bytes_max, int* closing_received);
20482052

picoquic/quicctx.c

Lines changed: 152 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1793,10 +1793,14 @@ void picoquic_delete_abandoned_paths(picoquic_cnx_t* cnx, uint64_t current_time,
17931793
}
17941794
}
17951795

1796-
while (cnx->nb_paths > path_index_good) {
1797-
int d_path = cnx->nb_paths - 1;
1798-
picoquic_dereference_stashed_cnxid(cnx, cnx->path[d_path], 0);
1799-
picoquic_delete_path(cnx, d_path);
1796+
if (cnx->nb_paths > path_index_good) {
1797+
do {
1798+
int d_path = cnx->nb_paths - 1;
1799+
picoquic_dereference_stashed_cnxid(cnx, cnx->path[d_path], 0);
1800+
picoquic_delete_path(cnx, d_path);
1801+
} while (cnx->nb_paths > path_index_good);
1802+
/* If paths have been deleted, it may become possible to create new ones. */
1803+
picoquic_test_and_signal_new_path_allowed(cnx);
18001804
}
18011805

18021806
/* TODO: what if there are no paths left? */
@@ -2094,52 +2098,164 @@ int picoquic_assign_peer_cnxid_to_path(picoquic_cnx_t* cnx, int path_index)
20942098
return ret;
20952099
}
20962100

2097-
/* Create a new path in order to trigger a migration */
2098-
int picoquic_probe_new_path_ex(picoquic_cnx_t* cnx, const struct sockaddr* addr_peer,
2099-
const struct sockaddr* addr_local, int if_index, uint64_t current_time, int to_preferred_address)
2101+
/* Check whether the connection state, number of paths, path ID and
2102+
* available CID will allow creation of a new path
2103+
*/
2104+
int picoquic_check_new_path_allowed(picoquic_cnx_t* cnx, int to_preferred_address)
21002105
{
21012106
int ret = 0;
2102-
int partial_match_path = -1;
2103-
int path_id = -1;
21042107

2105-
if ((cnx->remote_parameters.migration_disabled && !to_preferred_address ) ||
2108+
if ((cnx->remote_parameters.migration_disabled && !to_preferred_address) ||
21062109
cnx->local_parameters.migration_disabled) {
21072110
/* Do not create new paths if migration is disabled */
2108-
ret = PICOQUIC_ERROR_MIGRATION_DISABLED;
21092111
DBG_PRINTF("Tried to create probe with migration disabled = %d", cnx->remote_parameters.migration_disabled);
2112+
ret = PICOQUIC_ERROR_MIGRATION_DISABLED;
21102113
}
2111-
else if ((path_id = picoquic_find_path_by_address(cnx, addr_local, addr_peer, &partial_match_path)) >= 0) {
2112-
/* This path already exists. Will not create it, but will restore it in working order if disabled. */
2113-
ret = -1;
2114-
}
2115-
else if (partial_match_path >= 0 && addr_peer->sa_family == 0) {
2116-
/* This path already exists. Will not create it, but will restore it in working order if disabled. */
2117-
ret = -1;
2118-
}
2119-
else if (cnx->first_remote_cnxid_stash->cnxid_stash_first == NULL) {
2120-
/* No CNXID available yet. */
2121-
ret = -1;
2114+
else if (cnx->cnx_state < picoquic_state_client_almost_ready) {
2115+
ret = PICOQUIC_ERROR_PATH_NOT_READY;
21222116
}
21232117
else if (cnx->nb_paths >= PICOQUIC_NB_PATH_TARGET) {
21242118
/* Too many paths created already */
2125-
ret = -1;
2119+
ret = PICOQUIC_ERROR_PATH_LIMIT_EXCEEDED;
21262120
}
2127-
else if (picoquic_create_path(cnx, current_time, addr_local, addr_peer, UINT64_MAX) > 0) {
2128-
path_id = cnx->nb_paths - 1;
2129-
ret = picoquic_assign_peer_cnxid_to_path(cnx, path_id);
2121+
else {
2122+
/* testing availability of connection ID is sufficient.
2123+
* If multipath is enabled, connection IDs will
2124+
* only be received if both peers have negotiated a sufficient path ID.
2125+
* In any case, connection IDs can only be received if the connection
2126+
* is almost ready.
2127+
*/
2128+
uint64_t unique_path_id = 0;
2129+
if (cnx->is_multipath_enabled) {
2130+
unique_path_id = cnx->unique_path_id_next;
2131+
}
2132+
if (picoquic_obtain_stashed_cnxid(cnx, unique_path_id) == NULL) {
2133+
if (cnx->unique_path_id_next > cnx->max_path_id_remote) {
2134+
ret = PICOQUIC_ERROR_PATH_ID_BLOCKED;
2135+
}
2136+
else
2137+
{
2138+
ret = PICOQUIC_ERROR_PATH_CID_BLOCKED;
2139+
}
2140+
}
2141+
}
2142+
return ret;
2143+
}
21302144

2131-
if (ret != 0) {
2132-
/* delete the path that was just created! */
2133-
picoquic_dereference_stashed_cnxid(cnx, cnx->path[path_id], 0);
2134-
picoquic_delete_path(cnx, path_id);
2145+
int picoquic_subscribe_new_path_allowed(picoquic_cnx_t* cnx, int * is_already_allowed)
2146+
{
2147+
int ret = picoquic_check_new_path_allowed(cnx, 0);
2148+
2149+
*is_already_allowed = 0;
2150+
if (ret == 0) {
2151+
/* is allowed. Just say so -- get return code. */
2152+
*is_already_allowed = 1;
2153+
cnx->is_subscribed_to_path_allowed = 0;
2154+
cnx->is_notified_that_path_is_allowed = 0;
2155+
}
2156+
else if (ret == PICOQUIC_ERROR_PATH_NOT_READY ||
2157+
ret == PICOQUIC_ERROR_PATH_LIMIT_EXCEEDED ||
2158+
ret == PICOQUIC_ERROR_PATH_ID_BLOCKED ||
2159+
ret == PICOQUIC_ERROR_PATH_CID_BLOCKED) {
2160+
/* transient error. Subscribe to the event and return 0 */
2161+
cnx->is_subscribed_to_path_allowed = 1;
2162+
cnx->is_notified_that_path_is_allowed = 0;
2163+
ret = 0;
2164+
}
2165+
return ret;
2166+
}
2167+
2168+
/* Internal only API, notify that next path is now allowed. */
2169+
void picoquic_test_and_signal_new_path_allowed(picoquic_cnx_t* cnx)
2170+
{
2171+
if (cnx->is_subscribed_to_path_allowed &&
2172+
!cnx->is_notified_that_path_is_allowed)
2173+
{
2174+
if (picoquic_check_new_path_allowed(cnx, 0) == 0) {
2175+
cnx->is_notified_that_path_is_allowed = 1;
2176+
if (cnx->callback_fn != NULL) {
2177+
(void)cnx->callback_fn(cnx, 0, NULL, 0, picoquic_callback_next_path_allowed, cnx->callback_ctx, NULL);
2178+
}
2179+
}
2180+
}
2181+
}
2182+
2183+
/* Create a new path in order to trigger a migration, or just a parallel
2184+
* path if multipath is enabled.
2185+
*/
2186+
int picoquic_probe_new_path_ex(picoquic_cnx_t* cnx, const struct sockaddr* addr_peer,
2187+
const struct sockaddr* addr_local, int if_index, uint64_t current_time, int to_preferred_address)
2188+
{
2189+
int partial_match_path = -1;
2190+
int path_id = -1;
2191+
2192+
int ret = picoquic_check_new_path_allowed(cnx, to_preferred_address);
2193+
2194+
2195+
if (ret == 0) {
2196+
/* verify that the peer and local addresses are correctly set */
2197+
if (addr_peer == NULL || addr_peer->sa_family == 0) {
2198+
if (addr_local == NULL || addr_local->sa_family == 0) {
2199+
ret = PICOQUIC_ERROR_UNEXPECTED_ERROR;
2200+
}
2201+
else {
2202+
/* Find the peer address from existing paths */
2203+
for (int i = 0; i < cnx->nb_paths; i++) {
2204+
if (cnx->path[i]->peer_addr.ss_family == addr_local->sa_family) {
2205+
addr_peer = (struct sockaddr*)&cnx->path[i]->peer_addr;
2206+
break;
2207+
}
2208+
}
2209+
if (addr_peer == NULL || addr_peer->sa_family == 0) {
2210+
ret = PICOQUIC_ERROR_UNEXPECTED_ERROR;
2211+
}
2212+
}
2213+
}
2214+
else if (addr_local == NULL || addr_local->sa_family == 0) {
2215+
/* Find the local address from existing paths */
2216+
for (int i = 0; i < cnx->nb_paths; i++) {
2217+
if (cnx->path[i]->local_addr.ss_family == addr_peer->sa_family) {
2218+
addr_local = (struct sockaddr*)&cnx->path[i]->local_addr;
2219+
break;
2220+
}
2221+
}
2222+
if (addr_peer == NULL) {
2223+
ret = PICOQUIC_ERROR_UNEXPECTED_ERROR;
2224+
}
2225+
}
2226+
else if (addr_peer->sa_family != addr_local->sa_family) {
2227+
ret = PICOQUIC_ERROR_PATH_ADDRESS_FAMILY;
2228+
}
2229+
}
2230+
2231+
if (ret == 0 && !cnx->is_multipath_enabled) {
2232+
if ((path_id = picoquic_find_path_by_address(cnx, addr_local, addr_peer, &partial_match_path)) >= 0) {
2233+
/* This path already exists. Will not create it, but will restore it in working order if disabled. */
2234+
ret = PICOQUIC_ERROR_PATH_DUPLICATE;
2235+
}
2236+
}
2237+
2238+
if (ret == 0){
2239+
if (picoquic_create_path(cnx, current_time, addr_local, addr_peer, UINT64_MAX) > 0) {
2240+
path_id = cnx->nb_paths - 1;
2241+
ret = picoquic_assign_peer_cnxid_to_path(cnx, path_id);
2242+
2243+
if (ret != 0) {
2244+
/* delete the path that was just created! */
2245+
picoquic_dereference_stashed_cnxid(cnx, cnx->path[path_id], 0);
2246+
picoquic_delete_path(cnx, path_id);
2247+
}
2248+
else {
2249+
cnx->path[path_id]->path_is_published = 1;
2250+
picoquic_register_path(cnx, cnx->path[path_id]);
2251+
picoquic_set_path_challenge(cnx, path_id, current_time);
2252+
cnx->path[path_id]->path_is_preferred_path = to_preferred_address;
2253+
cnx->path[path_id]->is_nat_challenge = 0;
2254+
cnx->path[path_id]->if_index_dest = if_index;
2255+
}
21352256
}
21362257
else {
2137-
cnx->path[path_id]->path_is_published = 1;
2138-
picoquic_register_path(cnx, cnx->path[path_id]);
2139-
picoquic_set_path_challenge(cnx, path_id, current_time);
2140-
cnx->path[path_id]->path_is_preferred_path = to_preferred_address;
2141-
cnx->path[path_id]->is_nat_challenge = 0;
2142-
cnx->path[path_id]->if_index_dest = if_index;
2258+
ret = PICOQUIC_ERROR_MEMORY;
21432259
}
21442260
}
21452261

0 commit comments

Comments
 (0)