diff options
author | Fabrice Bellet <fabrice@bellet.info> | 2020-04-13 18:03:36 +0200 |
---|---|---|
committer | Olivier CrĂȘte <olivier.crete@ocrete.ca> | 2020-05-07 23:42:48 +0000 |
commit | e9cbb3dacb382c4ef14e7b2288b05f57ca26faad (patch) | |
tree | 017d97419e6f4eca34f593eb72aab7eab5356649 | |
parent | 0f6eadc93d4e090c776104bb3627edd5d4d1b5d7 (diff) | |
download | libnice-e9cbb3dacb382c4ef14e7b2288b05f57ca26faad.tar.gz |
conncheck: update the unfreeze method for RFC8445
The way pairs are unfrozen between RFC5245 and RFC8445 changed a bit,
and made the code much more simple. Previously pairs were unfrozen "per
stream", not they are unfrozen "per foundation". The principle of the
priv_conn_check_unfreeze_next function is now to unfreeze one and only
one frozen pair per foundation, all components and streams included.
The function is now idemporent: calling it when the connchecks still
contains waiting pairs does nothing.
-rw-r--r-- | agent/agent.c | 2 | ||||
-rw-r--r-- | agent/conncheck.c | 432 | ||||
-rw-r--r-- | agent/conncheck.h | 1 |
3 files changed, 130 insertions, 305 deletions
diff --git a/agent/agent.c b/agent/agent.c index 6cccd79..5d3fbb4 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -3532,6 +3532,8 @@ static void priv_update_pair_foundations (NiceAgent *agent, NICE_CANDIDATE_PAIR_MAX_FOUNDATION); nice_debug ("Agent %p : Updating pair %p foundation to '%s'", agent, pair, pair->foundation); + if (pair->state == NICE_CHECK_SUCCEEDED) + conn_check_unfreeze_related (agent, pair); if (component->selected_pair.local == pair->local && component->selected_pair.remote == pair->remote) { gchar priority[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE]; diff --git a/agent/conncheck.c b/agent/conncheck.c index df7c67a..c9b79fe 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -364,89 +364,6 @@ priv_get_pair_from_triggered_check_queue (NiceAgent *agent) } /* - * Check if the conncheck list if Active according to - * ICE spec, 5.7.4 (Computing States) - * - * note: the ICE spec in unclear about that, but the check list should - * be considered active when there is at least a pair in Waiting state - * OR a pair in In-Progress state. - */ -static gboolean -priv_is_checklist_active (NiceStream *stream) -{ - GSList *i; - - for (i = stream->conncheck_list; i ; i = i->next) { - CandidateCheckPair *p = i->data; - if (p->state == NICE_CHECK_WAITING || p->state == NICE_CHECK_IN_PROGRESS) - return TRUE; - } - return FALSE; -} - -/* - * Check if the conncheck list if Frozen according to - * ICE spec, 5.7.4 (Computing States) - */ -static gboolean -priv_is_checklist_frozen (NiceStream *stream) -{ - GSList *i; - - if (stream->conncheck_list == NULL) - return FALSE; - - for (i = stream->conncheck_list; i ; i = i->next) { - CandidateCheckPair *p = i->data; - if (p->state != NICE_CHECK_FROZEN) - return FALSE; - } - return TRUE; -} - -/* - * Check if all components of the stream have - * a valid pair (used for ICE spec, 7.1.3.2.3, point 2.) - */ -static gboolean -priv_all_components_have_valid_pair (NiceStream *stream) -{ - guint i; - GSList *j; - - for (i = 1; i <= stream->n_components; i++) { - for (j = stream->conncheck_list; j ; j = j->next) { - CandidateCheckPair *p = j->data; - if (p->component_id == i && p->valid) - break; - } - if (j == NULL) - return FALSE; - } - return TRUE; -} - -/* - * Check if the foundation in parameter matches the foundation - * of a valid pair in the conncheck list [of stream] (used for ICE spec, - * 7.1.3.2.3, point 2.) - */ -static gboolean -priv_foundation_matches_a_valid_pair (const gchar *foundation, NiceStream *stream) -{ - GSList *i; - - for (i = stream->conncheck_list; i ; i = i->next) { - CandidateCheckPair *p = i->data; - if (p->valid && - strncmp (p->foundation, foundation, - NICE_CANDIDATE_PAIR_MAX_FOUNDATION) == 0) - return TRUE; - } - return FALSE; -} - -/* * Finds the next connectivity check in WAITING state. */ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check_list) @@ -465,57 +382,6 @@ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check } /* - * Finds the next connectivity check in FROZEN state. - */ -static CandidateCheckPair * -priv_conn_check_find_next_frozen (GSList *conn_check_list) -{ - GSList *i; - - /* note: list is sorted in priority order to first frozen check has - * the highest priority */ - for (i = conn_check_list; i ; i = i->next) { - CandidateCheckPair *p = i->data; - if (p->state == NICE_CHECK_FROZEN) - return p; - } - - return NULL; -} - -/* - * Returns the number of active check lists of the agent - */ -static guint -priv_number_of_active_check_lists (NiceAgent *agent) -{ - guint n = 0; - GSList *i; - - for (i = agent->streams; i ; i = i->next) - if (priv_is_checklist_active (i->data)) - n++; - return n; -} - -/* - * Returns the first stream of the agent having a Frozen - * connection check list - */ -static NiceStream * -priv_find_first_frozen_check_list (NiceAgent *agent) -{ - GSList *i; - - for (i = agent->streams; i ; i = i->next) { - NiceStream *stream = i->data; - if (priv_is_checklist_frozen (stream)) - return stream; - } - return NULL; -} - -/* * Initiates a new connectivity check for a ICE candidate pair. * * @return TRUE on success, FALSE on error @@ -532,140 +398,142 @@ static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair * /* * Unfreezes the next connectivity check in the list. Follows the - * algorithm (2.) defined in 5.7.4 (Computing States) of the ICE spec - * (RFC5245) + * algorithm defined in sect 6.1.2.6 (Computing Candidate Pair States) + * and sect 6.1.4.2 (Performing Connectivity Checks) of the ICE spec + * (RFC8445) * - * See also sect 7.1.2.2.3 (Updating Pair States), and - * priv_conn_check_unfreeze_related(). + * Note that this algorithm is slightly simplified compared to previous + * version of the spec (RFC5245), and this new version is now + * idempotent. * * @return TRUE on success, and FALSE if no frozen candidates were found. */ -static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent, NiceStream *stream) +static gboolean +priv_conn_check_unfreeze_next (NiceAgent *agent) { GSList *i, *j; - GSList *found_list = NULL; + GSList *foundation_list = NULL; gboolean result = FALSE; - priv_print_conn_check_lists (agent, G_STRFUNC, NULL); + /* While a pair in state waiting exists, we do nothing */ + for (i = agent->streams; i ; i = i->next) { + NiceStream *s = i->data; + for (j = s->conncheck_list; j ; j = j->next) { + CandidateCheckPair *p = j->data; - for (i = stream->conncheck_list; i ; i = i->next) { - CandidateCheckPair *p1 = i->data; - CandidateCheckPair *pair = NULL; - guint lowest_component_id = stream->n_components + 1; - guint64 highest_priority = 0; + if (p->state == NICE_CHECK_WAITING) + return TRUE; + } + } - if (g_slist_find_custom (found_list, p1->foundation, (GCompareFunc)strcmp)) - continue; - found_list = g_slist_prepend (found_list, p1->foundation); + /* When there are no more pairs in waiting state, we unfreeze some + * pairs, so that we get a single waiting pair per foundation. + */ + for (i = agent->streams; i ; i = i->next) { + NiceStream *s = i->data; + for (j = s->conncheck_list; j ; j = j->next) { + CandidateCheckPair *p = j->data; - for (j = stream->conncheck_list; j ; j = j->next) { - CandidateCheckPair *p2 = i->data; - if (strncmp (p2->foundation, p1->foundation, - NICE_CANDIDATE_PAIR_MAX_FOUNDATION) == 0) { - if (p2->component_id < lowest_component_id || - (p2->component_id == lowest_component_id && - p2->priority > highest_priority)) { - pair = p2; - lowest_component_id = p2->component_id; - highest_priority = p2->priority; - } - } - } + if (g_slist_find_custom (foundation_list, p->foundation, + (GCompareFunc)strcmp)) + continue; - if (pair) { - nice_debug ("Agent %p : Pair %p with s/c-id %u/%u (%s) unfrozen.", - agent, pair, pair->stream_id, pair->component_id, pair->foundation); - SET_PAIR_STATE (agent, pair, NICE_CHECK_WAITING); - result = TRUE; + if (p->state == NICE_CHECK_FROZEN) { + nice_debug ("Agent %p : Pair %p with s/c-id %u/%u (%s) unfrozen.", + agent, p, p->stream_id, p->component_id, p->foundation); + SET_PAIR_STATE (agent, p, NICE_CHECK_WAITING); + foundation_list = g_slist_prepend (foundation_list, p->foundation); + result = TRUE; + } } } - g_slist_free (found_list); + g_slist_free (foundation_list); + + /* We dump the conncheck list when something interesting happened, ie + * when we unfroze some pairs. + */ + if (result) + priv_print_conn_check_lists (agent, G_STRFUNC, NULL); + return result; } /* - * Unfreezes the next next connectivity check in the list after + * Unfreezes the related connectivity check in the list after * check 'success_check' has successfully completed. * - * See sect 7.1.2.2.3 (Updating Pair States) of ICE spec (ID-19). + * See sect 7.2.5.3.3 (Updating Candidate Pair States) of ICE spec (RFC8445). + * + * Note that this algorithm is slightly simplified compared to previous + * version of the spec (RFC5245) * * @param agent context - * @param ok_check a connectivity check that has just completed + * @param pair a pair, whose connectivity check has just succeeded * - * @return TRUE on success, and FALSE if no frozen candidates were found. */ -static void priv_conn_check_unfreeze_related (NiceAgent *agent, NiceStream *stream, CandidateCheckPair *ok_check) +void +conn_check_unfreeze_related (NiceAgent *agent, CandidateCheckPair *pair) { GSList *i, *j; - g_assert (ok_check); - g_assert_cmpint (ok_check->state, ==, NICE_CHECK_SUCCEEDED); - g_assert (stream); - g_assert_cmpuint (stream->id, ==, ok_check->stream_id); + g_assert (pair); + g_assert (pair->state == NICE_CHECK_SUCCEEDED); priv_print_conn_check_lists (agent, G_STRFUNC, NULL); - /* step: perform the step (1) of 'Updating Pair States' */ - for (i = stream->conncheck_list; i ; i = i->next) { - CandidateCheckPair *p = i->data; + for (i = agent->streams; i ; i = i->next) { + NiceStream *s = i->data; + for (j = s->conncheck_list; j ; j = j->next) { + CandidateCheckPair *p = j->data; - if (p->stream_id == ok_check->stream_id) { + /* The states for all other Frozen candidates pairs in all + * checklists with the same foundation is set to waiting + */ if (p->state == NICE_CHECK_FROZEN && - strncmp (p->foundation, ok_check->foundation, + strncmp (p->foundation, pair->foundation, NICE_CANDIDATE_PAIR_MAX_FOUNDATION) == 0) { - nice_debug ("Agent %p : Unfreezing check %p (after successful check %p).", agent, p, ok_check); - SET_PAIR_STATE (agent, p, NICE_CHECK_WAITING); + nice_debug ("Agent %p : Unfreezing check %p " + "(after successful check %p).", agent, p, pair); + SET_PAIR_STATE (agent, p, NICE_CHECK_WAITING); } } } +} - /* step: perform the step (2) of 'Updating Pair States' */ - stream = agent_find_stream (agent, ok_check->stream_id); - if (priv_all_components_have_valid_pair (stream)) { - for (i = agent->streams; i ; i = i->next) { - /* the agent examines the check list for each other - * media stream in turn - */ - NiceStream *s = i->data; - if (s->id == ok_check->stream_id) - continue; - if (priv_is_checklist_active (s)) { - /* checklist is Active - */ - for (j = s->conncheck_list; j ; j = j->next) { - CandidateCheckPair *p = j->data; - if (p->state == NICE_CHECK_FROZEN && - priv_foundation_matches_a_valid_pair (p->foundation, stream)) { - nice_debug ("Agent %p : Unfreezing check %p from stream %u (after successful check %p).", agent, p, s->id, ok_check); - SET_PAIR_STATE (agent, p, NICE_CHECK_WAITING); - } - } - } else if (priv_is_checklist_frozen (s)) { - /* checklist is Frozen - */ - gboolean match_found = FALSE; - /* check if there is one pair in the check list whose - * foundation matches a pair in the valid list under - * consideration - */ - for (j = s->conncheck_list; j ; j = j->next) { - CandidateCheckPair *p = j->data; - if (priv_foundation_matches_a_valid_pair (p->foundation, stream)) { - match_found = TRUE; - nice_debug ("Agent %p : Unfreezing check %p from stream %u (after successful check %p).", agent, p, s->id, ok_check); - SET_PAIR_STATE (agent, p, NICE_CHECK_WAITING); - } - } +/* + * Unfreezes this connectivity check if its foundation is the same than + * the foundation of an already succeeded pair. + * + * See sect 7.2.5.3.3 (Updating Candidate Pair States) of ICE spec (RFC8445). + * + * @param agent context + * @param pair a pair, whose state is frozen + * + */ +static void +priv_conn_check_unfreeze_maybe (NiceAgent *agent, CandidateCheckPair *pair) +{ + GSList *i, *j; - if (!match_found) { - /* set the pair with the lowest component ID - * and highest priority to Waiting - */ - priv_conn_check_unfreeze_next (agent, s); - } + g_assert (pair); + g_assert (pair->state == NICE_CHECK_FROZEN); + + priv_print_conn_check_lists (agent, G_STRFUNC, NULL); + + for (i = agent->streams; i ; i = i->next) { + NiceStream *s = i->data; + for (j = s->conncheck_list; j ; j = j->next) { + CandidateCheckPair *p = j->data; + + if (p->state == NICE_CHECK_SUCCEEDED && + strncmp (p->foundation, pair->foundation, + NICE_CANDIDATE_PAIR_MAX_FOUNDATION) == 0) { + nice_debug ("Agent %p : Unfreezing check %p " + "(after successful check %p).", agent, pair, p); + SET_PAIR_STATE (agent, pair, NICE_CHECK_WAITING); } } - } + } } /* @@ -763,7 +631,9 @@ candidate_check_pair_fail (NiceStream *stream, NiceAgent *agent, CandidateCheckP * * @return will return FALSE when no more pending timers. */ -static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent) +static gboolean +priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent, + gboolean *stun_sent) { gboolean keep_timer_going = FALSE; GSList *i, *j; @@ -824,6 +694,9 @@ timer_return_timeout: stun_message_length (&stun->message), (gchar *)stun->buffer); + if (stun_sent) + *stun_sent = TRUE; + /* note: convert from milli to microseconds for g_time_val_add() */ stun->next_tick = now + timeout * 1000; @@ -866,39 +739,28 @@ timer_return_timeout: } } - /* step: perform an ordinary check, ICE spec, 5.8 "Scheduling Checks" + /* step: perform an ordinary check, sec 6.1.4.2 point 3. (Performing + * Connectivity Checks) of ICE spec (RFC8445) * note: This code is executed when the triggered checks list is * empty, and when no STUN message has been sent (pacing constraint) */ pair = priv_conn_check_find_next_waiting (stream->conncheck_list); - if (pair) { - priv_print_conn_check_lists (agent, G_STRFUNC, - ", got a pair in Waiting state"); - priv_conn_check_initiate (agent, pair); - return TRUE; + if (pair == NULL) { + /* step: there is no candidate in waiting state, try to unfreeze + * some pairs and retry, sect 6.1.4.2 point 2. (Performing Connectivity + * Checks) of ICE spec (RFC8445) + */ + priv_conn_check_unfreeze_next (agent); + pair = priv_conn_check_find_next_waiting (stream->conncheck_list); } - /* note: this is unclear in the ICE spec, but a check list in Frozen - * state (where all pairs are in Frozen state) is not supposed to - * change its state by an ordinary check, but only by the success of - * checks in other check lists, in priv_conn_check_unfreeze_related(). - * The underlying idea is to concentrate the checks on a single check - * list initially. - */ - if (priv_is_checklist_frozen (stream)) - return keep_timer_going; - - /* step: ordinary check continued, if there's no pair in the waiting - * state, pick a pair in the frozen state - */ - pair = priv_conn_check_find_next_frozen (stream->conncheck_list); if (pair) { priv_print_conn_check_lists (agent, G_STRFUNC, - ", got a pair in Frozen state"); - SET_PAIR_STATE (agent, pair, NICE_CHECK_WAITING); + ", got a pair in Waiting state"); priv_conn_check_initiate (agent, pair); return TRUE; } + return keep_timer_going; } @@ -1265,27 +1127,9 @@ static gboolean priv_conn_check_tick_agent_locked (NiceAgent *agent, gboolean keep_timer_going = FALSE; GSList *i, *j; - /* configure the initial state of the check lists of the agent - * as described in ICE spec, 5.7.4 - * - * if all pairs in all check lists are in frozen state, then - * we are in the initial state (5.7.4, point 1.) - */ - if (priv_number_of_active_check_lists (agent) == 0) { - /* set some pairs of the first stream in the waiting state - * ICE spec, 5.7.4, point 2. - * - * note: we adapt the ICE spec here, by selecting the first - * frozen check list, which is not necessarily the check - * list of the first stream (the first stream may be completed) - */ - NiceStream *stream = priv_find_first_frozen_check_list (agent); - if (stream) - priv_conn_check_unfreeze_next (agent, stream); - } - /* step: perform a test from the triggered checks list, - * ICE spec, 5.8 "Scheduling Checks" + * sect 6.1.4.2 point 1. (Performing Connectivity Checks) of ICE + * spec (RFC8445) */ pair = priv_get_pair_from_triggered_check_queue (agent); @@ -1304,29 +1148,15 @@ static gboolean priv_conn_check_tick_agent_locked (NiceAgent *agent, */ for (i = agent->streams; i ; i = i->next) { NiceStream *stream = i->data; - if (priv_conn_check_tick_stream (stream, agent)) + gboolean stun_sent = FALSE; + + if (priv_conn_check_tick_stream (stream, agent, &stun_sent)) keep_timer_going = TRUE; if (priv_conn_check_tick_stream_nominate (stream, agent)) keep_timer_going = TRUE; - } - - /* step: if no work left and a conncheck list of a stream is still - * frozen, set the pairs to waiting, according to ICE SPEC, sect - * 7.1.3.3. "Check List and Timer State Updates" - */ - if (!keep_timer_going) { - for (i = agent->streams; i ; i = i->next) { - NiceStream *stream = i->data; - if (priv_is_checklist_frozen (stream)) { - nice_debug ("Agent %p : stream %d conncheck list is still " - "frozen, while other lists are completed. Unfreeze it.", - agent, stream->id); - keep_timer_going = priv_conn_check_unfreeze_next (agent, stream); - } - if (!keep_timer_going && !stream->peer_gathering_done) { - keep_timer_going = TRUE; - } - } + /* A single stun request per timer callback, to respect stun pacing */ + if (stun_sent) + break; } /* note: we provide a grace period before declaring a component as @@ -2493,17 +2323,8 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, priv_candidate_transport_to_string (pair->remote->transport), stream_id, component->id); - /* If this is the first pair added into the check list and the first stream's - * components already have valid pairs, unfreeze the pair as it would happen - * in priv_conn_check_unfreeze_related() were the list not empty. */ - if (stream != agent->streams->data && - g_slist_length (stream->conncheck_list) == 1 && - priv_all_components_have_valid_pair (agent->streams->data)) { - nice_debug ("Agent %p : %p is the first pair in this stream's check list " - "and the first stream already has valid pairs. Unfreezing immediately.", - agent, pair); - priv_conn_check_unfreeze_next (agent, stream); - } + if (initial_state == NICE_CHECK_FROZEN) + priv_conn_check_unfreeze_maybe (agent, pair); /* implement the hard upper limit for number of checks (see sect 5.7.3 ICE ID-19): */ @@ -3713,10 +3534,11 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre local_candidate, remote_candidate); /* note: The success of this check might also - * cause the state of other checks to change as well, ICE - * spec 7.1.3.2.3 + * cause the state of other checks to change as well + * See sect 7.2.5.3.3 (Updating Candidate Pair States) of + * ICE spec (RFC8445). */ - priv_conn_check_unfreeze_related (agent, stream, p); + conn_check_unfreeze_related (agent, p); /* Note: this assignment helps to reduce the numbers of cases * to be tested. If ok_pair and p refer to distinct pairs, it diff --git a/agent/conncheck.h b/agent/conncheck.h index 6eb71a4..7b11743 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -123,6 +123,7 @@ void conn_check_update_selected_pair (NiceAgent *agent, NiceComponent *component, CandidateCheckPair *pair); void conn_check_update_check_list_state_for_ready (NiceAgent *agent, NiceStream *stream, NiceComponent *component); +void conn_check_unfreeze_related (NiceAgent *agent, CandidateCheckPair *pair); #endif /*_NICE_CONNCHECK_H */ |