conncheck: update the unfreeze method for RFC8445
authorFabrice Bellet <fabrice@bellet.info>
Mon, 13 Apr 2020 16:03:36 +0000 (18:03 +0200)
committerOlivier CrĂȘte <olivier.crete@ocrete.ca>
Thu, 7 May 2020 23:42:48 +0000 (23:42 +0000)
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.

agent/agent.c
agent/conncheck.c
agent/conncheck.h

index 6cccd79..5d3fbb4 100644 (file)
@@ -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];
index df7c67a..c9b79fe 100644 (file)
@@ -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
index 6eb71a4..7b11743 100644 (file)
@@ -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 */