#include "stun/usages/turn.h"
static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStream *stream);
-static void priv_update_check_list_state_for_ready (NiceAgent *agent, NiceStream *stream, NiceComponent *component);
-static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, guint component_id);
+static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, NiceComponent *component);
static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *local_socket, NiceCandidate *remote_cand);
static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceCandidate *localcand, NiceCandidate *remotecand);
static size_t priv_create_username (NiceAgent *agent, NiceStream *stream,
static CandidateCheckPair *priv_conn_check_add_for_candidate_pair_matched (
NiceAgent *agent, guint stream_id, NiceComponent *component,
NiceCandidate *local, NiceCandidate *remote, NiceCheckState initial_state);
-static gboolean priv_update_selected_pair (NiceAgent *agent,
- NiceComponent *component, CandidateCheckPair *pair);
-
-static int priv_timer_expired (GTimeVal *timer, GTimeVal *now)
-{
- return (now->tv_sec == timer->tv_sec) ?
- now->tv_usec >= timer->tv_usec :
- now->tv_sec >= timer->tv_sec;
-}
+static gboolean priv_conn_keepalive_tick_agent_locked (NiceAgent *agent,
+ gpointer pointer);
-static unsigned int priv_timer_remainder (GTimeVal *timer, GTimeVal *now)
+static gint64 priv_timer_remainder (gint64 timer, gint64 now)
{
- unsigned int delay;
- if (now->tv_sec > timer->tv_sec ||
- (now->tv_sec == timer->tv_sec && now->tv_usec > timer->tv_usec))
+ if (now >= timer)
return 0;
- delay = (timer->tv_sec - now->tv_sec) * 1000;
- delay += ((signed long)(timer->tv_usec - now->tv_usec)) / 1000;
- return delay;
+
+ return (timer - now) / 1000;
}
static gchar
}
}
+static const gchar *
+priv_candidate_transport_to_string (NiceCandidateTransport transport)
+{
+ switch (transport) {
+ case NICE_CANDIDATE_TRANSPORT_UDP:
+ return "udp";
+ case NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE:
+ return "tcp-act";
+ case NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE:
+ return "tcp-pass";
+ case NICE_CANDIDATE_TRANSPORT_TCP_SO:
+ return "tcp-so";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static const gchar *
+priv_socket_type_to_string (NiceSocketType type)
+{
+ switch (type) {
+ case NICE_SOCKET_TYPE_UDP_BSD:
+ return "udp";
+ case NICE_SOCKET_TYPE_TCP_BSD:
+ return "tcp";
+ case NICE_SOCKET_TYPE_PSEUDOSSL:
+ return "ssl";
+ case NICE_SOCKET_TYPE_HTTP:
+ return "http";
+ case NICE_SOCKET_TYPE_SOCKS5:
+ return "socks";
+ case NICE_SOCKET_TYPE_UDP_TURN:
+ return "udp-turn";
+ case NICE_SOCKET_TYPE_UDP_TURN_OVER_TCP:
+ return "tcp-turn";
+ case NICE_SOCKET_TYPE_TCP_ACTIVE:
+ return "tcp-act";
+ case NICE_SOCKET_TYPE_TCP_PASSIVE:
+ return "tcp-pass";
+ case NICE_SOCKET_TYPE_TCP_SO:
+ return "tcp-so";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/*
+ * Dump the component list of incoming checks
+ */
+static void
+print_component_incoming_checks (NiceAgent *agent, NiceStream *stream,
+ NiceComponent *component)
+{
+ GList *i;
+
+ for (i = component->incoming_checks.head; i; i = i->next) {
+ IncomingCheck *icheck = i->data;
+ gchar tmpbuf1[INET6_ADDRSTRLEN] = {0};
+ gchar tmpbuf2[INET6_ADDRSTRLEN] = {0};
+
+ nice_address_to_string (&icheck->local_socket->addr, tmpbuf1);
+ nice_address_to_string (&icheck->from, tmpbuf2);
+ nice_debug ("Agent %p : *** sc=%d/%d : icheck %p : "
+ "sock %s [%s]:%u > [%s]:%u",
+ agent, stream->id, component->id, icheck,
+ priv_socket_type_to_string (icheck->local_socket->type),
+ tmpbuf1, nice_address_get_port (&icheck->local_socket->addr),
+ tmpbuf2, nice_address_get_port (&icheck->from));
+ }
+}
+
/*
* Dump the conncheck lists of the agent
*/
{
GSList *i, *k, *l;
guint j, m;
- GTimeVal now;
+ gint64 now;
if (!nice_debug_is_verbose ())
return;
- g_get_current_time (&now);
+ now = g_get_monotonic_time ();
#define PRIORITY_LEN 32
nice_debug ("Agent %p : *** conncheck list DUMP (called from %s%s)",
agent, where, detail ? detail : "");
+ nice_debug ("Agent %p : *** agent nomination mode %s, %s",
+ agent, agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE ?
+ "aggressive" : "regular",
+ agent->controlling_mode ? "controlling" : "controlled");
for (i = agent->streams; i ; i = i->next) {
NiceStream *stream = i->data;
for (j = 1; j <= stream->n_components; j++) {
+ NiceComponent *component;
for (k = stream->conncheck_list; k ; k = k->next) {
CandidateCheckPair *pair = k->data;
if (pair->component_id == j) {
gchar local_addr[INET6_ADDRSTRLEN];
gchar remote_addr[INET6_ADDRSTRLEN];
+ gchar priority[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE];
nice_address_to_string (&pair->local->addr, local_addr);
nice_address_to_string (&pair->remote->addr, remote_addr);
+ nice_candidate_pair_priority_to_string (pair->priority, priority);
nice_debug ("Agent %p : *** sc=%d/%d : pair %p : "
- "f=%s t=%s:%s [%s]:%u > [%s]:%u state=%c%s%s%s",
+ "f=%s t=%s:%s sock=%s "
+ "%s:[%s]:%u > %s:[%s]:%u prio=%s/%08x state=%c%s%s%s%s",
agent, pair->stream_id, pair->component_id, pair,
pair->foundation,
priv_candidate_type_to_string (pair->local->type),
priv_candidate_type_to_string (pair->remote->type),
+ priv_socket_type_to_string (pair->sockptr->type),
+ priv_candidate_transport_to_string (pair->local->transport),
local_addr, nice_address_get_port (&pair->local->addr),
+ priv_candidate_transport_to_string (pair->remote->transport),
remote_addr, nice_address_get_port (&pair->remote->addr),
+ priority, pair->stun_priority,
priv_state_to_gchar (pair->state),
pair->valid ? "V" : "",
pair->nominated ? "N" : "",
+ pair->use_candidate_on_next_check ? "C" : "",
g_slist_find (agent->triggered_check_queue, pair) ? "T" : "");
for (l = pair->stun_transactions, m = 0; l; l = l->next, m++) {
StunTransaction *stun = l->data;
- nice_debug ("Agent %p : *** sc=%d/%d : pair %p : "
- "stun#=%d timer=%d/%d %d/%dms buf=%p %s",
+ nice_debug ("Agent %p : *** sc=%d/%d : pair %p : "
+ "stun#=%d timer=%d/%d %" G_GINT64_FORMAT "/%dms buf=%p %s",
agent, pair->stream_id, pair->component_id, pair, m,
stun->timer.retransmissions, stun->timer.max_retransmissions,
- stun->timer.delay - priv_timer_remainder (&stun->next_tick, &now),
+ stun->timer.delay - priv_timer_remainder (stun->next_tick, now),
stun->timer.delay,
stun->message.buffer,
(m == 0 && pair->retransmit) ? "(R)" : "");
}
}
}
+ if (agent_find_component (agent, stream->id, j, NULL, &component))
+ print_component_incoming_checks (agent, stream, component);
}
}
}
{
g_assert (pair);
- SET_PAIR_STATE (agent, pair, NICE_CHECK_IN_PROGRESS);
if (agent->triggered_check_queue == NULL ||
g_slist_find (agent->triggered_check_queue, pair) == NULL)
agent->triggered_check_queue = g_slist_append (agent->triggered_check_queue, pair);
}
/*
- * 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)
}
/*
- * 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
*/
-static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair *pair)
+static gboolean
+priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair *pair)
{
SET_PAIR_STATE (agent, pair, NICE_CHECK_IN_PROGRESS);
if (conn_check_send (agent, pair)) {
/*
* 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;
+ gboolean result = FALSE;
- g_assert (ok_check);
- g_assert (ok_check->state == NICE_CHECK_SUCCEEDED);
- g_assert (stream);
- g_assert (stream->id == ok_check->stream_id);
-
- priv_print_conn_check_lists (agent, G_STRFUNC, NULL);
+ g_assert (pair);
+ g_assert (pair->state == NICE_CHECK_SUCCEEDED);
- /* 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);
+ result = TRUE;
}
}
}
+ /* 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);
+}
- /* 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;
+ gboolean result = FALSE;
- 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);
+
+ 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);
+ result = TRUE;
}
}
- }
+ }
+ /* 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);
+}
+
+guint
+conn_check_stun_transactions_count (NiceAgent *agent)
+{
+ GSList *i, *j;
+ guint count = 0;
+
+ 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->stun_transactions)
+ count += g_slist_length (p->stun_transactions);
+ }
+ }
+ return count;
}
/*
priv_forget_stun_transaction (stun, component);
pair->stun_transactions = g_slist_remove (pair->stun_transactions, stun);
priv_free_stun_transaction (stun);
+ if (pair->stun_transactions == NULL)
+ pair->retransmit = FALSE;
}
/*
g_slist_foreach (pair->stun_transactions, priv_forget_stun_transaction, component);
g_slist_free_full (pair->stun_transactions, priv_free_stun_transaction);
pair->stun_transactions = NULL;
+ pair->retransmit = FALSE;
}
static void
* Helper function for connectivity check timer callback that
* runs through the stream specific part of the state machine.
*
- * @param schedule if TRUE, schedule a new check
- *
- * @return will return FALSE when no more pending timers.
+ * @param agent context pointer
+ * @param stream which stream (of the agent)
+ * @return will return TRUE if a new stun request has been sent
*/
-static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent)
+static gboolean
+priv_conn_check_tick_stream (NiceAgent *agent, NiceStream *stream)
{
- gboolean keep_timer_going = FALSE;
+ gboolean pair_failed = FALSE;
GSList *i, *j;
- CandidateCheckPair *pair;
unsigned int timeout;
- GTimeVal now;
+ gint64 now;
- g_get_current_time (&now);
+ now = g_get_monotonic_time ();
/* step: process ongoing STUN transactions */
for (i = stream->conncheck_list; i ; i = i->next) {
CandidateCheckPair *p = i->data;
gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN];
NiceComponent *component;
- StunTransaction *stun;
+ guint index = 0, remaining = 0;
if (p->stun_transactions == NULL)
continue;
NULL, &component))
continue;
- /* The first stun transaction of the list may eventually be
- * retransmitted, other stun transactions just have their
- * timer updated.
- */
-
- j = p->stun_transactions->next;
-
- /* process all stun transactions except the first one */
+ j = p->stun_transactions;
while (j) {
- StunTransaction *s = j->data;
+ StunTransaction *stun = j->data;
GSList *next = j->next;
- if (priv_timer_expired (&s->next_tick, &now))
- switch (stun_timer_refresh (&s->timer)) {
+ if (now < stun->next_tick)
+ remaining++;
+ else
+ switch (stun_timer_refresh (&stun->timer)) {
case STUN_USAGE_TIMER_RETURN_TIMEOUT:
- priv_remove_stun_transaction (p, s, component);
+timer_return_timeout:
+ priv_remove_stun_transaction (p, stun, component);
break;
case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
- timeout = stun_timer_remainder (&s->timer);
- s->next_tick = now;
- g_time_val_add (&s->next_tick, timeout * 1000);
- break;
- default:
- break;
- }
- j = next;
- }
+ /* case: retransmission stopped, due to the nomination of
+ * a pair with a higher priority than this in-progress pair,
+ * ICE spec, sect 8.1.2 "Updating States", item 2.2
+ */
+ if (!p->retransmit || index > 0)
+ goto timer_return_timeout;
- if (p->state != NICE_CHECK_IN_PROGRESS)
- continue;
+ /* case: not ready, so schedule a new timeout */
+ timeout = stun_timer_remainder (&stun->timer);
- /* process the first stun transaction of the list */
- stun = p->stun_transactions->data;
- if (!priv_timer_expired (&stun->next_tick, &now))
- continue;
+ nice_debug ("Agent %p :STUN transaction retransmitted on pair %p "
+ "(timer=%d/%d %d/%dms).",
+ agent, p,
+ stun->timer.retransmissions, stun->timer.max_retransmissions,
+ stun->timer.delay - timeout, stun->timer.delay);
- switch (stun_timer_refresh (&stun->timer)) {
- case STUN_USAGE_TIMER_RETURN_TIMEOUT:
-timer_return_timeout:
- /* case: error, abort processing */
- nice_address_to_string (&p->local->addr, tmpbuf1);
- nice_address_to_string (&p->remote->addr, tmpbuf2);
- nice_debug ("Agent %p : Retransmissions failed, giving up on pair %p",
- agent, p);
- nice_debug ("Agent %p : Failed pair is [%s]:%u --> [%s]:%u", agent,
- tmpbuf1, nice_address_get_port (&p->local->addr),
- tmpbuf2, nice_address_get_port (&p->remote->addr));
- candidate_check_pair_fail (stream, agent, p);
- priv_print_conn_check_lists (agent, G_STRFUNC,
- ", retransmission failed");
-
- /* perform a check if a transition state from connected to
- * ready can be performed. This may happen here, when the last
- * in-progress pair has expired its retransmission count
- * in priv_conn_check_tick_stream(), which is a condition to
- * make the transition connected to ready.
- */
- priv_update_check_list_state_for_ready (agent, stream, component);
- break;
- case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
- /* case: retransmission stopped, due to the nomination of
- * a pair with a higher priority than this in-progress pair,
- * ICE spec, sect 8.1.2 "Updating States", item 2.2
- */
- if (!p->retransmit)
- goto timer_return_timeout;
+ agent_socket_send (p->sockptr, &p->remote->addr,
+ stun_message_length (&stun->message),
+ (gchar *)stun->buffer);
- /* case: not ready, so schedule a new timeout */
- timeout = stun_timer_remainder (&stun->timer);
+ /* note: convert from milli to microseconds for g_time_val_add() */
+ stun->next_tick = now + timeout * 1000;
- nice_debug ("Agent %p :STUN transaction retransmitted on pair %p "
- "(timer=%d/%d %d/%dms).",
- agent, p,
- stun->timer.retransmissions, stun->timer.max_retransmissions,
- stun->timer.delay - timeout, stun->timer.delay);
+ return TRUE;
+ case STUN_USAGE_TIMER_RETURN_SUCCESS:
+ timeout = stun_timer_remainder (&stun->timer);
+ /* note: convert from milli to microseconds for g_time_val_add() */
+ stun->next_tick = now + timeout * 1000;
+ remaining++;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ j = next;
+ index++;
+ }
- agent_socket_send (p->sockptr, &p->remote->addr,
- stun_message_length (&stun->message),
- (gchar *)stun->buffer);
+ if (remaining == 0) {
+ nice_address_to_string (&p->local->addr, tmpbuf1);
+ nice_address_to_string (&p->remote->addr, tmpbuf2);
+ nice_debug ("Agent %p : Retransmissions failed, giving up on pair %p",
+ agent, p);
+ nice_debug ("Agent %p : Failed pair is [%s]:%u --> [%s]:%u", agent,
+ tmpbuf1, nice_address_get_port (&p->local->addr),
+ tmpbuf2, nice_address_get_port (&p->remote->addr));
+ candidate_check_pair_fail (stream, agent, p);
+ pair_failed = TRUE;
- /* note: convert from milli to microseconds for g_time_val_add() */
- stun->next_tick = now;
- g_time_val_add (&stun->next_tick, timeout * 1000);
+ /* perform a check if a transition state from connected to
+ * ready can be performed. This may happen here, when the last
+ * in-progress pair has expired its retransmission count
+ * in priv_conn_check_tick_stream(), which is a condition to
+ * make the transition connected to ready.
+ */
+ conn_check_update_check_list_state_for_ready (agent, stream, component);
+ }
+ }
- return TRUE;
- case STUN_USAGE_TIMER_RETURN_SUCCESS:
- timeout = stun_timer_remainder (&stun->timer);
+ if (pair_failed)
+ priv_print_conn_check_lists (agent, G_STRFUNC, ", retransmission failed");
- /* note: convert from milli to microseconds for g_time_val_add() */
- stun->next_tick = now;
- g_time_val_add (&stun->next_tick, timeout * 1000);
+ return FALSE;
+}
- keep_timer_going = TRUE;
- break;
- default:
- /* Nothing to do. */
- break;
- }
- }
+static gboolean
+priv_conn_check_ordinary_check (NiceAgent *agent, NiceStream *stream)
+{
+ CandidateCheckPair *pair;
+ gboolean stun_sent = FALSE;
- /* 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 == 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);
+ }
+
if (pair) {
+ stun_sent = priv_conn_check_initiate (agent, pair);
priv_print_conn_check_lists (agent, G_STRFUNC,
- ", got a pair in Waiting state");
- priv_conn_check_initiate (agent, pair);
- return TRUE;
+ ", initiated an ordinary connection check");
}
+ return stun_sent;
+}
- /* 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;
+static gboolean
+priv_conn_check_triggered_check (NiceAgent *agent, NiceStream *stream)
+{
+ CandidateCheckPair *pair;
+ gboolean stun_sent = FALSE;
- /* step: ordinary check continued, if there's no pair in the waiting
- * state, pick a pair in the frozen state
+ /* step: perform a test from the triggered checks list,
+ * sect 6.1.4.2 point 1. (Performing Connectivity Checks) of ICE
+ * spec (RFC8445)
*/
- pair = priv_conn_check_find_next_frozen (stream->conncheck_list);
+ pair = priv_get_pair_from_triggered_check_queue (agent);
+
if (pair) {
+ stun_sent = priv_conn_check_initiate (agent, pair);
priv_print_conn_check_lists (agent, G_STRFUNC,
- ", got a pair in Frozen state");
- SET_PAIR_STATE (agent, pair, NICE_CHECK_WAITING);
- priv_conn_check_initiate (agent, pair);
- return TRUE;
+ ", initiated a connection check from triggered check list");
}
- return keep_timer_going;
+ return stun_sent;
}
+
static gboolean
-priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent)
+priv_conn_check_tick_stream_nominate (NiceAgent *agent, NiceStream *stream)
{
gboolean keep_timer_going = FALSE;
+ /* s_xxx counters are stream-wide */
guint s_inprogress = 0;
guint s_succeeded = 0;
guint s_discovered = 0;
guint s_nominated = 0;
guint s_waiting_for_nomination = 0;
guint s_valid = 0;
- guint frozen = 0;
- guint waiting = 0;
- GSList *i, *k;
+ guint s_frozen = 0;
+ guint s_waiting = 0;
+ CandidateCheckPair *other_stream_pair = NULL;
+ GSList *i, *j;
+
+ /* Search for a nominated pair (or selected to be nominated pair)
+ * from another stream.
+ */
+ for (i = agent->streams; i ; i = i->next) {
+ NiceStream *s = i->data;
+ if (s->id == stream->id)
+ continue;
+ for (j = s->conncheck_list; j ; j = j->next) {
+ CandidateCheckPair *p = j->data;
+ if (p->nominated || (p->use_candidate_on_next_check &&
+ p->state != NICE_CHECK_FAILED)) {
+ other_stream_pair = p;
+ break;
+ }
+ }
+ if (other_stream_pair)
+ break;
+ }
+ /* we compute some stream-wide counter values */
for (i = stream->conncheck_list; i ; i = i->next) {
CandidateCheckPair *p = i->data;
if (p->state == NICE_CHECK_FROZEN)
- ++frozen;
+ s_frozen++;
else if (p->state == NICE_CHECK_IN_PROGRESS)
- ++s_inprogress;
+ s_inprogress++;
else if (p->state == NICE_CHECK_WAITING)
- ++waiting;
+ s_waiting++;
else if (p->state == NICE_CHECK_SUCCEEDED)
- ++s_succeeded;
+ s_succeeded++;
else if (p->state == NICE_CHECK_DISCOVERED)
- ++s_discovered;
+ s_discovered++;
if (p->valid)
- ++s_valid;
+ s_valid++;
if ((p->state == NICE_CHECK_SUCCEEDED || p->state == NICE_CHECK_DISCOVERED)
&& p->nominated)
- ++s_nominated;
+ s_nominated++;
else if ((p->state == NICE_CHECK_SUCCEEDED ||
p->state == NICE_CHECK_DISCOVERED) && !p->nominated)
- ++s_waiting_for_nomination;
+ s_waiting_for_nomination++;
}
- /* note: keep the timer going as long as there is work to be done */
+ /* note: keep the timer going as long as there is work to be done */
if (s_inprogress)
keep_timer_going = TRUE;
* and limiting the time spent waiting for in-progress connections
* checks until they finally fail.
*/
- GSList *component_item;
-
- for (component_item = stream->components; component_item;
- component_item = component_item->next) {
- NiceComponent *component = component_item->data;
+ for (i = stream->components; i; i = i->next) {
+ NiceComponent *component = i->data;
+ CandidateCheckPair *other_component_pair = NULL;
+ CandidateCheckPair *this_component_pair = NULL;
+ NiceCandidate *lcand1 = NULL;
+ NiceCandidate *rcand1 = NULL;
+ NiceCandidate *lcand2, *rcand2;
gboolean already_done = FALSE;
- gboolean stopping_criterion = FALSE;
+ gboolean found_other_component_pair = FALSE;
+ gboolean found_other_stream_pair = FALSE;
+ gboolean first_nomination = FALSE;
+ gboolean stopping_criterion;
+ /* p_xxx counters are component-wide */
guint p_valid = 0;
guint p_frozen = 0;
guint p_waiting = 0;
guint p_inprogress = 0;
guint p_host_host_valid = 0;
- /* verify that the choice of the pair to be nominated
- * has not already been done
- */
- for (k = stream->conncheck_list; k ; k = k->next) {
- CandidateCheckPair *p = k->data;
+ /* we compute some component-wide counter values */
+ for (j = stream->conncheck_list; j ; j = j->next) {
+ CandidateCheckPair *p = j->data;
if (p->component_id == component->id) {
+ /* verify that the choice of the pair to be nominated
+ * has not already been done
+ */
if (p->use_candidate_on_next_check)
already_done = TRUE;
if (p->state == NICE_CHECK_FROZEN)
if (already_done)
continue;
- stopping_criterion =
- (p_host_host_valid > 0 ||
- p_valid >= NICE_MIN_NUMBER_OF_VALID_PAIRS ||
- (p_waiting == 0 && p_inprogress == 0 && p_frozen == 0));
-
- if (!stopping_criterion)
- continue;
+ /* Search for a nominated pair (or selected to be nominated pair)
+ * from another component of this stream.
+ */
+ for (j = stream->conncheck_list; j ; j = j->next) {
+ CandidateCheckPair *p = j->data;
+ if (p->component_id == component->id)
+ continue;
+ if (p->nominated || (p->use_candidate_on_next_check &&
+ p->state != NICE_CHECK_FAILED)) {
+ other_component_pair = p;
+ break;
+ }
+ }
- /* when the stopping criterion is satisfied, we choose
- * a pair to be nominated in the list of valid pairs,
- * and add it to the triggered checks list
+ if (other_stream_pair == NULL && other_component_pair == NULL)
+ first_nomination = TRUE;
+
+ /* We choose a pair to be nominated in the list of valid
+ * pairs.
+ *
+ * this pair will be the one with the highest priority,
+ * when we don't have other nominated pairs in other
+ * components and in other streams
+ *
+ * this pair will be a pair compatible with another nominated
+ * pair from another component if we found one.
+ *
+ * else this pair will be a pair compatible with another
+ * nominated pair from another stream if we found one.
+ *
*/
- for (k = stream->conncheck_list; k ; k = k->next) {
- CandidateCheckPair *p = k->data;
+ for (j = stream->conncheck_list; j ; j = j->next) {
+ CandidateCheckPair *p = j->data;
/* note: highest priority item selected (list always sorted) */
if (p->component_id == component->id &&
!p->nominated &&
* to test its parent pair instead.
*/
if (p->succeeded_pair != NULL) {
- g_assert (p->state == NICE_CHECK_DISCOVERED);
+ g_assert_cmpint (p->state, ==, NICE_CHECK_DISCOVERED);
p = p->succeeded_pair;
}
- g_assert (p->state == NICE_CHECK_SUCCEEDED);
- nice_debug ("Agent %p : restarting check of pair %p with "
- "USE-CANDIDATE attrib (regular nomination)", agent, p);
- p->use_candidate_on_next_check = TRUE;
- priv_add_pair_to_triggered_check_queue (agent, p);
- keep_timer_going = TRUE;
- break; /* move to the next component */
+ g_assert_cmpint (p->state, ==, NICE_CHECK_SUCCEEDED);
+
+ if (this_component_pair == NULL)
+ /* highest priority pair */
+ this_component_pair = p;
+
+ lcand1 = p->local;
+ rcand1 = p->remote;
+
+ if (first_nomination)
+ /* use the highest priority pair */
+ break;
+
+ if (other_component_pair) {
+ lcand2 = other_component_pair->local;
+ rcand2 = other_component_pair->remote;
+ }
+ if (other_component_pair &&
+ lcand1->transport == lcand2->transport &&
+ nice_address_equal_no_port (&lcand1->addr, &lcand2->addr) &&
+ nice_address_equal_no_port (&rcand1->addr, &rcand2->addr)) {
+ /* else continue the research with lower priority
+ * pairs, compatible with a nominated pair of
+ * another component
+ */
+ this_component_pair = p;
+ found_other_component_pair = TRUE;
+ break;
+ }
+
+ if (other_stream_pair) {
+ lcand2 = other_stream_pair->local;
+ rcand2 = other_stream_pair->remote;
+ }
+ if (other_stream_pair &&
+ other_component_pair == NULL &&
+ lcand1->transport == lcand2->transport &&
+ nice_address_equal_no_port (&lcand1->addr, &lcand2->addr) &&
+ nice_address_equal_no_port (&rcand1->addr, &rcand2->addr)) {
+ /* else continue the research with lower priority
+ * pairs, compatible with a nominated pair of
+ * another stream
+ */
+ this_component_pair = p;
+ found_other_stream_pair = TRUE;
+ break;
+ }
}
}
+
+ /* No valid pair for this component */
+ if (this_component_pair == NULL)
+ continue;
+
+ /* The stopping criterion tries to select a set of pairs of
+ * the same kind (transport/type) for all components of a
+ * stream, and for all streams, when possible (see last
+ * paragraph).
+ *
+ * When no stream has nominated a pair yet, we apply the
+ * following criterion :
+ * - stop if we have a valid host-host pair
+ * - or stop if we have at least "some* (2 in the current
+ * implementation) valid pairs, and select the best one
+ * - or stop if the conncheck cannot evolve more
+ *
+ * Else when the stream has a nominated pair in another
+ * component we apply this criterion:
+ * - stop if we have a valid pair of the same kind than this
+ * other nominated pair.
+ * - or stop if the conncheck cannot evolve more
+ *
+ * Else when another stream has a nominated pair we apply the
+ * following criterion:
+ * - stop if we have a valid pair of the same kind than the
+ * other nominated pair.
+ * - or stop if the conncheck cannot evolve more
+ *
+ * When no further evolution of the conncheck is possible, we
+ * prefer to select the best valid pair we have, *even* if it
+ * is not compatible with the transport of another stream of
+ * component. We think it's still a better choice than marking
+ * this component 'failed'.
+ */
+ stopping_criterion = FALSE;
+ if (first_nomination && p_host_host_valid > 0) {
+ stopping_criterion = TRUE;
+ nice_debug ("Agent %p : stopping criterion: "
+ "valid host-host pair", agent);
+ } else if (first_nomination &&
+ p_valid >= NICE_MIN_NUMBER_OF_VALID_PAIRS) {
+ stopping_criterion = TRUE;
+ nice_debug ("Agent %p : stopping criterion: "
+ "*some* valid pairs", agent);
+ } else if (found_other_component_pair) {
+ stopping_criterion = TRUE;
+ nice_debug ("Agent %p : stopping criterion: "
+ "matching pair in another component", agent);
+ } else if (found_other_stream_pair) {
+ stopping_criterion = TRUE;
+ nice_debug ("Agent %p : stopping criterion: "
+ "matching pair in another stream", agent);
+ } else if (p_waiting == 0 && p_inprogress == 0 && p_frozen == 0) {
+ stopping_criterion = TRUE;
+ nice_debug ("Agent %p : stopping criterion: "
+ "no more pairs to check", agent);
+ }
+
+ if (!stopping_criterion)
+ continue;
+
+ /* when the stopping criterion is reached, we add the
+ * selected pair for this component to the triggered checks
+ * list
+ */
+ nice_debug ("Agent %p : restarting check of %s:%s pair %p with "
+ "USE-CANDIDATE attrib (regular nomination) for "
+ "stream %d component %d", agent,
+ priv_candidate_transport_to_string (
+ this_component_pair->local->transport),
+ priv_candidate_transport_to_string (
+ this_component_pair->remote->transport),
+ this_component_pair, stream->id, component->id);
+ this_component_pair->use_candidate_on_next_check = TRUE;
+ priv_add_pair_to_triggered_check_queue (agent, this_component_pair);
+ keep_timer_going = TRUE;
}
}
} else if (agent->controlling_mode) {
- GSList *component_item;
-
- for (component_item = stream->components; component_item;
- component_item = component_item->next) {
- NiceComponent *component = component_item->data;
+ for (i = stream->components; i; i = i->next) {
+ NiceComponent *component = i->data;
- for (k = stream->conncheck_list; k ; k = k->next) {
- CandidateCheckPair *p = k->data;
+ for (j = stream->conncheck_list; j ; j = j->next) {
+ CandidateCheckPair *p = j->data;
/* note: highest priority item selected (list always sorted) */
if (p->component_id == component->id &&
(p->state == NICE_CHECK_SUCCEEDED ||
nice_debug ("Agent %p : restarting check of pair %p as the "
"nominated pair.", agent, p);
p->nominated = TRUE;
- priv_update_selected_pair (agent, component, p);
+ conn_check_update_selected_pair (agent, component, p);
priv_add_pair_to_triggered_check_queue (agent, p);
keep_timer_going = TRUE;
break; /* move to the next component */
}
}
if (stream->tick_counter++ % 50 == 0)
- nice_debug ("Agent %p : stream %u: timer tick #%u: %u frozen, %u in-progress, "
- "%u waiting, %u succeeded, %u discovered, %u nominated, "
- "%u waiting-for-nom, %u valid.", agent, stream->id,
- stream->tick_counter, frozen, s_inprogress, waiting, s_succeeded,
- s_discovered, s_nominated, s_waiting_for_nomination, s_valid);
+ nice_debug ("Agent %p : stream %u: timer tick #%u: %u frozen, "
+ "%u in-progress, %u waiting, %u succeeded, %u discovered, "
+ "%u nominated, %u waiting-for-nom, %u valid",
+ agent, stream->id, stream->tick_counter,
+ s_frozen, s_inprogress, s_waiting, s_succeeded, s_discovered,
+ s_nominated, s_waiting_for_nomination, s_valid);
return keep_timer_going;
g_source_destroy (agent->conncheck_timer_source);
g_source_unref (agent->conncheck_timer_source);
agent->conncheck_timer_source = NULL;
- agent->conncheck_timer_grace_period = 0;
+ agent->conncheck_ongoing_idle_delay = 0;
}
static gboolean priv_conn_check_tick_agent_locked (NiceAgent *agent,
gpointer user_data)
{
- CandidateCheckPair *pair = NULL;
gboolean keep_timer_going = FALSE;
+ gboolean stun_sent = FALSE;
GSList *i, *j;
- /* configure the initial state of the check lists of the agent
- * as described in ICE spec, 5.7.4
+ /* step: process triggered checks
+ * these steps are ordered by priority, since a single stun request
+ * is sent per callback, we process the important steps first.
*
- * if all pairs in all check lists are in frozen state, then
- * we are in the initial state (5.7.4, point 1.)
+ * perform a single stun request per timer callback,
+ * to respect stun pacing
*/
- 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);
+ for (i = agent->streams; i && !stun_sent; i = i->next) {
+ NiceStream *stream = i->data;
+
+ stun_sent = priv_conn_check_triggered_check (agent, stream);
}
- /* step: perform a test from the triggered checks list,
- * ICE spec, 5.8 "Scheduling Checks"
- */
- pair = priv_get_pair_from_triggered_check_queue (agent);
+ /* step: process ongoing STUN transactions */
+ for (i = agent->streams; i && !stun_sent; i = i->next) {
+ NiceStream *stream = i->data;
- if (pair) {
- priv_print_conn_check_lists (agent, G_STRFUNC,
- ", got a pair from triggered check list");
- if (conn_check_send (agent, pair)) {
- SET_PAIR_STATE (agent, pair, NICE_CHECK_FAILED);
- return FALSE;
- }
- return TRUE;
+ stun_sent = priv_conn_check_tick_stream (agent, stream);
}
- /* step: process ongoing STUN transactions and
- * perform an ordinary check, ICE spec, 5.8, "Scheduling Checks"
- */
- for (i = agent->streams; i ; i = i->next) {
+ /* step: process ordinary checks */
+ for (i = agent->streams; i && !stun_sent; i = i->next) {
NiceStream *stream = i->data;
- if (priv_conn_check_tick_stream (stream, agent))
- keep_timer_going = TRUE;
- if (priv_conn_check_tick_stream_nominate (stream, agent))
- keep_timer_going = TRUE;
+
+ stun_sent = priv_conn_check_ordinary_check (agent, stream);
}
- /* 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 (stun_sent)
+ keep_timer_going = TRUE;
+
+ /* step: try to nominate a pair
*/
- 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;
- }
- }
+ for (i = agent->streams; i; i = i->next) {
+ NiceStream *stream = i->data;
+
+ if (priv_conn_check_tick_stream_nominate (agent, stream))
+ keep_timer_going = TRUE;
}
/* note: we provide a grace period before declaring a component as
* failed. Components marked connected, and then ready follow another
* code path, and are not concerned by this grace period.
*/
- if (!keep_timer_going && agent->conncheck_timer_grace_period == 0)
+ if (!keep_timer_going && agent->conncheck_ongoing_idle_delay == 0)
nice_debug ("Agent %p : waiting %d msecs before checking "
- "for failed components.", agent, NICE_AGENT_MAX_TIMER_GRACE_PERIOD);
+ "for failed components.", agent, agent->idle_timeout);
if (keep_timer_going)
- agent->conncheck_timer_grace_period = 0;
+ agent->conncheck_ongoing_idle_delay = 0;
else
- agent->conncheck_timer_grace_period += agent->timer_ta;
+ agent->conncheck_ongoing_idle_delay += agent->timer_ta;
/* step: stop timer if no work left */
if (!keep_timer_going &&
- agent->conncheck_timer_grace_period >= NICE_AGENT_MAX_TIMER_GRACE_PERIOD) {
+ agent->conncheck_ongoing_idle_delay >= agent->idle_timeout) {
nice_debug ("Agent %p : checking for failed components now.", agent);
for (i = agent->streams; i; i = i->next) {
NiceStream *stream = i->data;
priv_update_check_list_failed_components (agent, stream);
for (j = stream->components; j; j = j->next) {
NiceComponent *component = j->data;
- priv_update_check_list_state_for_ready (agent, stream, component);
+ conn_check_update_check_list_state_for_ready (agent, stream, component);
}
}
nice_debug ("Agent %p : Retransmitting keepalive conncheck",
agent);
- agent_timeout_add_with_context (agent,
- &pair->keepalive.tick_source,
- "Pair keepalive", stun_timer_remainder (&pair->keepalive.timer),
- priv_conn_keepalive_retransmissions_tick_agent_locked, pair);
- break;
+
+ G_GNUC_FALLTHROUGH;
case STUN_USAGE_TIMER_RETURN_SUCCESS:
agent_timeout_add_with_context (agent,
&pair->keepalive.tick_source,
return priority;
}
+/* Returns the priority of a local candidate of type peer-reflexive that
+ * would be learned as a consequence of a check from this local
+ * candidate. See RFC 5245, section 7.1.2.1. "PRIORITY and USE-CANDIDATE".
+ * RFC 5245 is more explanatory than RFC 8445 on this detail.
+ *
+ * Apply to local candidates of type host only, because candidates of type
+ * relay are supposed to have a public IP address, that wont generate
+ * a peer-reflexive address. Server-reflexive candidates are not
+ * concerned too, because no STUN request is sent with a local candidate
+ * of this type.
+ */
+static guint32 stun_request_priority (NiceAgent *agent,
+ NiceCandidate *local_candidate)
+{
+ if (local_candidate->type == NICE_CANDIDATE_TYPE_HOST)
+ return peer_reflexive_candidate_priority (agent, local_candidate);
+ else
+ return local_candidate->priority;
+}
+
static void ms_ice2_legacy_conncheck_send(StunMessage *msg, NiceSocket *sock,
const NiceAddress *remote_addr)
{
{
GSList *i, *j, *k;
int errors = 0;
- gboolean ret = FALSE;
size_t buf_len = 0;
+ guint64 now;
+ guint64 min_next_tick;
+ guint64 next_timer_tick;
+
+ now = g_get_monotonic_time ();
+ min_next_tick = now + 1000 * NICE_AGENT_TIMER_TR_DEFAULT;
/* case 1: session established and media flowing
- * (ref ICE sect 10 "Keepalives" ID-19) */
+ * (ref ICE sect 11 "Keepalives" RFC-8445)
+ * TODO: keepalives should be send only when no packet has been sent
+ * on that pair in the last Tr seconds, and not unconditionally.
+ */
for (i = agent->streams; i; i = i->next) {
NiceStream *stream = i->data;
!agent->keepalive_conncheck)
continue;
+ if (p->keepalive.next_tick) {
+ if (p->keepalive.next_tick < min_next_tick)
+ min_next_tick = p->keepalive.next_tick;
+ if (now < p->keepalive.next_tick)
+ continue;
+ }
+
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE ||
agent->keepalive_conncheck) {
uint8_t uname[NICE_STREAM_MAX_UNAME];
nice_address_to_string (&p->remote->addr, tmpbuf);
nice_debug ("Agent %p : Keepalive STUN-CC REQ to '%s:%u', "
"(c-id:%u), username='%.*s' (%" G_GSIZE_FORMAT "), "
- "password='%.*s' (%" G_GSIZE_FORMAT "), priority=%u.", agent,
- tmpbuf, nice_address_get_port (&p->remote->addr),
+ "password='%.*s' (%" G_GSIZE_FORMAT "), priority=%08x.",
+ agent, tmpbuf, nice_address_get_port (&p->remote->addr),
component->id, (int) uname_len, uname, uname_len,
(int) password_len, password, password_len,
- p->prflx_priority);
+ p->stun_priority);
}
if (uname_len > 0) {
buf_len = stun_usage_ice_conncheck_create (&component->stun_agent,
sizeof(p->keepalive.stun_buffer),
uname, uname_len, password, password_len,
agent->controlling_mode, agent->controlling_mode,
- p->prflx_priority,
+ p->stun_priority,
agent->tie_breaker,
NULL,
agent_to_ice_compatibility (agent));
p->keepalive.stream_id = stream->id;
p->keepalive.component_id = component->id;
+ p->keepalive.next_tick = now + 1000 * NICE_AGENT_TIMER_TR_DEFAULT;
agent_timeout_add_with_context (agent,
&p->keepalive.tick_source, "Pair keepalive",
stun_timer_remainder (&p->keepalive.timer),
priv_conn_keepalive_retransmissions_tick_agent_locked, p);
+
+ next_timer_tick = now + agent->timer_ta * 1000;
+ goto done;
} else {
++errors;
}
agent_socket_send (p->local->sockptr, &p->remote->addr, buf_len,
(gchar *)p->keepalive.stun_buffer);
+ p->keepalive.next_tick = now + 1000 * NICE_AGENT_TIMER_TR_DEFAULT;
+
if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) {
ms_ice2_legacy_conncheck_send (&p->keepalive.stun_message,
p->local->sockptr, &p->remote->addr);
}
- nice_debug ("Agent %p : stun_bind_keepalive for pair %p res %d.",
- agent, p, (int) buf_len);
+ if (nice_debug_is_enabled ()) {
+ gchar tmpbuf[INET6_ADDRSTRLEN];
+ nice_address_to_string (&p->local->base_addr, tmpbuf);
+ nice_debug ("Agent %p : resending STUN to keep the "
+ "selected base address %s:%u alive in s%d/c%d.", agent,
+ tmpbuf, nice_address_get_port (&p->local->base_addr),
+ stream->id, component->id);
+ }
+
+ next_timer_tick = now + agent->timer_ta * 1000;
+ goto done;
} else {
++errors;
}
}
/* case 2: connectivity establishment ongoing
- * (ref ICE sect 4.1.1.4 "Keeping Candidates Alive" ID-19) */
+ * (ref ICE sect 5.1.1.4 "Keeping Candidates Alive" RFC-8445)
+ */
for (i = agent->streams; i; i = i->next) {
NiceStream *stream = i->data;
for (j = stream->components; j; j = j->next) {
NiceComponent *component = j->data;
- if (component->state < NICE_COMPONENT_STATE_READY &&
+ if (component->state < NICE_COMPONENT_STATE_CONNECTED &&
agent->stun_server_ip) {
NiceAddress stun_server;
if (nice_address_set_from_string (&stun_server, agent->stun_server_ip)) {
candidate->transport == NICE_CANDIDATE_TRANSPORT_UDP &&
nice_address_ip_version (&candidate->addr) ==
nice_address_ip_version (&stun_server)) {
+
+ if (candidate->keepalive_next_tick) {
+ if (candidate->keepalive_next_tick < min_next_tick)
+ min_next_tick = candidate->keepalive_next_tick;
+ if (now < candidate->keepalive_next_tick)
+ continue;
+ }
+
/* send the conncheck */
- nice_debug ("Agent %p : resending STUN on %s to keep the "
- "candidate alive.", agent, candidate->foundation);
+ if (nice_debug_is_enabled ()) {
+ gchar tmpbuf[INET6_ADDRSTRLEN];
+ nice_address_to_string (&candidate->addr, tmpbuf);
+ nice_debug ("Agent %p : resending STUN to keep the local "
+ "candidate %s:%u alive in s%d/c%d.", agent,
+ tmpbuf, nice_address_get_port (&candidate->addr),
+ stream->id, component->id);
+ }
agent_socket_send (candidate->sockptr, &stun_server,
buffer_len, (gchar *)stun_buffer);
+ candidate->keepalive_next_tick = now +
+ 1000 * NICE_AGENT_TIMER_TR_DEFAULT;
+ next_timer_tick = now + agent->timer_ta * 1000;
+ goto done;
}
}
}
}
}
+ next_timer_tick = min_next_tick;
+
+ done:
if (errors) {
nice_debug ("Agent %p : %s: stopping keepalive timer", agent, G_STRFUNC);
- goto done;
+ return FALSE;
}
- ret = TRUE;
-
- done:
- return ret;
+ if (agent->keepalive_timer_source) {
+ g_source_destroy (agent->keepalive_timer_source);
+ g_source_unref (agent->keepalive_timer_source);
+ agent->keepalive_timer_source = NULL;
+ }
+ agent_timeout_add_with_context (agent, &agent->keepalive_timer_source,
+ "Connectivity keepalive timeout", (next_timer_tick - now)/ 1000,
+ priv_conn_keepalive_tick_agent_locked, NULL);
+ return TRUE;
}
static gboolean priv_conn_keepalive_tick_agent_locked (NiceAgent *agent,
agent_socket_send (cand->nicesock, &cand->server,
stun_message_length (&cand->stun_message), (gchar *)cand->stun_buffer);
- agent_timeout_add_with_context (agent, &cand->tick_source,
- "Candidate TURN refresh", stun_timer_remainder (&cand->timer),
- priv_turn_allocate_refresh_retransmissions_tick_agent_locked, cand);
- break;
+ G_GNUC_FALLTHROUGH;
case STUN_USAGE_TIMER_RETURN_SUCCESS:
agent_timeout_add_with_context (agent, &cand->tick_source,
"Candidate TURN refresh", stun_timer_remainder (&cand->timer),
if (turn_compat == STUN_USAGE_TURN_COMPATIBILITY_MSN ||
turn_compat == STUN_USAGE_TURN_COMPATIBILITY_OC2007) {
- username = g_base64_decode ((gchar *)username, &username_len);
- password = g_base64_decode ((gchar *)password, &password_len);
+ username = cand->candidate->turn->decoded_username;
+ password = cand->candidate->turn->decoded_password;
+ username_len = cand->candidate->turn->decoded_username_len;
+ password_len = cand->candidate->turn->decoded_password_len;
}
buffer_len = stun_usage_turn_create_refresh (&cand->stun_agent,
password, password_len,
turn_compat);
- if (turn_compat == STUN_USAGE_TURN_COMPATIBILITY_MSN ||
- turn_compat == STUN_USAGE_TURN_COMPATIBILITY_OC2007) {
- g_free (username);
- g_free (password);
- }
-
nice_debug ("Agent %p : Sending allocate Refresh %zd", agent,
buffer_len);
/* step: also start the keepalive timer */
if (agent->keepalive_timer_source == NULL) {
agent_timeout_add_with_context (agent, &agent->keepalive_timer_source,
- "Connectivity keepalive timeout", NICE_AGENT_TIMER_TR_DEFAULT,
+ "Connectivity keepalive timeout", agent->timer_ta,
priv_conn_keepalive_tick_agent_locked, NULL);
}
}
return 0;
}
+/* Find a transport compatible with a given socket.
+ *
+ * Returns TRUE when a matching transport can be guessed from
+ * the type of the socket in an unambiguous way.
+ */
+static gboolean
+nice_socket_has_compatible_transport (NiceSocket *socket,
+ NiceCandidateTransport *transport)
+{
+ gboolean found = TRUE;
+
+ g_assert (socket);
+ g_assert (transport);
+
+ switch (socket->type) {
+ case NICE_SOCKET_TYPE_TCP_BSD:
+ if (nice_tcp_bsd_socket_get_passive_parent (socket))
+ *transport = NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE;
+ else
+ *transport = NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE;
+ break;
+ case NICE_SOCKET_TYPE_TCP_PASSIVE:
+ *transport = NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE;
+ break;
+ case NICE_SOCKET_TYPE_TCP_ACTIVE:
+ *transport = NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE;
+ break;
+ case NICE_SOCKET_TYPE_UDP_BSD:
+ *transport = NICE_CANDIDATE_TRANSPORT_UDP;
+ break;
+ default:
+ found = FALSE;
+ }
+
+ return found;
+}
+
+/* Test if a local socket and a local candidate are compatible. This
+ * function does supplementary tests when the address and port are not
+ * sufficient to give a unique candidate. We try to avoid comparing
+ * directly the sockptr value, when possible, to rely on objective
+ * properties of the candidate and the socket instead, and we also
+ * choose to ignore the conncheck list for the same reason.
+ */
+static gboolean
+local_candidate_and_socket_compatible (NiceAgent *agent,
+ NiceCandidate *lcand, NiceSocket *socket)
+{
+ gboolean ret = TRUE;
+ NiceCandidateTransport transport;
+
+ g_assert (socket);
+ g_assert (lcand);
+
+ if (nice_socket_has_compatible_transport (socket, &transport)) {
+ ret = (lcand->transport == transport);
+ /* tcp-active discovered peer-reflexive local candidate, where
+ * socket is the tcp connect related socket */
+ if (ret && transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE &&
+ nice_address_get_port (&lcand->addr) > 0)
+ ret = (lcand->sockptr == socket);
+ } else if (socket->type == NICE_SOCKET_TYPE_UDP_TURN)
+ /* Socket of type udp-turn will match a unique local candidate
+ * by its sockptr value. An an udp-turn socket doesn't carry enough
+ * information when base socket is udp-turn-over-tcp to disambiguate
+ * between a tcp-act and a tcp-pass local candidate.
+ */
+ ret = (lcand->sockptr == socket);
+
+ return ret;
+}
+
+/* Test if a local socket and a remote candidate are compatible.
+ * This function is very close to its local candidate counterpart,
+ * the difference is that we also use information from the local
+ * candidate we may have identified previously. This is needed
+ * to disambiguate the transport of the candidate with a socket
+ * of type udp-turn.
+ *
+ */
+static gboolean
+remote_candidate_and_socket_compatible (NiceAgent *agent,
+ NiceCandidate *lcand, NiceCandidate *rcand, NiceSocket *socket)
+{
+ gboolean ret = TRUE;
+ NiceCandidateTransport transport;
+
+ g_assert (socket);
+ g_assert (rcand);
+
+ if (nice_socket_has_compatible_transport (socket, &transport))
+ ret = (conn_check_match_transport (rcand->transport) == transport);
+
+ /* This supplementary test with the local candidate is needed with
+ * socket of type udp-turn, the type doesn't allow to disambiguate
+ * between a tcp-pass and a tcp-act remote candidate
+ */
+ if (lcand && ret)
+ ret = (conn_check_match_transport (lcand->transport) == rcand->transport);
+
+ return ret;
+}
+
void
conn_check_remote_candidates_set(NiceAgent *agent, NiceStream *stream,
NiceComponent *component)
{
- GSList *l, *m;
- GList *k;
+ GList *i;
+ GSList *j;
+ NiceCandidate *lcand = NULL, *rcand = NULL;
+
+ nice_debug ("Agent %p : conn_check_remote_candidates_set %u %u",
+ agent, stream->id, component->id);
+
+ if (stream->remote_ufrag[0] == 0)
+ return;
- for (k = component->incoming_checks.head; k;) {
- IncomingCheck *icheck = k->data;
- GList *k_next = k->next;
+ if (component->incoming_checks.head)
+ nice_debug ("Agent %p : credentials have been set, "
+ "we can process incoming checks", agent);
+
+ for (i = component->incoming_checks.head; i;) {
+ IncomingCheck *icheck = i->data;
+ GList *i_next = i->next;
+
+ nice_debug ("Agent %p : replaying icheck=%p (sock=%p)",
+ agent, icheck, icheck->local_socket);
/* sect 7.2.1.3., "Learning Peer Reflexive Candidates", has to
* be handled separately */
- for (l = component->remote_candidates; l; l = l->next) {
- NiceCandidate *rcand = l->data;
- NiceCandidate *lcand = NULL;
-
- if (nice_address_equal (&rcand->addr, &icheck->from)) {
- for (m = component->local_candidates; m; m = m->next) {
- NiceCandidate *cand = m->data;
+ for (j = component->local_candidates; j; j = j->next) {
+ NiceCandidate *cand = j->data;
+ NiceAddress *addr;
+
+ if (cand->type == NICE_CANDIDATE_TYPE_RELAYED)
+ addr = &cand->addr;
+ else
+ addr = &cand->base_addr;
+
+ if (nice_address_equal (&icheck->local_socket->addr, addr) &&
+ local_candidate_and_socket_compatible (agent, cand,
+ icheck->local_socket)) {
+ lcand = cand;
+ break;
+ }
+ }
- if (nice_address_equal (&cand->addr, &icheck->local_socket->addr)) {
- lcand = cand;
- break;
- }
+ if (lcand == NULL) {
+ for (j = component->local_candidates; j; j = j->next) {
+ NiceCandidate *cand = j->data;
+ NiceAddress *addr = &cand->base_addr;
+
+ /* tcp-active (not peer-reflexive discovered) local candidate, where
+ * socket is the tcp connect related socket */
+ if (nice_address_equal_no_port (&icheck->local_socket->addr, addr) &&
+ nice_address_get_port (&cand->addr) == 0 &&
+ cand->transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE &&
+ local_candidate_and_socket_compatible (agent, cand,
+ icheck->local_socket)) {
+ lcand = cand;
+ break;
}
+ }
+ }
- g_assert (lcand != NULL);
- priv_schedule_triggered_check (agent, stream, component,
- icheck->local_socket, rcand);
- if (icheck->use_candidate)
- priv_mark_pair_nominated (agent, stream, component,
- lcand, rcand);
+ g_assert (lcand != NULL);
- g_queue_delete_link (&component->incoming_checks, k);
+ for (j = component->remote_candidates; j; j = j->next) {
+ NiceCandidate *cand = j->data;
+ if (nice_address_equal (&cand->addr, &icheck->from) &&
+ remote_candidate_and_socket_compatible (agent, lcand, cand,
+ icheck->local_socket)) {
+ rcand = cand;
break;
}
}
- k = k_next;
+
+ if (lcand->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) {
+ CandidateCheckPair *pair = NULL;
+
+ for (j = stream->conncheck_list; j; j = j->next) {
+ CandidateCheckPair *p = j->data;
+ if (lcand == p->local && rcand == p->remote) {
+ pair = p;
+ break;
+ }
+ }
+ if (pair == NULL)
+ priv_conn_check_add_for_candidate_pair_matched (agent,
+ stream->id, component, lcand, rcand, NICE_CHECK_WAITING);
+ }
+
+ priv_schedule_triggered_check (agent, stream, component,
+ icheck->local_socket, rcand);
+ if (icheck->use_candidate)
+ priv_mark_pair_nominated (agent, stream, component, lcand, rcand);
+
+ if (icheck->username)
+ g_free (icheck->username);
+ g_slice_free (IncomingCheck, icheck);
+ g_queue_delete_link (&component->incoming_checks, i);
+ i = i_next;
}
}
}
/*
- * Enforces the upper limit for connectivity checks as described
- * in ICE spec section 5.7.3 (ID-19). See also
+ * Enforces the upper limit for connectivity checks by dropping
+ * lower-priority pairs as described RFC 8445 section 6.1.2.5. See also
* conn_check_add_for_candidate().
+ * Returns TRUE if the pair in argument is one of the deleted pairs.
*/
-static GSList *priv_limit_conn_check_list_size (NiceAgent *agent,
- GSList *conncheck_list, guint upper_limit)
+static gboolean priv_limit_conn_check_list_size (NiceAgent *agent,
+ NiceStream *stream, CandidateCheckPair *pair)
{
guint valid = 0;
guint cancelled = 0;
- GSList *item = conncheck_list;
+ gboolean deleted = FALSE;
+ GSList *item = stream->conncheck_list;
while (item) {
- CandidateCheckPair *pair = item->data;
+ CandidateCheckPair *p = item->data;
GSList *next = item->next;
valid++;
- if (valid > upper_limit) {
- candidate_check_pair_free (agent, pair);
- conncheck_list = g_slist_delete_link (conncheck_list, item);
- cancelled++;
+ /* We remove lower-priority pairs, but only the ones that can be
+ * safely discarded without breaking an ongoing conncheck process.
+ * This only includes pairs that are in the frozen state (those
+ * initially added when remote candidates are received) or in failed
+ * state. Pairs in any other state play a role in the conncheck, and
+ * there removal may lead to a failing conncheck that would succeed
+ * otherwise.
+ *
+ * We also remove failed pairs from the list unconditionally.
+ */
+ if ((valid > agent->max_conn_checks && p->state == NICE_CHECK_FROZEN) ||
+ p->state == NICE_CHECK_FAILED) {
+ if (p == pair)
+ deleted = TRUE;
+ nice_debug ("Agent %p : pair %p removed.", agent, p);
+ candidate_check_pair_free (agent, p);
+ stream->conncheck_list = g_slist_delete_link (stream->conncheck_list,
+ item);
+ cancelled++;
}
item = next;
}
if (cancelled > 0)
- nice_debug ("Agent : Pruned %d candidates. Conncheck list has %d elements"
- " left. Maximum connchecks allowed : %d", cancelled, valid,
- upper_limit);
- return conncheck_list;
+ nice_debug ("Agent %p : Pruned %d pairs. "
+ "Conncheck list has %d elements left. "
+ "Maximum connchecks allowed : %d", agent, cancelled,
+ valid - cancelled, agent->max_conn_checks);
+
+ return deleted;
}
/*
- * Changes the selected pair for the component if 'pair' is nominated
- * and has higher priority than the currently selected pair. See
- * ICE sect 11.1.1. "Procedures for Full Implementations" (ID-19).
+ * Changes the selected pair for the component if 'pair'
+ * has higher priority than the currently selected pair. See
+ * RFC 8445 sect 8.1.1. "Nominating Pairs"
*/
-static gboolean priv_update_selected_pair (NiceAgent *agent, NiceComponent *component, CandidateCheckPair *pair)
+void
+conn_check_update_selected_pair (NiceAgent *agent, NiceComponent *component,
+ CandidateCheckPair *pair)
{
CandidatePair cpair = { 0, };
g_assert (component);
g_assert (pair);
+ /* pair is expected to have the nominated flag */
+ g_assert (pair->nominated);
if (pair->priority > component->selected_pair.priority) {
+ gchar priority[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE];
+ nice_candidate_pair_priority_to_string (pair->priority, priority);
nice_debug ("Agent %p : changing SELECTED PAIR for component %u: %s:%s "
- "(prio:%" G_GUINT64_FORMAT ").", agent, component->id,
- pair->local->foundation, pair->remote->foundation, pair->priority);
+ "(prio:%s).", agent, component->id,
+ pair->local->foundation, pair->remote->foundation, priority);
cpair.local = pair->local;
cpair.remote = pair->remote;
cpair.priority = pair->priority;
- /* cpair.keepalive is not used by nice_component_update_selected_pair() */
+ cpair.stun_priority = pair->stun_priority;
nice_component_update_selected_pair (agent, component, &cpair);
agent_signal_new_selected_pair (agent, pair->stream_id, component->id,
pair->local, pair->remote);
-
}
-
- return TRUE;
}
/*
for (i = stream->conncheck_list; i; i = i->next) {
CandidateCheckPair *p = i->data;
- g_assert (p->stream_id == stream->id);
+ g_assert_cmpuint (p->stream_id, ==, stream->id);
if (p->component_id == (c + 1)) {
if (p->nominated)
*
* Sends a component state changesignal via 'agent'.
*/
-static void priv_update_check_list_state_for_ready (NiceAgent *agent, NiceStream *stream, NiceComponent *component)
+void conn_check_update_check_list_state_for_ready (NiceAgent *agent,
+ NiceStream *stream, NiceComponent *component)
{
GSList *i;
guint valid = 0, nominated = 0;
/* Only go to READY if no checks are left in progress. If there are
* any that are kept, then this function will be called again when the
* conncheck tick timer finishes them all */
- if (priv_prune_pending_checks (agent, stream, component->id) == 0) {
+ if (priv_prune_pending_checks (agent, stream, component) == 0) {
/* Continue through the states to give client code a nice
* logical progression. See http://phabricator.freedesktop.org/D218 for
* discussion. */
if (pair->state == NICE_CHECK_SUCCEEDED &&
pair->discovered_pair != NULL) {
pair = pair->discovered_pair;
- g_assert (pair->state == NICE_CHECK_DISCOVERED);
+ g_assert_cmpint (pair->state, ==, NICE_CHECK_DISCOVERED);
}
- /* If the state of this pair is In-Progress, [...] the resulting
- * valid pair has its nominated flag set when the response
- * arrives.
+ /* If the received Binding request triggered a new check to be
+ * enqueued in the triggered-check queue (Section 7.3.1.4), once
+ * the check is sent and if it generates a successful response,
+ * and generates a valid pair, the agent sets the nominated flag
+ * of the pair to true
*/
- if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) &&
- pair->state == NICE_CHECK_IN_PROGRESS) {
+ if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) {
+ if (g_slist_find (agent->triggered_check_queue, pair) ||
+ pair->state == NICE_CHECK_IN_PROGRESS) {
+
+ /* This pair is not always in the triggered check list, for
+ * example if it is in-progress with a lower priority than an
+ * already nominated pair. Is that case, it is not rescheduled
+ * for a connection check, see function
+ * priv_schedule_triggered_check(), case NICE_CHECK_IN_PROGRESS.
+ */
pair->mark_nominated_on_response_arrival = TRUE;
- nice_debug ("Agent %p : pair %p (%s) is in-progress, "
+ nice_debug ("Agent %p : pair %p (%s) is %s, "
"will be nominated on response receipt.",
- agent, pair, pair->foundation);
+ agent, pair, pair->foundation,
+ priv_state_to_string (pair->state));
+ }
}
if (pair->valid ||
if (component->state == NICE_COMPONENT_STATE_FAILED)
agent_signal_component_state_change (agent,
stream->id, component->id, NICE_COMPONENT_STATE_CONNECTING);
- priv_update_selected_pair (agent, component, pair);
+ conn_check_update_selected_pair (agent, component, pair);
if (component->state == NICE_COMPONENT_STATE_CONNECTING)
/* step: notify the client of a new component state (must be done
* before the possible check list state update step */
}
if (pair->nominated)
- priv_update_check_list_state_for_ready (agent, stream, component);
- }
- }
-}
-
-guint32
-ensure_unique_priority (NiceStream *stream, NiceComponent *component,
- guint32 priority)
-{
- GSList *item;
-
- again:
- if (priority == 0)
- priority--;
-
- for (item = component->local_candidates; item; item = item->next) {
- NiceCandidate *cand = item->data;
-
- if (cand->priority == priority) {
- priority--;
- goto again;
- }
- }
-
- for (item = stream->conncheck_list; item; item = item->next) {
- CandidateCheckPair *p = item->data;
-
- if (p->component_id == component->id &&
- p->prflx_priority == priority) {
- priority--;
- goto again;
+ conn_check_update_check_list_state_for_ready (agent, stream, component);
}
}
-
- return priority;
}
-
/*
* Creates a new connectivity check pair and adds it to
* the agent's list of checks.
{
NiceStream *stream;
CandidateCheckPair *pair;
+ guint64 priority;
g_assert (local != NULL);
g_assert (remote != NULL);
+ priority = agent_candidate_pair_priority (agent, local, remote);
+
+ if (component->selected_pair.priority &&
+ priority < component->selected_pair.priority) {
+ gchar prio1[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE];
+ gchar prio2[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE];
+
+ nice_candidate_pair_priority_to_string (priority, prio1);
+ nice_candidate_pair_priority_to_string (component->selected_pair.priority,
+ prio2);
+ nice_debug ("Agent %p : do not create a pair that would have a priority "
+ "%s lower than selected pair priority %s.", agent, prio1, prio2);
+ return NULL;
+ }
+
stream = agent_find_stream (agent, stream_id);
pair = g_slice_new0 (CandidateCheckPair);
tmpbuf1, nice_address_get_port (&pair->local->addr),
tmpbuf2, nice_address_get_port (&pair->remote->addr));
}
- pair->prflx_priority = ensure_unique_priority (stream, component,
- peer_reflexive_candidate_priority (agent, local));
+ pair->stun_priority = stun_request_priority (agent, local);
stream->conncheck_list = g_slist_insert_sorted (stream->conncheck_list, pair,
(GCompareFunc)conn_check_compare);
- nice_debug ("Agent %p : added a new pair %p with foundation '%s' to "
- "stream %u component %u.", agent, pair, pair->foundation, stream_id,
- component->id);
+ nice_debug ("Agent %p : added a new pair %p with foundation '%s' and "
+ "transport %s:%s to stream %u component %u",
+ agent, pair, pair->foundation,
+ priv_candidate_transport_to_string (pair->local->transport),
+ priv_candidate_transport_to_string (pair->remote->transport),
+ stream_id, component->id);
+
+ 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): */
if (agent->compatibility == NICE_COMPATIBILITY_RFC5245) {
- stream->conncheck_list = priv_limit_conn_check_list_size (agent,
- stream->conncheck_list, agent->max_conn_checks);
+ if (priv_limit_conn_check_list_size (agent, stream, pair))
+ return NULL;
}
return pair;
pair = priv_add_new_check_pair (agent, stream_id, component, local, remote,
initial_state);
- if (component->state == NICE_COMPONENT_STATE_CONNECTED ||
- component->state == NICE_COMPONENT_STATE_READY) {
- agent_signal_component_state_change (agent,
- stream_id,
- component->id,
- NICE_COMPONENT_STATE_CONNECTED);
- } else {
- agent_signal_component_state_change (agent,
- stream_id,
- component->id,
- NICE_COMPONENT_STATE_CONNECTING);
+ if (pair) {
+ if (component->state == NICE_COMPONENT_STATE_CONNECTED ||
+ component->state == NICE_COMPONENT_STATE_READY) {
+ agent_signal_component_state_change (agent,
+ stream_id,
+ component->id,
+ NICE_COMPONENT_STATE_CONNECTED);
+ } else {
+ agent_signal_component_state_change (agent,
+ stream_id,
+ component->id,
+ NICE_COMPONENT_STATE_CONNECTING);
+ }
}
return pair;
g_assert (local != NULL);
g_assert (remote != NULL);
- /* note: do not create pairs where the local candidate is
- * a srv-reflexive (ICE 5.7.3. "Pruning the pairs" ID-9) */
+ /* note: do not create pairs where the local candidate is a srv-reflexive
+ * or peer-reflexive (ICE 6.1.2.4. "Pruning the pairs" RFC 8445)
+ */
if ((agent->compatibility == NICE_COMPATIBILITY_RFC5245 ||
agent->compatibility == NICE_COMPATIBILITY_WLM2009 ||
agent->compatibility == NICE_COMPATIBILITY_OC2007R2) &&
- local->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE) {
+ (local->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE ||
+ local->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE)) {
return FALSE;
}
/* note: match pairs only if transport and address family are the same */
if (local->transport == conn_check_match_transport (remote->transport) &&
local->addr.s.addr.sa_family == remote->addr.s.addr.sa_family) {
- priv_conn_check_add_for_candidate_pair_matched (agent, stream_id, component,
- local, remote, NICE_CHECK_FROZEN);
- ret = TRUE;
+ if (priv_conn_check_add_for_candidate_pair_matched (agent, stream_id,
+ component, local, remote, NICE_CHECK_FROZEN))
+ ret = TRUE;
}
return ret;
/* note: according to 7.2.1.3, "Learning Peer Reflexive Candidates",
* the agent does not pair this candidate with any local candidates.
*/
- if (agent->compatibility == NICE_COMPATIBILITY_RFC5245 &&
+ if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) &&
remote->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE)
{
return added;
* with other remote candidates
*/
- if (agent->compatibility == NICE_COMPATIBILITY_RFC5245 &&
+ if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) &&
local->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE)
{
return added;
return 0;
}
-/* Implement the computation specific in RFC 5245 section 16 */
+/* Implement the computation specific in RFC 8445 section 14 */
static unsigned int priv_compute_conncheck_timer (NiceAgent *agent, NiceStream *stream)
{
- GSList *i;
+ GSList *i, *j;
guint waiting_and_in_progress = 0;
- guint n = 0;
unsigned int rto = 0;
- for (i = stream->conncheck_list; i ; i = i->next) {
- CandidateCheckPair *p = i->data;
- if (p->state == NICE_CHECK_IN_PROGRESS ||
- p->state == NICE_CHECK_WAITING)
- waiting_and_in_progress++;
+ /* we can compute precisely the number of pairs in-progress or
+ * waiting for all streams, instead of limiting the value to one
+ * stream, and multiplying it by the number of active streams.
+ * Since RFC8445, this number of waiting and in-progress pairs
+ * if maxed by the number of different foundations in the conncheck
+ * list.
+ */
+ 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_IN_PROGRESS ||
+ p->state == NICE_CHECK_WAITING)
+ waiting_and_in_progress++;
+ }
}
- n = priv_number_of_active_check_lists (agent);
- rto = agent->timer_ta * n * waiting_and_in_progress;
+ rto = agent->timer_ta * waiting_and_in_progress;
- /* We assume non-reliable streams are RTP, so we use 100 as the max */
nice_debug ("Agent %p : timer set to %dms, "
- "waiting+in_progress=%d, nb_active=%d",
- agent, agent->reliable ? MAX (rto, 500) : MAX (rto, 100),
- waiting_and_in_progress, n);
- if (agent->reliable)
- return MAX (rto, 500);
- else
- return MAX (rto, 100);
+ "waiting+in_progress=%d", agent, MAX (rto, STUN_TIMER_DEFAULT_TIMEOUT),
+ waiting_and_in_progress);
+ return MAX (rto, STUN_TIMER_DEFAULT_TIMEOUT);
}
/*
nice_address_to_string (&pair->remote->addr, tmpbuf2);
nice_debug ("Agent %p : STUN-CC REQ [%s]:%u --> [%s]:%u, socket=%u, "
"pair=%p (c-id:%u), tie=%llu, username='%.*s' (%" G_GSIZE_FORMAT "), "
- "password='%.*s' (%" G_GSIZE_FORMAT "), prio=%u, %s.", agent,
+ "password='%.*s' (%" G_GSIZE_FORMAT "), prio=%08x, %s.", agent,
tmpbuf1, nice_address_get_port (&pair->local->addr),
tmpbuf2, nice_address_get_port (&pair->remote->addr),
pair->sockptr->fileno ? g_socket_get_fd(pair->sockptr->fileno) : -1,
(unsigned long long)agent->tie_breaker,
(int) uname_len, uname, uname_len,
(int) password_len, password, password_len,
- pair->prflx_priority,
+ pair->stun_priority,
controlling ? "controlling" : "controlled");
}
buffer_len = stun_usage_ice_conncheck_create (&component->stun_agent,
&stun->message, stun->buffer, sizeof(stun->buffer),
uname, uname_len, password, password_len,
- cand_use, controlling, pair->prflx_priority,
+ cand_use, controlling, pair->stun_priority,
agent->tie_breaker,
pair->local->foundation,
agent_to_ice_compatibility (agent));
stun_timer_start (&stun->timer, timeout, agent->stun_max_retransmissions);
}
- g_get_current_time (&stun->next_tick);
- g_time_val_add (&stun->next_tick, timeout * 1000);
+ stun->next_tick = g_get_monotonic_time () + timeout * 1000;
/* TCP-ACTIVE candidate must create a new socket before sending
* by connecting to the peer. The new socket is stored in the candidate
new_socket = nice_tcp_active_socket_connect (pair->sockptr,
&pair->remote->addr);
if (new_socket) {
+ nice_debug ("Agent %p: add to tcp-act socket %p a new "
+ "tcp connect socket %p on pair %p in s/c %d/%d",
+ agent, pair->sockptr, new_socket, pair, stream->id, component->id);
pair->sockptr = new_socket;
_priv_set_socket_tos (agent, pair->sockptr, stream2->tos);
* Implemented the pruning steps described in ICE sect 8.1.2
* "Updating States" (ID-19) after a pair has been nominated.
*
- * @see priv_update_check_list_state_failed_components()
+ * @see conn_check_update_check_list_state_failed_components()
*/
-static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, guint component_id)
+static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, NiceComponent *component)
{
GSList *i;
- guint64 highest_nominated_priority = 0;
+ guint64 priority;
guint in_progress = 0;
+ guint triggered_check = 0;
+ gchar prio[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE];
- nice_debug ("Agent %p: Finding highest priority for component %d",
- agent, component_id);
+ nice_debug ("Agent %p: Pruning pending checks for s%d/c%d",
+ agent, stream->id, component->id);
- for (i = stream->conncheck_list; i; i = i->next) {
- CandidateCheckPair *p = i->data;
- if (p->component_id == component_id && p->valid == TRUE &&
- p->nominated == TRUE) {
- if (p->priority > highest_nominated_priority) {
- highest_nominated_priority = p->priority;
- }
- }
- }
+ /* Called when we have at least one selected pair */
+ priority = component->selected_pair.priority;
+ g_assert (priority > 0);
- nice_debug ("Agent %p: Pruning pending checks. Highest nominated priority "
- "is %" G_GUINT64_FORMAT, agent, highest_nominated_priority);
+ nice_candidate_pair_priority_to_string (priority, prio);
+ nice_debug ("Agent %p : selected pair priority is %s", agent, prio);
- /* step: cancel all FROZEN and WAITING pairs for the component */
i = stream->conncheck_list;
while (i) {
CandidateCheckPair *p = i->data;
GSList *next = i->next;
- if (p->component_id == component_id) {
- if (p->state == NICE_CHECK_FROZEN || p->state == NICE_CHECK_WAITING) {
+ if (p->component_id != component->id) {
+ i = next;
+ continue;
+ }
+
+ /* We do not remove a pair from the conncheck list if it is also in
+ * the triggered check queue. This is not what suggests the ICE
+ * spec, but it proved to be more robust in the aggressive
+ * nomination scenario, precisely because these pairs may have the
+ * use-candidate flag set, and the peer agent may already have
+ * selected such one.
+ */
+ if (g_slist_find (agent->triggered_check_queue, p) &&
+ p->state != NICE_CHECK_IN_PROGRESS) {
+ if (p->priority < priority) {
nice_debug ("Agent %p : pair %p removed.", agent, p);
candidate_check_pair_free (agent, p);
stream->conncheck_list = g_slist_delete_link(stream->conncheck_list, i);
- }
+ } else
+ triggered_check++;
+ }
- /* note: a SHOULD level req. in ICE 8.1.2. "Updating States" (ID-19) */
- else if (p->state == NICE_CHECK_IN_PROGRESS) {
- if (p->priority < highest_nominated_priority) {
- p->retransmit = FALSE;
+ /* step: cancel all FROZEN and WAITING pairs for the component */
+ else if (p->state == NICE_CHECK_FROZEN || p->state == NICE_CHECK_WAITING) {
+ nice_debug ("Agent %p : pair %p removed.", agent, p);
+ candidate_check_pair_free (agent, p);
+ stream->conncheck_list = g_slist_delete_link(stream->conncheck_list, i);
+ }
+
+ /* note: a SHOULD level req. in ICE 8.1.2. "Updating States" (ID-19) */
+ else if (p->state == NICE_CHECK_IN_PROGRESS) {
+ if (p->priority < priority) {
+ priv_remove_pair_from_triggered_check_queue (agent, p);
+ if (p->retransmit) {
+ p->retransmit = FALSE;
nice_debug ("Agent %p : pair %p will not be retransmitted.",
agent, p);
- } else {
- /* We must keep the higher priority pairs running because if a udp
- * packet was lost, we might end up using a bad candidate */
- nice_debug ("Agent %p : pair %p kept IN_PROGRESS because priority %"
- G_GUINT64_FORMAT " is higher than currently nominated pair %"
- G_GUINT64_FORMAT, agent,
- p, p->priority, highest_nominated_priority);
- in_progress++;
}
+ } else {
+ /* We must keep the higher priority pairs running because if a udp
+ * packet was lost, we might end up using a bad candidate */
+ nice_candidate_pair_priority_to_string (p->priority, prio);
+ nice_debug ("Agent %p : pair %p kept IN_PROGRESS because priority "
+ "%s is higher than priority of best nominated pair.", agent, p, prio);
+ /* We may also have to enable the retransmit flag of pairs with
+ * a higher priority than the first nominated pair
+ */
+ if (!p->retransmit && p->stun_transactions) {
+ p->retransmit = TRUE;
+ nice_debug ("Agent %p : pair %p will be retransmitted.", agent, p);
+ }
+ in_progress++;
}
}
i = next;
}
- return in_progress;
+ return in_progress + triggered_check;
}
/*
g_assert (remote_cand != NULL);
+ nice_debug ("Agent %p : scheduling triggered check with socket=%p "
+ "and remote cand=%p.", agent, local_socket, remote_cand);
+
for (i = stream->conncheck_list; i ; i = i->next) {
p = i->data;
if (p->component_id == component->id &&
p->remote == remote_cand &&
- ((p->local->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE &&
- p->sockptr == local_socket) ||
- (p->local->transport != NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE &&
- p->local->sockptr == local_socket))) {
- /* We don't check for p->sockptr because in the case of
- * tcp-active we don't want to retrigger a check on a pair that
- * was FAILED when a peer-reflexive pair was created */
+ p->sockptr == local_socket) {
+ /* If we match with a peer-reflexive discovered pair, we
+ * use the parent succeeded pair instead */
if (p->succeeded_pair != NULL) {
- g_assert (p->state == NICE_CHECK_DISCOVERED);
+ g_assert_cmpint (p->state, ==, NICE_CHECK_DISCOVERED);
p = p->succeeded_pair;
}
* for that pair. The controlling role of this new check may
* be different from the role of this cancelled check.
*
- * note: the flag retransmit unset means that
- * another pair, with a higher priority is already nominated,
- * so there's no reason to recheck this pair, since it can in
- * no way replace the nominated one.
+ * When another pair, with a higher priority is already
+ * nominated, so there's no reason to recheck this pair,
+ * since it can in no way replace the nominated one.
*/
- if (!nice_socket_is_reliable (p->sockptr) && p->retransmit) {
+ if (p->priority > component->selected_pair.priority) {
nice_debug ("Agent %p : pair %p added for a triggered check.",
agent, p);
priv_add_pair_to_triggered_check_queue (agent, p);
}
break;
case NICE_CHECK_FAILED:
- if (p->retransmit) {
+ if (p->priority > component->selected_pair.priority) {
nice_debug ("Agent %p : pair %p added for a triggered check.",
agent, p);
priv_add_pair_to_triggered_check_queue (agent, p);
agent_signal_component_state_change (agent, stream->id,
component->id, NICE_COMPONENT_STATE_CONNECTING);
conn_check_schedule_next (agent);
+ /* If the component if in ready state, move it back to
+ * connected as this failed pair with a higher priority
+ * than the nominated pair requires to pursue the
+ * conncheck
+ */
+ } else if (component->state == NICE_COMPONENT_STATE_READY) {
+ agent_signal_component_state_change (agent, stream->id,
+ component->id, NICE_COMPONENT_STATE_CONNECTED);
+ conn_check_schedule_next (agent);
}
}
break;
case NICE_CHECK_SUCCEEDED:
nice_debug ("Agent %p : nothing to do for pair %p.", agent, p);
- /* note: this is a bit unsure corner-case -- let's do the
- same state update as for processing responses to our own checks */
- /* note: this update is required by the trickle test, to
- * ensure the transition ready -> connected -> ready, because
- * an incoming stun request generates a discovered peer reflexive,
- * that causes the ready -> connected transition.
- */
- priv_update_check_list_state_for_ready (agent, stream, component);
break;
default:
break;
nice_debug ("Agent %p : Adding a triggered check to conn.check list (local=%p).", agent, local);
p = priv_conn_check_add_for_candidate_pair_matched (agent, stream->id,
component, local, remote_cand, NICE_CHECK_WAITING);
- priv_add_pair_to_triggered_check_queue (agent, p);
+ if (p)
+ priv_add_pair_to_triggered_check_queue (agent, p);
return TRUE;
}
else {
}
g_snprintf (pair->foundation, NICE_CANDIDATE_PAIR_MAX_FOUNDATION, "%s:%s",
local_cand->foundation, parent_pair->remote->foundation);
+
if (agent->controlling_mode == TRUE)
pair->priority = nice_candidate_pair_priority (pair->local->priority,
pair->remote->priority);
pair->priority = nice_candidate_pair_priority (pair->remote->priority,
pair->local->priority);
pair->nominated = parent_pair->nominated;
- pair->prflx_priority = ensure_unique_priority (stream, component,
- peer_reflexive_candidate_priority (agent, local_cand));
- nice_debug ("Agent %p : added a new peer-discovered pair with "
- "foundation '%s'.", agent, pair->foundation);
+ /* the peer-reflexive priority used in stun request is copied from
+ * the parent succeeded pair. This value is not required for discovered
+ * pair, that won't emit stun requests themselves, but may be used when
+ * such pair becomes the selected pair, and when keepalive stun are emitted,
+ * using the sockptr and stun_priority values from the succeeded pair.
+ */
+ pair->stun_priority = parent_pair->stun_priority;
+ nice_debug ("Agent %p : added a new peer-discovered pair %p with "
+ "foundation '%s' and transport %s:%s to stream %u component %u",
+ agent, pair, pair->foundation,
+ priv_candidate_transport_to_string (pair->local->transport),
+ priv_candidate_transport_to_string (pair->remote->transport),
+ stream_id, component->id);
stream->conncheck_list = g_slist_insert_sorted (stream->conncheck_list, pair,
(GCompareFunc)conn_check_compare);
{
CandidateCheckPair *new_pair = NULL;
NiceAddress mapped;
- GSList *i, *j;
+ GSList *i;
NiceCandidate *local_cand = NULL;
nice_address_set_from_sockaddr (&mapped, mapped_sockaddr);
- for (j = component->local_candidates; j; j = j->next) {
- NiceCandidate *cand = j->data;
- if (nice_address_equal (&mapped, &cand->addr)) {
+ for (i = component->local_candidates; i; i = i->next) {
+ NiceCandidate *cand = i->data;
+
+ if (nice_address_equal (&mapped, &cand->addr) &&
+ local_candidate_and_socket_compatible (agent, cand, sockptr)) {
local_cand = cand;
+ break;
+ }
+ }
- /* We always need to select the peer-reflexive Candidate Pair in the case
- * of a TCP-ACTIVE local candidate, so we find it even if an incoming
- * check matched an existing pair because it could be the original
- * ACTIVE-PASSIVE candidate pair which was retriggered */
- for (i = stream->conncheck_list; i; i = i->next) {
- CandidateCheckPair *pair = i->data;
- if (pair->local == cand && remote_candidate == pair->remote) {
- new_pair = pair;
- break;
- }
- }
+ /* The mapped address allows to look for a previously discovered
+ * peer reflexive local candidate, and its related pair. This
+ * new_pair will be marked 'Valid', while the pair 'p' of the
+ * initial stun request will be marked 'Succeeded'
+ *
+ * In the case of a tcp-act/tcp-pass pair 'p', where the local
+ * candidate is of type tcp-act, and its port number is zero, a
+ * conncheck on this pair *always* leads to the creation of a
+ * discovered peer-reflexive tcp-act local candidate.
+ */
+ for (i = stream->conncheck_list; i; i = i->next) {
+ CandidateCheckPair *pair = i->data;
+ if (local_cand == pair->local && remote_candidate == pair->remote) {
+ new_pair = pair;
break;
}
}
nice_component_add_valid_candidate (agent, component, remote_candidate);
}
else {
- if (!local_cand) {
- if (!agent->force_relay) {
- /* step: find a new local candidate, see RFC 5245 7.1.3.2.1.
- * "Discovering Peer Reflexive Candidates"
- */
- local_cand = discovery_add_peer_reflexive_candidate (agent,
- stream->id,
- component->id,
- &mapped,
- sockptr,
- local_candidate,
- remote_candidate);
- nice_debug ("Agent %p : added a new peer-reflexive local candidate %p",
- agent, local_cand);
- }
+ if (local_cand == NULL && !agent->force_relay) {
+ /* step: find a new local candidate, see RFC 5245 7.1.3.2.1.
+ * "Discovering Peer Reflexive Candidates"
+ *
+ * The priority equal to the value of the PRIORITY attribute
+ * in the Binding request is taken from the "parent" pair p
+ */
+ local_cand = discovery_add_peer_reflexive_candidate (agent,
+ stream->id,
+ component->id,
+ p->stun_priority,
+ &mapped,
+ sockptr,
+ local_candidate,
+ remote_candidate);
+ nice_debug ("Agent %p : added a new peer-reflexive local candidate %p "
+ "with transport %s", agent, local_cand,
+ priv_candidate_transport_to_string (local_cand->transport));
}
/* step: add a new discovered pair (see RFC 5245 7.1.3.2.2
return TRUE;
}
+ if (remote_candidate == NULL) {
+ candidate_check_pair_fail (stream, agent, p);
+ if (nice_debug_is_enabled ()) {
+ nice_debug ("Agent %p : pair %p FAILED "
+ "(got a matching pair without a known remote candidate).", agent, p);
+ }
+ return TRUE;
+ }
+
/* note: CONNECTED but not yet READY, see docs */
/* step: handle the possible case of a peer-reflexive
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
}
if (ok_pair->nominated == TRUE) {
- priv_update_selected_pair (agent, component, ok_pair);
+ conn_check_update_selected_pair (agent, component, ok_pair);
priv_print_conn_check_lists (agent, G_STRFUNC,
", got a nominated pair");
/* step: update pair states (ICE 7.1.2.2.3 "Updating pair
states" and 8.1.2 "Updating States", ID-19) */
- priv_update_check_list_state_for_ready (agent, stream, component);
+ conn_check_update_check_list_state_for_ready (agent, stream, component);
} else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) {
uint64_t tie;
gboolean controlled_mode;
+ if (!p->retransmit) {
+ nice_debug ("Agent %p : Role conflict with pair %p, not restarting",
+ agent, p);
+ return TRUE;
+ }
+
/* case: role conflict error, need to restart with new role */
nice_debug ("Agent %p : Role conflict with pair %p, restarting",
agent, p);
return lifetime / 2;
}
-static CandidateRefresh *
+static void
priv_add_new_turn_refresh (NiceAgent *agent, CandidateDiscovery *cdisco,
NiceCandidate *relay_cand, guint lifetime)
{
CandidateRefresh *cand;
+ if (cdisco->turn->type == NICE_RELAY_TYPE_TURN_TLS &&
+ (agent->compatibility == NICE_COMPATIBILITY_OC2007 ||
+ agent->compatibility == NICE_COMPATIBILITY_OC2007R2))
+ return;
+
cand = g_slice_new0 (CandidateRefresh);
agent->refresh_list = g_slist_append (agent->refresh_list, cand);
sizeof(cand->stun_resp_buffer));
memcpy(&cand->stun_resp_msg, &cdisco->stun_resp_msg, sizeof(StunMessage));
cand->stun_resp_msg.buffer = cand->stun_resp_buffer;
- cand->stun_resp_msg.agent = NULL;
+ cand->stun_resp_msg.agent = &cand->stun_agent;
cand->stun_resp_msg.key = NULL;
}
nice_debug ("timer source is : %p", cand->timer_source);
- return cand;
+ return;
}
static void priv_handle_turn_alternate_server (NiceAgent *agent,
nice_address_to_string (&server, ip);
nice_debug ("Agent %p : Cancelling and setting alternate server %s for "
- "CandidateDiscovery %p", agent, ip, d);
+ "CandidateDiscovery %p on s%d/c%d", agent, ip, d,
+ d->stream_id, d->component_id);
d->server = alternate;
d->turn->server = alternate;
d->pending = FALSE;
agent->discovery_unsched_items++;
+
+ if (d->turn->type == NICE_RELAY_TYPE_TURN_TCP ||
+ d->turn->type == NICE_RELAY_TYPE_TURN_TLS) {
+ NiceStream *stream;
+ NiceComponent *component;
+
+ if (!agent_find_component (agent, d->stream_id, d->component_id,
+ &stream, &component)) {
+ nice_debug ("Could not find stream or component in "
+ "priv_handle_turn_alternate_server");
+ continue;
+ }
+ d->nicesock = agent_create_tcp_turn_socket (agent, stream, component,
+ d->nicesock, &d->server, d->turn->type,
+ nice_socket_is_reliable (d->nicesock));
+
+ nice_component_attach_socket (component, d->nicesock);
+ }
}
}
}
/* handle alternate server */
nice_address_set_from_sockaddr (&addr, &alternate.addr);
priv_handle_turn_alternate_server (agent, d, d->server, addr);
+ trans_found = TRUE;
} else if (res == STUN_USAGE_TURN_RETURN_RELAY_SUCCESS ||
res == STUN_USAGE_TURN_RETURN_MAPPED_SUCCESS) {
/* case: successful allocate, create a new local candidate */
&d->stun_message);
nice_udp_turn_socket_set_ms_connection_id(relay_cand->sockptr,
resp);
- } else {
- priv_add_new_turn_refresh (agent, d, relay_cand, lifetime);
}
+ priv_add_new_turn_refresh (agent, d, relay_cand, lifetime);
}
relay_cand = discovery_add_relay_candidate (
&d->stun_message);
nice_udp_turn_socket_set_ms_connection_id(relay_cand->sockptr,
resp);
- } else {
- priv_add_new_turn_refresh (agent, d, relay_cand, lifetime);
}
+ priv_add_new_turn_refresh (agent, d, relay_cand, lifetime);
/* In case a new candidate has been added */
conn_check_schedule_next (agent);
d->stun_message.buffer_len = 0;
d->done = TRUE;
}
- } else {
+ } else if (d->pending) {
/* case: STUN error, the check STUN context was freed */
d->stun_message.buffer = NULL;
d->stun_message.buffer_len = 0;
StunTransactionId response_id;
stun_message_id (resp, response_id);
- for (i = agent->refresh_list; i && trans_found != TRUE; i = i->next) {
+ for (i = agent->refresh_list; i && trans_found != TRUE;) {
CandidateRefresh *cand = i->data;
+ GSList *next = i->next;
if (!cand->disposing && cand->stun_message.buffer) {
stun_message_id (&cand->stun_message, refresh_id);
g_source_destroy (cand->tick_source);
g_source_unref (cand->tick_source);
cand->tick_source = NULL;
+ trans_found = TRUE;
} else if (res == STUN_USAGE_TURN_RETURN_ERROR) {
int code = -1;
uint8_t *sent_realm = NULL;
}
}
}
+ i = next;
}
return trans_found;
username = (uint8_t *) stun_message_find (&req, STUN_ATTRIBUTE_USERNAME,
&username_len);
- for (i = component->remote_candidates; i; i = i->next) {
+ for (i = component->local_candidates; i; i = i->next) {
NiceCandidate *cand = i->data;
- if (nice_address_equal (from, &cand->addr)) {
- remote_candidate = cand;
+ NiceAddress *addr;
+
+ if (cand->type == NICE_CANDIDATE_TYPE_RELAYED)
+ addr = &cand->addr;
+ else
+ addr = &cand->base_addr;
+
+ if (nice_address_equal (&nicesock->addr, addr) &&
+ local_candidate_and_socket_compatible (agent, cand, nicesock)) {
+ local_candidate = cand;
break;
}
}
- for (i = component->local_candidates; i; i = i->next) {
+
+ for (i = component->remote_candidates; i; i = i->next) {
NiceCandidate *cand = i->data;
- if (nice_address_equal (&nicesock->addr, &cand->addr)) {
- local_candidate = cand;
+ if (nice_address_equal (from, &cand->addr) &&
+ remote_candidate_and_socket_compatible (agent, local_candidate,
+ cand, nicesock)) {
+ remote_candidate = cand;
break;
}
}
return TRUE;
}
+ /* This is most likely caused by a second response to a request which
+ * already has received a valid reply.
+ */
+ if (valid == STUN_VALIDATION_UNMATCHED_RESPONSE) {
+ nice_debug ("Agent %p : Valid STUN response for which we don't have a request, ignoring", agent);
+ return TRUE;
+ }
+
if (valid != STUN_VALIDATION_SUCCESS) {
nice_debug ("Agent %p : STUN message is unsuccessful %d, ignoring", agent, valid);
return FALSE;
remote_candidate2 ? remote_candidate2 : remote_candidate);
if(remote_candidate && stream->remote_ufrag[0]) {
if (local_candidate &&
- local_candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) {
- CandidateCheckPair *pair;
-
- pair = priv_conn_check_add_for_candidate_pair_matched (agent,
+ local_candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE)
+ priv_conn_check_add_for_candidate_pair_matched (agent,
stream->id, component, local_candidate, remote_candidate,
- NICE_CHECK_SUCCEEDED);
- if (pair) {
- pair->valid = TRUE;
- }
- } else
+ NICE_CHECK_WAITING);
+ else
conn_check_add_for_candidate (agent, stream->id, component, remote_candidate);
}
}
/* step: send a reply immediately but postpone other processing */
priv_store_pending_check (agent, component, from, nicesock,
username, username_len, priority, use_candidate);
+ priv_print_conn_check_lists (agent, G_STRFUNC, ", icheck stored");
}
} else {
nice_debug ("Agent %p : Invalid STUN packet, ignoring... %s",
}
/* RENOMINATION attribute support */
- conn_check_handle_renomination(agent, stream, component, &req, local_candidate, remote_candidate);
+ conn_check_handle_renomination(agent, stream, component, &req, remote_candidate, local_candidate);
return TRUE;
}