Commit 36f306f4 authored by Fabrice Bellet's avatar Fabrice Bellet Committed by Olivier Crête

conncheck: support several stun requests per pair

This patch should improve the reliabily of the connection check by
keeping the record of several simultaneous ongoing stun requests per
pair. A new stun request on an in-progress pair typically is caused by
in inbound stun request from the peer on this same pair. This is named
"Triggered Checks" in the spec. When this situation arises, it is fair
to handle these two stun requests simultaneously, the triggered check,
and the initial ordinary check, since both can potentially succeed.

Differential Revision: https://phabricator.freedesktop.org/D1761
parent e860948b
...@@ -189,8 +189,8 @@ priv_candidate_type_to_string (NiceCandidateType type) ...@@ -189,8 +189,8 @@ priv_candidate_type_to_string (NiceCandidateType type)
static void static void
priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar *detail) priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar *detail)
{ {
GSList *i, *k; GSList *i, *k, *l;
guint j; guint j, m;
GTimeVal now; GTimeVal now;
if (!nice_debug_is_verbose ()) if (!nice_debug_is_verbose ())
...@@ -210,27 +210,34 @@ priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar * ...@@ -210,27 +210,34 @@ priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar *
if (pair->component_id == j) { if (pair->component_id == j) {
gchar local_addr[INET6_ADDRSTRLEN]; gchar local_addr[INET6_ADDRSTRLEN];
gchar remote_addr[INET6_ADDRSTRLEN]; gchar remote_addr[INET6_ADDRSTRLEN];
StunTimer *timer = &pair->timer;
nice_address_to_string (&pair->local->addr, local_addr); nice_address_to_string (&pair->local->addr, local_addr);
nice_address_to_string (&pair->remote->addr, remote_addr); nice_address_to_string (&pair->remote->addr, remote_addr);
nice_debug ("Agent %p : *** sc=%d/%d : pair %p : " nice_debug ("Agent %p : *** sc=%d/%d : pair %p : "
"f=%s t=%s:%s timer=%d/%d %d/%dms " "f=%s t=%s:%s [%s]:%u > [%s]:%u state=%c%s%s%s",
"[%s]:%u > [%s]:%u state=%c%s%s%s",
agent, pair->stream_id, pair->component_id, pair, agent, pair->stream_id, pair->component_id, pair,
pair->foundation, pair->foundation,
priv_candidate_type_to_string (pair->local->type), priv_candidate_type_to_string (pair->local->type),
priv_candidate_type_to_string (pair->remote->type), priv_candidate_type_to_string (pair->remote->type),
timer->retransmissions, timer->max_retransmissions,
timer->delay - priv_timer_remainder (&pair->next_tick, &now),
timer->delay,
local_addr, nice_address_get_port (&pair->local->addr), local_addr, nice_address_get_port (&pair->local->addr),
remote_addr, nice_address_get_port (&pair->remote->addr), remote_addr, nice_address_get_port (&pair->remote->addr),
priv_state_to_gchar (pair->state), priv_state_to_gchar (pair->state),
pair->valid ? "V" : "", pair->valid ? "V" : "",
pair->nominated ? "N" : "", pair->nominated ? "N" : "",
g_slist_find (agent->triggered_check_queue, pair) ? "T" : ""); 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",
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,
stun->message.buffer,
(m == 0 && pair->retransmit) ? "(R)" : "");
}
} }
} }
} }
...@@ -608,52 +615,92 @@ static void priv_conn_check_unfreeze_related (NiceAgent *agent, NiceStream *stre ...@@ -608,52 +615,92 @@ static void priv_conn_check_unfreeze_related (NiceAgent *agent, NiceStream *stre
} }
} }
/*
* Create a new STUN transaction and add it to the list
* of ongoing stun transactions of a pair.
*
* @pair the pair the new stun transaction should be added to.
* @return the created stun transaction.
*/
static StunTransaction *
priv_add_stun_transaction (CandidateCheckPair *pair)
{
StunTransaction *stun = g_slice_new0 (StunTransaction);
pair->stun_transactions = g_slist_prepend (pair->stun_transactions, stun);
pair->retransmit = TRUE;
return stun;
}
/*
* Forget a STUN transaction.
*
* @data the stun transaction to be forgotten.
* @user_data the component contained the concerned stun agent.
*/
static void static void
candidate_check_pair_fail (NiceStream *stream, NiceAgent *agent, CandidateCheckPair *p) priv_forget_stun_transaction (gpointer data, gpointer user_data)
{ {
StunTransaction *stun = data;
NiceComponent *component = user_data;
StunTransactionId id; StunTransactionId id;
NiceComponent *component;
component = nice_stream_find_component_by_id (stream, p->component_id);
p->state = NICE_CHECK_FAILED; if (stun->message.buffer != NULL) {
nice_debug ("Agent %p : pair %p state FAILED", agent, p); stun_message_id (&stun->message, id);
if (p->stun_message.buffer != NULL) {
stun_message_id (&p->stun_message, id);
stun_agent_forget_transaction (&component->stun_agent, id); stun_agent_forget_transaction (&component->stun_agent, id);
} }
}
p->stun_message.buffer = NULL; static void
p->stun_message.buffer_len = 0; priv_free_stun_transaction (gpointer data)
{
g_slice_free (StunTransaction, data);
} }
/* /*
* Function that resubmits a new connection check, after a previous * Remove a STUN transaction from a pair, and forget it
* check in in-progress state got cancelled due to an incoming stun * from the related component stun agent.
* request matching this same pair
* *
* @return will return TRUE if the pair is scheduled for recheck * @pair the pair the stun transaction should be removed from.
* @stun the stun transaction to be removed.
* @component the component containing the stun agent used to
* forget the stun transaction.
*/ */
static gboolean static void
priv_conn_recheck_on_timeout (NiceAgent *agent, CandidateCheckPair *p) priv_remove_stun_transaction (CandidateCheckPair *pair,
StunTransaction *stun, NiceComponent *component)
{ {
if (p->recheck_on_timeout) { priv_forget_stun_transaction (stun, component);
g_assert (p->state == NICE_CHECK_IN_PROGRESS); pair->stun_transactions = g_slist_remove (pair->stun_transactions, stun);
/* this cancelled pair may have the flag 'mark nominated priv_free_stun_transaction (stun);
* on response arrival' set, we want to keep it, because }
* this is needed to nominate this pair in aggressive
* nomination, when the agent is in controlled mode. /*
* Remove all STUN transactions from a pair, and forget them
* from the related component stun agent.
* *
* this cancelled pair may also have the flag 'use candidate * @pair the pair the stun list should be cleared.
* on next check' set, that we want to preserve too. * @component the component containing the stun agent used to
* forget the stun transactions.
*/ */
nice_debug ("Agent %p : pair %p was cancelled, " static void
"triggering a new connection check", agent, p); priv_free_all_stun_transactions (CandidateCheckPair *pair,
priv_add_pair_to_triggered_check_queue (agent, p); NiceComponent *component)
return TRUE; {
} if (component)
return 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;
}
static void
candidate_check_pair_fail (NiceStream *stream, NiceAgent *agent, CandidateCheckPair *p)
{
NiceComponent *component;
component = nice_stream_find_component_by_id (stream, p->component_id);
p->state = NICE_CHECK_FAILED;
nice_debug ("Agent %p : pair %p state FAILED", agent, p);
priv_free_all_stun_transactions (p, component);
} }
/* /*
...@@ -667,7 +714,7 @@ priv_conn_recheck_on_timeout (NiceAgent *agent, CandidateCheckPair *p) ...@@ -667,7 +714,7 @@ priv_conn_recheck_on_timeout (NiceAgent *agent, CandidateCheckPair *p)
static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent) static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent)
{ {
gboolean keep_timer_going = FALSE; gboolean keep_timer_going = FALSE;
GSList *i; GSList *i, *j;
CandidateCheckPair *pair; CandidateCheckPair *pair;
unsigned int timeout; unsigned int timeout;
GTimeVal now; GTimeVal now;
...@@ -679,39 +726,59 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen ...@@ -679,39 +726,59 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen
CandidateCheckPair *p = i->data; CandidateCheckPair *p = i->data;
gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN]; gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN];
NiceComponent *component; NiceComponent *component;
StunTransaction *stun;
if (p->stun_transactions == NULL)
continue;
if (!agent_find_component (agent, p->stream_id, p->component_id, if (!agent_find_component (agent, p->stream_id, p->component_id,
NULL, &component)) NULL, &component))
continue; continue;
if (p->state != NICE_CHECK_IN_PROGRESS) /* The first stun transaction of the list may eventually be
continue; * retransmitted, other stun transactions just have their
* timer updated.
*/
if (p->stun_message.buffer == NULL) { j = p->stun_transactions->next;
nice_debug ("Agent %p : STUN connectivity check was cancelled, marking as done.", agent);
p->state = NICE_CHECK_FAILED;
nice_debug ("Agent %p : pair %p state FAILED", agent, p);
continue;
}
if (!priv_timer_expired (&p->next_tick, &now)) /* process all stun transactions except the first one */
continue; while (j) {
StunTransaction *s = j->data;
GSList *next = j->next;
switch (stun_timer_refresh (&p->timer)) { if (priv_timer_expired (&s->next_tick, &now))
switch (stun_timer_refresh (&s->timer)) {
case STUN_USAGE_TIMER_RETURN_TIMEOUT: case STUN_USAGE_TIMER_RETURN_TIMEOUT:
timer_timeout: priv_remove_stun_transaction (p, s, component);
/* case: conncheck cancelled due to in-progress incoming break;
* check, requeing the pair, ICE spec, sect 7.2.1.4 case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
* "Triggered Checks", "If the state of that pair is timeout = stun_timer_remainder (&s->timer);
* In-Progress..." s->next_tick = now;
*/ g_time_val_add (&s->next_tick, timeout * 1000);
if (priv_conn_recheck_on_timeout (agent, p)) break;
default:
break; break;
}
j = next;
}
if (p->state != NICE_CHECK_IN_PROGRESS)
continue;
/* process the first stun transaction of the list */
stun = p->stun_transactions->data;
if (!priv_timer_expired (&stun->next_tick, &now))
continue;
switch (stun_timer_refresh (&stun->timer)) {
case STUN_USAGE_TIMER_RETURN_TIMEOUT:
timer_return_timeout:
/* case: error, abort processing */ /* case: error, abort processing */
nice_address_to_string (&p->local->addr, tmpbuf1); nice_address_to_string (&p->local->addr, tmpbuf1);
nice_address_to_string (&p->remote->addr, tmpbuf2); nice_address_to_string (&p->remote->addr, tmpbuf2);
nice_debug ("Agent %p : Retransmissions failed, giving up on connectivity check %p", agent, p); nice_debug ("Agent %p : Retransmissions failed, giving up on "
"connectivity check %p", agent, p);
nice_debug ("Agent %p : Failed pair is [%s]:%u --> [%s]:%u", agent, nice_debug ("Agent %p : Failed pair is [%s]:%u --> [%s]:%u", agent,
tmpbuf1, nice_address_get_port (&p->local->addr), tmpbuf1, nice_address_get_port (&p->local->addr),
tmpbuf2, nice_address_get_port (&p->remote->addr)); tmpbuf2, nice_address_get_port (&p->remote->addr));
...@@ -732,42 +799,33 @@ timer_timeout: ...@@ -732,42 +799,33 @@ timer_timeout:
* a pair with a higher priority than this in-progress pair, * a pair with a higher priority than this in-progress pair,
* ICE spec, sect 8.1.2 "Updating States", item 2.2 * ICE spec, sect 8.1.2 "Updating States", item 2.2
*/ */
if (!p->retransmit_on_timeout) if (!p->retransmit)
goto timer_timeout; goto timer_return_timeout;
/* case: conncheck cancelled due to in-progress incoming
* check, requeing the pair, ICE spec, sect 7.2.1.4
* "Triggered Checks", "If the state of that pair is
* In-Progress..."
*/
if (priv_conn_recheck_on_timeout (agent, p))
break;
/* case: not ready, so schedule a new timeout */ /* case: not ready, so schedule a new timeout */
timeout = stun_timer_remainder (&p->timer); timeout = stun_timer_remainder (&stun->timer);
nice_debug ("Agent %p :STUN transaction retransmitted on pair %p " nice_debug ("Agent %p :STUN transaction retransmitted on pair %p "
"(timer=%d/%d %d/%dms).", "(timer=%d/%d %d/%dms).",
agent, p, agent, p,
p->timer.retransmissions, p->timer.max_retransmissions, stun->timer.retransmissions, stun->timer.max_retransmissions,
p->timer.delay - timeout, p->timer.delay); stun->timer.delay - timeout, stun->timer.delay);
agent_socket_send (p->sockptr, &p->remote->addr, agent_socket_send (p->sockptr, &p->remote->addr,
stun_message_length (&p->stun_message), stun_message_length (&stun->message),
(gchar *)p->stun_buffer); (gchar *)stun->buffer);
/* note: convert from milli to microseconds for g_time_val_add() */ /* note: convert from milli to microseconds for g_time_val_add() */
p->next_tick = now; stun->next_tick = now;
g_time_val_add (&p->next_tick, timeout * 1000); g_time_val_add (&stun->next_tick, timeout * 1000);
return TRUE; return TRUE;
case STUN_USAGE_TIMER_RETURN_SUCCESS: case STUN_USAGE_TIMER_RETURN_SUCCESS:
timeout = stun_timer_remainder (&p->timer); timeout = stun_timer_remainder (&stun->timer);
/* note: convert from milli to microseconds for g_time_val_add() */ /* note: convert from milli to microseconds for g_time_val_add() */
p->next_tick = now; stun->next_tick = now;
g_time_val_add (&p->next_tick, timeout * 1000); g_time_val_add (&stun->next_tick, timeout * 1000);
keep_timer_going = TRUE; keep_timer_going = TRUE;
break; break;
...@@ -948,7 +1006,6 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) ...@@ -948,7 +1006,6 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent)
g_assert (p->state == NICE_CHECK_SUCCEEDED); g_assert (p->state == NICE_CHECK_SUCCEEDED);
nice_debug ("Agent %p : restarting check %p with " nice_debug ("Agent %p : restarting check %p with "
"USE-CANDIDATE attrib (regular nomination)", agent, p); "USE-CANDIDATE attrib (regular nomination)", agent, p);
p->recheck_on_timeout = FALSE;
p->use_candidate_on_next_check = TRUE; p->use_candidate_on_next_check = TRUE;
priv_add_pair_to_triggered_check_queue (agent, p); priv_add_pair_to_triggered_check_queue (agent, p);
keep_timer_going = TRUE; keep_timer_going = TRUE;
...@@ -972,7 +1029,6 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) ...@@ -972,7 +1029,6 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent)
p->state == NICE_CHECK_DISCOVERED)) { p->state == NICE_CHECK_DISCOVERED)) {
nice_debug ("Agent %p : restarting check %p as the nominated pair.", agent, p); nice_debug ("Agent %p : restarting check %p as the nominated pair.", agent, p);
p->nominated = TRUE; p->nominated = TRUE;
p->recheck_on_timeout = FALSE;
priv_update_selected_pair (agent, component, p); priv_update_selected_pair (agent, component, p);
priv_add_pair_to_triggered_check_queue (agent, p); priv_add_pair_to_triggered_check_queue (agent, p);
keep_timer_going = TRUE; keep_timer_going = TRUE;
...@@ -2186,7 +2242,6 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, ...@@ -2186,7 +2242,6 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent,
} }
pair->prflx_priority = ensure_unique_priority (component, pair->prflx_priority = ensure_unique_priority (component,
peer_reflexive_candidate_priority (agent, local)); peer_reflexive_candidate_priority (agent, local));
pair->retransmit_on_timeout = TRUE;
stream->conncheck_list = g_slist_insert_sorted (stream->conncheck_list, pair, stream->conncheck_list = g_slist_insert_sorted (stream->conncheck_list, pair,
(GCompareFunc)conn_check_compare); (GCompareFunc)conn_check_compare);
...@@ -2381,8 +2436,7 @@ static void conn_check_free_item (gpointer data) ...@@ -2381,8 +2436,7 @@ static void conn_check_free_item (gpointer data)
if (pair->agent) if (pair->agent)
priv_remove_pair_from_triggered_check_queue (pair->agent, pair); priv_remove_pair_from_triggered_check_queue (pair->agent, pair);
pair->stun_message.buffer = NULL; priv_free_all_stun_transactions (pair, NULL);
pair->stun_message.buffer_len = 0;
g_slice_free (CandidateCheckPair, pair); g_slice_free (CandidateCheckPair, pair);
} }
...@@ -2655,6 +2709,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) ...@@ -2655,6 +2709,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair)
bool cand_use = controlling; bool cand_use = controlling;
size_t buffer_len; size_t buffer_len;
unsigned int timeout; unsigned int timeout;
StunTransaction *stun;
if (!agent_find_component (agent, pair->stream_id, pair->component_id, if (!agent_find_component (agent, pair->stream_id, pair->component_id,
&stream, &component)) &stream, &component))
...@@ -2718,13 +2773,13 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) ...@@ -2718,13 +2773,13 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair)
if (uname_len == 0) { if (uname_len == 0) {
nice_debug ("Agent %p: no credentials found, cancelling conncheck", agent); nice_debug ("Agent %p: no credentials found, cancelling conncheck", agent);
pair->stun_message.buffer = NULL;
pair->stun_message.buffer_len = 0;
return -1; return -1;
} }
stun = priv_add_stun_transaction (pair);
buffer_len = stun_usage_ice_conncheck_create (&component->stun_agent, buffer_len = stun_usage_ice_conncheck_create (&component->stun_agent,
&pair->stun_message, pair->stun_buffer, sizeof(pair->stun_buffer), &stun->message, stun->buffer, sizeof(stun->buffer),
uname, uname_len, password, password_len, uname, uname_len, password, password_len,
cand_use, controlling, pair->prflx_priority, cand_use, controlling, pair->prflx_priority,
agent->tie_breaker, agent->tie_breaker,
...@@ -2732,7 +2787,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) ...@@ -2732,7 +2787,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair)
agent_to_ice_compatibility (agent)); agent_to_ice_compatibility (agent));
nice_debug ("Agent %p: conncheck created %zd - %p", agent, buffer_len, nice_debug ("Agent %p: conncheck created %zd - %p", agent, buffer_len,
pair->stun_message.buffer); stun->message.buffer);
if (agent->compatibility == NICE_COMPATIBILITY_MSN || if (agent->compatibility == NICE_COMPATIBILITY_MSN ||
agent->compatibility == NICE_COMPATIBILITY_OC2007) { agent->compatibility == NICE_COMPATIBILITY_OC2007) {
...@@ -2741,50 +2796,20 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) ...@@ -2741,50 +2796,20 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair)
if (buffer_len == 0) { if (buffer_len == 0) {
nice_debug ("Agent %p: buffer is empty, cancelling conncheck", agent); nice_debug ("Agent %p: buffer is empty, cancelling conncheck", agent);
pair->stun_message.buffer = NULL; priv_remove_stun_transaction (pair, stun, component);
pair->stun_message.buffer_len = 0;
return -1; return -1;
} }
if (nice_socket_is_reliable(pair->sockptr)) { if (nice_socket_is_reliable(pair->sockptr)) {
timeout = agent->stun_reliable_timeout; timeout = agent->stun_reliable_timeout;
stun_timer_start_reliable(&pair->timer, timeout); stun_timer_start_reliable(&stun->timer, timeout);
} else {
StunTimer *timer = &pair->timer;
if (pair->recheck_on_timeout) {
GTimeVal now;
/* The pair recheck on timeout can easily cause repetitive rechecks in
* a ping-pong effect, if both peers with the same behaviour try to
* check the same pair almost simultaneously, and if the network rtt
* is greater than the initial timer rto. The reply to the initial
* stun request may arrive after the in-progress conncheck
* cancellation (described in RFC 5245, sect 7.2.1.4). Cancellation
* creates a new stun request, and forgets the initial one.
* The conncheck timer is restarted with the same initial value,
* so the same situation happens again later.
*
* We choose to avoid resetting the timer in such situation.
* After enough retransmissions, the timeout delay becomes
* longer than the rtt, and the stun reply can be handled.
*/
g_get_current_time (&now);
timeout = priv_timer_remainder (&pair->next_tick, &now);
nice_debug("Agent %p : reusing timer of pair %p: %d/%d %d/%dms",
agent, pair,
timer->retransmissions, timer->max_retransmissions,
timer->delay - timeout,
timer->delay);
} else { } else {
timeout = priv_compute_conncheck_timer (agent, stream); timeout = priv_compute_conncheck_timer (agent, stream);
stun_timer_start (timer, timeout, agent->stun_max_retransmissions); stun_timer_start (&stun->timer, timeout, agent->stun_max_retransmissions);
}
pair->recheck_on_timeout = FALSE;
} }
g_get_current_time (&pair->next_tick); g_get_current_time (&stun->next_tick);
g_time_val_add (&pair->next_tick, timeout * 1000); g_time_val_add (&stun->next_tick, timeout * 1000);
/* TCP-ACTIVE candidate must create a new socket before sending /* TCP-ACTIVE candidate must create a new socket before sending
* by connecting to the peer. The new socket is stored in the candidate * by connecting to the peer. The new socket is stored in the candidate
...@@ -2814,10 +2839,10 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) ...@@ -2814,10 +2839,10 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair)
} }
/* send the conncheck */ /* send the conncheck */
agent_socket_send (pair->sockptr, &pair->remote->addr, agent_socket_send (pair->sockptr, &pair->remote->addr,
buffer_len, (gchar *)pair->stun_buffer); buffer_len, (gchar *)stun->buffer);
if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2)
ms_ice2_legacy_conncheck_send (&pair->stun_message, pair->sockptr, ms_ice2_legacy_conncheck_send (&stun->message, pair->sockptr,
&pair->remote->addr); &pair->remote->addr);
return 0; return 0;
...@@ -2881,8 +2906,7 @@ static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, gu ...@@ -2881,8 +2906,7 @@ static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, gu
(p->state == NICE_CHECK_WAITING && like_in_progress)) { (p->state == NICE_CHECK_WAITING && like_in_progress)) {
if (highest_nominated_priority != 0 && if (highest_nominated_priority != 0 &&
p->priority < highest_nominated_priority) { p->priority < highest_nominated_priority) {
p->retransmit_on_timeout = FALSE; p->retransmit = FALSE;
p->recheck_on_timeout = FALSE;
nice_debug ("Agent %p : pair %p will not be retransmitted.", nice_debug ("Agent %p : pair %p will not be retransmitted.",
agent, p); agent, p);
} else { } else {
...@@ -2952,45 +2976,19 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str ...@@ -2952,45 +2976,19 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str
* for that pair. The controlling role of this new check may * for that pair. The controlling role of this new check may
* be different from the role of this cancelled check. * be different from the role of this cancelled check.
* *
* note: the flag retransmit_on_timeout unset means that * note: the flag retransmit unset means that
* another pair, with a higher priority is already nominated, * another pair, with a higher priority is already nominated,
* so there's no reason to recheck this pair, since it can in * so there's no reason to recheck this pair, since it can in
* no way replace the nominated one. * no way replace the nominated one.
*/ */
if (!nice_socket_is_reliable (p->sockptr) && if (!nice_socket_is_reliable (p->sockptr) && p->retransmit) {
p->retransmit_on_timeout) { nice_debug ("Agent %p : pair %p added for a triggered check.",
nice_debug ("Agent %p : pair %p will be rechecked " agent, p);
"on stun timer timeout.", agent, p); priv_add_pair_to_triggered_check_queue (agent, p);
/* this flag will determine the action at the retransmission
* timeout of the stun timer
*/
p->recheck_on_timeout = TRUE;
} }
break; 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 dribble 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;
case NICE_CHECK_FAILED: case NICE_CHECK_FAILED:
/* 7.2.1.4 Triggered Checks if (p->retransmit) {
* If the state of the pair is Failed, it is changed to Waiting
* and the agent MUST create a new connectivity check for that
* pair (representing a new STUN Binding request transaction), by
* enqueueing the pair in the triggered check queue.
*
* note: the flag retransmit_on_timeout unset means that
* another pair, with a higher priority is already nominated,
* we apply the same strategy than with an in-progress pair
* above.
*/
if (p->retransmit_on_timeout) {
nice_debug ("Agent %p : pair %p added for a triggered check.", nice_debug ("Agent %p : pair %p added for a triggered check.",
agent, p); agent, p);
priv_add_pair_to_triggered_check_queue (agent, p); priv_add_pair_to_triggered_check_queue (agent, p);
...@@ -3004,6 +3002,17 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str ...@@ -3004,6 +3002,17 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str
} }
} }
break; 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 dribble 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: default:
break; break;
} }
...@@ -3260,16 +3269,8 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * ...@@ -3260,16 +3269,8 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent *
if (new_pair == p) if (new_pair == p)
p->valid = TRUE; p->valid = TRUE;
p->state = NICE_CHECK_SUCCEEDED; p->state = NICE_CHECK_SUCCEEDED;
/* note: we cancel the potential in-progress transaction
* cancellation, caused by sect 7.2.1.4 "Triggered Checks", if
* we receive a valid reply before transmission timeout...
*/
p->recheck_on_timeout = FALSE;
/* ... or just after the transmission timeout, while the pair is
* temporarily put on the triggered check list on the way to be
* be rechecked: we remove it from the list too.
*/
priv_remove_pair_from_triggered_check_queue (agent, p); priv_remove_pair_from_triggered_check_queue (agent, p);
priv_free_all_stun_transactions (p, component);
nice_debug ("Agent %p : conncheck %p SUCCEEDED.", agent, p); nice_debug ("Agent %p : conncheck %p SUCCEEDED.", agent, p);
nice_component_add_valid_candidate (component, remote_candidate); nice_component_add_valid_candidate (component, remote_candidate);
} }
...@@ -3304,8 +3305,8 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * ...@@ -3304,8 +3305,8 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent *
* Succeeded, RFC 5245, 7.1.3.2.3, "Updating Pair States" * Succeeded, RFC 5245, 7.1.3.2.3, "Updating Pair States"
*/ */
p->state = NICE_CHECK_SUCCEEDED; p->state = NICE_CHECK_SUCCEEDED;
p->recheck_on_timeout = FALSE;
priv_remove_pair_from_triggered_check_queue (agent, p); priv_remove_pair_from_triggered_check_queue (agent, p);
priv_free_all_stun_transactions (p, component);
nice_debug ("Agent %p : conncheck %p SUCCEEDED, %p DISCOVERED.", nice_debug ("Agent %p : conncheck %p SUCCEEDED, %p DISCOVERED.",
agent, p, new_pair); agent, p, new_pair);
} }
...@@ -3331,7 +3332,8 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre ...@@ -3331,7 +3332,8 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre
struct sockaddr addr; struct sockaddr addr;
} sockaddr; } sockaddr;
socklen_t socklen = sizeof (sockaddr); socklen_t socklen = sizeof (sockaddr);
GSList *i; GSList *i, *j;
guint k;
StunUsageIceReturn res; StunUsageIceReturn res;
StunTransactionId discovery_id; StunTransactionId discovery_id;
StunTransactionId response_id; StunTransactionId response_id;
...@@ -3340,10 +3342,10 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre ...@@ -3340,10 +3342,10 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre
for (i = stream->conncheck_list; i; i = i->next) { for (i = stream->conncheck_list; i; i = i->next) {
CandidateCheckPair *p = i->data; CandidateCheckPair *p = i->data;
if (p->stun_message.buffer == NULL) for (j = p->stun_transactions, k = 0; j; j = j->next, k++) {
continue; StunTransaction *stun = j->data;
stun_message_id (&p->stun_message, discovery_id); stun_message_id (&stun->message, discovery_id);
if (memcmp (discovery_id, response_id, sizeof(StunTransactionId))) if (memcmp (discovery_id, response_id, sizeof(StunTransactionId)))
continue; continue;
...@@ -3352,10 +3354,10 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre ...@@ -3352,10 +3354,10 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre
&sockaddr.storage, &socklen, &sockaddr.storage, &socklen,
agent_to_ice_compatibility (agent)); agent_to_ice_compatibility (agent));
nice_debug ("Agent %p : stun_bind_process/conncheck for %p: " nice_debug ("Agent %p : stun_bind_process/conncheck for %p: "
"%s,res=%s.", "%s,res=%s,stun#=%d.",
agent, p, agent, p,
agent->controlling_mode ? "controlling" : "controlled", agent->controlling_mode ? "controlling" : "controlled",
priv_ice_return_to_string (res)); priv_ice_return_to_string (res), k);
if (res == STUN_USAGE_ICE_RETURN_SUCCESS || if (res == STUN_USAGE_ICE_RETURN_SUCCESS ||
res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) {
...@@ -3364,15 +3366,13 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre ...@@ -3364,15 +3366,13 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre
CandidateCheckPair *ok_pair = NULL; CandidateCheckPair *ok_pair = NULL;
nice_debug ("Agent %p : conncheck %p MATCHED.", agent, p); nice_debug ("Agent %p : conncheck %p MATCHED.", agent, p);
p->stun_message.buffer = NULL; priv_remove_stun_transaction (p, stun, component);
p->stun_message.buffer_len = 0;
/* step: verify that response came from the same IP address we /* step: verify that response came from the same IP address we
* sent the original request to (see 7.1.2.1. "Failure * sent the original request to (see 7.1.2.1. "Failure
* Cases") */ * Cases") */
if (nice_address_equal (from, &p->remote->addr) == FALSE) { if (nice_address_equal (from, &p->remote->addr) == FALSE) {
candidate_check_pair_fail (stream, agent, p);
p->state = NICE_CHECK_FAILED;
if (nice_debug_is_enabled ()) { if (nice_debug_is_enabled ()) {
gchar tmpbuf[INET6_ADDRSTRLEN]; gchar tmpbuf[INET6_ADDRSTRLEN];
gchar tmpbuf2[INET6_ADDRSTRLEN]; gchar tmpbuf2[INET6_ADDRSTRLEN];
...@@ -3488,6 +3488,9 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre ...@@ -3488,6 +3488,9 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre
states" and 8.1.2 "Updating States", ID-19) */ states" and 8.1.2 "Updating States", ID-19) */
priv_update_check_list_state_for_ready (agent, stream, component); priv_update_check_list_state_for_ready (agent, stream, component);
} else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) { } else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) {
guint64 tie;
gboolean controlled_mode;
/* case: role conflict error, need to restart with new role */ /* case: role conflict error, need to restart with new role */
nice_debug ("Agent %p : conncheck %p ROLE CONFLICT, restarting", agent, p); nice_debug ("Agent %p : conncheck %p ROLE CONFLICT, restarting", agent, p);
...@@ -3505,29 +3508,21 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre ...@@ -3505,29 +3508,21 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre
* valid, if we retry the check after the role of both peers * valid, if we retry the check after the role of both peers
* has been fixed. * has been fixed.
*/ */
controlled_mode = (stun_message_find64 (&stun->message,
if (p->stun_message.buffer != NULL) {
guint64 tie;
gboolean controlled_mode;
controlled_mode = (stun_message_find64 (&p->stun_message,
STUN_ATTRIBUTE_ICE_CONTROLLED, &tie) == STUN_ATTRIBUTE_ICE_CONTROLLED, &tie) ==
STUN_MESSAGE_RETURN_SUCCESS); STUN_MESSAGE_RETURN_SUCCESS);
priv_check_for_role_conflict (agent, controlled_mode); priv_check_for_role_conflict (agent, controlled_mode);
priv_remove_stun_transaction (p, stun, component);
p->stun_message.buffer = NULL;
p->stun_message.buffer_len = 0;
priv_add_pair_to_triggered_check_queue (agent, p); priv_add_pair_to_triggered_check_queue (agent, p);
}
} else { } else {
/* case: STUN error, the check STUN context was freed */ /* case: STUN error, the check STUN context was freed */
nice_debug ("Agent %p : conncheck %p FAILED.", agent, p); nice_debug ("Agent %p : conncheck %p FAILED.", agent, p);
p->stun_message.buffer = NULL; candidate_check_pair_fail (stream, agent, p);
p->stun_message.buffer_len = 0;
} }
return TRUE; return TRUE;
} }
}
return FALSE; return FALSE;
} }
......
...@@ -71,6 +71,15 @@ typedef enum ...@@ -71,6 +71,15 @@ typedef enum
} NiceCheckState; } NiceCheckState;
typedef struct _CandidateCheckPair CandidateCheckPair; typedef struct _CandidateCheckPair CandidateCheckPair;
typedef struct _StunTransaction StunTransaction;
struct _StunTransaction
{
GTimeVal next_tick; /* next tick timestamp */
StunTimer timer;
uint8_t buffer[STUN_MAX_MESSAGE_SIZE_IPV6];
StunMessage message;
};
struct _CandidateCheckPair struct _CandidateCheckPair
{ {
...@@ -86,16 +95,12 @@ struct _CandidateCheckPair ...@@ -86,16 +95,12 @@ struct _CandidateCheckPair
gboolean valid; gboolean valid;
gboolean use_candidate_on_next_check; gboolean use_candidate_on_next_check;
gboolean mark_nominated_on_response_arrival; gboolean mark_nominated_on_response_arrival;
gboolean recheck_on_timeout; gboolean retransmit; /* if the first stun request must be retransmitted */
gboolean retransmit_on_timeout; CandidateCheckPair *discovered_pair;
struct _CandidateCheckPair *discovered_pair; CandidateCheckPair *succeeded_pair;
struct _CandidateCheckPair *succeeded_pair;
guint64 priority; guint64 priority;
guint32 prflx_priority; guint32 prflx_priority;
GTimeVal next_tick; /* next tick timestamp */ GSList *stun_transactions; /* a list of ongoing stun requests */
StunTimer timer;
uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6];
StunMessage stun_message;
}; };
int conn_check_add_for_candidate (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidate *remote); int conn_check_add_for_candidate (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidate *remote);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment