From a4bacb2fe2ff06ccb1a2d7f7c0b62bd41674565b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Mon, 3 Apr 2017 14:30:10 -0400 Subject: [PATCH 01/70] Version 0.1.14.1 --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 5fabdb4..b39bfe3 100644 --- a/configure.ac +++ b/configure.ac @@ -5,8 +5,8 @@ dnl Always compile with -Wall; if --enable-compile-warnings=error is passed, dnl also use -Werror. git and pre-releases default to -Werror dnl use a three digit version number for releases, and four for cvs/prerelease -AC_INIT([libnice],[0.1.14]) -LIBNICE_RELEASE="yes" +AC_INIT([libnice],[0.1.14.1]) +LIBNICE_RELEASE="no" AC_CANONICAL_TARGET -- 2.13.6 From 59ce41dfb837adf4222b25490cde2e394384ad15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Par=C3=ADs=20D=C3=ADaz?= Date: Fri, 31 Mar 2017 20:20:38 -0400 Subject: [PATCH 02/70] conncheck: consider answer received when remote credentials are set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consider that the answer is received when remote credentials are set instead of when a remote candidate is set, which could not happen or could cause more delay for the connection establishment. Ported to git master by Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1704 --- agent/agent.c | 4 +- agent/conncheck.c | 225 +++++++++++++++++++++++++++--------------------------- agent/conncheck.h | 2 +- 3 files changed, 117 insertions(+), 114 deletions(-) diff --git a/agent/agent.c b/agent/agent.c index 555fd16..4d9381c 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -3240,6 +3240,8 @@ nice_agent_set_remote_credentials ( g_strlcpy (stream->remote_ufrag, ufrag, NICE_STREAM_MAX_UFRAG); g_strlcpy (stream->remote_password, pwd, NICE_STREAM_MAX_PWD); + conn_check_remote_credentials_set(agent, stream); + ret = TRUE; goto done; } @@ -3342,8 +3344,6 @@ _set_remote_candidates_locked (NiceAgent *agent, NiceStream *stream, } } - conn_check_remote_candidates_set(agent, stream, component); - if (added > 0) { conn_check_schedule_next (agent); } diff --git a/agent/conncheck.c b/agent/conncheck.c index dda2f2f..2abbc5e 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -1243,124 +1243,124 @@ static GSList *prune_cancelled_conn_check (GSList *conncheck_list) /* * Handle any processing steps for connectivity checks after - * remote candidates have been set. This function handles + * remote credentials have been set. This function handles * the special case where answerer has sent us connectivity - * checks before the answer (containing candidate information), + * checks before the answer (containing credentials information), * reaches us. The special case is documented in sect 7.2 * if ICE spec (ID-19). */ -void conn_check_remote_candidates_set(NiceAgent *agent, NiceStream *stream, NiceComponent *component) +void conn_check_remote_credentials_set(NiceAgent *agent, NiceStream *stream) { - GSList *j, *k, *l, *m, *n; + GSList *j, *k, *l, *m, *n, *o; for (j = stream->conncheck_list; j ; j = j->next) { CandidateCheckPair *pair = j->data; - if (pair->component_id == component->id) { - gboolean match = FALSE; - - /* performn delayed processing of spec steps section 7.2.1.4, - and section 7.2.1.5 */ - priv_preprocess_conn_check_pending_data (agent, stream, component, pair); - - for (k = component->incoming_checks; k; k = k->next) { - IncomingCheck *icheck = k->data; - /* sect 7.2.1.3., "Learning Peer Reflexive Candidates", has to - * be handled separately */ - for (l = component->remote_candidates; l; l = l->next) { - NiceCandidate *cand = l->data; - if (nice_address_equal (&icheck->from, &cand->addr)) { - match = TRUE; - break; - } - } - if (match != TRUE) { - /* note: we have gotten an incoming connectivity check from - * an address that is not a known remote candidate */ - - NiceCandidate *local_candidate = NULL; - NiceCandidate *remote_candidate = NULL; - - if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE || - agent->compatibility == NICE_COMPATIBILITY_MSN || - agent->compatibility == NICE_COMPATIBILITY_OC2007) { - /* We need to find which local candidate was used */ - uint8_t uname[NICE_STREAM_MAX_UNAME]; - guint uname_len; - - nice_debug ("Agent %p: We have a peer-reflexive candidate in a " - "stored pending check", agent); - - for (m = component->remote_candidates; - m != NULL && remote_candidate == NULL; m = m->next) { - for (n = component->local_candidates; n; n = n->next) { - NiceCandidate *rcand = m->data; - NiceCandidate *lcand = n->data; - - uname_len = priv_create_username (agent, stream, - component->id, rcand, lcand, - uname, sizeof (uname), TRUE); - - stun_debug ("pending check, comparing usernames of len %d and %d, equal=%d", - icheck->username_len, uname_len, - icheck->username && uname_len == icheck->username_len && - memcmp (uname, icheck->username, icheck->username_len) == 0); - stun_debug_bytes (" first username: ", - icheck->username, - icheck->username? icheck->username_len : 0); - stun_debug_bytes (" second username: ", uname, uname_len); - - if (icheck->username && - uname_len == icheck->username_len && - memcmp (uname, icheck->username, icheck->username_len) == 0) { - local_candidate = lcand; - remote_candidate = rcand; - break; - } - } - } - } else { - for (l = component->local_candidates; l; l = l->next) { - NiceCandidate *cand = l->data; - if (nice_address_equal (&cand->addr, &icheck->local_socket->addr)) { - local_candidate = cand; + NiceComponent *component = + nice_stream_find_component_by_id (stream, pair->component_id); + gboolean match = FALSE; + + /* performn delayed processing of spec steps section 7.2.1.4, + and section 7.2.1.5 */ + priv_preprocess_conn_check_pending_data (agent, stream, component, pair); + + for (k = component->incoming_checks; k; k = k->next) { + IncomingCheck *icheck = k->data; + /* sect 7.2.1.3., "Learning Peer Reflexive Candidates", has to + * be handled separately */ + for (l = component->remote_candidates; l; l = l->next) { + NiceCandidate *cand = l->data; + if (nice_address_equal (&icheck->from, &cand->addr)) { + match = TRUE; + break; + } + } + if (match != TRUE) { + /* note: we have gotten an incoming connectivity check from + * an address that is not a known remote candidate */ + + NiceCandidate *local_candidate = NULL; + NiceCandidate *remote_candidate = NULL; + + if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE || + agent->compatibility == NICE_COMPATIBILITY_MSN || + agent->compatibility == NICE_COMPATIBILITY_OC2007) { + /* We need to find which local candidate was used */ + uint8_t uname[NICE_STREAM_MAX_UNAME]; + guint uname_len; + + nice_debug ("Agent %p: We have a peer-reflexive candidate in a " + "stored pending check", agent); + + for (m = component->remote_candidates; + m != NULL && remote_candidate == NULL; m = m->next) { + for (n = component->local_candidates; n; n = n->next) { + NiceCandidate *rcand = m->data; + NiceCandidate *lcand = n->data; + + uname_len = priv_create_username (agent, stream, + component->id, rcand, lcand, + uname, sizeof (uname), TRUE); + + stun_debug ("pending check, comparing usernames of len %d and %d, equal=%d", + icheck->username_len, uname_len, + icheck->username && uname_len == icheck->username_len && + memcmp (uname, icheck->username, icheck->username_len) == 0); + stun_debug_bytes (" first username: ", + icheck->username, + icheck->username? icheck->username_len : 0); + stun_debug_bytes (" second username: ", uname, uname_len); + + if (icheck->username && + uname_len == icheck->username_len && + memcmp (uname, icheck->username, icheck->username_len) == 0) { + local_candidate = lcand; + remote_candidate = rcand; break; } } } - - if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE && - local_candidate == NULL) { - /* if we couldn't match the username, then the matching remote - * candidate hasn't been received yet.. we must wait */ - nice_debug ("Agent %p : Username check failed. pending check has " - "to wait to be processed", agent); - } else { - NiceCandidate *candidate; - - nice_debug ("Agent %p : Discovered peer reflexive from early i-check", - agent); - candidate = - discovery_learn_remote_peer_reflexive_candidate (agent, - stream, - component, - icheck->priority, - &icheck->from, - icheck->local_socket, - local_candidate, remote_candidate); - if (candidate) { - if (local_candidate && - local_candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) - priv_conn_check_add_for_candidate_pair_matched (agent, - stream->id, component, local_candidate, candidate, NICE_CHECK_DISCOVERED); - else - conn_check_add_for_candidate (agent, stream->id, component, candidate); - - if (icheck->use_candidate) - priv_mark_pair_nominated (agent, stream, component, local_candidate, candidate); - priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, candidate, icheck->use_candidate); + } else { + for (l = component->local_candidates; l; l = l->next) { + NiceCandidate *cand = l->data; + if (nice_address_equal (&cand->addr, &icheck->local_socket->addr)) { + local_candidate = cand; + break; } } } + + if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE && + local_candidate == NULL) { + /* if we couldn't match the username, then the matching remote + * candidate hasn't been received yet.. we must wait */ + nice_debug ("Agent %p : Username check failed. pending check has " + "to wait to be processed", agent); + } else { + NiceCandidate *candidate; + + nice_debug ("Agent %p : Discovered peer reflexive from early i-check", + agent); + candidate = + discovery_learn_remote_peer_reflexive_candidate (agent, + stream, + component, + icheck->priority, + &icheck->from, + icheck->local_socket, + local_candidate, remote_candidate); + if (candidate) { + if (local_candidate && + local_candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) + priv_conn_check_add_for_candidate_pair_matched (agent, + stream->id, component, local_candidate, candidate, NICE_CHECK_DISCOVERED); + else + conn_check_add_for_candidate (agent, stream->id, component, candidate); + + if (icheck->use_candidate) + priv_mark_pair_nominated (agent, stream, component, local_candidate, candidate); + priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, candidate, icheck->use_candidate); + } + } } } } @@ -1368,9 +1368,12 @@ void conn_check_remote_candidates_set(NiceAgent *agent, NiceStream *stream, Nice /* Once we process the pending checks, we should free them to avoid * reprocessing them again if a dribble-mode set_remote_candidates * is called */ - g_slist_free_full (component->incoming_checks, - (GDestroyNotify) incoming_check_free); - component->incoming_checks = NULL; + for (o = stream->components; o; o = o->next) { + NiceComponent *component = o->data; + g_slist_free_full (component->incoming_checks, + (GDestroyNotify) incoming_check_free); + component->incoming_checks = NULL; + } stream->conncheck_list = prune_cancelled_conn_check (stream->conncheck_list); @@ -3628,14 +3631,14 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, if (stream->initial_binding_request_received != TRUE) agent_signal_initial_binding_request_received (agent, stream); - if (component->remote_candidates && remote_candidate == NULL) { + if (remote_candidate == NULL) { nice_debug ("Agent %p : No matching remote candidate for incoming check ->" "peer-reflexive candidate.", agent); remote_candidate = discovery_learn_remote_peer_reflexive_candidate ( agent, stream, component, priority, from, nicesock, local_candidate, remote_candidate2 ? remote_candidate2 : remote_candidate); - if(remote_candidate) { + if(remote_candidate && stream->remote_ufrag != NULL) { if (local_candidate && local_candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) { CandidateCheckPair *pair; @@ -3654,10 +3657,10 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, priv_reply_to_conn_check (agent, stream, component, local_candidate, remote_candidate, from, nicesock, rbuf_len, &msg, use_candidate); - if (component->remote_candidates == NULL) { + if (stream->remote_ufrag == NULL) { /* case: We've got a valid binding request to a local candidate - * but we do not yet know remote credentials nor - * candidates. As per sect 7.2 of ICE (ID-19), we send a reply + * but we do not yet know remote credentials. + * As per sect 7.2 of ICE (ID-19), we send a reply * immediately but postpone all other processing until * we get information about the remote candidates */ diff --git a/agent/conncheck.h b/agent/conncheck.h index 431c606..10319cc 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -105,7 +105,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair); void conn_check_prune_stream (NiceAgent *agent, NiceStream *stream); gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *udp_socket, const NiceAddress *from, gchar *buf, guint len); gint conn_check_compare (const CandidateCheckPair *a, const CandidateCheckPair *b); -void conn_check_remote_candidates_set(NiceAgent *agent, NiceStream *stream, NiceComponent *component); +void conn_check_remote_credentials_set(NiceAgent *agent, NiceStream *stream); NiceCandidateTransport conn_check_match_transport (NiceCandidateTransport transport); void conn_check_prune_socket (NiceAgent *agent, NiceStream *stream, NiceComponent *component, -- 2.13.6 From 0de1657e4d15d4f1911ab1fad84ea23e7013070f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 4 Apr 2017 12:25:50 -0400 Subject: [PATCH 03/70] conncheck: Use the right test for empty remote_frag It's now an array, not a pointer, so needs to test to emptyness. It's a bugfix on the previous commit, 59ce41df --- agent/conncheck.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 2abbc5e..7096b42 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -3638,7 +3638,7 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, agent, stream, component, priority, from, nicesock, local_candidate, remote_candidate2 ? remote_candidate2 : remote_candidate); - if(remote_candidate && stream->remote_ufrag != NULL) { + if(remote_candidate && stream->remote_ufrag[0]) { if (local_candidate && local_candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) { CandidateCheckPair *pair; @@ -3657,7 +3657,7 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, priv_reply_to_conn_check (agent, stream, component, local_candidate, remote_candidate, from, nicesock, rbuf_len, &msg, use_candidate); - if (stream->remote_ufrag == NULL) { + if (stream->remote_ufrag[0] == 0) { /* case: We've got a valid binding request to a local candidate * but we do not yet know remote credentials. * As per sect 7.2 of ICE (ID-19), we send a reply -- 2.13.6 From 0672758b9621801c8f0d9e3c920370983b267a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 4 Apr 2017 12:29:29 -0400 Subject: [PATCH 04/70] agent: Don't set variable that won't be used It exits the loop immediately, so no point to set the variable. And it makes the clang static analyzer happy. --- agent/agent.c | 1 - 1 file changed, 1 deletion(-) diff --git a/agent/agent.c b/agent/agent.c index 4d9381c..8ba99bc 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -4245,7 +4245,6 @@ nice_agent_recv_messages_blocking_or_nonblocking (NiceAgent *agent, "Component removed during call."); component = NULL; - error_reported = TRUE; goto recv_error; } -- 2.13.6 From db05e8b0fdc713df93cd6a4c3914e5aee38b2391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 4 Apr 2017 12:30:27 -0400 Subject: [PATCH 05/70] Make clang-analyzer happy Various little things, none of which should make a functional difference. --- agent/agent.c | 1 - agent/conncheck.c | 2 +- socket/udp-turn.c | 5 ++--- stun/usages/bind.c | 4 +++- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/agent/agent.c b/agent/agent.c index 8ba99bc..28d7ccf 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -1552,7 +1552,6 @@ pseudo_tcp_socket_recv_messages (PseudoTcpSocket *self, if (len == 0) { /* Reached EOS. */ - len = 0; goto done; } else if (len < 0 && pseudo_tcp_socket_get_error (self) == EWOULDBLOCK) { diff --git a/agent/conncheck.c b/agent/conncheck.c index 7096b42..1dc13dd 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -606,7 +606,7 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) /* step: when there's no pair in the Waiting state, * unfreeze a new pair and check it */ - res = priv_conn_check_unfreeze_next (agent); + priv_conn_check_unfreeze_next (agent); for (i = agent->streams; i ; i = i->next) { NiceStream *stream = i->data; diff --git a/socket/udp-turn.c b/socket/udp-turn.c index cc3409b..a9c57e5 100644 --- a/socket/udp-turn.c +++ b/socket/udp-turn.c @@ -406,9 +406,8 @@ socket_recv_messages (NiceSocket *sock, /* Split up the monolithic buffer again into the caller-provided buffers. */ if (parsed_buffer_length > 0 && allocated_buffer) { - parsed_buffer_length = - memcpy_buffer_to_input_message (message, buffer, - parsed_buffer_length); + memcpy_buffer_to_input_message (message, buffer, + parsed_buffer_length); } if (allocated_buffer) diff --git a/stun/usages/bind.c b/stun/usages/bind.c index 8dd7afc..d56790f 100644 --- a/stun/usages/bind.c +++ b/stun/usages/bind.c @@ -479,7 +479,7 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv, size_t len; StunUsageTransReturn ret; int val; - struct sockaddr_storage alternate_server; + struct sockaddr_storage alternate_server = { AF_UNSPEC } ; socklen_t alternate_server_len = sizeof (alternate_server); StunUsageBindReturn bind_ret; @@ -548,6 +548,8 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv, if (bind_ret == STUN_USAGE_BIND_RETURN_ALTERNATE_SERVER) { stun_trans_deinit (&trans); + assert (alternate_server.ss_family != AF_UNSPEC); + ret = stun_trans_create (&trans, SOCK_DGRAM, 0, (struct sockaddr *) &alternate_server, alternate_server_len); -- 2.13.6 From cd255bddc7fa0ddae056b5358a22b380c4eefc42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 4 Apr 2017 15:24:43 -0400 Subject: [PATCH 06/70] udp-turn: Add some const to internal APIs --- agent/component.c | 2 +- socket/udp-turn.c | 10 +++++----- socket/udp-turn.h | 7 ++++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/agent/component.c b/agent/component.c index 32f7463..a679b30 100644 --- a/agent/component.c +++ b/agent/component.c @@ -380,7 +380,7 @@ nice_component_restart (NiceComponent *cmp) for (i = cmp->remote_candidates; i; i = i->next) { NiceCandidate *candidate = i->data; - /* note: do not remove the local candidate that is + /* note: do not remove the remote candidate that is * currently part of the 'selected pair', see ICE * 9.1.1.1. "ICE Restarts" (ID-19) */ if (candidate == cmp->selected_pair.remote) { diff --git a/socket/udp-turn.c b/socket/udp-turn.c index a9c57e5..190a9ea 100644 --- a/socket/udp-turn.c +++ b/socket/udp-turn.c @@ -174,8 +174,8 @@ priv_send_data_queue_destroy (gpointer user_data) NiceSocket * nice_udp_turn_socket_new (GMainContext *ctx, NiceAddress *addr, - NiceSocket *base_socket, NiceAddress *server_addr, - gchar *username, gchar *password, + NiceSocket *base_socket, const NiceAddress *server_addr, + const gchar *username, const gchar *password, NiceTurnSocketCompatibility compatibility) { UdpTurnPriv *priv; @@ -1184,7 +1184,7 @@ nice_udp_turn_socket_parse_recv_message (NiceSocket *sock, NiceSocket **from_soc gsize nice_udp_turn_socket_parse_recv (NiceSocket *sock, NiceSocket **from_sock, NiceAddress *from, gsize len, guint8 *buf, - NiceAddress *recv_from, guint8 *_recv_buf, gsize recv_len) + const NiceAddress *recv_from, const guint8 *_recv_buf, gsize recv_len) { UdpTurnPriv *priv = (UdpTurnPriv *) sock->priv; @@ -1194,8 +1194,8 @@ nice_udp_turn_socket_parse_recv (NiceSocket *sock, NiceSocket **from_sock, ChannelBinding *binding = NULL; union { - guint8 *u8; - guint16 *u16; + const guint8 *u8; + const guint16 *u16; } recv_buf; /* In the case of a reliable UDP-TURN-OVER-TCP (which means MS-TURN) diff --git a/socket/udp-turn.h b/socket/udp-turn.h index b1eeeb4..df10a1c 100644 --- a/socket/udp-turn.h +++ b/socket/udp-turn.h @@ -59,15 +59,16 @@ nice_udp_turn_socket_parse_recv_message (NiceSocket *sock, NiceSocket **from_soc gsize nice_udp_turn_socket_parse_recv (NiceSocket *sock, NiceSocket **from_sock, NiceAddress *from, gsize len, guint8 *buf, - NiceAddress *recv_from, guint8 *recv_buf, gsize recv_len); + const NiceAddress *recv_from, const guint8 *recv_buf, gsize recv_len); gboolean nice_udp_turn_socket_set_peer (NiceSocket *sock, NiceAddress *peer); NiceSocket * nice_udp_turn_socket_new (GMainContext *ctx, NiceAddress *addr, - NiceSocket *base_socket, NiceAddress *server_addr, - gchar *username, gchar *password, NiceTurnSocketCompatibility compatibility); + NiceSocket *base_socket, const NiceAddress *server_addr, + const gchar *username, const gchar *password, + NiceTurnSocketCompatibility compatibility); void nice_udp_turn_socket_set_ms_realm(NiceSocket *sock, StunMessage *msg); -- 2.13.6 From e56b910d2d8b70f5677bbd4be579d5b95aff33ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 4 Apr 2017 16:16:05 -0400 Subject: [PATCH 07/70] agent: Separate return from NiceSocket and internal enum The same variable was used for return values from NiceSocket and for the internal enum, but 0 and -1 have different meanings in both. --- agent/agent.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/agent/agent.c b/agent/agent.c index 28d7ccf..7b8a9fc 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -3422,7 +3422,8 @@ agent_recv_message_unlocked ( { NiceAddress from; GList *item; - gint retval; + RecvStatus retval; + gint sockret; gboolean is_turn = FALSE; /* We need an address for packet parsing, below. */ @@ -3483,8 +3484,8 @@ agent_recv_message_unlocked ( local_bufs[i + 1].buffer = message->buffers[i].buffer; local_bufs[i + 1].size = message->buffers[i].size; } - retval = nice_socket_recv_messages (nicesock, &local_message, 1); - if (retval == 1) { + sockret = nice_socket_recv_messages (nicesock, &local_message, 1); + if (sockret == 1) { message->length = ntohs (rfc4571_frame); } } else { @@ -3499,7 +3500,7 @@ agent_recv_message_unlocked ( _priv_set_socket_tos (agent, new_socket, stream->tos); nice_component_attach_socket (component, new_socket); } - retval = 0; + sockret = 0; } else { /* In the case of a real ICE-TCP connection, we can use the socket as a * bytestream and do the read here with caching of data being read @@ -3508,9 +3509,9 @@ agent_recv_message_unlocked ( /* TODO: Support bytestream reads */ message->length = 0; - retval = 0; + sockret = 0; if (available <= 0) { - retval = available; + sockret = available; /* If we don't call check_connect_result on an outbound connection, * then is_connected will always return FALSE. That's why we check @@ -3523,7 +3524,7 @@ agent_recv_message_unlocked ( * not connected, it means that it failed to connect, so we must * return an error to make the socket fail/closed */ - retval = -1; + sockret = -1; } else { gint flags = G_SOCKET_MSG_PEEK; @@ -3536,7 +3537,7 @@ agent_recv_message_unlocked ( */ if (g_socket_receive_message (nicesock->fileno, NULL, NULL, 0, NULL, NULL, &flags, NULL, NULL) == 0) - retval = -1; + sockret = -1; } } else if (agent->rfc4571_expecting_length == 0) { if ((gsize) available >= sizeof(guint16)) { @@ -3544,8 +3545,8 @@ agent_recv_message_unlocked ( GInputVector local_buf = { &rfc4571_frame, sizeof(guint16)}; NiceInputMessage local_message = { &local_buf, 1, message->from, 0}; - retval = nice_socket_recv_messages (nicesock, &local_message, 1); - if (retval == 1) { + sockret = nice_socket_recv_messages (nicesock, &local_message, 1); + if (sockret == 1) { agent->rfc4571_expecting_length = ntohs (rfc4571_frame); available = g_socket_get_available_bytes (nicesock->fileno); } @@ -3589,8 +3590,8 @@ agent_recv_message_unlocked ( off += local_bufs[i].size; } } - retval = nice_socket_recv_messages (nicesock, &local_message, 1); - if (retval == 1) { + sockret = nice_socket_recv_messages (nicesock, &local_message, 1); + if (sockret == 1) { message->length = local_message.length; agent->rfc4571_expecting_length -= local_message.length; } @@ -3598,20 +3599,22 @@ agent_recv_message_unlocked ( } } } else { - retval = nice_socket_recv_messages (nicesock, message, 1); + sockret = nice_socket_recv_messages (nicesock, message, 1); } - if (retval == 0) { + if (sockret == 0) { retval = RECV_WOULD_BLOCK; /* EWOULDBLOCK */ nice_debug_verbose ("%s: Agent %p: no message available on read attempt", G_STRFUNC, agent); goto done; - } else if (retval < 0) { + } else if (sockret < 0) { nice_debug ("Agent %p: %s returned %d, errno (%d) : %s", - agent, G_STRFUNC, retval, errno, g_strerror (errno)); + agent, G_STRFUNC, sockret, errno, g_strerror (errno)); retval = RECV_ERROR; goto done; + } else { + retval = sockret; } if (retval == RECV_OOB || message->length == 0) { -- 2.13.6 From 4e605885c9dcaeb3ee443ec902c9c9189b19043f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 4 Apr 2017 16:16:46 -0400 Subject: [PATCH 08/70] agent: Remove impossible case --- agent/agent.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agent/agent.c b/agent/agent.c index 7b8a9fc..77f27e3 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -3617,7 +3617,8 @@ agent_recv_message_unlocked ( retval = sockret; } - if (retval == RECV_OOB || message->length == 0) { + g_assert (retval != RECV_OOB); + if (message->length == 0) { retval = RECV_OOB; nice_debug_verbose ("%s: Agent %p: message handled out-of-band", G_STRFUNC, agent); -- 2.13.6 From efc6a9be8cb34c899f0454c32e8a1e62b38df474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 4 Apr 2017 18:42:57 -0400 Subject: [PATCH 09/70] tests: Use automake test-driver for valgrind This fixes the valgrind integration with the new test drivers. --- common.mk | 4 +- scripts/valgrind-test-driver | 162 +++++++++++++++++++++++++++++++++++++++++++ scripts/valgrind.sh | 28 -------- 3 files changed, 165 insertions(+), 29 deletions(-) create mode 100755 scripts/valgrind-test-driver delete mode 100755 scripts/valgrind.sh diff --git a/common.mk b/common.mk index e2ca3f4..b16380d 100644 --- a/common.mk +++ b/common.mk @@ -4,6 +4,8 @@ pkgincludedir = $(includedir)/nice check-valgrind: - $(MAKE) TESTS_ENVIRONMENT="sh $$(cd "$(top_srcdir)" && pwd)/scripts/valgrind.sh" check + $(MAKE) TESTS_ENVIRONMENT="USE_VALGRIND=1 " check + +LOG_DRIVER=$(top_srcdir)/scripts/valgrind-test-driver .PHONY: check-valgrind diff --git a/scripts/valgrind-test-driver b/scripts/valgrind-test-driver new file mode 100755 index 0000000..5b660ee --- /dev/null +++ b/scripts/valgrind-test-driver @@ -0,0 +1,162 @@ +#! /bin/sh +# test-driver - basic testsuite driver script. + +scriptversion=2017-04-04.22; # UTC + +# Copyright (C) 2011-2014 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +usage_error () +{ + echo "$0: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat <$log_file 2>&1 +else + "$@" >$log_file 2>&1 +fi +estatus=$? + +if test $enable_hard_errors = no && test $estatus -eq 99; then + tweaked_estatus=1 +else + tweaked_estatus=$estatus +fi + +case $tweaked_estatus:$expect_failure in + 0:yes) col=$red res=XPASS recheck=yes gcopy=yes;; + 0:*) col=$grn res=PASS recheck=no gcopy=no;; + 77:*) col=$blu res=SKIP recheck=no gcopy=yes;; + 99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;; + *:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;; + *:*) col=$red res=FAIL recheck=yes gcopy=yes;; +esac + +# Report the test outcome and exit status in the logs, so that one can +# know whether the test passed or failed simply by looking at the '.log' +# file, without the need of also peaking into the corresponding '.trs' +# file (automake bug#11814). +echo "$res $test_name (exit status: $estatus)" >>$log_file + +# Report outcome to console. +echo "${col}${res}${std}: $test_name" + +# Register the test result, and other relevant metadata. +echo ":test-result: $res" > $trs_file +echo ":global-test-result: $res" >> $trs_file +echo ":recheck: $recheck" >> $trs_file +echo ":copy-in-global-log: $gcopy" >> $trs_file + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/scripts/valgrind.sh b/scripts/valgrind.sh deleted file mode 100755 index 2864b6f..0000000 --- a/scripts/valgrind.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -export G_SLICE=always-malloc -export G_DEBUG=gc-friendly - -tests_dir="`dirname $0`/../tests" - -report=`libtool --mode=execute valgrind \ - --leak-check=full \ - --show-reachable=no \ - --error-exitcode=1 \ - --suppressions=$tests_dir/libnice.supp \ - --num-callers=30 \ - $1 2>&1` - -#if echo "$report" | grep -q ==; then -if test $? != 0; then - echo "$report" - exit 1 -fi - -if echo "$report" | grep -q "definitely lost"; then - if ! echo "$report" | grep -q "definitely lost: 0 bytes"; then - echo "$report" - exit 1 - fi -fi - -- 2.13.6 From ae6d939e48366b80570d713b83334191b0982e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 4 Apr 2017 20:34:05 -0400 Subject: [PATCH 10/70] debug: Use libnice-verbose, not libnice-nice-verbose --- agent/debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/debug.c b/agent/debug.c index e1a298c..84fee94 100644 --- a/agent/debug.c +++ b/agent/debug.c @@ -102,7 +102,7 @@ void nice_debug_init (void) flags |= g_parse_debug_string (gflags_string, gkeys, 4); if (gflags_string && strstr (gflags_string, "libnice-pseudotcp-verbose")) flags |= NICE_DEBUG_PSEUDOTCP_VERBOSE; - if (gflags_string && strstr (gflags_string, "libnice-nice-verbose")) { + if (gflags_string && strstr (gflags_string, "libnice-verbose")) { flags |= NICE_DEBUG_NICE_VERBOSE; } -- 2.13.6 From 10c557f23f8337f1304fff27bd85d2eb713cb249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Wed, 5 Apr 2017 17:01:35 -0400 Subject: [PATCH 11/70] test-credentials: Fix leak --- tests/test-credentials.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test-credentials.c b/tests/test-credentials.c index 1de4e49..f1ea89d 100644 --- a/tests/test-credentials.c +++ b/tests/test-credentials.c @@ -184,6 +184,8 @@ int main (void) nice_agent_get_local_credentials (lagent, 1, &ufrag, &password); g_assert (g_strcmp0("unicorns", ufrag) == 0); g_assert (g_strcmp0("awesome", password) == 0); + g_free (ufrag); + g_free (password); nice_agent_gather_candidates (lagent, 1); nice_agent_gather_candidates (ragent, 1); -- 2.13.6 From 1e9e28dbc98b4f6a7cf4bda0ca73b5abc2735ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 4 Apr 2017 14:41:51 -0400 Subject: [PATCH 12/70] candidate: Add equality check function Add a function that can check if two candidates point to the same place. https://phabricator.freedesktop.org/T104 Reviewed-by: Philip Withnall Differential Revision: https://phabricator.freedesktop.org/D1715 --- agent/candidate.c | 11 +++++++++++ agent/candidate.h | 18 +++++++++++++++++- docs/reference/libnice/libnice-docs.xml | 4 ++++ docs/reference/libnice/libnice-sections.txt | 1 + nice/libnice.sym | 1 + 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/agent/candidate.c b/agent/candidate.c index 27966ef..85f8f65 100644 --- a/agent/candidate.c +++ b/agent/candidate.c @@ -360,3 +360,14 @@ nice_candidate_copy (const NiceCandidate *candidate) return copy; } + +NICEAPI_EXPORT gboolean +nice_candidate_equal_target (const NiceCandidate *candidate1, + const NiceCandidate *candidate2) +{ + g_return_val_if_fail (candidate1 != NULL, FALSE); + g_return_val_if_fail (candidate2 != NULL, FALSE); + + return (candidate1->transport == candidate2->transport && + nice_address_equal (&candidate1->addr, &candidate2->addr)); +} diff --git a/agent/candidate.h b/agent/candidate.h index fadfce3..e556c16 100644 --- a/agent/candidate.h +++ b/agent/candidate.h @@ -230,7 +230,23 @@ nice_candidate_free (NiceCandidate *candidate); NiceCandidate * nice_candidate_copy (const NiceCandidate *candidate); -GType nice_candidate_get_type (void); +/** + * nice_candidate_equal_target: + * @candidate1: A candidate + * @candidate2: A candidate + * + * Verifies that the candidates point to the same place, meaning they have + * the same transport and the same address. It ignores all other aspects. + * + * Returns: %TRUE if the candidates point to the same place + * + * Since: 0.1.15 + */ +gboolean +nice_candidate_equal_target (const NiceCandidate *candidate1, + const NiceCandidate *candidate2); + + GType nice_candidate_get_type (void); /** * NICE_TYPE_CANDIDATE: diff --git a/docs/reference/libnice/libnice-docs.xml b/docs/reference/libnice/libnice-docs.xml index 53487bc..391be1a 100644 --- a/docs/reference/libnice/libnice-docs.xml +++ b/docs/reference/libnice/libnice-docs.xml @@ -105,6 +105,10 @@ Index of new symbols in 0.1.14 + + Index of new symbols in 0.1.15 + + diff --git a/docs/reference/libnice/libnice-sections.txt b/docs/reference/libnice/libnice-sections.txt index d377257..88a6cd2 100644 --- a/docs/reference/libnice/libnice-sections.txt +++ b/docs/reference/libnice/libnice-sections.txt @@ -76,6 +76,7 @@ NICE_CANDIDATE_MAX_FOUNDATION nice_candidate_new nice_candidate_free nice_candidate_copy +nice_candidate_equal_target NICE_TYPE_CANDIDATE nice_candidate_get_type diff --git a/nice/libnice.sym b/nice/libnice.sym index b04bb95..1e522ad 100644 --- a/nice/libnice.sym +++ b/nice/libnice.sym @@ -58,6 +58,7 @@ nice_agent_set_software nice_agent_set_stream_name nice_agent_set_stream_tos nice_candidate_copy +nice_candidate_equal_target nice_candidate_free nice_candidate_new nice_component_state_to_string -- 2.13.6 From ffc7fddac42728bac6e4753a17bc52e5e610ae8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 4 Apr 2017 21:27:39 -0400 Subject: [PATCH 13/70] agent: Drop packets not from validated addresses This is required by the WebRTC spec. Remove test-mainloop as it doesnt even try to do a negotiation. https://phabricator.freedesktop.org/T104 Differential Revision: https://phabricator.freedesktop.org/D1716 --- agent/agent-priv.h | 2 + agent/agent.c | 20 +++++++++- agent/component.c | 90 +++++++++++++++++++++++++++++++++++++++++ agent/component.h | 10 +++++ agent/conncheck.c | 8 +++- tests/Makefile.am | 1 - tests/test-mainloop.c | 108 -------------------------------------------------- 7 files changed, 127 insertions(+), 112 deletions(-) delete mode 100644 tests/test-mainloop.c diff --git a/agent/agent-priv.h b/agent/agent-priv.h index 4d8c9b8..ada3630 100644 --- a/agent/agent-priv.h +++ b/agent/agent-priv.h @@ -114,6 +114,8 @@ nice_input_message_iter_compare (const NiceInputMessageIter *a, * MTU and estimated typical sizes of ICE STUN packet */ #define MAX_STUN_DATAGRAM_PAYLOAD 1300 +#define NICE_COMPONENT_MAX_VALID_CANDIDATES 50 /* maximum number of validates remote candidates to keep, the number is arbitrary but hopefully large enough */ + struct _NiceAgent { GObject parent; /* gobject pointer */ diff --git a/agent/agent.c b/agent/agent.c index 77f27e3..eff62f0 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -3682,8 +3682,6 @@ agent_recv_message_unlocked ( if (retval == RECV_OOB) goto done; - agent->media_after_tick = TRUE; - /* If the message’s stated length is equal to its actual length, it’s probably * a STUN message; otherwise it’s probably data. */ if (stun_message_validate_buffer_length_fast ( @@ -3715,6 +3713,7 @@ agent_recv_message_unlocked ( nice_debug ("%s: Valid STUN packet received.", G_STRFUNC); retval = RECV_OOB; g_free (big_buf); + agent->media_after_tick = TRUE; goto done; } } @@ -3725,6 +3724,23 @@ agent_recv_message_unlocked ( g_free (big_buf); } + if (!nice_component_verify_remote_candidate (component, + message->from, nicesock)) { + if (nice_debug_is_verbose ()) { + gchar str[INET6_ADDRSTRLEN]; + + nice_address_to_string (message->from, str); + nice_debug_verbose ("Agent %p : %d:%d DROPPING packet from unknown source" + " %s:%d sock-type: %d\n", agent, stream->id, component->id, str, + nice_address_get_port (message->from), nicesock->type); + } + + retval = RECV_OOB; + goto done; + } + + agent->media_after_tick = TRUE; + /* Unhandled STUN; try handling TCP data, then pass to the client. */ if (message->length > 0 && agent->reliable) { if (!nice_socket_is_reliable (nicesock) && diff --git a/agent/component.c b/agent/component.c index a679b30..ba28ffa 100644 --- a/agent/component.c +++ b/agent/component.c @@ -435,6 +435,8 @@ nice_component_update_selected_pair (NiceComponent *component, const CandidatePa component->selected_pair.remote = pair->remote; component->selected_pair.priority = pair->priority; component->selected_pair.prflx_priority = pair->prflx_priority; + + nice_component_add_valid_candidate (component, pair->remote); } /* @@ -514,6 +516,11 @@ nice_component_set_selected_remote_candidate (NiceComponent *component, component->selected_pair.remote = remote; component->selected_pair.priority = priority; + /* Get into fallback mode where packets from any source is accepted once + * this has been called. This is the expected behavior of pre-ICE SIP. + */ + component->fallback_mode = TRUE; + return local; } @@ -1107,6 +1114,9 @@ nice_component_finalize (GObject *obj) g_warn_if_fail (cmp->remote_candidates == NULL); g_warn_if_fail (cmp->incoming_checks == NULL); + g_list_free_full (cmp->valid_candidates, + (GDestroyNotify) nice_candidate_free); + g_clear_object (&cmp->tcp); g_clear_object (&cmp->stop_cancellable); g_clear_object (&cmp->iostream); @@ -1421,3 +1431,83 @@ turn_server_unref (TurnServer *turn) g_slice_free (TurnServer, turn); } } + +void +nice_component_add_valid_candidate (NiceComponent *component, + const NiceCandidate *candidate) +{ + guint count = 0; + GList *item, *last = NULL; + + for (item = component->valid_candidates; item; item = item->next) { + NiceCandidate *cand = item->data; + + last = item; + count++; + if (nice_candidate_equal_target (cand, candidate)) + return; + } + + /* New candidate */ + + if (nice_debug_is_enabled ()) { + char str[INET6_ADDRSTRLEN]; + nice_address_to_string (&candidate->addr, str); + nice_debug ("Agent %p : %d:%d Adding valid source" + " candidate: %s:%d trans: %d\n", component->agent, + candidate->stream_id, candidate->component_id, str, + nice_address_get_port (&candidate->addr), candidate->transport); + } + + component->valid_candidates = g_list_prepend ( + component->valid_candidates, nice_candidate_copy (candidate)); + + /* Delete the last one to make sure we don't have a list that is too long, + * the candidates are not freed on ICE restart as this would be more complex, + * we just keep the list not too long. + */ + if (count > NICE_COMPONENT_MAX_VALID_CANDIDATES) { + NiceCandidate *cand = last->data; + + component->valid_candidates = g_list_delete_link ( + component->valid_candidates, last); + nice_candidate_free (cand); + } +} + +gboolean +nice_component_verify_remote_candidate (NiceComponent *component, + const NiceAddress *address, NiceSocket *nicesock) +{ + GList *item; + + if (component->fallback_mode) + return TRUE; + + for (item = component->valid_candidates; item; item = item->next) { + NiceCandidate *cand = item->data; + + if (((nicesock->type == NICE_SOCKET_TYPE_TCP_BSD && + (cand->transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE || + cand->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE || + cand->transport == NICE_CANDIDATE_TRANSPORT_TCP_SO)) || + cand->transport == NICE_CANDIDATE_TRANSPORT_UDP) && + nice_address_equal (address, &cand->addr)) { + /* fast return if it's already the first */ + if (item == component->valid_candidates) + return TRUE; + + /* Put the current candidate at the top so that in the normal use-case, + * this function becomes O(1). + */ + component->valid_candidates = g_list_remove_link ( + component->valid_candidates, item); + component->valid_candidates = g_list_concat (item, + component->valid_candidates); + + return TRUE; + } + } + + return FALSE; +} diff --git a/agent/component.h b/agent/component.h index 6712794..a8a1222 100644 --- a/agent/component.h +++ b/agent/component.h @@ -159,12 +159,14 @@ struct _NiceComponent { NiceComponentState state; GSList *local_candidates; /* list of NiceCandidate objs */ GSList *remote_candidates; /* list of NiceCandidate objs */ + GList *valid_candidates; /* list of owned remote NiceCandidates that are part of valid pairs */ GSList *socket_sources; /* list of SocketSource objs; must only grow monotonically */ guint socket_sources_age; /* incremented when socket_sources changes */ GSList *incoming_checks; /* list of IncomingCheck objs */ GList *turn_servers; /* List of TurnServer objs */ CandidatePair selected_pair; /* independent from checklists, see ICE 11.1. "Sending Media" (ID-19) */ + gboolean fallback_mode; /* in this case, accepts packets from all, ignore candidate validation */ NiceCandidate *restart_candidate; /* for storing active remote candidate during a restart */ NiceCandidate *turn_candidate; /* for storing active turn candidate if turn servers have been cleared */ /* I/O handling. The main context must always be non-NULL, and is used for all @@ -301,6 +303,14 @@ turn_server_ref (TurnServer *turn); void turn_server_unref (TurnServer *turn); +void +nice_component_add_valid_candidate (NiceComponent *component, + const NiceCandidate *candidate); + +gboolean +nice_component_verify_remote_candidate (NiceComponent *component, + const NiceAddress *address, NiceSocket *nicesock); + G_END_DECLS diff --git a/agent/conncheck.c b/agent/conncheck.c index 1dc13dd..7ffa3db 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -2627,6 +2627,7 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * p->state = NICE_CHECK_SUCCEEDED; nice_debug ("Agent %p : conncheck %p SUCCEEDED.", agent, p); priv_conn_check_unfreeze_related (agent, stream, p); + nice_component_add_valid_candidate (component, remote_candidate); } else { if (!local_cand) { @@ -2652,8 +2653,10 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * /* note: this is same as "adding to VALID LIST" in the spec text */ - if (new_pair) + if (new_pair) { new_pair->valid = TRUE; + nice_component_add_valid_candidate (component, remote_candidate); + } return new_pair; } @@ -2739,6 +2742,7 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre nice_debug ("Agent %p : Mapped address not found." " conncheck %p SUCCEEDED.", agent, p); priv_conn_check_unfreeze_related (agent, stream, p); + nice_component_add_valid_candidate (component, p->remote); } else { ok_pair = priv_process_response_check_for_reflexive (agent, stream, component, p, sockptr, &sockaddr.addr, @@ -3654,6 +3658,8 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, } } + nice_component_add_valid_candidate (component, remote_candidate); + priv_reply_to_conn_check (agent, stream, component, local_candidate, remote_candidate, from, nicesock, rbuf_len, &msg, use_candidate); diff --git a/tests/Makefile.am b/tests/Makefile.am index 7bfe075..d24a2aa 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -45,7 +45,6 @@ check_PROGRAMS = \ test-send-recv \ test-socket-is-based-on \ test-priority \ - test-mainloop \ test-fullmode \ test-restart \ test-fallback \ diff --git a/tests/test-mainloop.c b/tests/test-mainloop.c deleted file mode 100644 index 7c52daa..0000000 --- a/tests/test-mainloop.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This file is part of the Nice GLib ICE library. - * - * (C) 2006, 2007 Collabora Ltd. - * Contact: Dafydd Harries - * (C) 2006, 2007 Nokia Corporation. All rights reserved. - * Contact: Kai Vehmanen - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Nice GLib ICE library. - * - * The Initial Developers of the Original Code are Collabora Ltd and Nokia - * Corporation. All Rights Reserved. - * - * Contributors: - * Dafydd Harries, Collabora Ltd. - * Kai Vehmanen, Nokia - * - * Alternatively, the contents of this file may be used under the terms of the - * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which - * case the provisions of LGPL are applicable instead of those above. If you - * wish to allow use of your version of this file only under the terms of the - * LGPL and not to allow others to use your version of this file under the - * MPL, indicate your decision by deleting the provisions above and replace - * them with the notice and other provisions required by the LGPL. If you do - * not delete the provisions above, a recipient may use your version of this - * file under either the MPL or the LGPL. - */ -#ifdef HAVE_CONFIG_H -# include -#endif - -#include - -#include -#include "socket/socket.h" - -static GMainLoop *loop = NULL; - -static void -recv_cb ( - NiceAgent *agent, - guint stream_id, - guint component_id, - guint len, - gchar *buf, - gpointer data) -{ - g_assert (agent != NULL); - g_assert (stream_id == 1); - g_assert (component_id == 1); - g_assert (len == 6); - g_assert (0 == strncmp (buf, "\x80hello", len)); - g_assert (42 == GPOINTER_TO_UINT (data)); - g_main_loop_quit (loop); -} - -int -main (void) -{ - NiceAgent *agent; - NiceAddress addr; - guint stream; - - nice_address_init (&addr); - - loop = g_main_loop_new (NULL, FALSE); - - agent = nice_agent_new (g_main_loop_get_context (loop), NICE_COMPATIBILITY_RFC5245); - nice_address_set_ipv4 (&addr, 0x7f000001); - nice_agent_add_local_address (agent, &addr); - stream = nice_agent_add_stream (agent, 1); - nice_agent_gather_candidates (agent, stream); - - // attach to default main context - nice_agent_attach_recv (agent, stream, NICE_COMPONENT_TYPE_RTP, - g_main_loop_get_context (loop), recv_cb, GUINT_TO_POINTER (42)); - - { - NiceCandidate *candidate; - GSList *candidates, *i; - - candidates = nice_agent_get_local_candidates (agent, 1, 1); - candidate = candidates->data; - - nice_socket_send (candidate->sockptr, &(candidate->addr), 6, "\x80hello"); - for (i = candidates; i; i = i->next) - nice_candidate_free ((NiceCandidate *) i->data); - g_slist_free (candidates); - } - - g_main_loop_run (loop); - - nice_agent_remove_stream (agent, stream); - g_object_unref (agent); - - return 0; -} - -- 2.13.6 From 8fc22b0034d04cbc222e0637152b1cee2879eef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Wed, 5 Apr 2017 17:43:26 -0400 Subject: [PATCH 14/70] tests_: Add test to verify that only packets from validated addresses pass https://phabricator.freedesktop.org/T104 Reviewed-by: Philip Withnall Differential Revision: https://phabricator.freedesktop.org/D1717 --- tests/Makefile.am | 5 +- tests/test-drop-invalid.c | 517 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 tests/test-drop-invalid.c diff --git a/tests/Makefile.am b/tests/Makefile.am index d24a2aa..62d5d64 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -54,7 +54,8 @@ check_PROGRAMS = \ test-tcp \ test-icetcp \ test-credentials \ - test-turn + test-turn \ + test-drop-invalid dist_check_SCRIPTS = \ check-test-fullmode-with-stun.sh \ @@ -128,6 +129,8 @@ test_credentials_LDADD = $(COMMON_LDADD) test_turn_LDADD = $(COMMON_LDADD) +test_drop_invalid_LDADD = $(COMMON_LDADD) + test_gstreamer_CFLAGS = $(AM_CFLAGS) $(GST_CHECK_CFLAGS) test_gstreamer_LDADD = -lnice -L$(top_builddir)/nice/.libs $(GLIB_LIBS) $(GUPNP_LIBS) $(GST_CHECK_LIBS) $(GST_LIBS) diff --git a/tests/test-drop-invalid.c b/tests/test-drop-invalid.c new file mode 100644 index 0000000..97e3586 --- /dev/null +++ b/tests/test-drop-invalid.c @@ -0,0 +1,517 @@ +/* + * This file is part of the Nice GLib ICE library. + * + * Unit test for ICE full-mode related features. + * + * (C) 2007 Nokia Corporation. All rights reserved. + * Contact: Kai Vehmanen + * (C) 2017 Collabora Ltd + * Contact: Olivier Crete + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Nice GLib ICE library. + * + * The Initial Developers of the Original Code are Collabora Ltd and Nokia + * Corporation. All Rights Reserved. + * + * Contributors: + * Kai Vehmanen, Nokia + * + * Alternatively, the contents of this file may be used under the terms of the + * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which + * case the provisions of LGPL are applicable instead of those above. If you + * wish to allow use of your version of this file only under the terms of the + * LGPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the LGPL. + */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "agent.h" + +#include "socket/socket.h" + +#include +#include + + + +static NiceComponentState global_lagent_state[2] = { NICE_COMPONENT_STATE_LAST, NICE_COMPONENT_STATE_LAST }; +static NiceComponentState global_ragent_state[2] = { NICE_COMPONENT_STATE_LAST, NICE_COMPONENT_STATE_LAST }; +static guint global_components_ready = 0; +static guint global_components_ready_exit = 0; +static guint global_components_failed = 0; +static guint global_components_failed_exit = 0; +static GMainLoop *global_mainloop = NULL; +static gboolean global_lagent_gathering_done = FALSE; +static gboolean global_ragent_gathering_done = FALSE; +static gboolean global_lagent_ibr_received = FALSE; +static gboolean global_ragent_ibr_received = FALSE; +static int global_lagent_cands = 0; +static int global_ragent_cands = 0; +static gint global_ragent_read = 0; +static guint global_exit_when_ibr_received = 0; + +static void priv_print_global_status (void) +{ + g_debug ("\tgathering_done=%d", global_lagent_gathering_done && global_ragent_gathering_done); + g_debug ("\tlstate[rtp]=%d [rtcp]=%d", global_lagent_state[0], global_lagent_state[1]); + g_debug ("\trstate[rtp]=%d [rtcp]=%d", global_ragent_state[0], global_ragent_state[1]); + g_debug ("\tL cands=%d R cands=%d", global_lagent_cands, global_ragent_cands); +} + +static gboolean timer_cb (gpointer pointer) +{ + g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, pointer); + + /* signal status via a global variable */ + + /* note: should not be reached, abort */ + g_error ("ERROR: test has got stuck, aborting..."); + + return FALSE; +} + +static void cb_nice_recv (NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer user_data) +{ + g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, user_data); + + /* XXX: dear compiler, these are for you: */ + (void)agent; (void)stream_id; (void)component_id; (void)buf; + + /* Core of the test + * Assert on any unreleated packet received. This would include anything + * send before the negotiation is over. + */ + g_assert (len == 16); + g_assert (strncmp ("1234567812345678", buf, 16) == 0); + + if (component_id == 2) + return; + + if (GPOINTER_TO_UINT (user_data) == 2) { + g_debug ("right agent received %d bytes, stopping mainloop", len); + global_ragent_read = len; + g_main_loop_quit (global_mainloop); + } +} + +static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer data) +{ + g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, data); + + if (GPOINTER_TO_UINT (data) == 1) + global_lagent_gathering_done = TRUE; + else if (GPOINTER_TO_UINT (data) == 2) + global_ragent_gathering_done = TRUE; + + if (global_lagent_gathering_done && + global_ragent_gathering_done) + g_main_loop_quit (global_mainloop); + + /* XXX: dear compiler, these are for you: */ + (void)agent; +} + +static void cb_component_state_changed (NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer data) +{ + gboolean ready_to_connected = FALSE; + g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, data); + + if (GPOINTER_TO_UINT (data) == 1) { + if (global_lagent_state[component_id - 1] == NICE_COMPONENT_STATE_READY && + state == NICE_COMPONENT_STATE_CONNECTED) + ready_to_connected = TRUE; + global_lagent_state[component_id - 1] = state; + } else if (GPOINTER_TO_UINT (data) == 2) { + if (global_ragent_state[component_id - 1] == NICE_COMPONENT_STATE_READY && + state == NICE_COMPONENT_STATE_CONNECTED) + ready_to_connected = TRUE; + global_ragent_state[component_id - 1] = state; + } + + if (state == NICE_COMPONENT_STATE_READY) + global_components_ready++; + else if (state == NICE_COMPONENT_STATE_CONNECTED && ready_to_connected) + global_components_ready--; + if (state == NICE_COMPONENT_STATE_FAILED) + global_components_failed++; + + g_debug ("test-drop-invalid: checks READY/EXIT-AT %u/%u.", global_components_ready, global_components_ready_exit); + g_debug ("test-drop-invalid: checks FAILED/EXIT-AT %u/%u.", global_components_failed, global_components_failed_exit); + + /* signal status via a global variable */ + if (global_components_ready == global_components_ready_exit && + global_components_failed == global_components_failed_exit) { + g_debug ("Components ready/failed achieved. Stopping mailoop"); + g_main_loop_quit (global_mainloop); + return; + } + + /* XXX: dear compiler, these are for you: */ + (void)agent; (void)stream_id; (void)data; (void)component_id; +} + +static void cb_new_selected_pair(NiceAgent *agent, guint stream_id, guint component_id, + gchar *lfoundation, gchar* rfoundation, gpointer data) +{ + g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, data); + + if (GPOINTER_TO_UINT (data) == 1) + ++global_lagent_cands; + else if (GPOINTER_TO_UINT (data) == 2) + ++global_ragent_cands; + + /* XXX: dear compiler, these are for you: */ + (void)agent; (void)stream_id; (void)component_id; (void)lfoundation; (void)rfoundation; +} + +static void cb_new_candidate(NiceAgent *agent, guint stream_id, guint component_id, + gchar *foundation, gpointer data) +{ + g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, data); + + /* XXX: dear compiler, these are for you: */ + (void)agent; (void)stream_id; (void)data; (void)component_id; (void)foundation; +} + +static void cb_initial_binding_request_received(NiceAgent *agent, guint stream_id, gpointer data) +{ + g_debug ("test-drop-invalid:%s: %p", G_STRFUNC, data); + + if (GPOINTER_TO_UINT (data) == 1) + global_lagent_ibr_received = TRUE; + else if (GPOINTER_TO_UINT (data) == 2) + global_ragent_ibr_received = TRUE; + + if (global_exit_when_ibr_received) { + g_debug ("Received initial binding request. Stopping mailoop"); + g_main_loop_quit (global_mainloop); + } + + /* XXX: dear compiler, these are for you: */ + (void)agent; (void)stream_id; (void)data; +} + +static void set_candidates (NiceAgent *from, guint from_stream, + NiceAgent *to, guint to_stream, guint component) +{ + GSList *cands = NULL; + GSList *peer_cands = NULL; + GSList *item1, *item2; + + cands = nice_agent_get_local_candidates (from, from_stream, component); + peer_cands = nice_agent_get_local_candidates (to, to_stream, component); + + /* + * Core of the test: + * + * Send packets that shoudl be dropped. + */ + + for (item1 = cands; item1; item1 = item1->next) { + NiceCandidate *cand = item1->data; + NiceSocket *nicesock = cand->sockptr; + + g_assert (nicesock); + + for (item2 = peer_cands; item2; item2 = item2->next) { + NiceCandidate *target_cand = item2->data; + + nice_socket_send (nicesock, &target_cand->addr, 12, "123456789AB"); + } + + } + + nice_agent_set_remote_candidates (to, to_stream, component, cands); + + g_slist_free_full (cands, (GDestroyNotify) nice_candidate_free); + g_slist_free_full (peer_cands, (GDestroyNotify) nice_candidate_free); +} + +static void set_credentials (NiceAgent *lagent, guint lstream, + NiceAgent *ragent, guint rstream) +{ + gchar *ufrag = NULL, *password = NULL; + + nice_agent_get_local_credentials(lagent, lstream, &ufrag, &password); + nice_agent_set_remote_credentials (ragent, rstream, ufrag, password); + g_free (ufrag); + g_free (password); + nice_agent_get_local_credentials(ragent, rstream, &ufrag, &password); + nice_agent_set_remote_credentials (lagent, lstream, ufrag, password); + g_free (ufrag); + g_free (password); +} + +static int run_full_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress *baseaddr, guint ready, guint failed) +{ + guint ls_id, rs_id; + gint ret; + + /* XXX: dear compiler, this is for you */ + (void)baseaddr; + + /* step: initialize variables modified by the callbacks */ + global_components_ready = 0; + global_components_ready_exit = ready; + global_components_failed = 0; + global_components_failed_exit = failed; + global_lagent_gathering_done = FALSE; + global_ragent_gathering_done = FALSE; + global_lagent_ibr_received = + global_ragent_ibr_received = FALSE; + global_lagent_cands = + global_ragent_cands = 0; + + g_object_set (G_OBJECT (lagent), "controlling-mode", TRUE, NULL); + g_object_set (G_OBJECT (ragent), "controlling-mode", FALSE, NULL); + + /* step: add one stream, with RTP+RTCP components, to each agent */ + ls_id = nice_agent_add_stream (lagent, 2); + + rs_id = nice_agent_add_stream (ragent, 2); + g_assert (ls_id > 0); + g_assert (rs_id > 0); + + /* Gather candidates and test nice_agent_set_port_range */ + nice_agent_set_port_range (lagent, ls_id, 1, 10000, 10000); + nice_agent_set_port_range (lagent, ls_id, 2, 10001, 10001); + nice_agent_set_port_range (ragent, rs_id, 1, 12345, 12345); + nice_agent_set_port_range (ragent, rs_id, 2, 10000, 10001); + g_assert (nice_agent_gather_candidates (lagent, ls_id) == TRUE); + g_assert (nice_agent_gather_candidates (ragent, rs_id) == FALSE); + g_assert (nice_agent_get_local_candidates (ragent, rs_id, 1) == NULL); + g_assert (nice_agent_get_local_candidates (ragent, rs_id, 2) == NULL); + nice_agent_set_port_range (ragent, rs_id, 2, 10000, 10002); + g_assert (nice_agent_gather_candidates (ragent, rs_id) == TRUE); + + { + GSList *cands = NULL, *i; + NiceCandidate *cand = NULL; + + cands = nice_agent_get_local_candidates (lagent, ls_id, 1); + g_assert (g_slist_length (cands) == 1); + cand = cands->data; + g_assert (cand->type == NICE_CANDIDATE_TYPE_HOST); + g_assert (nice_address_get_port (&cand->addr) == 10000); + for (i = cands; i; i = i->next) + nice_candidate_free ((NiceCandidate *) i->data); + g_slist_free (cands); + + cands = nice_agent_get_local_candidates (lagent, ls_id, 2); + g_assert (g_slist_length (cands) == 1); + cand = cands->data; + g_assert (cand->type == NICE_CANDIDATE_TYPE_HOST); + g_assert (nice_address_get_port (&cand->addr) == 10001); + for (i = cands; i; i = i->next) + nice_candidate_free ((NiceCandidate *) i->data); + g_slist_free (cands); + + cands = nice_agent_get_local_candidates (ragent, rs_id, 1); + g_assert (g_slist_length (cands) == 1); + cand = cands->data; + g_assert (cand->type == NICE_CANDIDATE_TYPE_HOST); + g_assert (nice_address_get_port (&cand->addr) == 12345); + for (i = cands; i; i = i->next) + nice_candidate_free ((NiceCandidate *) i->data); + g_slist_free (cands); + + cands = nice_agent_get_local_candidates (ragent, rs_id, 2); + g_assert (g_slist_length (cands) == 1); + cand = cands->data; + g_assert (cand->type == NICE_CANDIDATE_TYPE_HOST); + g_assert (nice_address_get_port (&cand->addr) == 10002); + for (i = cands; i; i = i->next) + nice_candidate_free ((NiceCandidate *) i->data); + g_slist_free (cands); + + } + + /* step: attach to mainloop (needed to register the fds) */ + nice_agent_attach_recv (lagent, ls_id, NICE_COMPONENT_TYPE_RTP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, + GUINT_TO_POINTER (1)); + nice_agent_attach_recv (lagent, ls_id, NICE_COMPONENT_TYPE_RTCP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, + GUINT_TO_POINTER (1)); + nice_agent_attach_recv (ragent, rs_id, NICE_COMPONENT_TYPE_RTP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, + GUINT_TO_POINTER (2)); + nice_agent_attach_recv (ragent, rs_id, NICE_COMPONENT_TYPE_RTCP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, + GUINT_TO_POINTER (2)); + + /* step: run mainloop until local candidates are ready + * (see timer_cb() above) */ + if (global_lagent_gathering_done != TRUE || + global_ragent_gathering_done != TRUE) { + g_debug ("test-drop-invalid: Added streams, running mainloop until 'candidate-gathering-done'..."); + g_main_loop_run (global_mainloop); + g_assert (global_lagent_gathering_done == TRUE); + g_assert (global_ragent_gathering_done == TRUE); + } + + set_credentials (lagent, ls_id, ragent, rs_id); + + /* step: pass the remote candidates to agents */ + set_candidates (ragent, rs_id, lagent, ls_id, NICE_COMPONENT_TYPE_RTP); + set_candidates (ragent, rs_id, lagent, ls_id, NICE_COMPONENT_TYPE_RTCP); + set_candidates (lagent, ls_id, ragent, rs_id, NICE_COMPONENT_TYPE_RTP); + set_candidates (lagent, ls_id, ragent, rs_id, NICE_COMPONENT_TYPE_RTCP); + + g_debug ("test-drop-invalid: Set properties, next running mainloop until connectivity checks succeed..."); + + /* step: run the mainloop until connectivity checks succeed + * (see timer_cb() above) */ + g_main_loop_run (global_mainloop); + + /* note: verify that STUN binding requests were sent */ + g_assert (global_lagent_ibr_received == TRUE); + g_assert (global_ragent_ibr_received == TRUE); + + /* note: Send a packet from another address */ + /* These should also be ignored */ + { + NiceCandidate *local_cand = NULL; + NiceCandidate *remote_cand = NULL; + NiceSocket *tmpsock; + + g_assert (nice_agent_get_selected_pair (lagent, ls_id, 1, &local_cand, + &remote_cand)); + g_assert (local_cand); + g_assert (remote_cand); + + tmpsock = nice_udp_bsd_socket_new (NULL); + nice_socket_send (tmpsock, &remote_cand->addr, 4, "ABCD"); + nice_socket_send (tmpsock, &local_cand->addr, 5, "ABCDE"); + nice_socket_free (tmpsock); + } + + /* note: test payload send and receive */ + global_ragent_read = 0; + ret = nice_agent_send (lagent, ls_id, 1, 16, "1234567812345678"); + g_assert (ret != -1); + g_debug ("Sent %d bytes", ret); + g_assert (ret == 16); + while (global_ragent_read != 16) + g_main_context_iteration (NULL, TRUE); + g_assert (global_ragent_read == 16); + + g_debug ("test-drop-invalid: Ran mainloop, removing streams..."); + + /* step: clean up resources and exit */ + + nice_agent_remove_stream (lagent, ls_id); + nice_agent_remove_stream (ragent, rs_id); + + return 0; +} + +int main (void) +{ + NiceAgent *lagent, *ragent; /* agent's L and R */ + NiceAddress baseaddr; + int result; + guint timer_id; + +#ifdef G_OS_WIN32 + WSADATA w; + + WSAStartup(0x0202, &w); +#endif + + global_mainloop = g_main_loop_new (NULL, FALSE); + + /* step: create the agents L and R */ + lagent = nice_agent_new (g_main_loop_get_context (global_mainloop), + NICE_COMPATIBILITY_RFC5245); + ragent = nice_agent_new (g_main_loop_get_context (global_mainloop), + NICE_COMPATIBILITY_RFC5245); + + g_object_set (G_OBJECT (lagent), "ice-tcp", FALSE, NULL); + g_object_set (G_OBJECT (ragent), "ice-tcp", FALSE, NULL); + + + nice_agent_set_software (lagent, "test-drop-invalid, Left Agent"); + nice_agent_set_software (ragent, "test-drop-invalid, Right Agent"); + + /* step: add a timer to catch state changes triggered by signals */ + timer_id = g_timeout_add (30000, timer_cb, NULL); + + /* step: specify which local interface to use */ + if (!nice_address_set_from_string (&baseaddr, "127.0.0.1")) + g_assert_not_reached (); + nice_agent_add_local_address (lagent, &baseaddr); + nice_agent_add_local_address (ragent, &baseaddr); + + g_signal_connect (G_OBJECT (lagent), "candidate-gathering-done", + G_CALLBACK (cb_candidate_gathering_done), GUINT_TO_POINTER(1)); + g_signal_connect (G_OBJECT (ragent), "candidate-gathering-done", + G_CALLBACK (cb_candidate_gathering_done), GUINT_TO_POINTER (2)); + g_signal_connect (G_OBJECT (lagent), "component-state-changed", + G_CALLBACK (cb_component_state_changed), GUINT_TO_POINTER (1)); + g_signal_connect (G_OBJECT (ragent), "component-state-changed", + G_CALLBACK (cb_component_state_changed), GUINT_TO_POINTER (2)); + g_signal_connect (G_OBJECT (lagent), "new-selected-pair", + G_CALLBACK (cb_new_selected_pair), GUINT_TO_POINTER(1)); + g_signal_connect (G_OBJECT (ragent), "new-selected-pair", + G_CALLBACK (cb_new_selected_pair), GUINT_TO_POINTER (2)); + g_signal_connect (G_OBJECT (lagent), "new-candidate", + G_CALLBACK (cb_new_candidate), GUINT_TO_POINTER (1)); + g_signal_connect (G_OBJECT (ragent), "new-candidate", + G_CALLBACK (cb_new_candidate), GUINT_TO_POINTER (2)); + g_signal_connect (G_OBJECT (lagent), "initial-binding-request-received", + G_CALLBACK (cb_initial_binding_request_received), + GUINT_TO_POINTER (1)); + g_signal_connect (G_OBJECT (ragent), "initial-binding-request-received", + G_CALLBACK (cb_initial_binding_request_received), + GUINT_TO_POINTER (2)); + + g_object_set (G_OBJECT (lagent), "upnp", FALSE, NULL); + g_object_set (G_OBJECT (ragent), "upnp", FALSE, NULL); + + + /* step: run test the first time */ + g_debug ("test-drop-invalid: TEST STARTS / running test for the 1st time"); + result = run_full_test (lagent, ragent, &baseaddr, 4 ,0); + priv_print_global_status (); + g_assert (result == 0); + g_assert (global_lagent_state[0] == NICE_COMPONENT_STATE_READY); + g_assert (global_lagent_state[1] == NICE_COMPONENT_STATE_READY); + g_assert (global_ragent_state[0] == NICE_COMPONENT_STATE_READY); + g_assert (global_ragent_state[1] == NICE_COMPONENT_STATE_READY); + /* When using TURN, we get peer reflexive candidates for the host cands + that we removed so we can get another new_selected_pair signal later + depending on timing/racing, we could double (or not) the amount we expected + */ + + /* note: verify that correct number of local candidates were reported */ + g_assert (global_lagent_cands == 2); + g_assert (global_ragent_cands == 2); + + g_object_unref (lagent); + g_object_unref (ragent); + + g_main_loop_unref (global_mainloop); + global_mainloop = NULL; + + g_source_remove (timer_id); +#ifdef G_OS_WIN32 + WSACleanup(); +#endif + return result; +} -- 2.13.6 From f49ab88f957a3a250ef2ada0c0fab4fbbd3e5da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 11 Apr 2017 16:42:55 -0400 Subject: [PATCH 15/70] conncheck: Check the controlling state when the req was sent It was checking when the pair was created, but the role may have already changed when the request is sent. --- agent/conncheck.c | 30 +++++++++++++++++++----------- agent/conncheck.h | 1 - 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 7ffa3db..5501c2b 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -1666,7 +1666,6 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, tmpbuf2, nice_address_get_port (&pair->remote->addr)); } pair->nominated = use_candidate; - pair->controlling = agent->controlling_mode; pair->prflx_priority = ensure_unique_priority (component, peer_reflexive_candidate_priority (agent, local)); @@ -2531,7 +2530,6 @@ static CandidateCheckPair *priv_add_peer_reflexive_pair (NiceAgent *agent, guint pair->priority = nice_candidate_pair_priority (pair->remote->priority, pair->local->priority); pair->nominated = FALSE; - pair->controlling = agent->controlling_mode; pair->prflx_priority = ensure_unique_priority (component, peer_reflexive_candidate_priority (agent, local_cand)); nice_debug ("Agent %p : added a new peer-discovered pair with foundation of '%s'.", agent, pair->foundation); @@ -2777,16 +2775,26 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre } else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) { /* case: role conflict error, need to restart with new role */ nice_debug ("Agent %p : conncheck %p ROLE CONFLICT, restarting", agent, p); - /* note: our role might already have changed due to an - * incoming request, but if not, change role now; - * follows ICE 7.1.2.1 "Failure Cases" (ID-19) */ - priv_check_for_role_conflict (agent, !p->controlling); - p->stun_message.buffer = NULL; - p->stun_message.buffer_len = 0; - p->state = NICE_CHECK_WAITING; - priv_add_pair_to_triggered_check_queue (agent, p); - nice_debug ("Agent %p : pair %p state WAITING", agent, p); + if (p->stun_message.buffer != NULL) { + guint64 tie; + gboolean controlled_mode; + + /* note: our role might already have changed due to an + * incoming request, but if not, change role now; + * follows ICE 7.1.2.1 "Failure Cases" (ID-19) */ + controlled_mode = (stun_message_find64 (&p->stun_message, + STUN_ATTRIBUTE_ICE_CONTROLLED, &tie) == + STUN_MESSAGE_RETURN_SUCCESS); + + priv_check_for_role_conflict (agent, controlled_mode); + + p->stun_message.buffer = NULL; + p->stun_message.buffer_len = 0; + p->state = NICE_CHECK_WAITING; + priv_add_pair_to_triggered_check_queue (agent, p); + nice_debug ("Agent %p : pair %p state WAITING", agent, p); + } trans_found = TRUE; } else { /* case: STUN error, the check STUN context was freed */ diff --git a/agent/conncheck.h b/agent/conncheck.h index 10319cc..c204475 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -85,7 +85,6 @@ struct _CandidateCheckPair gchar foundation[NICE_CANDIDATE_PAIR_MAX_FOUNDATION]; NiceCheckState state; gboolean nominated; - gboolean controlling; gboolean timer_restarted; gboolean valid; guint64 priority; -- 2.13.6 From b0538d8c51f65019867b56a45cf90a70bef38f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 11 Apr 2017 18:31:21 -0400 Subject: [PATCH 16/70] agent: Ignore remote candidate of non-accepted types If we disable ice-tcp or ice-udp, ignore the remote candidates for those types. --- agent/agent.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/agent/agent.c b/agent/agent.c index eff62f0..77fb1eb 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -3112,6 +3112,13 @@ static gboolean priv_add_remote_candidate ( NiceComponent *component; NiceCandidate *candidate; + if (transport == NICE_CANDIDATE_TRANSPORT_UDP && + !agent->use_ice_udp) + return FALSE; + if (transport != NICE_CANDIDATE_TRANSPORT_UDP && + !agent->use_ice_tcp) + return FALSE; + if (!agent_find_component (agent, stream_id, component_id, NULL, &component)) return FALSE; -- 2.13.6 From f6f704c5e8d2193bc67ba2b697c77694e1698c43 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Thu, 9 Jun 2016 22:22:33 +0200 Subject: [PATCH 17/70] stun timer: fix timeout of the last retransmission According to RFC 5389, section 7.2.1, a special timeout is applied to the last retransmission (Rm * RTO), with Rm default value of 16, instead of (64 * RTO), 2^6 when the number of transmissions Rc is set to 7. As spotted by Olivier Crete, stun_timer_* is a public API, that cannot be changed, and the initial delay (RTO) is not preserved in the stun_timer_s struct. So we use a hack that implicitely guess Rm from the number of transmissions Rc, by generalizing the default value of the spec for Rm and Rc to other values of Rc passed in stun_timer_start( According to the spec, with the default value of Rc=7, the last delay should be (64 * RTO), and it is instead (16 * RTO). So the last delay can be computed by dividing the penultimate delay by two, instead of multiplying it by two. Differential Revision: https://phabricator.freedesktop.org/D1108 --- stun/usages/timer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/stun/usages/timer.c b/stun/usages/timer.c index 2862ab8..5370cba 100644 --- a/stun/usages/timer.c +++ b/stun/usages/timer.c @@ -145,7 +145,11 @@ StunUsageTimerReturn stun_timer_refresh (StunTimer *timer) if (timer->retransmissions >= timer->max_retransmissions) return STUN_USAGE_TIMER_RETURN_TIMEOUT; - add_delay (&timer->deadline, timer->delay *= 2); + if (timer->retransmissions == timer->max_retransmissions - 1) + timer->delay = timer->delay / 2; + else + timer->delay = timer->delay * 2; + add_delay (&timer->deadline, timer->delay); timer->retransmissions++; return STUN_USAGE_TIMER_RETURN_RETRANSMIT; } -- 2.13.6 From 0a2cb0a9b14a5a1a4b01ba68ab2e5a2aa965f342 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 5 Apr 2016 21:32:39 +0200 Subject: [PATCH 18/70] agent: do not create a GSource for UDP TURN socket With this patch, we don't create a new GSource for udp-turn socket, because it would duplicate the packets already received on the base UDP socket, as the underlying GSocket is the same. This is a race condition, because an UDP packet arriving on the base socket, may randomly be handled by the GSource callback created for the base socket (udp-bsd) of the callback created for the udp-turn socket. Moreover this callback already knows how to parse UDP datagrams received from a known turn server. This patch also prevents a subtle bug, when a STUN request is received directly from a peer, is handled by the udp turn socket. If the agent already has a valid permission for this remote candidate, established for another pair, it will happily send the STUN reply through the turn relay. This generates a source address mismatch on the peer agent, when it'll receive the STUN response from the turn relay instead of the initial address the request has been sent to. Differential Revision: https://phabricator.freedesktop.org/D932 --- agent/component.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/agent/component.c b/agent/component.c index ba28ffa..ab665b6 100644 --- a/agent/component.c +++ b/agent/component.c @@ -105,6 +105,13 @@ socket_source_attach (SocketSource *socket_source, GMainContext *context) if (socket_source->socket->fileno == NULL) return; + /* Do not create a GSource for UDP turn socket, because it + * would duplicate the packets already received on the base + * UDP socket. + */ + if (socket_source->socket->type == NICE_SOCKET_TYPE_UDP_TURN) + return; + /* Create a source. */ source = g_socket_create_source (socket_source->socket->fileno, G_IO_IN, NULL); -- 2.13.6 From d5446a72233eab8501be0b3fb9060c8be3ba034b Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 1 May 2017 08:51:40 +0100 Subject: [PATCH 19/70] examples: Stop installing the examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There’s no point in installing them; their benefit is in providing example code to developers. Debian doesn’t package them; Fedora packages them in a separate subpackage which will have to disappear. Signed-off-by: Philip Withnall Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1737 --- examples/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Makefile.am b/examples/Makefile.am index 1e7decf..9c80854 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -18,7 +18,7 @@ AM_CFLAGS = \ $(GLIB_CFLAGS) \ $(GUPNP_CFLAGS) -bin_PROGRAMS = simple-example threaded-example sdp-example +noinst_PROGRAMS = simple-example threaded-example sdp-example simple_example_SOURCES = simple-example.c simple_example_LDADD = $(top_builddir)/agent/libagent.la \ -- 2.13.6 From b4abda09c79e4ce372a3771300abf568c85c7ff5 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Thu, 21 Apr 2016 18:18:59 +0200 Subject: [PATCH 20/70] interfaces: ignore predefined network interfaces Some interfaces, like the one managed by libvirtd to provide a network bridge to locally hosted virtual machines, can be completely ignored when gathering ICE candidates. The motivation for adding this possibility is that, ignoring them doesn't remove capabilities, and improves the overall speed of the connection check method, by reducing the number of pairs to be tested. This patch adds the possibility to define such interfaces in the configuration script. Differential Revision: https://phabricator.freedesktop.org/D948 --- agent/interfaces.c | 6 ++++++ configure.ac | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/agent/interfaces.c b/agent/interfaces.c index 0fa2fd7..a81888e 100644 --- a/agent/interfaces.c +++ b/agent/interfaces.c @@ -276,6 +276,12 @@ nice_interfaces_get_local_ips (gboolean include_loopback) nice_debug ("Ignoring loopback interface"); g_free (addr_string); } +#ifdef IGNORED_IFACE_PREFIX + } else if (g_str_has_prefix (ifa->ifa_name, IGNORED_IFACE_PREFIX)) { + nice_debug ("Ignoring interface %s as it matches prefix %s", + ifa->ifa_name, IGNORED_IFACE_PREFIX); + g_free (addr_string); +#endif } else { if (nice_interfaces_is_private_ip (ifa->ifa_addr)) ips = add_ip_to_list (ips, addr_string, TRUE); diff --git a/configure.ac b/configure.ac index b39bfe3..98bbc08 100644 --- a/configure.ac +++ b/configure.ac @@ -354,6 +354,20 @@ AM_CONDITIONAL([ENABLE_GTK_DOC], false) # GObject introspection GOBJECT_INTROSPECTION_CHECK([1.30.0]) +dnl Ignore a specific network interface name prefix from the connection check +AC_MSG_CHECKING([whether to ignore a specific network interface name prefix]) +AC_ARG_WITH([ignored-network-interface-prefix], + [AS_HELP_STRING([--with-ignored-network-interface-prefix=string], + [Ignore network interfaces whose name starts with "string" from the ICE connection + check algorithm. For example, interfaces "virbr" in the case of the virtual bridge + handled by libvirtd, do not help in finding connectivity.])], + [interface_prefix="$withval"]) +AS_IF([test -n "$interface_prefix"], + [AC_DEFINE_UNQUOTED([IGNORED_IFACE_PREFIX],["$interface_prefix"], + [Ignore this network interface prefix from the connection check]) + AC_MSG_RESULT([yes, $interface_prefix])], + [AC_MSG_RESULT([no])]) + AC_CONFIG_MACRO_DIR(m4) AC_OUTPUT -- 2.13.6 From 80c613699786567fd93db74377138600794a86e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Thu, 8 Jun 2017 16:34:21 -0400 Subject: [PATCH 21/70] agent: Use base_addr to generate rport in SDP Reported by Capricornus (zhushengliang) https://phabricator.freedesktop.org/T7763 --- agent/agent.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agent.c b/agent/agent.c index 77fb1eb..1ff09af 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -5690,7 +5690,7 @@ _generate_candidate_sdp (NiceAgent *agent, g_string_append_printf (sdp, " typ %s", _cand_type_to_sdp (candidate->type)); if (nice_address_is_valid (&candidate->base_addr) && !nice_address_equal (&candidate->addr, &candidate->base_addr)) { - port = nice_address_get_port (&candidate->addr); + port = nice_address_get_port (&candidate->base_addr); nice_address_to_string (&candidate->base_addr, ip4); g_string_append_printf (sdp, " raddr %s rport %d", ip4, port == 0 ? 9 : port); -- 2.13.6 From 8bb210c5af4bcaf342d7fa4fef6034269e976532 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Thu, 9 Jun 2016 23:28:43 +0200 Subject: [PATCH 22/70] stun timer: make properties for stun timer tunables Three STUN binding request properties should be customisable. RFC 5245 describes the retransmission timer of the STUN transaction 'RTO', and RFC 5389 describes the number of retransmissions to send until a response is received 'Rc'. The third property is the 'RTO' when a reliable connection is used. RFC 5389 introduces a supplementary property 'Rm' as a multiplier used to compute the final timeout RTO * Rm. However, this property is not added in libnice, because this would require breaking the public API for STUN. Currently, our STUN implementation hardcodes a division by two for this final timeout. Differential Revision: https://phabricator.freedesktop.org/D1109 --- agent/agent-priv.h | 4 ++- agent/agent.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++ agent/conncheck.c | 16 +++++---- agent/discovery.c | 8 ++--- stun/usages/timer.h | 6 +++- 5 files changed, 118 insertions(+), 13 deletions(-) diff --git a/agent/agent-priv.h b/agent/agent-priv.h index ada3630..162ea63 100644 --- a/agent/agent-priv.h +++ b/agent/agent-priv.h @@ -106,7 +106,6 @@ nice_input_message_iter_compare (const NiceInputMessageIter *a, #define NICE_AGENT_TIMER_TA_DEFAULT 20 /* timer Ta, msecs (impl. defined) */ #define NICE_AGENT_TIMER_TR_DEFAULT 25000 /* timer Tr, msecs (impl. defined) */ -#define NICE_AGENT_TIMER_TR_MIN 15000 /* timer Tr, msecs (ICE ID-19) */ #define NICE_AGENT_MAX_CONNECTIVITY_CHECKS_DEFAULT 100 /* see spec 5.7.3 (ID-19) */ @@ -132,6 +131,9 @@ struct _NiceAgent guint timer_ta; /* property: timer Ta */ guint max_conn_checks; /* property: max connectivity checks */ gboolean force_relay; /* property: force relay */ + guint stun_max_retransmissions; /* property: stun max retransmissions, Rc */ + guint stun_initial_timeout; /* property: stun initial timeout, RTO */ + guint stun_reliable_timeout; /* property: stun reliable timeout */ GSList *local_addresses; /* list of NiceAddresses for local interfaces */ diff --git a/agent/agent.c b/agent/agent.c index 1ff09af..25d7886 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -113,6 +113,9 @@ enum PROP_BYTESTREAM_TCP, PROP_KEEPALIVE_CONNCHECK, PROP_FORCE_RELAY, + PROP_STUN_MAX_RETRANSMISSIONS, + PROP_STUN_INITIAL_TIMEOUT, + PROP_STUN_RELIABLE_TIMEOUT, }; @@ -708,6 +711,76 @@ nice_agent_class_init (NiceAgentClass *klass) FALSE, G_PARAM_READWRITE)); + /** + * NiceAgent:stun-max-retransmissions + * + * The maximum number of retransmissions of the STUN binding requests + * used in the gathering stage, to find our local candidates, and used + * in the connection check stage, to test the validity of each + * constructed pair. This property is described as 'Rc' in the RFC + * 5389, with a default value of 7. The timeout of each STUN request + * is doubled for each retransmission, so the choice of this value has + * a direct impact on the time needed to move from the CONNECTED state + * to the READY state, and on the time needed to complete the GATHERING + * state. + * + * Since: UNRELEASED + */ + + g_object_class_install_property (gobject_class, PROP_STUN_MAX_RETRANSMISSIONS, + g_param_spec_uint ( + "stun-max-retransmissions", + "STUN Max Retransmissions", + "Maximum number of STUN binding requests retransmissions " + "described as 'Rc' in the STUN specification.", + 1, 99, + STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** + * NiceAgent:stun-initial-timeout + * + * The initial timeout (msecs) of the STUN binding requests + * used in the gathering stage, to find our local candidates. + * This property is described as 'RTO' in the RFC 5389 and RFC 5245. + * This timeout is doubled for each retransmission, until + * #NiceAgent:stun-max-retransmissions have been done, + * with an exception for the last restransmission, where the timeout is + * divided by two instead (RFC 5389 indicates that a customisable + * multiplier 'Rm' to 'RTO' should be used). + * + * Since: UNRELEASED + */ + + g_object_class_install_property (gobject_class, PROP_STUN_INITIAL_TIMEOUT, + g_param_spec_uint ( + "stun-initial-timeout", + "STUN Initial Timeout", + "STUN timeout in msecs of the initial binding requests used in the " + "gathering state, described as 'RTO' in the ICE specification.", + 20, 9999, + STUN_TIMER_DEFAULT_TIMEOUT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** + * NiceAgent:stun-reliable-timeout + * + * The initial timeout of the STUN binding requests used + * for a reliable timer. + * + * Since: UNRELEASED + */ + + g_object_class_install_property (gobject_class, PROP_STUN_RELIABLE_TIMEOUT, + g_param_spec_uint ( + "stun-reliable-timeout", + "STUN Reliable Timeout", + "STUN timeout in msecs of the initial binding requests used for " + "a reliable timer.", + 20, 99999, + STUN_TIMER_DEFAULT_RELIABLE_TIMEOUT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + /* install signals */ /** @@ -1187,6 +1260,18 @@ nice_agent_get_property ( g_value_set_boolean (value, agent->force_relay); break; + case PROP_STUN_MAX_RETRANSMISSIONS: + g_value_set_uint (value, agent->stun_max_retransmissions); + break; + + case PROP_STUN_INITIAL_TIMEOUT: + g_value_set_uint (value, agent->stun_initial_timeout); + break; + + case PROP_STUN_RELIABLE_TIMEOUT: + g_value_set_uint (value, agent->stun_reliable_timeout); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -1374,6 +1459,18 @@ nice_agent_set_property ( agent->force_relay = g_value_get_boolean (value); break; + case PROP_STUN_MAX_RETRANSMISSIONS: + agent->stun_max_retransmissions = g_value_get_uint (value); + break; + + case PROP_STUN_INITIAL_TIMEOUT: + agent->stun_initial_timeout = g_value_get_uint (value); + break; + + case PROP_STUN_RELIABLE_TIMEOUT: + agent->stun_reliable_timeout = g_value_get_uint (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } diff --git a/agent/conncheck.c b/agent/conncheck.c index 5501c2b..14fdcd9 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -888,8 +888,9 @@ static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent) agent, buf_len, p->keepalive.stun_message.buffer); if (buf_len > 0) { - stun_timer_start (&p->keepalive.timer, STUN_TIMER_DEFAULT_TIMEOUT, - STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS); + stun_timer_start (&p->keepalive.timer, + agent->stun_initial_timeout, + agent->stun_max_retransmissions); agent->media_after_tick = FALSE; @@ -1116,8 +1117,9 @@ static void priv_turn_allocate_refresh_tick_unlocked (CandidateRefresh *cand) } if (buffer_len > 0) { - stun_timer_start (&cand->timer, STUN_TIMER_DEFAULT_TIMEOUT, - STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS); + stun_timer_start (&cand->timer, + cand->agent->stun_initial_timeout, + cand->agent->stun_max_retransmissions); /* send the refresh */ agent_socket_send (cand->nicesock, &cand->server, @@ -2171,11 +2173,11 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) if (buffer_len > 0) { if (nice_socket_is_reliable(pair->sockptr)) { - stun_timer_start_reliable(&pair->timer, STUN_TIMER_DEFAULT_RELIABLE_TIMEOUT); + stun_timer_start_reliable(&pair->timer, agent->stun_reliable_timeout); } else { stun_timer_start (&pair->timer, priv_compute_conncheck_timer (agent), - STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS); + agent->stun_max_retransmissions); } /* TCP-ACTIVE candidate must create a new socket before sending @@ -2340,7 +2342,7 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str if (!nice_socket_is_reliable (p->sockptr) && !p->timer_restarted) { stun_timer_start (&p->timer, priv_compute_conncheck_timer (agent), - STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS); + agent->stun_max_retransmissions); p->timer_restarted = TRUE; } } diff --git a/agent/discovery.c b/agent/discovery.c index 7a890a0..4cc99c2 100644 --- a/agent/discovery.c +++ b/agent/discovery.c @@ -1072,11 +1072,11 @@ static gboolean priv_discovery_tick_unlocked (gpointer pointer) if (buffer_len > 0) { if (nice_socket_is_reliable (cand->nicesock)) { - stun_timer_start_reliable (&cand->timer, - STUN_TIMER_DEFAULT_RELIABLE_TIMEOUT); + stun_timer_start_reliable (&cand->timer, agent->stun_reliable_timeout); } else { - stun_timer_start (&cand->timer, 200, - STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS); + stun_timer_start (&cand->timer, + agent->stun_initial_timeout, + agent->stun_max_retransmissions); } /* send the conncheck */ diff --git a/stun/usages/timer.h b/stun/usages/timer.h index e74353b..097e75b 100644 --- a/stun/usages/timer.h +++ b/stun/usages/timer.h @@ -132,7 +132,11 @@ struct stun_timer_s { * The default intial timeout to use for the timer * RFC recommendds 500, but it's ridiculous, 50ms is known to work in most * cases as it is also what is used by SIP style VoIP when sending A-Law and - * mu-Law audio, so 200ms should be hyper safe. + * mu-Law audio, so 200ms should be hyper safe. With an initial timeout + * of 200ms, a default of 7 transmissions, the last timeout will be + * 16 * 200ms, and we expect to receive a response from the stun server + * before (1 + 2 + 4 + 8 + 16 + 32 + 16) * 200ms = 15200 ms after the initial + * stun request has been sent. */ #define STUN_TIMER_DEFAULT_TIMEOUT 200 -- 2.13.6 From 7a2c1edf502849a868b6f1026e8e2c343dee4ded Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Mon, 6 Jun 2016 22:24:50 +0200 Subject: [PATCH 23/70] conncheck: update selected pair when nominated flag is set This modifies commit 8f1f615. It is better focused to update the selected pair just after its nominated flag has been set. We also keep the code homogeneous with other places, where the call to priv_update_selected_pair() immediately follows the setting of pair->nominated. Moreover in priv_update_check_list_state_for_ready(), we would call priv_update_selected_pair() more times that necessary when iterating on all nominated pairs. Differential Revision: https://phabricator.freedesktop.org/D1125 --- agent/conncheck.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 14fdcd9..8c55269 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -76,6 +76,8 @@ static void conn_check_free_item (gpointer data); 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) { @@ -515,6 +517,7 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen p->state == NICE_CHECK_DISCOVERED)) { nice_debug ("Agent %p : restarting check %p as the nominated pair.", agent, p); p->nominated = TRUE; + priv_update_selected_pair (agent, component, p); priv_add_pair_to_triggered_check_queue (agent, p); break; /* move to the next component */ } @@ -1530,7 +1533,6 @@ static void priv_update_check_list_state_for_ready (NiceAgent *agent, NiceStream ++valid; if (p->nominated == TRUE) { ++nominated; - priv_update_selected_pair (agent, component, p); } } } -- 2.13.6 From 3a58ba6120b188d78c5709e0349c0346bfa21c1a Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Mon, 1 Feb 2016 11:10:21 +0100 Subject: [PATCH 24/70] conncheck: peer reflexive candidates are not paired This patch makes the code compliant with ICE RFC, 7.2.1.3 "Learning Peer Reflexive Candidates" and 7.1.3.2.1 "Discovering Peer Reflexive Candidates", where discovered candidates do not cause the creation of new pairs to be checked. Differential Revision: https://phabricator.freedesktop.org/D805 --- agent/conncheck.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/agent/conncheck.c b/agent/conncheck.c index 8c55269..cdf1025 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -1787,6 +1787,15 @@ int conn_check_add_for_candidate (NiceAgent *agent, guint stream_id, NiceCompone g_assert (remote != NULL); + /* 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 && + remote->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) + { + return added; + } + for (i = component->local_candidates; i ; i = i->next) { NiceCandidate *local = i->data; @@ -1821,6 +1830,18 @@ int conn_check_add_for_local_candidate (NiceAgent *agent, guint stream_id, NiceC g_assert (local != NULL); + /* + * note: according to 7.1.3.2.1 "Discovering Peer Reflexive + * Candidates", the peer reflexive candidate is not paired + * with other remote candidates + */ + + if (agent->compatibility == NICE_COMPATIBILITY_RFC5245 && + local->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) + { + return added; + } + for (i = component->remote_candidates; i ; i = i->next) { NiceCandidate *remote = i->data; -- 2.13.6 From a602ff57aae6a6afdeab843954c48e6fb5d82d31 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 12 Apr 2016 13:02:45 +0200 Subject: [PATCH 25/70] conncheck: fix pair state transition when successful response is received According the ICE RFC 5245, 7.1.3.2.3, the pair that *generated* a successful check should go to state succeeded, not only the valid pair built in section 7.1.3.2.2. Differential Revision: https://phabricator.freedesktop.org/D810 --- agent/conncheck.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index cdf1025..7fc2a1d 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -2654,7 +2654,10 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * } else { if (!local_cand) { - if (!agent->force_relay) + 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, @@ -2662,8 +2665,9 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * sockptr, local_candidate, remote_candidate); - p->state = NICE_CHECK_FAILED; - nice_debug ("Agent %p : pair %p state FAILED", agent, p); + nice_debug ("Agent %p : added a new peer-reflexive local candidate %p", + agent, local_cand); + } } /* step: add a new discovered pair (see RFC 5245 7.1.3.2.2 @@ -2671,7 +2675,12 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * if (local_cand) new_pair = priv_add_peer_reflexive_pair (agent, stream->id, component, local_cand, p); - nice_debug ("Agent %p : conncheck %p FAILED, %p DISCOVERED.", agent, p, new_pair); + /* step: The agent sets the state of the pair that *generated* the check to + * Succeeded, RFC 5245, 7.1.3.2.3, "Updating Pair States" + */ + p->state = NICE_CHECK_SUCCEEDED; + nice_debug ("Agent %p : conncheck %p SUCCEEDED, %p DISCOVERED.", + agent, p, new_pair); } /* note: this is same as "adding to VALID LIST" in the spec -- 2.13.6 From 0636f9addc041cf93c4ff4eaa351b1768d48a32e Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 19 Apr 2016 13:12:48 +0200 Subject: [PATCH 26/70] conncheck: implement ice regular nomination method This patch implements Regular Nomation as described in RFC5245 8.1.1.1. The controlling agent lets valid pairs accumulate, and decides which pair to recheck with the use-candidate attribute set. priv_mark_pair_nominated() follows 7.2.1.5, to update the nominated pair when acting as a STUN server, and priv_map_reply_to_conn_check_request() implements 7.1.3.2.4 to update the nominated pair when acting as a STUN client. A new property is also added to the agent to control the nomination mode, which can be regular of aggressive, with default value set to aggressive. Two new flags are introduced in the CandidateCheckPair structure: - use_candidate_on_next_check indicates the STUN client to add the use-candidate attribute when the pair will be checked. At this time, the nominated flag has not been set on this pair yet. - mark_nominated_on_response_arrival indicates the STUN server to nominate the pair when its succesfull response to a previous triggered check will arrive (7.2.1.5, item #2) Differential Revision: https://phabricator.freedesktop.org/D811 --- agent/Makefile.am | 23 ++++ agent/agent-priv.h | 8 ++ agent/agent.c | 59 +++++++++ agent/agent.h | 43 ++++++- agent/conncheck.c | 178 +++++++++++++++++++++++++++- agent/conncheck.h | 2 + configure.ac | 1 + docs/reference/libnice/libnice-sections.txt | 2 + 8 files changed, 309 insertions(+), 7 deletions(-) diff --git a/agent/Makefile.am b/agent/Makefile.am index b585393..915f312 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -22,6 +22,12 @@ if WINDOWS AM_CFLAGS += -DWINVER=0x0501 # _WIN32_WINNT_WINXP endif +BUILT_SOURCES = \ + agent-enum-types.h \ + agent-enum-types.c + +CLEANFILES += $(BUILT_SOURCES) + noinst_LTLIBRARIES = libagent.la libagent_la_SOURCES = \ @@ -54,6 +60,23 @@ libagent_la_SOURCES = \ outputstream.c \ $(BUILT_SOURCES) +agent-enum-types.h: agent.h Makefile + $(AM_V_GEN)$(GLIB_MKENUMS) \ + --fhead "#ifndef __AGENT_ENUM_TYPES_H__\n#define __AGENT_ENUM_TYPES_H__ 1\n\n#include \n\nG_BEGIN_DECLS\n" \ + --fprod "/* enumerations from \"@filename@\" */\n" \ + --vhead "GType @enum_name@_get_type (void) G_GNUC_CONST;\n#define NICE_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \ + --ftail "G_END_DECLS\n\n#endif /* !AGENT_ENUM_TYPES_H */" \ + $(addprefix $(srcdir)/,agent.h) > $@ + +agent-enum-types.c: agent.h Makefile agent-enum-types.h + $(AM_V_GEN)$(GLIB_MKENUMS) \ + --fhead "#include \n#include \n#include \"agent.h\"\n#include \"agent-enum-types.h\"" \ + --fprod "\n/* enumerations from \"@filename@\" */" \ + --vhead "GType\n@enum_name@_get_type (void)\n{\n static GType type = 0;\n if (!type) {\n static const G@Type@Value values[] = {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n };\n type = g_@type@_register_static (\"@EnumName@\", values);\n }\n return type;\n}\n\n" \ + $(addprefix $(srcdir)/,agent.h) > $@ + libagent_la_LIBADD = \ $(top_builddir)/random/libnice-random.la \ $(top_builddir)/socket/libsocket.la \ diff --git a/agent/agent-priv.h b/agent/agent-priv.h index 162ea63..3384180 100644 --- a/agent/agent-priv.h +++ b/agent/agent-priv.h @@ -115,6 +115,13 @@ nice_input_message_iter_compare (const NiceInputMessageIter *a, #define NICE_COMPONENT_MAX_VALID_CANDIDATES 50 /* maximum number of validates remote candidates to keep, the number is arbitrary but hopefully large enough */ +/* A convenient macro to test if the agent is compatible with RFC5245 + * or OC2007R2. Specifically these two modes share the support + * of the regular or aggressive nomination mode */ +#define NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2(obj) \ + ((obj)->compatibility == NICE_COMPATIBILITY_RFC5245 || \ + (obj)->compatibility == NICE_COMPATIBILITY_OC2007R2) + struct _NiceAgent { GObject parent; /* gobject pointer */ @@ -134,6 +141,7 @@ struct _NiceAgent guint stun_max_retransmissions; /* property: stun max retransmissions, Rc */ guint stun_initial_timeout; /* property: stun initial timeout, RTO */ guint stun_reliable_timeout; /* property: stun reliable timeout */ + NiceNominationMode nomination_mode; /* property: Nomination mode */ GSList *local_addresses; /* list of NiceAddresses for local interfaces */ diff --git a/agent/agent.c b/agent/agent.c index 25d7886..577a7e0 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -73,6 +73,7 @@ #include "interfaces.h" #include "pseudotcp.h" +#include "agent-enum-types.h" /* Maximum size of a UDP packet’s payload, as the packet’s length field is 16b * wide. */ @@ -116,6 +117,7 @@ enum PROP_STUN_MAX_RETRANSMISSIONS, PROP_STUN_INITIAL_TIMEOUT, PROP_STUN_RELIABLE_TIMEOUT, + PROP_NOMINATION_MODE, }; @@ -440,6 +442,24 @@ nice_agent_class_init (NiceAgentClass *klass) G_PARAM_READWRITE)); /** + * NiceAgent:nomination-mode: + * + * The nomination mode used in the ICE specification for describing + * the selection of valid pairs to be used upstream. + * See also: #NiceNominationMode + * + * Since: UNRELEASED + */ + g_object_class_install_property (gobject_class, PROP_NOMINATION_MODE, + g_param_spec_enum ( + "nomination-mode", + "ICE nomination mode", + "Nomination mode used in the ICE specification for describing " + "the selection of valid pairs to be used upstream", + NICE_TYPE_NOMINATION_MODE, NICE_NOMINATION_MODE_AGGRESSIVE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** * NiceAgent:proxy-ip: * * The proxy server IP used to bypass a proxy firewall @@ -1096,6 +1116,7 @@ nice_agent_init (NiceAgent *agent) agent->stun_server_port = DEFAULT_STUN_PORT; agent->controlling_mode = TRUE; agent->max_conn_checks = NICE_AGENT_MAX_CONNECTIVITY_CHECKS_DEFAULT; + agent->nomination_mode = NICE_NOMINATION_MODE_AGGRESSIVE; agent->discovery_list = NULL; agent->discovery_unsched_items = 0; @@ -1144,6 +1165,23 @@ nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat) } +NICEAPI_EXPORT NiceAgent * +nice_agent_new_full (GMainContext *ctx, + NiceCompatibility compat, + gboolean reliable, + NiceNominationMode nomination) +{ + NiceAgent *agent = g_object_new (NICE_TYPE_AGENT, + "compatibility", compat, + "main-context", ctx, + "reliable", reliable, + "nomination-mode", nomination, + NULL); + + return agent; +} + + static void nice_agent_get_property ( GObject *object, @@ -1190,6 +1228,10 @@ nice_agent_get_property ( /* XXX: should we prune the list of already existing checks? */ break; + case PROP_NOMINATION_MODE: + g_value_set_enum (value, agent->nomination_mode); + break; + case PROP_PROXY_IP: g_value_set_string (value, agent->proxy_ip); break; @@ -1394,6 +1436,10 @@ nice_agent_set_property ( agent->max_conn_checks = g_value_get_uint (value); break; + case PROP_NOMINATION_MODE: + agent->nomination_mode = g_value_get_enum (value); + break; + case PROP_PROXY_IP: g_free (agent->proxy_ip); agent->proxy_ip = g_value_dup_string (value); @@ -3298,6 +3344,19 @@ static gboolean priv_add_remote_candidate ( username, password, priority); } + if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { + /* note: If there are TCP candidates for a media stream, + * a controlling agent MUST use the regular selection algorithm, + * RFC 6544, sect 8, "Concluding ICE Processing" + */ + if (agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE && + transport != NICE_CANDIDATE_TRANSPORT_UDP) { + nice_debug ("Agent %p : we have TCP candidates, switching back " + "to regular nomination mode", agent); + agent->nomination_mode = NICE_NOMINATION_MODE_REGULAR; + } + } + if (base_addr) candidate->base_addr = *base_addr; diff --git a/agent/agent.h b/agent/agent.h index 47c4d5a..6e233c6 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -377,6 +377,26 @@ typedef enum NICE_PROXY_TYPE_LAST = NICE_PROXY_TYPE_HTTP, } NiceProxyType; +/** + * NiceNominationMode: + * @NICE_NOMINATION_MODE_AGGRESSIVE: Aggressive nomination mode + * @NICE_NOMINATION_MODE_REGULAR: Regular nomination mode + * + * An enum to specity the kind of nomination mode to use by + * the agent, as described in RFC 5245. Two modes exists, + * regular and aggressive. They differ by the way the controlling + * agent chooses to put the USE-CANDIDATE attribute in its STUN + * messages. The aggressive mode is supposed to nominate a pair + * faster, than the regular mode, potentially causing the nominated + * pair to change until the connection check completes. + * + * Since: UNRELEASED + */ +typedef enum +{ + NICE_NOMINATION_MODE_REGULAR = 0, + NICE_NOMINATION_MODE_AGGRESSIVE, +} NiceNominationMode; /** * NiceAgentRecvFunc: @@ -429,6 +449,28 @@ NiceAgent * nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat); /** + * nice_agent_new_full: + * @ctx: The Glib Mainloop Context to use for timers + * @compat: The compatibility mode of the agent + * @reliable: The reliability mode of the agent + * @nomination: The nomination mode of the agent + * + * Create a new #NiceAgent with parameters that must be be defined at + * construction time. + * The returned object must be freed with g_object_unref() + * See also: #NiceNominationMode + * + * Since: UNRELEASED + * + * Returns: The new agent GObject + */ +NiceAgent * +nice_agent_new_full (GMainContext *ctx, + NiceCompatibility compat, + gboolean reliable, + NiceNominationMode nomination); + +/** * nice_agent_add_local_address: * @agent: The #NiceAgent Object * @addr: The address to listen to @@ -447,7 +489,6 @@ nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat); gboolean nice_agent_add_local_address (NiceAgent *agent, NiceAddress *addr); - /** * nice_agent_add_stream: * @agent: The #NiceAgent Object diff --git a/agent/conncheck.c b/agent/conncheck.c index 7fc2a1d..6827e6e 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -502,7 +502,62 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen if (s_nominated < stream->n_components && s_waiting_for_nomination) { keep_timer_going = TRUE; - if (agent->controlling_mode) { + if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { + if (agent->nomination_mode == NICE_NOMINATION_MODE_REGULAR && + agent->controlling_mode && + ((waiting == 0 && s_inprogress == 0) || + (s_succeeded + s_discovered) >= 5 * stream->n_components)){ + /* ICE 8.1.1.1 Regular nomination + * we choose to nominate the valid pair if + * there is no pair left waiting or in-progress or + * if there are at least 5 valid pairs per stream on average. + * + * This is the "stopping criterion" described in 8.1.1.1, and is + * a "local optimization" between accumulating more valid pairs, + * 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; + gboolean already_done = FALSE; + + /* 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; + if (p->component_id == component->id && + p->use_candidate_on_next_check) { + already_done = TRUE; + break; + } + } + + /* choose a pair to be nominated in the list of valid + * pairs, and add it to the triggered checks list + */ + if (!already_done) { + for (k = stream->conncheck_list; k ; k = k->next) { + CandidateCheckPair *p = k->data; + /* note: highest priority item selected (list always sorted) */ + if (p->component_id == component->id && + !p->nominated && + !p->use_candidate_on_next_check && + p->valid) { + nice_debug ("Agent %p : restarting check %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); + break; /* move to the next component */ + } + } + } + } + } + } else if (agent->controlling_mode) { GSList *component_item; for (component_item = stream->components; component_item; @@ -1571,10 +1626,40 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice g_assert (component); + if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) && + agent->controlling_mode) + return; + /* step: search for at least one nominated pair */ for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *pair = i->data; - if (pair->local == localcand && pair->remote == remotecand) { + if (pair->local == localcand && pair->remote == remotecand && + NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { + /* ICE, 7.2.1.5. Updating the Nominated Flag */ + /* note: TCP candidates typically produce peer reflexive + * candidate, generating a "discovered" pair that can be + * nominated. + */ + if (pair->valid) { + nice_debug ("Agent %p : marking pair %p (%s) as nominated", + agent, pair, pair->foundation); + pair->nominated = TRUE; + priv_update_selected_pair (agent, component, pair); + /* Do not step down to CONNECTED if we're already at state READY*/ + if (component->state != NICE_COMPONENT_STATE_READY) { + /* step: notify the client of a new component state (must be done + * before the possible check list state update step */ + agent_signal_component_state_change (agent, + stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); + } + priv_update_check_list_state_for_ready (agent, stream, component); + } else if (pair->state == NICE_CHECK_IN_PROGRESS) { + pair->mark_nominated_on_response_arrival = TRUE; + nice_debug ("Agent %p : pair %p (%s) is in-progress, " + "will be nominated on response receipt.", + agent, pair, pair->foundation); + } + } else if (pair->local == localcand && pair->remote == remotecand) { nice_debug ("Agent %p : marking pair %p (%s) as nominated", agent, pair, pair->foundation); pair->nominated = TRUE; if (pair->valid) { @@ -2174,7 +2259,35 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) pair->prflx_priority, controlling); } - if (cand_use) + if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { + switch (agent->nomination_mode) { + case NICE_NOMINATION_MODE_REGULAR: + { + /* We are doing regular nomination, so we set the use-candidate + * attrib, when the controlling agent decided which valid pair to + * resend with this flag in priv_conn_check_tick_stream() + */ + cand_use = pair->use_candidate_on_next_check; + nice_debug ("Agent %p : %s: set cand_use=%d " + "(regular nomination).", agent, G_STRFUNC, cand_use); + break; + } + case NICE_NOMINATION_MODE_AGGRESSIVE: + { + /* We are doing aggressive nomination, we set the use-candidate + * attrib in every check we send, when we are the controlling + * agent, RFC 5245, 8.1.1.2 + */ + cand_use = controlling; + nice_debug ("Agent %p : %s: set cand_use=%d " + "(aggressive nomination).", agent, G_STRFUNC, cand_use); + break; + } + default: + /* Nothing to do. */ + break; + } + } else if (cand_use) pair->nominated = controlling; if (uname_len > 0) { @@ -2781,12 +2894,66 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre local_candidate, remote_candidate); } - + /* Note: this assignment helps to reduce the numbers of cases + * to be tested. If ok_pair and p refer to distinct pairs, it + * means that ok_pair is a discovered peer reflexive one, + * caused by the check made on pair p. In that case, the + * flags to be tested are on p, but the nominated flag will be + * set on ok_pair. When there's no discovered pair, p and + * ok_pair refer to the same pair. + * To summarize : p is a SUCCEEDED pair, ok_pair is a + * DISCOVERED, VALID, and eventually NOMINATED pair. + */ if (!ok_pair) ok_pair = p; /* step: updating nominated flag (ICE 7.1.2.2.4 "Updating the Nominated Flag" (ID-19) */ + if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { + nice_debug ("Agent %p : Updating nominated flag (%s): " + "ok_pair=%p (%d/%d) p=%p (%d/%d) (ucnc/mnora)", + agent, p->local->transport == NICE_CANDIDATE_TRANSPORT_UDP ? + "UDP" : "TCP", + ok_pair, ok_pair->use_candidate_on_next_check, + ok_pair->mark_nominated_on_response_arrival, + p, p->use_candidate_on_next_check, + p->mark_nominated_on_response_arrival); + + if (agent->controlling_mode) { + switch (agent->nomination_mode) { + case NICE_NOMINATION_MODE_REGULAR: + if (p->use_candidate_on_next_check) { + nice_debug ("Agent %p : marking pair %p (%s) as nominated " + "(regular nomination, control=1, " + "use_cand_on_next_check=1).", + agent, ok_pair, ok_pair->foundation); + ok_pair->nominated = TRUE; + } + break; + case NICE_NOMINATION_MODE_AGGRESSIVE: + if (!p->nominated) { + nice_debug ("Agent %p : marking pair %p (%s) as nominated " + "(aggressive nomination, control=1).", + agent, ok_pair, ok_pair->foundation); + ok_pair->nominated = TRUE; + } + break; + default: + /* Nothing to do */ + break; + } + } else { + if (p->mark_nominated_on_response_arrival) { + nice_debug ("Agent %p : marking pair %p (%s) as nominated " + "(%s nomination, control=0, mark_on_response=1).", + agent, ok_pair, ok_pair->foundation, + agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE ? + "aggressive" : "regular"); + ok_pair->nominated = TRUE; + } + } + } + if (ok_pair->nominated == TRUE) { priv_update_selected_pair (agent, component, ok_pair); priv_print_conn_check_lists (agent, G_STRFUNC, @@ -3668,8 +3835,7 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, stun_usage_ice_conncheck_use_candidate (&req); uint32_t priority = stun_usage_ice_conncheck_priority (&req); - if (agent->controlling_mode || - agent->compatibility == NICE_COMPATIBILITY_GOOGLE || + if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE || agent->compatibility == NICE_COMPATIBILITY_MSN || agent->compatibility == NICE_COMPATIBILITY_OC2007) use_candidate = TRUE; diff --git a/agent/conncheck.h b/agent/conncheck.h index c204475..0f988de 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -87,6 +87,8 @@ struct _CandidateCheckPair gboolean nominated; gboolean timer_restarted; gboolean valid; + gboolean use_candidate_on_next_check; + gboolean mark_nominated_on_response_arrival; guint64 priority; guint32 prflx_priority; GTimeVal next_tick; /* next tick timestamp */ diff --git a/configure.ac b/configure.ac index 98bbc08..6c106ff 100644 --- a/configure.ac +++ b/configure.ac @@ -57,6 +57,7 @@ AC_PROG_CC AM_PROG_AR LT_PREREQ([2.2.6]) LT_INIT([dlopen win32-dll disable-static]) +AC_PATH_PROG([GLIB_MKENUMS],[glib-mkenums]) # Check Operating System AC_MSG_CHECKING([operating system]) diff --git a/docs/reference/libnice/libnice-sections.txt b/docs/reference/libnice/libnice-sections.txt index 88a6cd2..a481106 100644 --- a/docs/reference/libnice/libnice-sections.txt +++ b/docs/reference/libnice/libnice-sections.txt @@ -5,6 +5,7 @@ NiceAgent NiceComponentState NiceComponentType NiceProxyType +NiceNominationMode NiceCompatibility NiceAgentRecvFunc NiceInputMessage @@ -12,6 +13,7 @@ NiceOutputMessage NICE_AGENT_MAX_REMOTE_CANDIDATES nice_agent_new nice_agent_new_reliable +nice_agent_new_full nice_agent_add_local_address nice_agent_set_port_range nice_agent_add_stream -- 2.13.6 From 4497d9b7afaaea7124db4a2cd13546d9366b5986 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Wed, 22 Jun 2016 15:44:39 +0200 Subject: [PATCH 27/70] test-nomination: added a new test for the nomination mode Differential Revision: https://phabricator.freedesktop.org/D1107 --- tests/Makefile.am | 5 +- tests/test-nomination.c | 263 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 tests/test-nomination.c diff --git a/tests/Makefile.am b/tests/Makefile.am index 62d5d64..b623764 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -55,7 +55,8 @@ check_PROGRAMS = \ test-icetcp \ test-credentials \ test-turn \ - test-drop-invalid + test-drop-invalid \ + test-nomination dist_check_SCRIPTS = \ check-test-fullmode-with-stun.sh \ @@ -131,6 +132,8 @@ test_turn_LDADD = $(COMMON_LDADD) test_drop_invalid_LDADD = $(COMMON_LDADD) +test_nomination_LDADD = $(COMMON_LDADD) + test_gstreamer_CFLAGS = $(AM_CFLAGS) $(GST_CHECK_CFLAGS) test_gstreamer_LDADD = -lnice -L$(top_builddir)/nice/.libs $(GLIB_LIBS) $(GUPNP_LIBS) $(GST_CHECK_LIBS) $(GST_LIBS) diff --git a/tests/test-nomination.c b/tests/test-nomination.c new file mode 100644 index 0000000..b5a5e5f --- /dev/null +++ b/tests/test-nomination.c @@ -0,0 +1,263 @@ +#include +#include +#include + +#include +#include + +static NiceComponentState global_lagent_state[2] = { NICE_COMPONENT_STATE_LAST, NICE_COMPONENT_STATE_LAST }; +static NiceComponentState global_ragent_state[2] = { NICE_COMPONENT_STATE_LAST, NICE_COMPONENT_STATE_LAST }; +static guint global_components_ready = 0; +static gboolean global_lagent_gathering_done = FALSE; +static gboolean global_ragent_gathering_done = FALSE; +static int global_lagent_cands = 0; +static int global_ragent_cands = 0; + +static gboolean timer_cb (gpointer pointer) +{ + g_debug ("test-nomination:%s: %p", G_STRFUNC, pointer); + + /* signal status via a global variable */ + + /* note: should not be reached, abort */ + g_error ("ERROR: test has got stuck, aborting..."); + + return FALSE; +} + +static void cb_nice_recv (NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer user_data) +{ + g_debug ("test-nomination:%s: %p", G_STRFUNC, user_data); + + /* XXX: dear compiler, these are for you: */ + (void)agent; (void)stream_id; (void)component_id; (void)buf; + + /* + * Lets ignore stun packets that got through + */ + if (len < 8) + return; + if (strncmp ("12345678", buf, 8)) + return; + + if (component_id != 1) + return; +} + +static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer data) +{ + g_debug ("test-nomination:%s: %p", G_STRFUNC, data); + + if (GPOINTER_TO_UINT (data) == 1) + global_lagent_gathering_done = TRUE; + else if (GPOINTER_TO_UINT (data) == 2) + global_ragent_gathering_done = TRUE; +} + + +static void cb_component_state_changed (NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer data) +{ + gboolean ready_to_connected = FALSE; + g_debug ("test-nomination:%s: %p", G_STRFUNC, data); + + if (GPOINTER_TO_UINT (data) == 1) { + if (global_lagent_state[component_id - 1] == NICE_COMPONENT_STATE_READY && + state == NICE_COMPONENT_STATE_CONNECTED) + ready_to_connected = TRUE; + global_lagent_state[component_id - 1] = state; + } else if (GPOINTER_TO_UINT (data) == 2) { + if (global_ragent_state[component_id - 1] == NICE_COMPONENT_STATE_READY && + state == NICE_COMPONENT_STATE_CONNECTED) + ready_to_connected = TRUE; + global_ragent_state[component_id - 1] = state; + } + + if (state == NICE_COMPONENT_STATE_READY) + global_components_ready++; + else if (state == NICE_COMPONENT_STATE_CONNECTED && ready_to_connected) + global_components_ready--; + g_assert (state != NICE_COMPONENT_STATE_FAILED); + + g_debug ("test-nomination: checks READY %u.", global_components_ready); +} + +static void cb_new_selected_pair(NiceAgent *agent, guint stream_id, + guint component_id, gchar *lfoundation, gchar* rfoundation, gpointer data) +{ + g_debug ("test-nomination:%s: %p", G_STRFUNC, data); + + if (GPOINTER_TO_UINT (data) == 1) + ++global_lagent_cands; + else if (GPOINTER_TO_UINT (data) == 2) + ++global_ragent_cands; +} + +static void set_candidates (NiceAgent *from, guint from_stream, + NiceAgent *to, guint to_stream, guint component) +{ + GSList *cands = NULL, *i; + + cands = nice_agent_get_local_candidates (from, from_stream, component); + nice_agent_set_remote_candidates (to, to_stream, component, cands); + + for (i = cands; i; i = i->next) + nice_candidate_free ((NiceCandidate *) i->data); + g_slist_free (cands); +} + +static void set_credentials (NiceAgent *lagent, guint lstream, + NiceAgent *ragent, guint rstream) +{ + gchar *ufrag = NULL, *password = NULL; + + nice_agent_get_local_credentials(lagent, lstream, &ufrag, &password); + nice_agent_set_remote_credentials (ragent, rstream, ufrag, password); + g_free (ufrag); + g_free (password); + nice_agent_get_local_credentials(ragent, rstream, &ufrag, &password); + nice_agent_set_remote_credentials (lagent, lstream, ufrag, password); + g_free (ufrag); + g_free (password); +} + +static void +run_test(NiceNominationMode l_nomination_mode, + NiceNominationMode r_nomination_mode) +{ + NiceAgent *lagent, *ragent; /* agent's L and R */ + const gchar *localhost; + NiceAddress localaddr; + guint ls_id, rs_id; + gulong timer_id; + + localhost = "127.0.0.1"; + + /* step: initialize variables modified by the callbacks */ + global_components_ready = 0; + global_lagent_gathering_done = FALSE; + global_ragent_gathering_done = FALSE; + global_lagent_cands = global_ragent_cands = 0; + + lagent = nice_agent_new_full (NULL, + NICE_COMPATIBILITY_RFC5245, + FALSE, /* reliable */ + l_nomination_mode); + + ragent = nice_agent_new_full (NULL, + NICE_COMPATIBILITY_RFC5245, + FALSE, /* reliable */ + r_nomination_mode); + + g_object_set (G_OBJECT (lagent), "ice-tcp", FALSE, NULL); + g_object_set (G_OBJECT (ragent), "ice-tcp", FALSE, NULL); + + g_object_set (G_OBJECT (lagent), "upnp", FALSE, NULL); + g_object_set (G_OBJECT (ragent), "upnp", FALSE, NULL); + nice_agent_set_software (lagent, "Test-nomination, Left Agent"); + nice_agent_set_software (ragent, "Test-nomination, Right Agent"); + + timer_id = g_timeout_add (30000, timer_cb, NULL); + + if (!nice_address_set_from_string (&localaddr, localhost)) + g_assert_not_reached (); + nice_agent_add_local_address (lagent, &localaddr); + nice_agent_add_local_address (ragent, &localaddr); + + g_signal_connect (G_OBJECT (lagent), "candidate-gathering-done", + G_CALLBACK (cb_candidate_gathering_done), GUINT_TO_POINTER(1)); + g_signal_connect (G_OBJECT (ragent), "candidate-gathering-done", + G_CALLBACK (cb_candidate_gathering_done), GUINT_TO_POINTER (2)); + g_signal_connect (G_OBJECT (lagent), "component-state-changed", + G_CALLBACK (cb_component_state_changed), GUINT_TO_POINTER (1)); + g_signal_connect (G_OBJECT (ragent), "component-state-changed", + G_CALLBACK (cb_component_state_changed), GUINT_TO_POINTER (2)); + g_signal_connect (G_OBJECT (lagent), "new-selected-pair", + G_CALLBACK (cb_new_selected_pair), GUINT_TO_POINTER(1)); + g_signal_connect (G_OBJECT (ragent), "new-selected-pair", + G_CALLBACK (cb_new_selected_pair), GUINT_TO_POINTER (2)); + + g_object_set (G_OBJECT (lagent), "controlling-mode", TRUE, NULL); + g_object_set (G_OBJECT (ragent), "controlling-mode", FALSE, NULL); + + ls_id = nice_agent_add_stream (lagent, 1); + rs_id = nice_agent_add_stream (ragent, 1); + g_assert (ls_id > 0); + g_assert (rs_id > 0); + + /* Gather candidates and test nice_agent_set_port_range */ + g_assert (nice_agent_gather_candidates (lagent, ls_id) == TRUE); + g_assert (nice_agent_gather_candidates (ragent, rs_id) == TRUE); + + nice_agent_attach_recv (lagent, ls_id, NICE_COMPONENT_TYPE_RTP, + g_main_context_default (), cb_nice_recv, GUINT_TO_POINTER (1)); + nice_agent_attach_recv (ragent, rs_id, NICE_COMPONENT_TYPE_RTP, + g_main_context_default (), cb_nice_recv, GUINT_TO_POINTER (2)); + + g_debug ("test-nomination: Added streams, running context until 'candidate-gathering-done'..."); + while (!global_lagent_gathering_done) + g_main_context_iteration (NULL, TRUE); + g_assert (global_lagent_gathering_done == TRUE); + while (!global_ragent_gathering_done) + g_main_context_iteration (NULL, TRUE); + g_assert (global_ragent_gathering_done == TRUE); + + set_credentials (lagent, ls_id, ragent, rs_id); + + set_candidates (ragent, rs_id, lagent, ls_id, NICE_COMPONENT_TYPE_RTP); + set_candidates (lagent, ls_id, ragent, rs_id, NICE_COMPONENT_TYPE_RTP); + + while (global_lagent_state[0] != NICE_COMPONENT_STATE_READY || + global_ragent_state[0] != NICE_COMPONENT_STATE_READY) + g_main_context_iteration (NULL, TRUE); + g_assert (global_lagent_state[0] == NICE_COMPONENT_STATE_READY); + g_assert (global_ragent_state[0] == NICE_COMPONENT_STATE_READY); + + nice_agent_remove_stream (lagent, ls_id); + nice_agent_remove_stream (ragent, rs_id); + + g_source_remove (timer_id); + + g_clear_object(&lagent); + g_clear_object(&ragent); +} + +static void +regular (void) +{ + run_test(NICE_NOMINATION_MODE_REGULAR, NICE_NOMINATION_MODE_REGULAR); +} + +static void +aggressive (void) +{ + run_test(NICE_NOMINATION_MODE_AGGRESSIVE, NICE_NOMINATION_MODE_AGGRESSIVE); +} + +static void +mixed_ra (void) +{ + run_test(NICE_NOMINATION_MODE_REGULAR, NICE_NOMINATION_MODE_AGGRESSIVE); +} + +static void +mixed_ar (void) +{ + run_test(NICE_NOMINATION_MODE_AGGRESSIVE, NICE_NOMINATION_MODE_REGULAR); +} + +int +main (int argc, char **argv) +{ + int ret; + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/nice/nomination/regular", regular); + g_test_add_func ("/nice/nomination/aggressive", aggressive); + g_test_add_func ("/nice/nomination/mixed_ra", mixed_ra); + g_test_add_func ("/nice/nomination/mixed_ar", mixed_ar); + + ret = g_test_run (); + + return ret; +} -- 2.13.6 From 58d061df8f5425dc1add9c6030a2f891ebda4616 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Mon, 7 Mar 2016 16:35:09 +0100 Subject: [PATCH 28/70] conncheck: update pair valid property selectively With this patch, we fix a corner case when the succeeded pair is a peer-reflexive candidate pair, that already has been discovered previously, In this case, the current pair -p- should not be marked valid, because the valid flag is already set on the discovered pair. Differential Revision: https://phabricator.freedesktop.org/D1124 --- agent/conncheck.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 6827e6e..ef8df68 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -2760,6 +2760,13 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * } if (new_pair) { + /* note: when new_pair is distinct from p, it means new_pair is a + * previously discovered peer-reflexive candidate pair, so we don't + * set the valid flag on p in this case, because the valid flag is + * already set on the discovered pair. + */ + if (new_pair == p) + p->valid = TRUE; p->state = NICE_CHECK_SUCCEEDED; nice_debug ("Agent %p : conncheck %p SUCCEEDED.", agent, p); priv_conn_check_unfreeze_related (agent, stream, p); @@ -2788,6 +2795,10 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * if (local_cand) new_pair = priv_add_peer_reflexive_pair (agent, stream->id, component, local_cand, p); + /* note: this is same as "adding to VALID LIST" in the spec + text */ + if (new_pair) + new_pair->valid = TRUE; /* step: The agent sets the state of the pair that *generated* the check to * Succeeded, RFC 5245, 7.1.3.2.3, "Updating Pair States" */ @@ -2796,12 +2807,9 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * agent, p, new_pair); } - /* note: this is same as "adding to VALID LIST" in the spec - text */ - if (new_pair) { - new_pair->valid = TRUE; + if (new_pair && new_pair->valid) nice_component_add_valid_candidate (component, remote_candidate); - } + return new_pair; } -- 2.13.6 From 15c0546f624113b8c0546a1f883a48bff7020f1b Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 19 Apr 2016 17:06:32 +0200 Subject: [PATCH 29/70] conncheck: improve the selection of the pairs to be checked This patch aims to implement more closely the algorithm described in RFC 5245 indicating how pairs are transitionned from state Frozen to Waiting. This is described in 7.1.3.2 when a check succeeded, and correspond to modifications in function priv_conn_check_unfreeze_related(). This is also described in 5.7.4 when defining the initial state of the pairs in a conncheck, and correspond to modifications in function priv_conn_check_unfreeze_next(). This patch introduces the notion of active and frozen check list. It allows us to define the timer restranmission delay as described in 16.1. Another modification in priv_conn_check_tick_unlocked() is that every stream in handled consecutively, and in an independant way. The pacing was previously of a single STUN request emitted per callback, it is now of a triggered check per callback OR a single STUN per callback AND per stream per callback. The description of ordinary checks per stream in 5.8 is detailled in function priv_conn_check_tick_stream(), and a remaining of the code used to nominate a pair by the controlling agent is put in a dedicated function priv_conn_check_tick_stream_nominate() Differential Revision: https://phabricator.freedesktop.org/D813 --- agent/conncheck.c | 535 ++++++++++++++++++++++++++++++++++++++---------------- agent/stream.c | 21 --- agent/stream.h | 3 - 3 files changed, 381 insertions(+), 178 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index ef8df68..6b1b7e3 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -212,6 +212,89 @@ priv_get_pair_from_triggered_check_queue (NiceAgent *agent) } /* + * Check if the conncheck list if Active according to + * ICE spec, 5.7.4 (Computing States) + * + * note: the ICE spec in unclear about that, but the check list should + * be considered active when there is at least a pair in Waiting state + * OR a pair in In-Progress state. + */ +static gboolean +priv_is_checklist_active (NiceStream *stream) +{ + GSList *i; + + for (i = stream->conncheck_list; i ; i = i->next) { + CandidateCheckPair *p = i->data; + if (p->state == NICE_CHECK_WAITING || p->state == NICE_CHECK_IN_PROGRESS) + return TRUE; + } + return FALSE; +} + +/* + * Check if the conncheck list if Frozen according to + * ICE spec, 5.7.4 (Computing States) + */ +static gboolean +priv_is_checklist_frozen (NiceStream *stream) +{ + GSList *i; + + if (stream->conncheck_list == NULL) + return FALSE; + + for (i = stream->conncheck_list; i ; i = i->next) { + CandidateCheckPair *p = i->data; + if (p->state != NICE_CHECK_FROZEN) + return FALSE; + } + return TRUE; +} + +/* + * Check if all components of the stream have + * a valid pair (used for ICE spec, 7.1.3.2.3, point 2.) + */ +static gboolean +priv_all_components_have_valid_pair (NiceStream *stream) +{ + guint i; + GSList *j; + + for (i = 1; i <= stream->n_components; i++) { + for (j = stream->conncheck_list; j ; j = j->next) { + CandidateCheckPair *p = j->data; + if (p->component_id == i && p->valid) + break; + } + if (j == NULL) + return FALSE; + } + return TRUE; +} + +/* + * Check if the foundation in parameter matches the foundation + * of a valid pair in the conncheck list [of stream] (used for ICE spec, + * 7.1.3.2.3, point 2.) + */ +static gboolean +priv_foundation_matches_a_valid_pair (const gchar *foundation, NiceStream *stream) +{ + GSList *i; + + for (i = stream->conncheck_list; i ; i = i->next) { + CandidateCheckPair *p = i->data; + if (p->valid && + strncmp (p->foundation, foundation, + NICE_CANDIDATE_PAIR_MAX_FOUNDATION) == 0) + return TRUE; + } + return FALSE; +} + +/* * Finds the next connectivity check in WAITING state. */ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check_list) @@ -220,7 +303,6 @@ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check /* note: list is sorted in priority order to first waiting check has * the highest priority */ - for (i = conn_check_list; i ; i = i->next) { CandidateCheckPair *p = i->data; if (p->state == NICE_CHECK_WAITING) @@ -231,6 +313,74 @@ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check } /* + * Finds the next connectivity check in FROZEN state. + */ +static CandidateCheckPair * +priv_conn_check_find_next_frozen (GSList *conn_check_list) +{ + GSList *i; + + /* note: list is sorted in priority order to first frozen check has + * the highest priority */ + for (i = conn_check_list; i ; i = i->next) { + CandidateCheckPair *p = i->data; + if (p->state == NICE_CHECK_FROZEN) + return p; + } + + return NULL; +} + +/* + * Returns the number of check lists of the agent + */ +static guint +priv_number_of_check_lists (NiceAgent *agent) +{ + guint n = 0; + GSList *i; + + for (i = agent->streams; i ; i = i->next) { + NiceStream *stream = i->data; + if (stream->conncheck_list != NULL) + n++; + } + return n; +} + +/* + * 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 @@ -248,58 +398,55 @@ static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair * /* * Unfreezes the next connectivity check in the list. Follows the * algorithm (2.) defined in 5.7.4 (Computing States) of the ICE spec - * (ID-19), with some exceptions (see comments in code). + * (RFC5245) * * See also sect 7.1.2.2.3 (Updating Pair States), and * priv_conn_check_unfreeze_related(). * * @return TRUE on success, and FALSE if no frozen candidates were found. */ -static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent) +static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent, NiceStream *stream) { - CandidateCheckPair *pair = NULL; GSList *i, *j; - - /* XXX: the unfreezing is implemented a bit differently than in the - * current ICE spec, but should still be interoperate: - * - checks are not grouped by foundation - * - one frozen check is unfrozen (lowest component-id, highest - * priority) - */ + GSList *found_list = NULL; + gboolean result = FALSE; priv_print_conn_check_lists (agent, G_STRFUNC, NULL); - for (i = agent->streams; i; i = i->next) { - NiceStream *stream = i->data; - guint64 max_frozen_priority = 0; + 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 (g_slist_find_custom (found_list, p1->foundation, (GCompareFunc)strcmp)) + continue; + found_list = g_slist_prepend (found_list, p1->foundation); for (j = stream->conncheck_list; j ; j = j->next) { - CandidateCheckPair *p = j->data; - - /* XXX: the prio check could be removed as the pairs are sorted - * already */ - - if (p->state == NICE_CHECK_FROZEN) { - if (p->priority > max_frozen_priority) { - max_frozen_priority = p->priority; - pair = p; - } + 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 (pair) - break; - } - - 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); - pair->state = NICE_CHECK_WAITING; - nice_debug ("Agent %p : pair %p state WAITING", agent, pair); - return TRUE; + 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); + pair->state = NICE_CHECK_WAITING; + nice_debug ("Agent %p : pair %p state WAITING", agent, pair); + result = TRUE; + } } - - return FALSE; + g_slist_free (found_list); + return result; } /* @@ -316,7 +463,6 @@ static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent) static void priv_conn_check_unfreeze_related (NiceAgent *agent, NiceStream *stream, CandidateCheckPair *ok_check) { GSList *i, *j; - guint unfrozen = 0; g_assert (ok_check); g_assert (ok_check->state == NICE_CHECK_SUCCEEDED); @@ -336,40 +482,59 @@ static void priv_conn_check_unfreeze_related (NiceAgent *agent, NiceStream *stre nice_debug ("Agent %p : Unfreezing check %p (after successful check %p).", agent, p, ok_check); p->state = NICE_CHECK_WAITING; nice_debug ("Agent %p : pair %p state WAITING", agent, p); - ++unfrozen; } } } /* step: perform the step (2) of 'Updating Pair States' */ stream = agent_find_stream (agent, ok_check->stream_id); - if (nice_stream_all_components_ready (stream)) { - /* step: unfreeze checks from other streams */ + 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; - for (j = stream->conncheck_list; j ; j = j->next) { - CandidateCheckPair *p = j->data; - - if (p->stream_id == s->id && - p->stream_id != ok_check->stream_id) { - if (p->state == NICE_CHECK_FROZEN && - strcmp (p->foundation, ok_check->foundation) == 0) { + 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); p->state = NICE_CHECK_WAITING; nice_debug ("Agent %p : pair %p state WAITING", agent, p); - ++unfrozen; - - } - } + } + } + } 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); + p->state = NICE_CHECK_WAITING; + nice_debug ("Agent %p : pair %p state WAITING", agent, p); + } + } + + if (!match_found) { + /* set the pair with the lowest component ID + * and highest priority to Waiting + */ + priv_conn_check_unfreeze_next (agent, s); + } } - /* note: only unfreeze check from one stream at a time */ - if (unfrozen) - break; } } - - if (unfrozen == 0) - priv_conn_check_unfreeze_next (agent); } static void @@ -400,14 +565,13 @@ candidate_check_pair_fail (NiceStream *stream, NiceAgent *agent, CandidateCheckP * * @return will return FALSE when no more pending timers. */ -static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent, GTimeVal *now, gboolean *stun_transmitted) +static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent, GTimeVal *now) { gboolean keep_timer_going = FALSE; - guint s_inprogress = 0, s_succeeded = 0, s_discovered = 0, - s_nominated = 0, s_waiting_for_nomination = 0, s_valid = 0; - guint frozen = 0, waiting = 0; - GSList *i, *k; + GSList *i; + CandidateCheckPair *pair; + /* step: process ongoing STUN transactions */ for (i = stream->conncheck_list; i ; i = i->next) { CandidateCheckPair *p = i->data; @@ -451,7 +615,6 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen p->next_tick = *now; g_time_val_add (&p->next_tick, timeout * 1000); - *stun_transmitted = TRUE; return TRUE; } case STUN_USAGE_TIMER_RETURN_SUCCESS: @@ -471,7 +634,57 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen } } } + } + /* step: perform an ordinary check, ICE spec, 5.8 "Scheduling Checks" + * note: This code is executed when the triggered checks list is + * empty, and when no STUN message has been sent (pacing constraint) + */ + pair = priv_conn_check_find_next_waiting (stream->conncheck_list); + if (pair) { + priv_conn_check_initiate (agent, pair); + return TRUE; + } + + /* note: this is unclear in the ICE spec, but a check list in Frozen + * state (where all pairs are in Frozen state) is not supposed to + * change its state by an ordinary check, but only by the success of + * checks in other check lists, in priv_conn_check_unfreeze_related(). + * The underlying idea is to concentrate the checks on a single check + * list initially. + */ + if (priv_is_checklist_frozen (stream)) + return keep_timer_going; + + /* step: ordinary check continued, if there's no pair in the waiting + * state, pick a pair in the frozen state + */ + pair = priv_conn_check_find_next_frozen (stream->conncheck_list); + if (pair) { + pair->state = NICE_CHECK_WAITING; + nice_debug ("Agent %p : pair %p state WAITING", agent, pair); + priv_conn_check_initiate (agent, pair); + return TRUE; + } + return keep_timer_going; +} + +static gboolean +priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) +{ + gboolean keep_timer_going = FALSE; + 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; + + for (i = stream->conncheck_list; i ; i = i->next) { + CandidateCheckPair *p = i->data; if (p->state == NICE_CHECK_FROZEN) ++frozen; else if (p->state == NICE_CHECK_IN_PROGRESS) @@ -504,13 +717,13 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen keep_timer_going = TRUE; if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { if (agent->nomination_mode == NICE_NOMINATION_MODE_REGULAR && - agent->controlling_mode && - ((waiting == 0 && s_inprogress == 0) || - (s_succeeded + s_discovered) >= 5 * stream->n_components)){ + agent->controlling_mode) { +#define NICE_MIN_NUMBER_OF_VALID_PAIRS 2 /* ICE 8.1.1.1 Regular nomination - * we choose to nominate the valid pair if - * there is no pair left waiting or in-progress or - * if there are at least 5 valid pairs per stream on average. + * we choose to nominate the valid pair of a component if + * - there is no pair left frozen, waiting or in-progress, or + * - if there are at least two valid pairs, or + * - if there is at least one valid pair of type HOST-HOST * * This is the "stopping criterion" described in 8.1.1.1, and is * a "local optimization" between accumulating more valid pairs, @@ -523,36 +736,63 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen component_item = component_item->next) { NiceComponent *component = component_item->data; gboolean already_done = FALSE; + gboolean stopping_criterion = FALSE; + 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; - if (p->component_id == component->id && - p->use_candidate_on_next_check) { - already_done = TRUE; - break; + if (p->component_id == component->id) { + if (p->use_candidate_on_next_check) + already_done = TRUE; + if (p->state == NICE_CHECK_FROZEN) + p_frozen++; + else if (p->state == NICE_CHECK_WAITING) + p_waiting++; + else if (p->state == NICE_CHECK_IN_PROGRESS) + p_inprogress++; + if (p->valid) + p_valid++; + if (p->valid && + p->local->type == NICE_CANDIDATE_TYPE_HOST && + p->remote->type == NICE_CANDIDATE_TYPE_HOST) + p_host_host_valid++; } } - /* choose a pair to be nominated in the list of valid - * pairs, and add it to the triggered checks list + 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; + + /* 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 (!already_done) { - for (k = stream->conncheck_list; k ; k = k->next) { - CandidateCheckPair *p = k->data; - /* note: highest priority item selected (list always sorted) */ - if (p->component_id == component->id && - !p->nominated && - !p->use_candidate_on_next_check && - p->valid) { - nice_debug ("Agent %p : restarting check %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); - break; /* move to the next component */ - } + for (k = stream->conncheck_list; k ; k = k->next) { + CandidateCheckPair *p = k->data; + /* note: highest priority item selected (list always sorted) */ + if (p->component_id == component->id && + !p->nominated && + !p->use_candidate_on_next_check && + p->valid) { + nice_debug ("Agent %p : restarting check %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); + break; /* move to the next component */ } } } @@ -615,70 +855,55 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) { CandidateCheckPair *pair = NULL; gboolean keep_timer_going = FALSE; - gboolean res; - /* note: we try to only generate a single stun transaction per timer - * callback, to respect some pacing of STUN transaction, as per - * appendix B.1 of ICE spec. - */ - gboolean stun_transmitted = FALSE; GSList *i, *j; GTimeVal now; - /* step: process ongoing STUN transactions */ g_get_current_time (&now); - for (j = agent->streams; j; j = j->next) { - NiceStream *stream = j->data; - res = priv_conn_check_tick_stream (stream, agent, &now, &stun_transmitted); - if (res) - keep_timer_going = res; - if (stun_transmitted) - return TRUE; - } - - /* step: first initiate a conncheck with a pair from the triggered list */ - pair = priv_get_pair_from_triggered_check_queue (agent); - - if (pair) { - priv_print_conn_check_lists (agent, G_STRFUNC, - ", got a pair from triggered check list"); - priv_conn_check_initiate (agent, pair); + /* the conncheck really starts when we have built + * a connection check list for each stream + */ + if (priv_number_of_check_lists (agent) < g_slist_length (agent->streams)) return TRUE; - } - /* step: when the triggered list is empty, - * find the highest priority waiting check and send it */ - for (i = agent->streams; i ; i = i->next) { - NiceStream *stream = i->data; - - pair = priv_conn_check_find_next_waiting (stream->conncheck_list); - if (pair) - break; + /* configure the initial state of the check lists of the agent + * as described in ICE spec, 5.7.4 + * + * if all pairs in all check lists are in frozen state, then + * we are in the initial state (5.7.4, point 1.) + */ + if (priv_number_of_active_check_lists (agent) == 0) { + /* set some pairs of the first stream in the waiting state + * ICE spec, 5.7.4, point 2. + * + * note: we adapt the ICE spec here, by selecting the first + * frozen check list, which is not necessarily the check + * list of the first stream (the first stream may be completed) + */ + NiceStream *stream = priv_find_first_frozen_check_list (agent); + if (stream) + priv_conn_check_unfreeze_next (agent, stream); } + /* step: perform a test from the triggered checks list, + * ICE spec, 5.8 "Scheduling Checks" + */ + pair = priv_get_pair_from_triggered_check_queue (agent); + if (pair) { priv_conn_check_initiate (agent, pair); return TRUE; } - /* step: when there's no pair in the Waiting state, - * unfreeze a new pair and check it + /* step: process ongoing STUN transactions and + * perform an ordinary check, ICE spec, 5.8, "Scheduling Checks" */ - priv_conn_check_unfreeze_next (agent); - for (i = agent->streams; i ; i = i->next) { NiceStream *stream = i->data; - - pair = priv_conn_check_find_next_waiting (stream->conncheck_list); - if (pair) - break; - } - - if (pair) { - priv_print_conn_check_lists (agent, G_STRFUNC, - ", got a pair in Waiting state"); - priv_conn_check_initiate (agent, pair); - return TRUE; + if (priv_conn_check_tick_stream (stream, agent, &now)) + keep_timer_going = TRUE; + if (priv_conn_check_tick_stream_nominate (stream, agent)) + keep_timer_going = TRUE; } /* step: stop timer if no work left */ @@ -2169,30 +2394,28 @@ size_t priv_get_password (NiceAgent *agent, NiceStream *stream, /* Implement the computation specific in RFC 5245 section 16 */ -static unsigned int priv_compute_conncheck_timer (NiceAgent *agent) +static unsigned int priv_compute_conncheck_timer (NiceAgent *agent, NiceStream *stream) { - GSList *item1, *item2; + GSList *i; guint waiting_and_in_progress = 0; + guint n = 0; unsigned int rto = 0; - - for (item1 = agent->streams; item1; item1 = item1->next) { - NiceStream *stream = item1->data;; - for (item2 = stream->conncheck_list; item2; item2 = item2->next) { - CandidateCheckPair *pair = item2->data; - - if (pair->state == NICE_CHECK_IN_PROGRESS || - pair->state == NICE_CHECK_WAITING) - waiting_and_in_progress++; - } + 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++; } - rto = agent->timer_ta * waiting_and_in_progress; + n = priv_number_of_active_check_lists (agent); + rto = agent->timer_ta * n * 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)", + 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); + waiting_and_in_progress, n); if (agent->reliable) return MAX (rto, 500); else @@ -2312,7 +2535,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) stun_timer_start_reliable(&pair->timer, agent->stun_reliable_timeout); } else { stun_timer_start (&pair->timer, - priv_compute_conncheck_timer (agent), + priv_compute_conncheck_timer (agent, stream), agent->stun_max_retransmissions); } @@ -2477,7 +2700,7 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str p->timer_restarted ? "no" : "yes"); if (!nice_socket_is_reliable (p->sockptr) && !p->timer_restarted) { stun_timer_start (&p->timer, - priv_compute_conncheck_timer (agent), + priv_compute_conncheck_timer (agent, stream), agent->stun_max_retransmissions); p->timer_restarted = TRUE; } @@ -2769,7 +2992,6 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * p->valid = TRUE; p->state = NICE_CHECK_SUCCEEDED; nice_debug ("Agent %p : conncheck %p SUCCEEDED.", agent, p); - priv_conn_check_unfreeze_related (agent, stream, p); nice_component_add_valid_candidate (component, remote_candidate); } else { @@ -2894,7 +3116,6 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre g_assert_not_reached (); nice_debug ("Agent %p : Mapped address not found." " conncheck %p SUCCEEDED.", agent, p); - priv_conn_check_unfreeze_related (agent, stream, p); nice_component_add_valid_candidate (component, p->remote); } else { ok_pair = priv_process_response_check_for_reflexive (agent, @@ -2902,6 +3123,12 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre local_candidate, remote_candidate); } + /* note: The success of this check might also + * cause the state of other checks to change as well, ICE + * spec 7.1.3.2.3 + */ + priv_conn_check_unfreeze_related (agent, stream, p); + /* Note: this assignment helps to reduce the numbers of cases * to be tested. If ok_pair and p refer to distinct pairs, it * means that ok_pair is a discovered peer reflexive one, diff --git a/agent/stream.c b/agent/stream.c index 8121e12..533ff15 100644 --- a/agent/stream.c +++ b/agent/stream.c @@ -104,27 +104,6 @@ nice_stream_find_component_by_id (NiceStream *stream, guint id) } /* - * Returns true if all components of the stream are either - * 'CONNECTED' or 'READY' (connected plus nominated). - */ -gboolean -nice_stream_all_components_ready (NiceStream *stream) -{ - GSList *i; - - for (i = stream->components; i; i = i->next) { - NiceComponent *component = i->data; - if (component && - !(component->state == NICE_COMPONENT_STATE_CONNECTED || - component->state == NICE_COMPONENT_STATE_READY)) - return FALSE; - } - - return TRUE; -} - - -/* * Initialized the local crendentials for the stream. */ void diff --git a/agent/stream.h b/agent/stream.h index f9188cb..954ba66 100644 --- a/agent/stream.h +++ b/agent/stream.h @@ -103,9 +103,6 @@ nice_stream_new (guint n_components, NiceAgent *agent); void nice_stream_close (NiceStream *stream); -gboolean -nice_stream_all_components_ready (NiceStream *stream); - NiceComponent * nice_stream_find_component_by_id (NiceStream *stream, guint id); -- 2.13.6 From ead3453d04fc70865d176ab073636f8b9078cbbc Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 12 Apr 2016 13:20:38 +0200 Subject: [PATCH 30/70] conncheck: invoke the debug dump in more places Differential Revision: https://phabricator.freedesktop.org/D1123 --- agent/conncheck.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/agent/conncheck.c b/agent/conncheck.c index 6b1b7e3..2d2224d 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -642,6 +642,8 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen */ pair = priv_conn_check_find_next_waiting (stream->conncheck_list); if (pair) { + priv_print_conn_check_lists (agent, G_STRFUNC, + ", got a pair in Waiting state"); priv_conn_check_initiate (agent, pair); return TRUE; } @@ -661,6 +663,8 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen */ pair = priv_conn_check_find_next_frozen (stream->conncheck_list); if (pair) { + priv_print_conn_check_lists (agent, G_STRFUNC, + ", got a pair in Frozen state"); pair->state = NICE_CHECK_WAITING; nice_debug ("Agent %p : pair %p state WAITING", agent, pair); priv_conn_check_initiate (agent, pair); @@ -891,6 +895,8 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) pair = priv_get_pair_from_triggered_check_queue (agent); if (pair) { + priv_print_conn_check_lists (agent, G_STRFUNC, + ", got a pair from triggered check list"); priv_conn_check_initiate (agent, pair); return TRUE; } -- 2.13.6 From 2fd7808419f459d5f6e97701ca6a350ddee6b7f2 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 19 Apr 2016 17:59:27 +0200 Subject: [PATCH 31/70] conncheck: improve triggered check of in-progress pairs This patch update the way triggered checks of in-progress pairs are handled, according to ICE spec, section 7.2.1.4. Previously the same connection check was retransmitted with an updated timeout. This causes problems when a controlling role switch occurs in this time frame. This is the reason why a new connection check must be generated reflecting the updated role. We introduce a new flag "recheck_on_timeout" in the pair indicating that the pair must be rechecked at the next timer expiration. Differential Revision: https://phabricator.freedesktop.org/D875 --- agent/conncheck.c | 88 +++++++++++++++++++++++++++++++++++++++++++++---------- agent/conncheck.h | 2 +- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 2d2224d..3a489fe 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -558,6 +558,37 @@ candidate_check_pair_fail (NiceStream *stream, NiceAgent *agent, CandidateCheckP } /* + * Function that resubmits a new connection check, after a previous + * check in in-progress state got cancelled due to an incoming stun + * request matching this same pair + * + * @return will return TRUE if the pair is scheduled for recheck + */ +static gboolean +priv_conn_recheck_on_timeout (NiceAgent *agent, CandidateCheckPair *p) +{ + if (p->recheck_on_timeout) { + g_assert (p->state == NICE_CHECK_IN_PROGRESS); + /* this cancelled pair may have the flag 'mark nominated + * 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. + * + * this cancelled pair may also have the flag 'use candidate + * on next check' set, that we want to preserve too. + */ + nice_debug ("Agent %p : pair %p was cancelled, " + "triggering a new connection check", agent, p); + p->recheck_on_timeout = FALSE; + p->state = NICE_CHECK_WAITING; + nice_debug ("Agent %p : pair %p state WAITING", agent, p); + priv_add_pair_to_triggered_check_queue (agent, p); + return TRUE; + } + return FALSE; +} + +/* * Helper function for connectivity check timer callback that * runs through the stream specific part of the state machine. * @@ -584,8 +615,17 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen switch (stun_timer_refresh (&p->timer)) { case STUN_USAGE_TIMER_RETURN_TIMEOUT: { - /* case: error, abort processing */ gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN]; + + /* 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: 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 connectivity check %p", agent, p); @@ -600,8 +640,17 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen } case STUN_USAGE_TIMER_RETURN_RETRANSMIT: { - /* case: not ready, so schedule a new timeout */ unsigned int timeout = stun_timer_remainder (&p->timer); + + /* 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 */ nice_debug ("Agent %p :STUN transaction retransmitted on pair %p " "(timeout %dms, delay=%dms, retrans=%d).", agent, p, timeout, p->timer.delay, p->timer.retransmissions); @@ -642,6 +691,12 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen */ pair = priv_conn_check_find_next_waiting (stream->conncheck_list); if (pair) { + /* remove the pair from the triggered check list if needed. This + * may happen with the cancelled pair, that's just been added + * in state waiting to the triggered check list above in the + * same function. + */ + priv_remove_pair_from_triggered_check_queue (agent, pair); priv_print_conn_check_lists (agent, G_STRFUNC, ", got a pair in Waiting state"); priv_conn_check_initiate (agent, pair); @@ -794,6 +849,7 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) p->valid) { nice_debug ("Agent %p : restarting check %p with " "USE-CANDIDATE attrib (regular nomination)", agent, p); + p->recheck_on_timeout = FALSE; p->use_candidate_on_next_check = TRUE; priv_add_pair_to_triggered_check_queue (agent, p); break; /* move to the next component */ @@ -816,6 +872,7 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) p->state == NICE_CHECK_DISCOVERED)) { nice_debug ("Agent %p : restarting check %p as the nominated pair.", agent, p); p->nominated = TRUE; + p->recheck_on_timeout = FALSE; priv_update_selected_pair (agent, component, p); priv_add_pair_to_triggered_check_queue (agent, p); break; /* move to the next component */ @@ -2697,19 +2754,20 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str p->state == NICE_CHECK_FROZEN) priv_add_pair_to_triggered_check_queue (agent, p); else if (p->state == NICE_CHECK_IN_PROGRESS) { - /* XXX: according to ICE 7.2.1.4 "Triggered Checks" (ID-19), - * we should cancel the existing one, instead we reset our timer, so - * we'll resend the exiting transactions faster if needed...? :P - */ - nice_debug ("Agent %p : check already in progress, " - "restarting the timer again?: %s ..", agent, - p->timer_restarted ? "no" : "yes"); - if (!nice_socket_is_reliable (p->sockptr) && !p->timer_restarted) { - stun_timer_start (&p->timer, - priv_compute_conncheck_timer (agent, stream), - agent->stun_max_retransmissions); - p->timer_restarted = TRUE; - } + /* note: according to ICE SPEC sect 7.2.1.4 "Triggered Checks" + * we cancel the in-progress transaction, and after the + * retransmission timeout, we create a new connectivity check + * for that pair. The controlling role of this new check may + * be different from the role of this cancelled check. + */ + if (!nice_socket_is_reliable (p->sockptr)) { + nice_debug ("Agent %p : check already in progress, " + "cancelling this check..", agent); + /* this flag will determine the action at the retransmission + * timeout of the stun timer + */ + p->recheck_on_timeout = TRUE; + } } else if (p->state == NICE_CHECK_SUCCEEDED || p->state == NICE_CHECK_DISCOVERED) { diff --git a/agent/conncheck.h b/agent/conncheck.h index 0f988de..785a6cd 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -85,10 +85,10 @@ struct _CandidateCheckPair gchar foundation[NICE_CANDIDATE_PAIR_MAX_FOUNDATION]; NiceCheckState state; gboolean nominated; - gboolean timer_restarted; gboolean valid; gboolean use_candidate_on_next_check; gboolean mark_nominated_on_response_arrival; + gboolean recheck_on_timeout; guint64 priority; guint32 prflx_priority; GTimeVal next_tick; /* next tick timestamp */ -- 2.13.6 From 72ee528f7fdf82fb1a44958c18a0d4d5055d2d1a Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 12 Apr 2016 13:25:16 +0200 Subject: [PATCH 32/70] conncheck: link succeeded and discovered pairs When the agent has the role of the stun server, is in controlled mode, and receives a pair with the "use-candidate" attribute set, it must find a matching succeded or discovered pair in its conncheck list. This is described in ICE spec 7.2.1.5, "Updating the Nominated Flag", item #1. When a matching pair is in succeeded state, the agent must nominate the valid pair (a discovered pair) constructed from section 7.1.3.2.2, that's been created from this succeeded one. To make this lookup, we introduce a new "discovered_pair" member of the CandidateCheckPair struct, that links the succeeded pair, and its discovered pair if any. Differential Revision: https://phabricator.freedesktop.org/D878 --- agent/conncheck.c | 7 +++++++ agent/conncheck.h | 1 + 2 files changed, 8 insertions(+) diff --git a/agent/conncheck.c b/agent/conncheck.c index 3a489fe..99cb6d2 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -1928,6 +1928,12 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice * candidate, generating a "discovered" pair that can be * nominated. */ + if (pair->state == NICE_CHECK_SUCCEEDED && + pair->discovered_pair != NULL) { + pair = pair->discovered_pair; + g_assert (pair->state == NICE_CHECK_DISCOVERED); + } + if (pair->valid) { nice_debug ("Agent %p : marking pair %p (%s) as nominated", agent, pair, pair->foundation); @@ -2936,6 +2942,7 @@ static CandidateCheckPair *priv_add_peer_reflexive_pair (NiceAgent *agent, guint pair->remote = parent_pair->remote; pair->sockptr = local_cand->sockptr; pair->state = NICE_CHECK_DISCOVERED; + parent_pair->discovered_pair = pair; nice_debug ("Agent %p : new pair %p state DISCOVERED", agent, pair); { gchar tmpbuf1[INET6_ADDRSTRLEN]; diff --git a/agent/conncheck.h b/agent/conncheck.h index 785a6cd..dd47ebe 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -89,6 +89,7 @@ struct _CandidateCheckPair gboolean use_candidate_on_next_check; gboolean mark_nominated_on_response_arrival; gboolean recheck_on_timeout; + struct _CandidateCheckPair *discovered_pair; guint64 priority; guint32 prflx_priority; GTimeVal next_tick; /* next tick timestamp */ -- 2.13.6 From 9103a5f2e184211fc160d1d3070ce4d043c71ff0 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 19 Apr 2016 18:16:26 +0200 Subject: [PATCH 33/70] conncheck: use the right pair when retriggering a check This patch completes the previous patch by adding a link back from the discovered pair, to the parent pair that generated this check. This link is needed by the ICE spec, to comply with section 8.1.1.1, "Regular nomination", where the check to be retriggered is the initial check that caused the discovery of the valid pair. When the valid pair is a peer-reflexive pair, the retriggered check must target the succeeded pair, and not the valid discovered pair. Differential Revision: https://phabricator.freedesktop.org/D879 --- agent/conncheck.c | 21 ++++++++++++++++++--- agent/conncheck.h | 1 + 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 99cb6d2..79685df 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -847,6 +847,16 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) !p->nominated && !p->use_candidate_on_next_check && p->valid) { + /* According a ICE spec, sect 8.1.1.1. "Regular + * Nomination", we enqueue the check that produced this + * valid pair. When this pair has been discovered, we want + * to test its parent pair instead. + */ + if (p->succeeded_pair != NULL) { + g_assert (p->state == NICE_CHECK_DISCOVERED); + p = p->succeeded_pair; + } + g_assert (p->state == NICE_CHECK_SUCCEEDED); nice_debug ("Agent %p : restarting check %p with " "USE-CANDIDATE attrib (regular nomination)", agent, p); p->recheck_on_timeout = FALSE; @@ -2754,6 +2764,11 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str * tcp-active we don't want to retrigger a check on a pair that * was FAILED when a peer-reflexive pair was created */ + if (p->succeeded_pair != NULL) { + g_assert (p->state == NICE_CHECK_DISCOVERED); + p = p->succeeded_pair; + } + nice_debug ("Agent %p : Found a matching pair %p for triggered check.", agent, p); if (p->state == NICE_CHECK_WAITING || @@ -2775,8 +2790,7 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str p->recheck_on_timeout = TRUE; } } - else if (p->state == NICE_CHECK_SUCCEEDED || - p->state == NICE_CHECK_DISCOVERED) { + else if (p->state == NICE_CHECK_SUCCEEDED) { nice_debug ("Agent %p : Skipping triggered check, already completed..", agent); /* note: this is a bit unsure corner-case -- let's do the same state update as for processing responses to our own checks */ @@ -2943,6 +2957,7 @@ static CandidateCheckPair *priv_add_peer_reflexive_pair (NiceAgent *agent, guint pair->sockptr = local_cand->sockptr; pair->state = NICE_CHECK_DISCOVERED; parent_pair->discovered_pair = pair; + pair->succeeded_pair = parent_pair; nice_debug ("Agent %p : new pair %p state DISCOVERED", agent, pair); { gchar tmpbuf1[INET6_ADDRSTRLEN]; @@ -4163,7 +4178,7 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, pair = priv_conn_check_add_for_candidate_pair_matched (agent, stream->id, component, local_candidate, remote_candidate, - NICE_CHECK_DISCOVERED); + NICE_CHECK_SUCCEEDED); if (pair) { pair->valid = TRUE; } diff --git a/agent/conncheck.h b/agent/conncheck.h index dd47ebe..c07fb22 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -90,6 +90,7 @@ struct _CandidateCheckPair gboolean mark_nominated_on_response_arrival; gboolean recheck_on_timeout; struct _CandidateCheckPair *discovered_pair; + struct _CandidateCheckPair *succeeded_pair; guint64 priority; guint32 prflx_priority; GTimeVal next_tick; /* next tick timestamp */ -- 2.13.6 From 3916b8bcbf7e78e1dcb6b77882075c2c22719b4e Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 12 Apr 2016 13:30:04 +0200 Subject: [PATCH 34/70] conncheck: fix a nomination corner case This patch add two supplementary cases, not covered by the ICE spec, sect 7.2.1.5 "Updating the Nominated Flag" when a controlled agent receives a STUN request with the USE-CANDIDATE flag, for a pair that is in the waiting state. We consider that this case is similar to the in-progress state, and should be handled in the same way. We also accept when the pair is in frozen state. This latter case happens in the new-dribble test, when an agent replays incoming early connchecks. Differential Revision: https://phabricator.freedesktop.org/D880 --- agent/conncheck.c | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 79685df..4f4af40 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -1963,6 +1963,29 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice "will be nominated on response receipt.", agent, pair, pair->foundation); } + /* note: this case is not covered by the ICE spec, 7.2.1.5, + * "Updating the Nominated Flag", but a pair in waiting state + * deserves the same treatment than a pair in-progress. + */ + if (pair->state == NICE_CHECK_WAITING) { + pair->mark_nominated_on_response_arrival = TRUE; + nice_debug ("Agent %p : pair %p (%s) is waiting, " + "will be nominated on response receipt.", + agent, pair, pair->foundation); + } + /* note: this case is not covered by the ICE spec, 7.2.1.5, + * "Updating the Nominated Flag" either, but a pair in frozen + * state, and in the triggered check list should also be + * considered like a pair in-progress. This case happens with + * the new-dribble test, when an agent replays incoming early + * connchecks. + */ + if (pair->state == NICE_CHECK_FROZEN) { + pair->mark_nominated_on_response_arrival = TRUE; + nice_debug ("Agent %p : pair %p (%s) is frozen, " + "will be nominated on response receipt.", + agent, pair, pair->foundation); + } } else if (pair->local == localcand && pair->remote == remotecand) { nice_debug ("Agent %p : marking pair %p (%s) as nominated", agent, pair, pair->foundation); pair->nominated = TRUE; @@ -2703,17 +2726,25 @@ static guint priv_prune_pending_checks (NiceStream *stream, guint component_id) "is %" G_GUINT64_FORMAT, highest_nominated_priority); /* step: cancel all FROZEN and WAITING pairs for the component */ + /* note: this case is not covered by the ICE spec, 8.1.2 + * "Updating States", but a pair in waiting state which will be + * nominated on response receipt should be treated the same way + * that an in-progress pair. + */ for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; if (p->component_id == component_id) { if (p->state == NICE_CHECK_FROZEN || - p->state == NICE_CHECK_WAITING) { + (p->state == NICE_CHECK_WAITING && + !p->mark_nominated_on_response_arrival)) { p->state = NICE_CHECK_CANCELLED; nice_debug ("Agent XXX : pair %p state CANCELED", p); } /* note: a SHOULD level req. in ICE 8.1.2. "Updating States" (ID-19) */ - if (p->state == NICE_CHECK_IN_PROGRESS) { + if (p->state == NICE_CHECK_IN_PROGRESS || + (p->state == NICE_CHECK_WAITING && + p->mark_nominated_on_response_arrival)) { if (highest_nominated_priority != 0 && p->priority < highest_nominated_priority) { p->stun_message.buffer = NULL; -- 2.13.6 From afd8d41bb34afb3864e838ef79026ae4ef15c0d4 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 12 Apr 2016 13:32:49 +0200 Subject: [PATCH 35/70] conncheck: new pairs never have the nominated flag preset This patch disables the possibility to set the nominated flag of a candidate pair at creation time. This possibility was used when a new pair is created from a new peer reflexive remote candidate, when the agent is in controlled mode, and an stun request with USE-CANDIDATE is received. In this case, since previous commit "conncheck: fix a nomination corner case", we set the nominated flag when the stun response of this new pair will arrive, and not before. Consequently, this flag is no longer required when the pair is created. Differential Revision: https://phabricator.freedesktop.org/D881 --- agent/conncheck.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 4f4af40..3cd0424 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -65,7 +65,7 @@ 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 (NiceStream *stream, guint component_id); -static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *local_socket, NiceCandidate *remote_cand, gboolean use_candidate); +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, guint component_id, NiceCandidate *remote, NiceCandidate *local, @@ -1573,7 +1573,8 @@ static void priv_preprocess_conn_check_pending_data (NiceAgent *agent, NiceStrea nice_debug ("Agent %p : Updating check %p with stored early-icheck %p, %p/%u/%u (agent/stream/component).", agent, pair, icheck, agent, stream->id, component->id); if (icheck->use_candidate) priv_mark_pair_nominated (agent, stream, component, pair->local, pair->remote); - priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, pair->remote, icheck->use_candidate); + priv_schedule_triggered_check (agent, stream, component, + icheck->local_socket, pair->remote); } } } @@ -1716,7 +1717,8 @@ void conn_check_remote_credentials_set(NiceAgent *agent, NiceStream *stream) if (icheck->use_candidate) priv_mark_pair_nominated (agent, stream, component, local_candidate, candidate); - priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, candidate, icheck->use_candidate); + priv_schedule_triggered_check (agent, stream, component, + icheck->local_socket, candidate); } } } @@ -2043,7 +2045,7 @@ ensure_unique_priority (NiceComponent *component, guint32 priority) */ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidate *local, - NiceCandidate *remote, NiceCheckState initial_state, gboolean use_candidate) + NiceCandidate *remote, NiceCheckState initial_state) { NiceStream *stream; CandidateCheckPair *pair; @@ -2081,7 +2083,6 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, tmpbuf1, nice_address_get_port (&pair->local->addr), tmpbuf2, nice_address_get_port (&pair->remote->addr)); } - pair->nominated = use_candidate; pair->prflx_priority = ensure_unique_priority (component, peer_reflexive_candidate_priority (agent, local)); @@ -2127,7 +2128,7 @@ static CandidateCheckPair *priv_conn_check_add_for_candidate_pair_matched ( agent, local->foundation, remote->foundation, stream_id, component->id); pair = priv_add_new_check_pair (agent, stream_id, component, local, remote, - initial_state, FALSE); + initial_state); if (component->state == NICE_COMPONENT_STATE_CONNECTED || component->state == NICE_COMPONENT_STATE_READY) { agent_signal_component_state_change (agent, @@ -2774,9 +2775,8 @@ static guint priv_prune_pending_checks (NiceStream *stream, guint component_id) * @param component the check is related to * @param local_socket socket from which the inbound check was received * @param remote_cand remote candidate from which the inbound check was sent - * @param use_candidate whether the original check had USE-CANDIDATE attribute set */ -static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *local_socket, NiceCandidate *remote_cand, gboolean use_candidate) +static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *local_socket, NiceCandidate *remote_cand) { GSList *i; NiceCandidate *local = NULL; @@ -2872,7 +2872,8 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str if (i) { nice_debug ("Agent %p : Adding a triggered check to conn.check list (local=%p).", agent, local); - priv_add_new_check_pair (agent, stream->id, component, local, remote_cand, NICE_CHECK_WAITING, use_candidate); + priv_add_new_check_pair (agent, stream->id, component, + local, remote_cand, NICE_CHECK_WAITING); return TRUE; } else { @@ -2926,7 +2927,7 @@ static void priv_reply_to_conn_check (NiceAgent *agent, NiceStream *stream, if (rcand) { /* note: upon successful check, make the reserve check immediately */ - priv_schedule_triggered_check (agent, stream, component, sockptr, rcand, use_candidate); + priv_schedule_triggered_check (agent, stream, component, sockptr, rcand); if (use_candidate) priv_mark_pair_nominated (agent, stream, component, lcand, rcand); -- 2.13.6 From 25b3eeec70b4e8e3b2154a18cdc8c5604f572012 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 12 Apr 2016 12:56:28 +0200 Subject: [PATCH 36/70] conncheck: update the pair state in triggered check list With this patch, we update the state of the pair to waiting when it is put in the triggered check queue. We also take care to call priv_schedule_triggered_check() before priv_mark_pair_nominated() so a pair to be rechecked and put on the triggered check queue will have a unique state to be tested in the following call to priv_mark_pair_nominated() when evaluating its nomination attributes. Differential Revision: https://phabricator.freedesktop.org/D883 --- agent/conncheck.c | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 3cd0424..9950970 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -183,6 +183,8 @@ priv_add_pair_to_triggered_check_queue (NiceAgent *agent, CandidateCheckPair *pa { g_assert (pair); + pair->state = NICE_CHECK_WAITING; + nice_debug ("Agent %p : pair %p state WAITING", agent, pair); 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); @@ -580,8 +582,6 @@ priv_conn_recheck_on_timeout (NiceAgent *agent, CandidateCheckPair *p) nice_debug ("Agent %p : pair %p was cancelled, " "triggering a new connection check", agent, p); p->recheck_on_timeout = FALSE; - p->state = NICE_CHECK_WAITING; - nice_debug ("Agent %p : pair %p state WAITING", agent, p); priv_add_pair_to_triggered_check_queue (agent, p); return TRUE; } @@ -1571,10 +1571,10 @@ static void priv_preprocess_conn_check_pending_data (NiceAgent *agent, NiceStrea if (nice_address_equal (&icheck->from, &pair->remote->addr) && icheck->local_socket == pair->sockptr) { nice_debug ("Agent %p : Updating check %p with stored early-icheck %p, %p/%u/%u (agent/stream/component).", agent, pair, icheck, agent, stream->id, component->id); - if (icheck->use_candidate) - priv_mark_pair_nominated (agent, stream, component, pair->local, pair->remote); priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, pair->remote); + if (icheck->use_candidate) + priv_mark_pair_nominated (agent, stream, component, pair->local, pair->remote); } } } @@ -1715,10 +1715,10 @@ void conn_check_remote_credentials_set(NiceAgent *agent, NiceStream *stream) else conn_check_add_for_candidate (agent, stream->id, component, candidate); - if (icheck->use_candidate) - priv_mark_pair_nominated (agent, stream, component, local_candidate, candidate); priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, candidate); + if (icheck->use_candidate) + priv_mark_pair_nominated (agent, stream, component, local_candidate, candidate); } } } @@ -1967,7 +1967,9 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice } /* note: this case is not covered by the ICE spec, 7.2.1.5, * "Updating the Nominated Flag", but a pair in waiting state - * deserves the same treatment than a pair in-progress. + * deserves the same treatment than a pair in-progress. A pair + * can be in waiting state as the result of being enqueued in + * the triggered check list for example. */ if (pair->state == NICE_CHECK_WAITING) { pair->mark_nominated_on_response_arrival = TRUE; @@ -1975,19 +1977,6 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice "will be nominated on response receipt.", agent, pair, pair->foundation); } - /* note: this case is not covered by the ICE spec, 7.2.1.5, - * "Updating the Nominated Flag" either, but a pair in frozen - * state, and in the triggered check list should also be - * considered like a pair in-progress. This case happens with - * the new-dribble test, when an agent replays incoming early - * connchecks. - */ - if (pair->state == NICE_CHECK_FROZEN) { - pair->mark_nominated_on_response_arrival = TRUE; - nice_debug ("Agent %p : pair %p (%s) is frozen, " - "will be nominated on response receipt.", - agent, pair, pair->foundation); - } } else if (pair->local == localcand && pair->remote == remotecand) { nice_debug ("Agent %p : marking pair %p (%s) as nominated", agent, pair, pair->foundation); pair->nominated = TRUE; @@ -2926,9 +2915,7 @@ static void priv_reply_to_conn_check (NiceAgent *agent, NiceStream *stream, } if (rcand) { - /* note: upon successful check, make the reserve check immediately */ priv_schedule_triggered_check (agent, stream, component, sockptr, rcand); - if (use_candidate) priv_mark_pair_nominated (agent, stream, component, lcand, rcand); } @@ -3345,9 +3332,7 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre p->stun_message.buffer = NULL; p->stun_message.buffer_len = 0; - p->state = NICE_CHECK_WAITING; priv_add_pair_to_triggered_check_queue (agent, p); - nice_debug ("Agent %p : pair %p state WAITING", agent, p); } trans_found = TRUE; } else { -- 2.13.6 From 11d4e37a030eb144a355dc26c705ef5aa5a975a7 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Fri, 1 Apr 2016 17:31:44 +0200 Subject: [PATCH 37/70] conncheck: remove a useless pair recheck This exception to the ICE spec is no longer needed: when a pair is in the succeeded state, there is no needed to recheck it again upon reception of an incoming stun request on it. Differential Revision: https://phabricator.freedesktop.org/D884 --- agent/conncheck.c | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 9950970..95e2556 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -2820,23 +2820,6 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str * that causes the ready -> connected transition. */ priv_update_check_list_state_for_ready (agent, stream, component); - - /* note: this new check is required by the new-dribble test, - * when early icheck on the peer controlled agent causes an - * incoming stun request to an already succeeded (and - * nominated) pair on the controlling agent. If the - * controlling agent doesn't retrigger a check with - * USE-CANDIDATE=1, the peer agent has no way to nominate it. - * - * This behavior differs from ICE spec 7.2.1.4 - */ - if ((agent->compatibility == NICE_COMPATIBILITY_RFC5245 || - agent->compatibility == NICE_COMPATIBILITY_WLM2009 || - agent->compatibility == NICE_COMPATIBILITY_OC2007R2) && - agent->controlling_mode) { - priv_add_pair_to_triggered_check_queue (agent, p); - conn_check_schedule_next(agent); - } } else if (p->state == NICE_CHECK_FAILED) { /* 7.2.1.4 Triggered Checks * If the state of the pair is Failed, it is changed to Waiting -- 2.13.6 From 8fa648a15a6700d08165fe97a09f5c068abae1e6 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Mon, 11 Apr 2016 13:13:51 +0200 Subject: [PATCH 38/70] conncheck: dont cancel a pair for triggered check This patch adds another supplementary "corner" case, not covered by the ICE spec, sect 8.1.2, "Updating States". A pair in waiting state and in the triggered check list should be considered like an in-progress pair, and cancelled only if its priority is lower than the priority of the nominated pair. This is required in some aggressive nomination situations for both peers to select the same pair, having the highest priority. Differential Revision: https://phabricator.freedesktop.org/D933 --- agent/conncheck.c | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 95e2556..79f678a 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -64,7 +64,7 @@ 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 (NiceStream *stream, guint component_id); +static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, guint component_id); 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, @@ -176,6 +176,16 @@ priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar * } } +/* Verify if the pair is in the triggered checks list + */ + +static gboolean +priv_is_pair_in_triggered_check_queue (NiceAgent *agent, CandidateCheckPair *pair) +{ + g_assert (pair); + return (g_slist_find (agent->triggered_check_queue, pair) != NULL); +} + /* Add the pair to the triggered checks list, if not already present */ static void @@ -1897,7 +1907,7 @@ static void priv_update_check_list_state_for_ready (NiceAgent *agent, NiceStream /* 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 (stream, component->id) == 0) { + if (priv_prune_pending_checks (agent, stream, component->id) == 0) { /* Continue through the states to give client code a nice * logical progression. See http://phabricator.freedesktop.org/D218 for * discussion. */ @@ -2693,14 +2703,14 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) * * @see priv_update_check_list_state_failed_components() */ -static guint priv_prune_pending_checks (NiceStream *stream, guint component_id) +static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, guint component_id) { GSList *i; guint64 highest_nominated_priority = 0; guint in_progress = 0; - nice_debug ("Agent XXX: Finding highest priority for component %d", - component_id); + nice_debug ("Agent %p: Finding highest priority for component %d", + agent, component_id); for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; @@ -2712,41 +2722,47 @@ static guint priv_prune_pending_checks (NiceStream *stream, guint component_id) } } - nice_debug ("Agent XXX: Pruning pending checks. Highest nominated priority " - "is %" G_GUINT64_FORMAT, highest_nominated_priority); + nice_debug ("Agent %p: Pruning pending checks. Highest nominated priority " + "is %" G_GUINT64_FORMAT, agent, highest_nominated_priority); /* step: cancel all FROZEN and WAITING pairs for the component */ /* note: this case is not covered by the ICE spec, 8.1.2 * "Updating States", but a pair in waiting state which will be * nominated on response receipt should be treated the same way - * that an in-progress pair. + * that an in-progress pair. A pair in waiting state and in + * the triggered check list should also be treated like an in-progress + * pair. */ for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; + if (p->component_id == component_id) { + gboolean like_in_progress = + p->mark_nominated_on_response_arrival || + priv_is_pair_in_triggered_check_queue (agent, p); + if (p->state == NICE_CHECK_FROZEN || - (p->state == NICE_CHECK_WAITING && - !p->mark_nominated_on_response_arrival)) { + (p->state == NICE_CHECK_WAITING && !like_in_progress)) { p->state = NICE_CHECK_CANCELLED; - nice_debug ("Agent XXX : pair %p state CANCELED", p); + nice_debug ("Agent %p : pair %p state CANCELED", agent, p); } /* note: a SHOULD level req. in ICE 8.1.2. "Updating States" (ID-19) */ if (p->state == NICE_CHECK_IN_PROGRESS || - (p->state == NICE_CHECK_WAITING && - p->mark_nominated_on_response_arrival)) { + (p->state == NICE_CHECK_WAITING && like_in_progress)) { if (highest_nominated_priority != 0 && p->priority < highest_nominated_priority) { p->stun_message.buffer = NULL; p->stun_message.buffer_len = 0; p->state = NICE_CHECK_CANCELLED; - nice_debug ("Agent XXX : pair %p state CANCELED", p); + nice_debug ("Agent %p : pair %p state CANCELED", 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 XXX : pair %p kept IN_PROGRESS because priority %" + nice_debug ("Agent %p : pair %p kept IN_PROGRESS because priority %" G_GUINT64_FORMAT " is higher than currently nominated pair %" - G_GUINT64_FORMAT, p, p->priority, highest_nominated_priority); + G_GUINT64_FORMAT, agent, + p, p->priority, highest_nominated_priority); in_progress++; } } -- 2.13.6 From 6a512b6eca9603ce8bf3ed0814fd314684c66ea7 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 14 Jun 2016 21:04:49 +0200 Subject: [PATCH 39/70] conncheck: try to change earlier to state ready We check if we can move from state connected to ready just after a pair expired its retransmission count. This pair will be marked failed, and will no longer be in-progress. The number of in-progress dropping down to zero is one of the conditions needed to make the transition to ready, per component (and not globally as it's the case in other locations where this check function is called). Differential Revision: https://phabricator.freedesktop.org/D1117 --- agent/conncheck.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/agent/conncheck.c b/agent/conncheck.c index 79f678a..d31b77f 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -626,6 +626,7 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen case STUN_USAGE_TIMER_RETURN_TIMEOUT: { gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN]; + NiceComponent *component; /* case: conncheck cancelled due to in-progress incoming * check, requeing the pair, ICE spec, sect 7.2.1.4 @@ -646,6 +647,16 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen 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. + */ + if (agent_find_component (agent, p->stream_id, p->component_id, + NULL, &component)) + priv_update_check_list_state_for_ready (agent, stream, + component); break; } case STUN_USAGE_TIMER_RETURN_RETRANSMIT: -- 2.13.6 From 59fe48517c0b7db77b99183d31fdd84b55adb5d4 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 14 Jun 2016 21:12:16 +0200 Subject: [PATCH 40/70] conncheck: fix a state transition case When a new stun request hits a valid pair, of a failed component, we may have a transition from state failed to connected. In this situation, we do a logical progression failed -> connecting -> connected, like we do in function priv_update_check_list_state_for_ready() Similarily, when a new stun request hits a failed pair, of a failed component, triggering a new conncheck for this pair may also cause the component state to move back from failed to connecting state. Differential Revision: https://phabricator.freedesktop.org/D1118 --- agent/conncheck.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index d31b77f..e1a5cf1 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -1973,12 +1973,14 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice pair->nominated = TRUE; priv_update_selected_pair (agent, component, pair); /* Do not step down to CONNECTED if we're already at state READY*/ - if (component->state != NICE_COMPONENT_STATE_READY) { + if (component->state == NICE_COMPONENT_STATE_FAILED) + agent_signal_component_state_change (agent, + stream->id, component->id, NICE_COMPONENT_STATE_CONNECTING); + 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 */ agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); - } priv_update_check_list_state_for_ready (agent, stream, component); } else if (pair->state == NICE_CHECK_IN_PROGRESS) { pair->mark_nominated_on_response_arrival = TRUE; @@ -2004,13 +2006,14 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice if (pair->valid) { priv_update_selected_pair (agent, component, pair); /* Do not step down to CONNECTED if we're already at state READY*/ - if (component->state != NICE_COMPONENT_STATE_READY) { + if (component->state == NICE_COMPONENT_STATE_FAILED) + agent_signal_component_state_change (agent, + stream->id, component->id, NICE_COMPONENT_STATE_CONNECTING); + 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 */ agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); - } - } priv_update_check_list_state_for_ready (agent, stream, component); } @@ -2854,6 +2857,14 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str pair (representing a new STUN Binding request transaction), by enqueueing the pair in the triggered check queue. */ priv_add_pair_to_triggered_check_queue (agent, p); + /* If the component for this pair is in failed state, move it + * back to connecting, and reinitiate the timers + */ + if (component->state == NICE_COMPONENT_STATE_FAILED) { + agent_signal_component_state_change (agent, stream->id, + component->id, NICE_COMPONENT_STATE_CONNECTING); + conn_check_schedule_next (agent); + } } /* note: the spec says the we SHOULD retransmit in-progress -- 2.13.6 From f19d209decac432a1597d84c3d5809d2208f7457 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 14 Jun 2016 21:20:49 +0200 Subject: [PATCH 41/70] conncheck: do not recheck a just succeeded pair We cancel the potential in-progress transaction cancellation, caused by sect 7.2.1.4 "Triggered Checks", when we receive a valid reply before transmission timeout, or just after timeout, when the pair is temporarily put on the triggered check list on the way to be rechecked. This situation is not covered by the RFC 5245. Differential Revision: https://phabricator.freedesktop.org/D1119 --- agent/conncheck.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/agent/conncheck.c b/agent/conncheck.c index e1a5cf1..4b785b5 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -3117,6 +3117,16 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * if (new_pair == p) p->valid = TRUE; 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); nice_debug ("Agent %p : conncheck %p SUCCEEDED.", agent, p); nice_component_add_valid_candidate (component, remote_candidate); } @@ -3151,6 +3161,8 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * * Succeeded, RFC 5245, 7.1.3.2.3, "Updating Pair States" */ p->state = NICE_CHECK_SUCCEEDED; + p->recheck_on_timeout = FALSE; + priv_remove_pair_from_triggered_check_queue (agent, p); nice_debug ("Agent %p : conncheck %p SUCCEEDED, %p DISCOVERED.", agent, p, new_pair); } -- 2.13.6 From d516fca1b0e0a6606afec797bdc0690104e779a9 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 14 Jun 2016 21:32:26 +0200 Subject: [PATCH 42/70] conncheck: adjust recheck on timeout strategy 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, that doubles after each timeout, becomes longer than the rtt, and the stun reply can be handled. Differential Revision: https://phabricator.freedesktop.org/D1115 --- agent/conncheck.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 4b785b5..88d2534 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -591,7 +591,6 @@ priv_conn_recheck_on_timeout (NiceAgent *agent, CandidateCheckPair *p) */ nice_debug ("Agent %p : pair %p was cancelled, " "triggering a new connection check", agent, p); - p->recheck_on_timeout = FALSE; priv_add_pair_to_triggered_check_queue (agent, p); return TRUE; } @@ -2650,9 +2649,32 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) if (nice_socket_is_reliable(pair->sockptr)) { stun_timer_start_reliable(&pair->timer, agent->stun_reliable_timeout); } else { - stun_timer_start (&pair->timer, - priv_compute_conncheck_timer (agent, stream), - agent->stun_max_retransmissions); + StunTimer *timer = &pair->timer; + + if (pair->recheck_on_timeout) + /* 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. + */ + nice_debug("Agent %p : reusing timer of pair %p: %d/%d %d/%dms", + agent, pair, + timer->retransmissions, timer->max_retransmissions, + timer->delay - stun_timer_remainder (timer), timer->delay); + else + stun_timer_start (timer, + priv_compute_conncheck_timer (agent, stream), + agent->stun_max_retransmissions); + pair->recheck_on_timeout = FALSE; } /* TCP-ACTIVE candidate must create a new socket before sending -- 2.13.6 From 95f8805eb7b77755337e28daf1f134587d42b35f Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Thu, 16 Jun 2016 17:32:39 +0200 Subject: [PATCH 43/70] conncheck: remove cancelled pair state Pairs with the state NICE_CHECK_CANCELLED are the pairs targeted for removal after the nomination of a pair with an higher priority, described in Section 8.1.2 "Updating States", item 2 of RFC 5245. They include also pairs that overflow the conncheck list size, but this is a somewhat more marginal situation. So we are mainly interested in the first use case of this state. This state mixes two different situations, that deserve a distinct handling : on one side, there are waiting or frozen pairs that must be removed, this is an immediate action that doesn't need a dedicated state for that. And on the other side, there are in-progress pairs that should no longer be retransmitted, because another pair with a higher priority has already been nominated. This patch removes the cancelled state, and adds a flag retransmit_on_timeout to deal with this last situation. Note that this case should not generate a triggered check, as per described in section 7.2.1.4, when the state of the pair is In-Progress or Failed, since this pair of lower priority has no hope to replace the nominated one. Differential Revision: https://phabricator.freedesktop.org/D1114 --- agent/conncheck.c | 142 +++++++++++++++++++++++++++++------------------------- agent/conncheck.h | 3 +- 2 files changed, 77 insertions(+), 68 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 88d2534..b0e2222 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -100,8 +100,6 @@ priv_state_to_gchar (NiceCheckState state) return 'F'; case NICE_CHECK_FROZEN: return 'Z'; - case NICE_CHECK_CANCELLED: - return 'C'; case NICE_CHECK_DISCOVERED: return 'D'; default: @@ -627,6 +625,7 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN]; NiceComponent *component; +timer_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 @@ -662,6 +661,13 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen { unsigned int timeout = stun_timer_remainder (&p->timer); + /* 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_on_timeout) + goto timer_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 @@ -1600,26 +1606,6 @@ static void priv_preprocess_conn_check_pending_data (NiceAgent *agent, NiceStrea } -static GSList *prune_cancelled_conn_check (GSList *conncheck_list) -{ - GSList *item = conncheck_list; - - while (item) { - CandidateCheckPair *pair = item->data; - GSList *next = item->next; - - if (pair->state == NICE_CHECK_CANCELLED) { - conn_check_free_item (pair); - conncheck_list = g_slist_delete_link (conncheck_list, item); - } - - item = next; - } - - return conncheck_list; -} - - /* * Handle any processing steps for connectivity checks after * remote credentials have been set. This function handles @@ -1754,9 +1740,6 @@ void conn_check_remote_credentials_set(NiceAgent *agent, NiceStream *stream) (GDestroyNotify) incoming_check_free); component->incoming_checks = NULL; } - - stream->conncheck_list = - prune_cancelled_conn_check (stream->conncheck_list); } /* @@ -1764,7 +1747,7 @@ void conn_check_remote_credentials_set(NiceAgent *agent, NiceStream *stream) * in ICE spec section 5.7.3 (ID-19). See also * conn_check_add_for_candidate(). */ -static void priv_limit_conn_check_list_size (GSList *conncheck_list, guint upper_limit) +static GSList *priv_limit_conn_check_list_size (GSList *conncheck_list, guint upper_limit) { guint valid = 0; guint cancelled = 0; @@ -1772,22 +1755,22 @@ static void priv_limit_conn_check_list_size (GSList *conncheck_list, guint upper while (item) { CandidateCheckPair *pair = item->data; + GSList *next = item->next; - if (pair->state != NICE_CHECK_CANCELLED) { - valid++; - if (valid > upper_limit) { - pair->state = NICE_CHECK_CANCELLED; + valid++; + if (valid > upper_limit) { + conn_check_free_item (pair); + conncheck_list = g_slist_delete_link (conncheck_list, item); cancelled++; - } } - - item = item->next; + 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; } /* @@ -2097,6 +2080,7 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, } pair->prflx_priority = ensure_unique_priority (component, peer_reflexive_candidate_priority (agent, local)); + pair->retransmit_on_timeout = TRUE; stream->conncheck_list = g_slist_insert_sorted (stream->conncheck_list, pair, (GCompareFunc)conn_check_compare); @@ -2106,7 +2090,8 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, /* implement the hard upper limit for number of checks (see sect 5.7.3 ICE ID-19): */ if (agent->compatibility == NICE_COMPATIBILITY_RFC5245) { - priv_limit_conn_check_list_size (stream->conncheck_list, agent->max_conn_checks); + stream->conncheck_list = priv_limit_conn_check_list_size ( + stream->conncheck_list, agent->max_conn_checks); } return pair; @@ -2769,8 +2754,10 @@ static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, gu * the triggered check list should also be treated like an in-progress * pair. */ - for (i = stream->conncheck_list; i; i = i->next) { + i = stream->conncheck_list; + while (i) { CandidateCheckPair *p = i->data; + GSList *next = i->next; if (p->component_id == component_id) { gboolean like_in_progress = @@ -2779,19 +2766,20 @@ static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, gu if (p->state == NICE_CHECK_FROZEN || (p->state == NICE_CHECK_WAITING && !like_in_progress)) { - p->state = NICE_CHECK_CANCELLED; - nice_debug ("Agent %p : pair %p state CANCELED", agent, p); + nice_debug ("Agent %p : pair %p removed.", agent, p); + conn_check_free_item (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) */ - if (p->state == NICE_CHECK_IN_PROGRESS || + else if (p->state == NICE_CHECK_IN_PROGRESS || (p->state == NICE_CHECK_WAITING && like_in_progress)) { if (highest_nominated_priority != 0 && p->priority < highest_nominated_priority) { - p->stun_message.buffer = NULL; - p->stun_message.buffer_len = 0; - p->state = NICE_CHECK_CANCELLED; - nice_debug ("Agent %p : pair %p state CANCELED", agent, p); + p->retransmit_on_timeout = FALSE; + p->recheck_on_timeout = 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 */ @@ -2803,6 +2791,7 @@ static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, gu } } } + i = next; } return in_progress; @@ -2841,29 +2830,42 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str p = p->succeeded_pair; } - nice_debug ("Agent %p : Found a matching pair %p for triggered check.", agent, p); + nice_debug ("Agent %p : Found a matching pair %p (%s) (state=%c) ...", + agent, p, p->foundation, priv_state_to_gchar (p->state)); if (p->state == NICE_CHECK_WAITING || - p->state == NICE_CHECK_FROZEN) + p->state == NICE_CHECK_FROZEN) { + nice_debug ("Agent %p : pair %p added for a triggered check.", + agent, p); priv_add_pair_to_triggered_check_queue (agent, p); + } else if (p->state == NICE_CHECK_IN_PROGRESS) { /* note: according to ICE SPEC sect 7.2.1.4 "Triggered Checks" * we cancel the in-progress transaction, and after the * retransmission timeout, we create a new connectivity check * for that pair. The controlling role of this new check may * be different from the role of this cancelled check. + * + * note: the flag retransmit_on_timeout 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. */ if (!nice_socket_is_reliable (p->sockptr)) { - nice_debug ("Agent %p : check already in progress, " - "cancelling this check..", agent); - /* this flag will determine the action at the retransmission - * timeout of the stun timer - */ - p->recheck_on_timeout = TRUE; + if (p->retransmit_on_timeout) { + nice_debug ("Agent %p : pair %p will be rechecked " + "on stun timer timeout.", agent, p); + /* this flag will determine the action at the retransmission + * timeout of the stun timer + */ + p->recheck_on_timeout = TRUE; + } else + nice_debug ("Agent %p : pair %p won't be retransmitted.", + agent, p); } } else if (p->state == NICE_CHECK_SUCCEEDED) { - nice_debug ("Agent %p : Skipping triggered check, already completed..", agent); + 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 @@ -2875,18 +2877,30 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str } else if (p->state == NICE_CHECK_FAILED) { /* 7.2.1.4 Triggered Checks * 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. */ - priv_add_pair_to_triggered_check_queue (agent, p); - /* If the component for this pair is in failed state, move it - * back to connecting, and reinitiate the timers + * 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 (component->state == NICE_COMPONENT_STATE_FAILED) { - agent_signal_component_state_change (agent, stream->id, - component->id, NICE_COMPONENT_STATE_CONNECTING); - conn_check_schedule_next (agent); - } + if (p->retransmit_on_timeout) { + nice_debug ("Agent %p : pair %p added for a triggered check.", + agent, p); + priv_add_pair_to_triggered_check_queue (agent, p); + /* If the component for this pair is in failed state, move it + * back to connecting, and reinitiate the timers + */ + if (component->state == NICE_COMPONENT_STATE_FAILED) { + agent_signal_component_state_change (agent, stream->id, + component->id, NICE_COMPONENT_STATE_CONNECTING); + conn_check_schedule_next (agent); + } + } else + nice_debug ("Agent %p : pair %p won't be retransmitted.", + agent, p); } /* note: the spec says the we SHOULD retransmit in-progress @@ -3401,10 +3415,6 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre } } - - stream->conncheck_list = - prune_cancelled_conn_check (stream->conncheck_list); - return trans_found; } diff --git a/agent/conncheck.h b/agent/conncheck.h index c07fb22..909d469 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -56,7 +56,6 @@ * @NICE_CHECK_SUCCEEDED: Connection successfully checked. * @NICE_CHECK_FAILED: No connectivity; retransmissions ceased. * @NICE_CHECK_FROZEN: Waiting to be scheduled to %NICE_CHECK_WAITING. - * @NICE_CHECK_CANCELLED: Check cancelled. * @NICE_CHECK_DISCOVERED: A valid candidate pair not on the check list. * * States for checking a candidate pair. @@ -68,7 +67,6 @@ typedef enum NICE_CHECK_SUCCEEDED, NICE_CHECK_FAILED, NICE_CHECK_FROZEN, - NICE_CHECK_CANCELLED, NICE_CHECK_DISCOVERED, } NiceCheckState; @@ -89,6 +87,7 @@ struct _CandidateCheckPair gboolean use_candidate_on_next_check; gboolean mark_nominated_on_response_arrival; gboolean recheck_on_timeout; + gboolean retransmit_on_timeout; struct _CandidateCheckPair *discovered_pair; struct _CandidateCheckPair *succeeded_pair; guint64 priority; -- 2.13.6 From 07366a5bca7e4818b8df29d9c7c220da8f752547 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 21 Jun 2016 21:47:42 +0200 Subject: [PATCH 44/70] conncheck: fix the component failed transition This patch fixes the transition of a component from connecting to failed, that previously occured due to the propagation of the keep_timer_going variable, and to the final call to function priv_update_check_list_failed_components(), after the global agent timer was stopped. Previously, the code almost never entered to failed state, because the timer was going one, as long as the number of nominated pair was not enough, and as long as there were valid pairs not yet nominated. Even if all pair timers were over. The definition of the Failed state of a conncheck list is somewhat contradictory in the spec, depending on weather you read : * sect 5.7.4. "Computing States", "Failed: In this state, the ICE checks have not completed successfully for this media stream." or * sect 7.1.3.3. "Check List and Timer State Updates", "If all of the pairs in the check list are now either in the Failed or Succeeded state: If there is not a pair in the valid list for each component of the media stream, the state of the check list is set to Failed." Our understanding of the ICE spec is that, the proper way to enter failed state instead in when all connchecks have no longer in-progress pairs. All pairs are either in state succeeded, discovered, or failed. No timer is still running, and we have no hope that the conncheck list changes again, except if an unexpected STUN packet arrives later. All pairs in frozen state is a special case, that is handled separately (sect 7.1.3.3). A special grace delay is added before declaring a component in state Failed. This delay is not part of the RFC, and it is aimed to limit the cases when a conncheck list is reactivated just after it's been declared failed, causing a user visible transition from connecting to failed, and back from failed to connecting again. This is also required by the test suite, that counts exactly the number of time each state is entered, and doesn't expect these transcient failed states to happen (frequent due to the nature of the testsuite, less frequent in real life). Differential Revision: https://phabricator.freedesktop.org/D1111 --- agent/agent-priv.h | 14 +++++++++++ agent/conncheck.c | 71 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/agent/agent-priv.h b/agent/agent-priv.h index 3384180..714ecff 100644 --- a/agent/agent-priv.h +++ b/agent/agent-priv.h @@ -122,6 +122,18 @@ nice_input_message_iter_compare (const NiceInputMessageIter *a, ((obj)->compatibility == NICE_COMPATIBILITY_RFC5245 || \ (obj)->compatibility == NICE_COMPATIBILITY_OC2007R2) +/* A grace period before declaring a component as failed, in msecs. This + * delay is added to reduce the chance to see the agent receiving new + * stun activity just after the conncheck list has been declared failed, + * reactiviting conncheck activity, and causing a (valid) state + * transitions like that: connecting -> failed -> connecting -> + * connected -> ready. + * Such transitions are not buggy per-se, but may break the + * test-suite, that counts precisely the number of time each state + * has been set, and doesnt expect these transcient failed states. + */ +#define NICE_AGENT_MAX_TIMER_GRACE_PERIOD 1000 + struct _NiceAgent { GObject parent; /* gobject pointer */ @@ -176,6 +188,8 @@ struct _NiceAgent guint16 rfc4571_expecting_length; gboolean use_ice_udp; gboolean use_ice_tcp; + + guint conncheck_timer_grace_period; /* ongoing delay before timer stop */ /* XXX: add pointer to internal data struct for ABI-safe extensions */ }; diff --git a/agent/conncheck.c b/agent/conncheck.c index b0e2222..63db471 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -709,7 +709,7 @@ timer_timeout: } } } - } + } /* step: perform an ordinary check, ICE spec, 5.8 "Scheduling Checks" * note: This code is executed when the triggered checks list is @@ -795,11 +795,8 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) if (s_inprogress) keep_timer_going = TRUE; - /* note: if some components have established connectivity, - * but yet no nominated pair, keep timer going */ if (s_nominated < stream->n_components && s_waiting_for_nomination) { - keep_timer_going = TRUE; if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { if (agent->nomination_mode == NICE_NOMINATION_MODE_REGULAR && agent->controlling_mode) { @@ -888,6 +885,7 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) p->recheck_on_timeout = FALSE; 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 */ } } @@ -911,6 +909,7 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) p->recheck_on_timeout = FALSE; priv_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 */ } } @@ -937,6 +936,7 @@ conn_check_stop (NiceAgent *agent) 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; } @@ -1005,9 +1005,39 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) keep_timer_going = TRUE; } + /* step: if no work left and a conncheck list of a stream is still + * frozen, set the pairs to waiting, according to ICE SPEC, sect + * 7.1.3.3. "Check List and Timer State Updates" + */ + if (!keep_timer_going) { + for (i = agent->streams; i ; i = i->next) { + NiceStream *stream = i->data; + if (priv_is_checklist_frozen (stream)) { + nice_debug ("Agent %p : stream %d conncheck list is still " + "frozen, while other lists are completed. Unfreeze it.", + agent, stream->id); + keep_timer_going = priv_conn_check_unfreeze_next (agent, stream); + } + } + } + + /* 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) + nice_debug ("Agent %p : waiting %d msecs before checking " + "for failed components.", agent, NICE_AGENT_MAX_TIMER_GRACE_PERIOD); + + if (keep_timer_going) + agent->conncheck_timer_grace_period = 0; + else + agent->conncheck_timer_grace_period += agent->timer_ta; + /* step: stop timer if no work left */ - if (keep_timer_going != TRUE) { - nice_debug ("Agent %p : %s: stopping conncheck timer", agent, G_STRFUNC); + if (!keep_timer_going && + agent->conncheck_timer_grace_period >= NICE_AGENT_MAX_TIMER_GRACE_PERIOD) { + 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); @@ -1017,6 +1047,7 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) } } + nice_debug ("Agent %p : %s: stopping conncheck timer", agent, G_STRFUNC); priv_print_conn_check_lists (agent, G_STRFUNC, ", conncheck timer stopped"); @@ -1027,9 +1058,10 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) /* XXX: what to signal, is all processing now really done? */ nice_debug ("Agent %p : changing conncheck state to COMPLETED.", agent); + return FALSE; } - return keep_timer_going; + return TRUE; } static gboolean priv_conn_check_tick (gpointer pointer) @@ -1810,15 +1842,18 @@ static gboolean priv_update_selected_pair (NiceAgent *agent, NiceComponent *comp * Updates the check list state. * * Implements parts of the algorithm described in - * ICE sect 8.1.2. "Updating States" (ID-19): if for any + * ICE sect 8.1.2. "Updating States" (RFC 5245): if for any * component, all checks have been completed and have - * failed, mark that component's state to NICE_CHECK_FAILED. + * failed to produce a nominated pair, mark that component's + * state to NICE_CHECK_FAILED. * * Sends a component state changesignal via 'agent'. */ static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStream *stream) { GSList *i; + gboolean completed; + guint nominated; /* note: emitting a signal might cause the client * to remove the stream, thus the component count * must be fetched before entering the loop*/ @@ -1842,6 +1877,8 @@ static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStre if (!agent_find_component (agent, stream->id, c+1, NULL, &comp)) continue; + nominated = 0; + completed = TRUE; for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; @@ -1849,16 +1886,22 @@ static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStre g_assert (p->stream_id == stream->id); if (p->component_id == (c + 1)) { - if (p->state != NICE_CHECK_FAILED) - break; + if (p->nominated) + ++nominated; + if (p->state != NICE_CHECK_FAILED && + p->state != NICE_CHECK_SUCCEEDED && + p->state != NICE_CHECK_DISCOVERED) + completed = FALSE; } } - /* note: all checks have failed + /* note: all pairs are either failed or succeeded, and the component + * has not produced a nominated pair. * Set the component to FAILED only if it actually had remote candidates * that failed.. */ - if (i == NULL && comp != NULL && comp->remote_candidates != NULL) - agent_signal_component_state_change (agent, + if (completed && nominated == 0 && + comp != NULL && comp->remote_candidates != NULL) + agent_signal_component_state_change (agent, stream->id, (c + 1), /* component-id */ NICE_COMPONENT_STATE_FAILED); -- 2.13.6 From 195db6b344fc4f9fadc39419dfeec2fc14b23fac Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Fri, 15 Jul 2016 23:31:42 +0200 Subject: [PATCH 45/70] agent: add new pairs only for gathering streams At the end of the local candidate gathering process, we only create new pairs for streams that are in gathering state. Other stream that may be in ready state for example, due to a previously succeeded conncheck process, may have accumulated some couples (local,remote) candidates that have not resulted in the creation a new pair during this previous conncheck process, and we don't want these new pairs to be added now, because it would generate unneeded transition changes for a stream unconcerned by this gathering. Differential Revision: https://phabricator.freedesktop.org/D1755 --- agent/agent.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/agent/agent.c b/agent/agent.c index 577a7e0..e3705ed 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -2032,6 +2032,17 @@ void agent_gathering_done (NiceAgent *agent) for (i = agent->streams; i; i = i->next) { NiceStream *stream = i->data; + + /* We ignore streams not in gathering state, typically already in + * ready state. Such streams may have couples (local,remote) + * candidates that have not resulted in the creation a new pair + * during a previous conncheck session, and we don't want these new + * pairs to be added now, because it would generate unneeded + * transition changes for a stream unconcerned by this gathering. + */ + if (!stream->gathering) + continue; + for (j = stream->components; j; j = j->next) { NiceComponent *component = j->data; -- 2.13.6 From b4b8d6628c8c5d4f10af0101f846db4938a3f6c4 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Sun, 28 May 2017 22:20:36 +0200 Subject: [PATCH 46/70] stun: fix gcc7 implicit fallthrough warning Differential Revision: https://phabricator.freedesktop.org/D1754 --- stun/stunmessage.c | 1 + 1 file changed, 1 insertion(+) diff --git a/stun/stunmessage.c b/stun/stunmessage.c index e8184c4..4cc3392 100644 --- a/stun/stunmessage.c +++ b/stun/stunmessage.c @@ -120,6 +120,7 @@ stun_message_find (const StunMessage *msg, StunAttribute type, /* Only fingerprint may come after M-I */ if (type == STUN_ATTRIBUTE_FINGERPRINT) break; + return NULL; case STUN_ATTRIBUTE_FINGERPRINT: /* Nothing may come after FPR */ -- 2.13.6 From c7a5a92b66f9b83baf2aa446966bdfb2cf39ecd1 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Sun, 18 Jun 2017 10:12:58 +0200 Subject: [PATCH 47/70] agent: remove spurious newlines Differential Revision: https://phabricator.freedesktop.org/D1756 --- agent/agent.c | 2 +- agent/component.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/agent.c b/agent/agent.c index e3705ed..27e6193 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -3905,7 +3905,7 @@ agent_recv_message_unlocked ( nice_address_to_string (message->from, str); nice_debug_verbose ("Agent %p : %d:%d DROPPING packet from unknown source" - " %s:%d sock-type: %d\n", agent, stream->id, component->id, str, + " %s:%d sock-type: %d", agent, stream->id, component->id, str, nice_address_get_port (message->from), nicesock->type); } diff --git a/agent/component.c b/agent/component.c index ab665b6..6e207d3 100644 --- a/agent/component.c +++ b/agent/component.c @@ -1461,7 +1461,7 @@ nice_component_add_valid_candidate (NiceComponent *component, char str[INET6_ADDRSTRLEN]; nice_address_to_string (&candidate->addr, str); nice_debug ("Agent %p : %d:%d Adding valid source" - " candidate: %s:%d trans: %d\n", component->agent, + " candidate: %s:%d trans: %d", component->agent, candidate->stream_id, candidate->component_id, str, nice_address_get_port (&candidate->addr), candidate->transport); } -- 2.13.6 From e3ddaa285e389baf3f26cfb6964919718a8f6a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Wed, 21 Jun 2017 16:55:32 -0400 Subject: [PATCH 48/70] agent: Adjust the nice_agent_new_full() to use flags This makes it easier to read and more extensible. --- agent/agent.c | 9 +++++---- agent/agent.h | 27 ++++++++++++++++++++++----- tests/test-nomination.c | 8 ++++---- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/agent/agent.c b/agent/agent.c index 27e6193..8fd8ead 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -1168,14 +1168,15 @@ nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat) NICEAPI_EXPORT NiceAgent * nice_agent_new_full (GMainContext *ctx, NiceCompatibility compat, - gboolean reliable, - NiceNominationMode nomination) + NiceAgentOption flags) { NiceAgent *agent = g_object_new (NICE_TYPE_AGENT, "compatibility", compat, "main-context", ctx, - "reliable", reliable, - "nomination-mode", nomination, + "reliable", (flags & NICE_AGENT_OPTION_RELIABLE) ? TRUE : FALSE, + "nomination-mode", (flags & NICE_AGENT_OPTION_REGULAR_NOMINATION) ? + NICE_NOMINATION_MODE_REGULAR : NICE_NOMINATION_MODE_AGGRESSIVE, + "full-mode", (flags & NICE_AGENT_OPTION_LITE_MODE) ? FALSE : TRUE, NULL); return agent; diff --git a/agent/agent.h b/agent/agent.h index 6e233c6..ed6f6e4 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -399,6 +399,25 @@ typedef enum } NiceNominationMode; /** + * NiceAgentOption: + * @NICE_AGENT_OPTION_REGULAR_NOMINATION: Enables regular nomination, default + * is aggrssive mode (see #NiceNominationMode). + * @NICE_AGENT_OPTION_RELIABLE: Enables reliable mode, possibly using PseudoTCP, * see nice_agent_new_reliable(). + * @NICE_AGENT_OPTION_LITE_MODE: Enable lite mode + * + * These are options that can be passed to nice_agent_new_full(). They set + * various properties on the agent. Not including them sets the property to + * the other value. + * + * Since: UNRELEASED + */ +typedef enum { + NICE_AGENT_OPTION_REGULAR_NOMINATION = 1 << 0, + NICE_AGENT_OPTION_RELIABLE = 1 << 1, + NICE_AGENT_OPTION_LITE_MODE = 1 << 2, +} NiceAgentOption; + +/** * NiceAgentRecvFunc: * @agent: The #NiceAgent Object * @stream_id: The id of the stream @@ -452,13 +471,12 @@ nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat); * nice_agent_new_full: * @ctx: The Glib Mainloop Context to use for timers * @compat: The compatibility mode of the agent - * @reliable: The reliability mode of the agent - * @nomination: The nomination mode of the agent + * @flags: Flags to set the properties * * Create a new #NiceAgent with parameters that must be be defined at * construction time. * The returned object must be freed with g_object_unref() - * See also: #NiceNominationMode + * See also: #NiceNominationMode and #NiceAgentOption * * Since: UNRELEASED * @@ -467,8 +485,7 @@ nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat); NiceAgent * nice_agent_new_full (GMainContext *ctx, NiceCompatibility compat, - gboolean reliable, - NiceNominationMode nomination); + NiceAgentOption flags); /** * nice_agent_add_local_address: diff --git a/tests/test-nomination.c b/tests/test-nomination.c index b5a5e5f..bf21557 100644 --- a/tests/test-nomination.c +++ b/tests/test-nomination.c @@ -140,13 +140,13 @@ run_test(NiceNominationMode l_nomination_mode, lagent = nice_agent_new_full (NULL, NICE_COMPATIBILITY_RFC5245, - FALSE, /* reliable */ - l_nomination_mode); + l_nomination_mode == NICE_NOMINATION_MODE_REGULAR ? + NICE_AGENT_OPTION_REGULAR_NOMINATION : 0); ragent = nice_agent_new_full (NULL, NICE_COMPATIBILITY_RFC5245, - FALSE, /* reliable */ - r_nomination_mode); + r_nomination_mode == NICE_NOMINATION_MODE_REGULAR ? + NICE_AGENT_OPTION_REGULAR_NOMINATION : 0); g_object_set (G_OBJECT (lagent), "ice-tcp", FALSE, NULL); g_object_set (G_OBJECT (ragent), "ice-tcp", FALSE, NULL); -- 2.13.6 From dcb0d647174416a292492f8deca86f83a2ef124c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Wed, 21 Jun 2017 17:07:17 -0400 Subject: [PATCH 49/70] Repleace UNRELEASED with 0.1.15 --- agent/agent.c | 8 ++++---- agent/agent.h | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/agent/agent.c b/agent/agent.c index 8fd8ead..15af9ed 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -448,7 +448,7 @@ nice_agent_class_init (NiceAgentClass *klass) * the selection of valid pairs to be used upstream. * See also: #NiceNominationMode * - * Since: UNRELEASED + * Since: 0.1.15 */ g_object_class_install_property (gobject_class, PROP_NOMINATION_MODE, g_param_spec_enum ( @@ -744,7 +744,7 @@ nice_agent_class_init (NiceAgentClass *klass) * to the READY state, and on the time needed to complete the GATHERING * state. * - * Since: UNRELEASED + * Since: 0.1.15 */ g_object_class_install_property (gobject_class, PROP_STUN_MAX_RETRANSMISSIONS, @@ -769,7 +769,7 @@ nice_agent_class_init (NiceAgentClass *klass) * divided by two instead (RFC 5389 indicates that a customisable * multiplier 'Rm' to 'RTO' should be used). * - * Since: UNRELEASED + * Since: 0.1.15 */ g_object_class_install_property (gobject_class, PROP_STUN_INITIAL_TIMEOUT, @@ -788,7 +788,7 @@ nice_agent_class_init (NiceAgentClass *klass) * The initial timeout of the STUN binding requests used * for a reliable timer. * - * Since: UNRELEASED + * Since: 0.1.15 */ g_object_class_install_property (gobject_class, PROP_STUN_RELIABLE_TIMEOUT, diff --git a/agent/agent.h b/agent/agent.h index ed6f6e4..520c4c5 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -390,7 +390,7 @@ typedef enum * faster, than the regular mode, potentially causing the nominated * pair to change until the connection check completes. * - * Since: UNRELEASED + * Since: 0.1.15 */ typedef enum { @@ -409,7 +409,7 @@ typedef enum * various properties on the agent. Not including them sets the property to * the other value. * - * Since: UNRELEASED + * Since: 0.1.15 */ typedef enum { NICE_AGENT_OPTION_REGULAR_NOMINATION = 1 << 0, @@ -478,7 +478,7 @@ nice_agent_new_reliable (GMainContext *ctx, NiceCompatibility compat); * The returned object must be freed with g_object_unref() * See also: #NiceNominationMode and #NiceAgentOption * - * Since: UNRELEASED + * Since: 0.1.15 * * Returns: The new agent GObject */ -- 2.13.6 From 2c50d73b82f2ec2422a8e0ea393194486c193c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Wed, 10 Feb 2016 23:20:39 -0500 Subject: [PATCH 50/70] agent: Don't crash if recv cancelled without a GError --- agent/agent.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/agent/agent.c b/agent/agent.c index 15af9ed..e48d7f3 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -4279,7 +4279,10 @@ static gboolean nice_agent_recv_cancelled_cb (GCancellable *cancellable, gpointer user_data) { GError **error = user_data; - return !g_cancellable_set_error_if_cancelled (cancellable, error); + + if (error && *error) + g_cancellable_set_error_if_cancelled (cancellable, error); + return G_SOURCE_REMOVE; } static gint -- 2.13.6 From 63d273cea42def3567701ad9feab91f63cf9345f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Thu, 11 Feb 2016 22:16:48 -0500 Subject: [PATCH 51/70] component: Use non-GClosure dummy callbacks GClosures are not that cheap to setup --- agent/component.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/agent/component.c b/agent/component.c index 6e207d3..6eee90e 100644 --- a/agent/component.c +++ b/agent/component.c @@ -1005,6 +1005,18 @@ nice_component_class_init (NiceComponentClass *klass) G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } +static gboolean +dummy_callback (gpointer data) +{ + return G_SOURCE_CONTINUE; +} + +static void +source_set_dummy_callback (GSource *source) +{ + g_source_set_callback (source, dummy_callback, NULL, NULL); +} + static void nice_component_init (NiceComponent *component) { @@ -1027,7 +1039,7 @@ nice_component_init (NiceComponent *component) component->stop_cancellable = g_cancellable_new (); component->stop_cancellable_source = g_cancellable_source_new (component->stop_cancellable); - g_source_set_dummy_callback (component->stop_cancellable_source); + source_set_dummy_callback (component->stop_cancellable_source); g_source_attach (component->stop_cancellable_source, component->own_ctx); component->ctx = g_main_context_ref (component->own_ctx); @@ -1242,7 +1254,7 @@ component_source_prepare (GSource *source, gint *timeout_) child_socket_source->source = g_socket_create_source (child_socket_source->socket->fileno, G_IO_IN, NULL); - g_source_set_dummy_callback (child_socket_source->source); + source_set_dummy_callback (child_socket_source->source); g_source_add_child_source (source, child_socket_source->source); g_source_unref (child_socket_source->source); component_source->socket_sources = @@ -1387,7 +1399,7 @@ nice_component_input_source_new (NiceAgent *agent, guint stream_id, GSource *cancellable_source; cancellable_source = g_cancellable_source_new (cancellable); - g_source_set_dummy_callback (cancellable_source); + source_set_dummy_callback (cancellable_source); g_source_add_child_source ((GSource *) component_source, cancellable_source); g_source_unref (cancellable_source); -- 2.13.6 From 9f800d3597767855accccc592c34bc4e945f5bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Wed, 21 Jun 2017 20:42:57 -0400 Subject: [PATCH 52/70] configure: Remove -Wswitch-enum Creates useless warnings when other libraries change. https://phabricator.freedesktop.org/T7770 --- configure.ac | 1 - 1 file changed, 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6c106ff..16988ad 100644 --- a/configure.ac +++ b/configure.ac @@ -154,7 +154,6 @@ AS_IF([test "$enable_compile_warnings" = "yes" -o \ ]) AS_IF([test "$enable_compile_warnings" = "maximum" -o \ "$enable_compile_warnings" = "error"],[ - NICE_ADD_FLAG([-Wswitch-enum]) NICE_ADD_FLAG([-Wswitch-default]) NICE_ADD_FLAG([-Waggregate-return]) ]) -- 2.13.6 From dbaf8f5ccd76089e340883887c7e08e6c04de80a Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 12 Apr 2016 13:22:21 +0200 Subject: [PATCH 53/70] conncheck: improve role conflict debug This patch displays explicitely the controlling or controlled role of the agent. Differential Revision: https://phabricator.freedesktop.org/D874 --- agent/conncheck.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 63db471..8945e0f 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -3134,14 +3134,16 @@ static void priv_check_for_role_conflict (NiceAgent *agent, gboolean control) { /* role conflict, change mode; wait for a new conn. check */ if (control != agent->controlling_mode) { - nice_debug ("Agent %p : Role conflict, changing agent role to %d.", agent, control); + nice_debug ("Agent %p : Role conflict, changing agent role to \"%s\".", + agent, control ? "controlling" : "controlled"); agent->controlling_mode = control; /* the pair priorities depend on the roles, so recalculation * is needed */ priv_recalculate_pair_priorities (agent); } else - nice_debug ("Agent %p : Role conflict, agent role already changed to %d.", agent, control); + nice_debug ("Agent %p : Role conflict, staying with role \"%s\".", + agent, control ? "controlling" : "controlled"); } /* @@ -3429,13 +3431,25 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre /* case: role conflict error, need to restart with new role */ nice_debug ("Agent %p : conncheck %p ROLE CONFLICT, restarting", agent, p); + /* note: this res value indicates that the role of the peer + * agent has not changed after the tie-breaker comparison, so + * this is our role that must change. see ICE sect. 7.1.3.1 + * "Failure Cases". Our role might already have changed due to + * an earlier incoming request, but if not, change role now. + * + * Sect. 7.1.3.1 is not clear on this point, but we choose to + * put the candidate pair in the triggered check list even + * when the agent did not switch its role. The reason for this + * interpretation is that the reception of the stun reply, even + * an error reply, is a good sign that this pair will be + * valid, if we retry the check after the role of both peers + * has been fixed. + */ + if (p->stun_message.buffer != NULL) { guint64 tie; gboolean controlled_mode; - /* note: our role might already have changed due to an - * incoming request, but if not, change role now; - * follows ICE 7.1.2.1 "Failure Cases" (ID-19) */ controlled_mode = (stun_message_find64 (&p->stun_message, STUN_ATTRIBUTE_ICE_CONTROLLED, &tie) == STUN_MESSAGE_RETURN_SUCCESS); -- 2.13.6 From 5a42089aeb2dbbb52d820cd1b6efdfcfbe9b055e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Tue, 5 Sep 2017 14:50:29 -0400 Subject: [PATCH 54/70] agent: Set error if it isn't set --- agent/agent.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agent.c b/agent/agent.c index e48d7f3..a4dcc0c 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -4280,7 +4280,7 @@ nice_agent_recv_cancelled_cb (GCancellable *cancellable, gpointer user_data) { GError **error = user_data; - if (error && *error) + if (error && !*error) g_cancellable_set_error_if_cancelled (cancellable, error); return G_SOURCE_REMOVE; } -- 2.13.6 From 25be00271a4c8c684a2d435d29ae0811dbf5e21c Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Mon, 26 Jun 2017 20:36:35 +0200 Subject: [PATCH 55/70] conncheck: reorder some chunks of code With this patch we simplify the levels of code indentation. Differential Revision: https://phabricator.freedesktop.org/D1758 --- agent/conncheck.c | 858 +++++++++++++++++++++++++++--------------------------- 1 file changed, 422 insertions(+), 436 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 8945e0f..874f7b1 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -608,106 +608,106 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen gboolean keep_timer_going = FALSE; GSList *i; CandidateCheckPair *pair; + unsigned int timeout; /* 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; + + if (!agent_find_component (agent, p->stream_id, p->component_id, + NULL, &component)) + continue; + + if (p->state != NICE_CHECK_IN_PROGRESS) + continue; - if (p->state == NICE_CHECK_IN_PROGRESS) { - if (p->stun_message.buffer == NULL) { - 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); - } else if (priv_timer_expired (&p->next_tick, now)) { - switch (stun_timer_refresh (&p->timer)) { - case STUN_USAGE_TIMER_RETURN_TIMEOUT: - { - gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN]; - NiceComponent *component; + if (p->stun_message.buffer == NULL) { + 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)) + continue; + + switch (stun_timer_refresh (&p->timer)) { + case STUN_USAGE_TIMER_RETURN_TIMEOUT: timer_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: 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: 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 connectivity check %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. - */ - if (agent_find_component (agent, p->stream_id, p->component_id, - NULL, &component)) - priv_update_check_list_state_for_ready (agent, stream, - component); - break; - } - case STUN_USAGE_TIMER_RETURN_RETRANSMIT: - { - unsigned int timeout = stun_timer_remainder (&p->timer); + /* 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 connectivity check %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: + timeout = stun_timer_remainder (&p->timer); - /* 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_on_timeout) - goto timer_timeout; + /* 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_on_timeout) + goto timer_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: 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 */ - nice_debug ("Agent %p :STUN transaction retransmitted on pair %p " - "(timeout %dms, delay=%dms, retrans=%d).", - agent, p, timeout, p->timer.delay, p->timer.retransmissions); + /* case: not ready, so schedule a new timeout */ + nice_debug ("Agent %p :STUN transaction retransmitted on pair %p " + "(timeout %dms, delay=%dms, retrans=%d).", + agent, p, timeout, p->timer.delay, p->timer.retransmissions); - agent_socket_send (p->sockptr, &p->remote->addr, - stun_message_length (&p->stun_message), - (gchar *)p->stun_buffer); + agent_socket_send (p->sockptr, &p->remote->addr, + stun_message_length (&p->stun_message), + (gchar *)p->stun_buffer); - /* note: convert from milli to microseconds for g_time_val_add() */ - p->next_tick = *now; - g_time_val_add (&p->next_tick, timeout * 1000); + /* note: convert from milli to microseconds for g_time_val_add() */ + p->next_tick = *now; + g_time_val_add (&p->next_tick, timeout * 1000); - return TRUE; - } - case STUN_USAGE_TIMER_RETURN_SUCCESS: - { - unsigned int timeout = stun_timer_remainder (&p->timer); + return TRUE; + case STUN_USAGE_TIMER_RETURN_SUCCESS: + timeout = stun_timer_remainder (&p->timer); - /* note: convert from milli to microseconds for g_time_val_add() */ - p->next_tick = *now; - g_time_val_add (&p->next_tick, timeout * 1000); + /* note: convert from milli to microseconds for g_time_val_add() */ + p->next_tick = *now; + g_time_val_add (&p->next_tick, timeout * 1000); - keep_timer_going = TRUE; - break; - } - default: - /* Nothing to do. */ - break; - } - } + keep_timer_going = TRUE; + break; + default: + /* Nothing to do. */ + break; } } @@ -2628,27 +2628,23 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { switch (agent->nomination_mode) { case NICE_NOMINATION_MODE_REGULAR: - { - /* We are doing regular nomination, so we set the use-candidate - * attrib, when the controlling agent decided which valid pair to - * resend with this flag in priv_conn_check_tick_stream() - */ - cand_use = pair->use_candidate_on_next_check; - nice_debug ("Agent %p : %s: set cand_use=%d " - "(regular nomination).", agent, G_STRFUNC, cand_use); - break; - } + /* We are doing regular nomination, so we set the use-candidate + * attrib, when the controlling agent decided which valid pair to + * resend with this flag in priv_conn_check_tick_stream() + */ + cand_use = pair->use_candidate_on_next_check; + nice_debug ("Agent %p : %s: set cand_use=%d " + "(regular nomination).", agent, G_STRFUNC, cand_use); + break; case NICE_NOMINATION_MODE_AGGRESSIVE: - { - /* We are doing aggressive nomination, we set the use-candidate - * attrib in every check we send, when we are the controlling - * agent, RFC 5245, 8.1.1.2 - */ - cand_use = controlling; - nice_debug ("Agent %p : %s: set cand_use=%d " - "(aggressive nomination).", agent, G_STRFUNC, cand_use); - break; - } + /* We are doing aggressive nomination, we set the use-candidate + * attrib in every check we send, when we are the controlling + * agent, RFC 5245, 8.1.1.2 + */ + cand_use = controlling; + nice_debug ("Agent %p : %s: set cand_use=%d " + "(aggressive nomination).", agent, G_STRFUNC, cand_use); + break; default: /* Nothing to do. */ break; @@ -2656,107 +2652,105 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) } else if (cand_use) pair->nominated = controlling; - if (uname_len > 0) { - buffer_len = stun_usage_ice_conncheck_create (&component->stun_agent, - &pair->stun_message, pair->stun_buffer, sizeof(pair->stun_buffer), - uname, uname_len, password, password_len, - cand_use, controlling, pair->prflx_priority, - agent->tie_breaker, - pair->local->foundation, - agent_to_ice_compatibility (agent)); + if (uname_len == 0) { + nice_debug ("Agent %p: no credentials found, cancelling conncheck", agent); + pair->stun_message.buffer = NULL; + pair->stun_message.buffer_len = 0; + return -1; + } - nice_debug ("Agent %p: conncheck created %zd - %p", agent, buffer_len, - pair->stun_message.buffer); + buffer_len = stun_usage_ice_conncheck_create (&component->stun_agent, + &pair->stun_message, pair->stun_buffer, sizeof(pair->stun_buffer), + uname, uname_len, password, password_len, + cand_use, controlling, pair->prflx_priority, + agent->tie_breaker, + pair->local->foundation, + agent_to_ice_compatibility (agent)); - if (agent->compatibility == NICE_COMPATIBILITY_MSN || - agent->compatibility == NICE_COMPATIBILITY_OC2007) { - g_free (password); - } + nice_debug ("Agent %p: conncheck created %zd - %p", agent, buffer_len, + pair->stun_message.buffer); - if (buffer_len > 0) { - if (nice_socket_is_reliable(pair->sockptr)) { - stun_timer_start_reliable(&pair->timer, agent->stun_reliable_timeout); - } else { - StunTimer *timer = &pair->timer; - - if (pair->recheck_on_timeout) - /* 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. - */ - nice_debug("Agent %p : reusing timer of pair %p: %d/%d %d/%dms", - agent, pair, - timer->retransmissions, timer->max_retransmissions, - timer->delay - stun_timer_remainder (timer), timer->delay); - else - stun_timer_start (timer, - priv_compute_conncheck_timer (agent, stream), - agent->stun_max_retransmissions); - pair->recheck_on_timeout = FALSE; - } + if (agent->compatibility == NICE_COMPATIBILITY_MSN || + agent->compatibility == NICE_COMPATIBILITY_OC2007) { + g_free (password); + } - /* TCP-ACTIVE candidate must create a new socket before sending - * by connecting to the peer. The new socket is stored in the candidate - * check pair, until we discover a new local peer reflexive */ - if (pair->sockptr->fileno == NULL && - pair->sockptr->type != NICE_SOCKET_TYPE_UDP_TURN && - pair->local->transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE) { - NiceStream *stream2 = NULL; - NiceComponent *component2 = NULL; - NiceSocket *new_socket; - - if (agent_find_component (agent, pair->stream_id, pair->component_id, - &stream2, &component2)) { - new_socket = nice_tcp_active_socket_connect (pair->sockptr, - &pair->remote->addr); - if (new_socket) { - pair->sockptr = new_socket; - _priv_set_socket_tos (agent, pair->sockptr, stream2->tos); - - if (agent->reliable) { - nice_socket_set_writable_callback (pair->sockptr, - _tcp_sock_is_writable, component2); - } + if (buffer_len == 0) { + nice_debug ("Agent %p: buffer is empty, cancelling conncheck", agent); + pair->stun_message.buffer = NULL; + pair->stun_message.buffer_len = 0; + return -1; + } - nice_component_attach_socket (component2, new_socket); - } - } - } - /* send the conncheck */ - agent_socket_send (pair->sockptr, &pair->remote->addr, - buffer_len, (gchar *)pair->stun_buffer); + if (nice_socket_is_reliable(pair->sockptr)) + stun_timer_start_reliable(&pair->timer, agent->stun_reliable_timeout); + else { + StunTimer *timer = &pair->timer; + + if (pair->recheck_on_timeout) + /* 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. + */ + nice_debug("Agent %p : reusing timer of pair %p: %d/%d %d/%dms", + agent, pair, + timer->retransmissions, timer->max_retransmissions, + timer->delay - stun_timer_remainder (timer), timer->delay); + else + stun_timer_start (timer, + priv_compute_conncheck_timer (agent, stream), + agent->stun_max_retransmissions); + pair->recheck_on_timeout = FALSE; + } - if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) { - ms_ice2_legacy_conncheck_send (&pair->stun_message, pair->sockptr, - &pair->remote->addr); + /* TCP-ACTIVE candidate must create a new socket before sending + * by connecting to the peer. The new socket is stored in the candidate + * check pair, until we discover a new local peer reflexive */ + if (pair->sockptr->fileno == NULL && + pair->sockptr->type != NICE_SOCKET_TYPE_UDP_TURN && + pair->local->transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE) { + NiceStream *stream2 = NULL; + NiceComponent *component2 = NULL; + NiceSocket *new_socket; + + if (agent_find_component (agent, pair->stream_id, pair->component_id, + &stream2, &component2)) { + new_socket = nice_tcp_active_socket_connect (pair->sockptr, + &pair->remote->addr); + if (new_socket) { + pair->sockptr = new_socket; + _priv_set_socket_tos (agent, pair->sockptr, stream2->tos); + + if (agent->reliable) + nice_socket_set_writable_callback (pair->sockptr, + _tcp_sock_is_writable, component2); + + nice_component_attach_socket (component2, new_socket); } - - timeout = stun_timer_remainder (&pair->timer); - /* note: convert from milli to microseconds for g_time_val_add() */ - g_get_current_time (&pair->next_tick); - g_time_val_add (&pair->next_tick, timeout * 1000); - } else { - nice_debug ("Agent %p: buffer is empty, cancelling conncheck", agent); - pair->stun_message.buffer = NULL; - pair->stun_message.buffer_len = 0; - return -1; } - } else { - nice_debug ("Agent %p: no credentials found, cancelling conncheck", agent); - pair->stun_message.buffer = NULL; - pair->stun_message.buffer_len = 0; - return -1; } + /* send the conncheck */ + agent_socket_send (pair->sockptr, &pair->remote->addr, + buffer_len, (gchar *)pair->stun_buffer); + + if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) + ms_ice2_legacy_conncheck_send (&pair->stun_message, pair->sockptr, + &pair->remote->addr); + + timeout = stun_timer_remainder (&pair->timer); + /* note: convert from milli to microseconds for g_time_val_add() */ + g_get_current_time (&pair->next_tick); + g_time_val_add (&pair->next_tick, timeout * 1000); return 0; } @@ -2876,74 +2870,74 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str nice_debug ("Agent %p : Found a matching pair %p (%s) (state=%c) ...", agent, p, p->foundation, priv_state_to_gchar (p->state)); - if (p->state == NICE_CHECK_WAITING || - p->state == NICE_CHECK_FROZEN) { - nice_debug ("Agent %p : pair %p added for a triggered check.", - agent, p); - priv_add_pair_to_triggered_check_queue (agent, p); - } - else if (p->state == NICE_CHECK_IN_PROGRESS) { - /* note: according to ICE SPEC sect 7.2.1.4 "Triggered Checks" - * we cancel the in-progress transaction, and after the - * retransmission timeout, we create a new connectivity check - * for that pair. The controlling role of this new check may - * be different from the role of this cancelled check. - * - * note: the flag retransmit_on_timeout 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. - */ - if (!nice_socket_is_reliable (p->sockptr)) { - if (p->retransmit_on_timeout) { - nice_debug ("Agent %p : pair %p will be rechecked " - "on stun timer timeout.", agent, p); - /* this flag will determine the action at the retransmission - * timeout of the stun timer - */ - p->recheck_on_timeout = TRUE; - } else - nice_debug ("Agent %p : pair %p won't be retransmitted.", - agent, p); - } - } - else if (p->state == 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); - } else if (p->state == NICE_CHECK_FAILED) { - /* 7.2.1.4 Triggered Checks - * 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.", + switch (p->state) { + case NICE_CHECK_WAITING: + case NICE_CHECK_FROZEN: + nice_debug ("Agent %p : pair %p added for a triggered check.", agent, p); priv_add_pair_to_triggered_check_queue (agent, p); - /* If the component for this pair is in failed state, move it - * back to connecting, and reinitiate the timers + break; + case NICE_CHECK_IN_PROGRESS: + /* note: according to ICE SPEC sect 7.2.1.4 "Triggered Checks" + * we cancel the in-progress transaction, and after the + * retransmission timeout, we create a new connectivity check + * for that pair. The controlling role of this new check may + * be different from the role of this cancelled check. + * + * note: the flag retransmit_on_timeout 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. */ - if (component->state == NICE_COMPONENT_STATE_FAILED) { - agent_signal_component_state_change (agent, stream->id, - component->id, NICE_COMPONENT_STATE_CONNECTING); - conn_check_schedule_next (agent); + if (!nice_socket_is_reliable (p->sockptr) && + p->retransmit_on_timeout) { + nice_debug ("Agent %p : pair %p will be rechecked " + "on stun timer timeout.", agent, p); + /* this flag will determine the action at the retransmission + * timeout of the stun timer + */ + p->recheck_on_timeout = TRUE; } - } else - nice_debug ("Agent %p : pair %p won't be retransmitted.", - agent, p); + 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: + /* 7.2.1.4 Triggered Checks + * 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.", + agent, p); + priv_add_pair_to_triggered_check_queue (agent, p); + /* If the component for this pair is in failed state, move it + * back to connecting, and reinitiate the timers + */ + if (component->state == NICE_COMPONENT_STATE_FAILED) { + agent_signal_component_state_change (agent, stream->id, + component->id, NICE_COMPONENT_STATE_CONNECTING); + conn_check_schedule_next (agent); + } + } + break; + default: + break; } /* note: the spec says the we SHOULD retransmit in-progress @@ -3271,208 +3265,200 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre socklen_t socklen = sizeof (sockaddr); GSList *i; StunUsageIceReturn res; - gboolean trans_found = FALSE; StunTransactionId discovery_id; StunTransactionId response_id; stun_message_id (resp, response_id); - for (i = stream->conncheck_list; i && trans_found != TRUE; i = i->next) { + for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; - if (p->stun_message.buffer) { - stun_message_id (&p->stun_message, discovery_id); + if (p->stun_message.buffer == NULL) + continue; - if (memcmp (discovery_id, response_id, sizeof(StunTransactionId)) == 0) { - res = stun_usage_ice_conncheck_process (resp, - &sockaddr.storage, &socklen, - agent_to_ice_compatibility (agent)); - nice_debug ("Agent %p : stun_bind_process/conncheck for %p res %d " - "(controlling=%d).", agent, p, (int)res, agent->controlling_mode); - - if (res == STUN_USAGE_ICE_RETURN_SUCCESS || - res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { - /* case: found a matching connectivity check request */ - - CandidateCheckPair *ok_pair = NULL; - - nice_debug ("Agent %p : conncheck %p MATCHED.", agent, p); - p->stun_message.buffer = NULL; - p->stun_message.buffer_len = 0; - - /* step: verify that response came from the same IP address we - * sent the original request to (see 7.1.2.1. "Failure - * Cases") */ - if (nice_address_equal (from, &p->remote->addr) != TRUE) { - - p->state = NICE_CHECK_FAILED; - if (nice_debug_is_enabled ()) { - gchar tmpbuf[INET6_ADDRSTRLEN]; - gchar tmpbuf2[INET6_ADDRSTRLEN]; - nice_debug ("Agent %p : conncheck %p FAILED" - " (mismatch of source address).", agent, p); - nice_address_to_string (&p->remote->addr, tmpbuf); - nice_address_to_string (from, tmpbuf2); - nice_debug ("Agent %p : '%s:%u' != '%s:%u'", agent, - tmpbuf, nice_address_get_port (&p->remote->addr), - tmpbuf2, nice_address_get_port (from)); - } - trans_found = TRUE; - break; - } + stun_message_id (&p->stun_message, discovery_id); - /* note: CONNECTED but not yet READY, see docs */ - - /* step: handle the possible case of a peer-reflexive - * candidate where the mapped-address in response does - * not match any local candidate, see 7.1.2.2.1 - * "Discovering Peer Reflexive Candidates" ICE ID-19) */ - - if (res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { - /* note: this is same as "adding to VALID LIST" in the spec - text */ - p->state = NICE_CHECK_SUCCEEDED; - p->valid = TRUE; - g_assert_not_reached (); - nice_debug ("Agent %p : Mapped address not found." - " conncheck %p SUCCEEDED.", agent, p); - nice_component_add_valid_candidate (component, p->remote); - } else { - ok_pair = priv_process_response_check_for_reflexive (agent, - stream, component, p, sockptr, &sockaddr.addr, - local_candidate, remote_candidate); - } + if (memcmp (discovery_id, response_id, sizeof(StunTransactionId))) + continue; - /* 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 - */ - priv_conn_check_unfreeze_related (agent, stream, p); - - /* Note: this assignment helps to reduce the numbers of cases - * to be tested. If ok_pair and p refer to distinct pairs, it - * means that ok_pair is a discovered peer reflexive one, - * caused by the check made on pair p. In that case, the - * flags to be tested are on p, but the nominated flag will be - * set on ok_pair. When there's no discovered pair, p and - * ok_pair refer to the same pair. - * To summarize : p is a SUCCEEDED pair, ok_pair is a - * DISCOVERED, VALID, and eventually NOMINATED pair. - */ - if (!ok_pair) - ok_pair = p; - - /* step: updating nominated flag (ICE 7.1.2.2.4 "Updating the - Nominated Flag" (ID-19) */ - if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { - nice_debug ("Agent %p : Updating nominated flag (%s): " - "ok_pair=%p (%d/%d) p=%p (%d/%d) (ucnc/mnora)", - agent, p->local->transport == NICE_CANDIDATE_TRANSPORT_UDP ? - "UDP" : "TCP", - ok_pair, ok_pair->use_candidate_on_next_check, - ok_pair->mark_nominated_on_response_arrival, - p, p->use_candidate_on_next_check, - p->mark_nominated_on_response_arrival); - - if (agent->controlling_mode) { - switch (agent->nomination_mode) { - case NICE_NOMINATION_MODE_REGULAR: - if (p->use_candidate_on_next_check) { - nice_debug ("Agent %p : marking pair %p (%s) as nominated " - "(regular nomination, control=1, " - "use_cand_on_next_check=1).", - agent, ok_pair, ok_pair->foundation); - ok_pair->nominated = TRUE; - } - break; - case NICE_NOMINATION_MODE_AGGRESSIVE: - if (!p->nominated) { - nice_debug ("Agent %p : marking pair %p (%s) as nominated " - "(aggressive nomination, control=1).", - agent, ok_pair, ok_pair->foundation); - ok_pair->nominated = TRUE; - } - break; - default: - /* Nothing to do */ - break; + res = stun_usage_ice_conncheck_process (resp, + &sockaddr.storage, &socklen, + agent_to_ice_compatibility (agent)); + nice_debug ("Agent %p : stun_bind_process/conncheck for %p res %d " + "(controlling=%d).", agent, p, (int)res, agent->controlling_mode); + + if (res == STUN_USAGE_ICE_RETURN_SUCCESS || + res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { + /* case: found a matching connectivity check request */ + + CandidateCheckPair *ok_pair = NULL; + + nice_debug ("Agent %p : conncheck %p MATCHED.", agent, p); + p->stun_message.buffer = NULL; + p->stun_message.buffer_len = 0; + + /* step: verify that response came from the same IP address we + * sent the original request to (see 7.1.2.1. "Failure + * Cases") */ + if (nice_address_equal (from, &p->remote->addr) == FALSE) { + + p->state = NICE_CHECK_FAILED; + if (nice_debug_is_enabled ()) { + gchar tmpbuf[INET6_ADDRSTRLEN]; + gchar tmpbuf2[INET6_ADDRSTRLEN]; + nice_debug ("Agent %p : conncheck %p FAILED" + " (mismatch of source address).", agent, p); + nice_address_to_string (&p->remote->addr, tmpbuf); + nice_address_to_string (from, tmpbuf2); + nice_debug ("Agent %p : '%s:%u' != '%s:%u'", agent, + tmpbuf, nice_address_get_port (&p->remote->addr), + tmpbuf2, nice_address_get_port (from)); + } + return TRUE; + } + + /* note: CONNECTED but not yet READY, see docs */ + + /* step: handle the possible case of a peer-reflexive + * candidate where the mapped-address in response does + * not match any local candidate, see 7.1.2.2.1 + * "Discovering Peer Reflexive Candidates" ICE ID-19) */ + + if (res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { + p->state = NICE_CHECK_SUCCEEDED; + p->valid = TRUE; + nice_debug ("Agent %p : Mapped address not found." + " conncheck %p SUCCEEDED.", agent, p); + nice_component_add_valid_candidate (component, p->remote); + } else + ok_pair = priv_process_response_check_for_reflexive (agent, + stream, component, p, sockptr, &sockaddr.addr, + 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 + */ + priv_conn_check_unfreeze_related (agent, stream, p); + + /* Note: this assignment helps to reduce the numbers of cases + * to be tested. If ok_pair and p refer to distinct pairs, it + * means that ok_pair is a discovered peer reflexive one, + * caused by the check made on pair p. In that case, the + * flags to be tested are on p, but the nominated flag will be + * set on ok_pair. When there's no discovered pair, p and + * ok_pair refer to the same pair. + * To summarize : p is a SUCCEEDED pair, ok_pair is a + * DISCOVERED, VALID, and eventually NOMINATED pair. + */ + if (!ok_pair) + ok_pair = p; + + /* step: updating nominated flag (ICE 7.1.2.2.4 "Updating the + Nominated Flag" (ID-19) */ + if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { + nice_debug ("Agent %p : Updating nominated flag (%s): " + "ok_pair=%p (%d/%d) p=%p (%d/%d) (ucnc/mnora)", + agent, p->local->transport == NICE_CANDIDATE_TRANSPORT_UDP ? + "UDP" : "TCP", + ok_pair, ok_pair->use_candidate_on_next_check, + ok_pair->mark_nominated_on_response_arrival, + p, p->use_candidate_on_next_check, + p->mark_nominated_on_response_arrival); + + if (agent->controlling_mode) { + switch (agent->nomination_mode) { + case NICE_NOMINATION_MODE_REGULAR: + if (p->use_candidate_on_next_check) { + nice_debug ("Agent %p : marking pair %p (%s) as nominated " + "(regular nomination, control=1, " + "use_cand_on_next_check=1).", + agent, ok_pair, ok_pair->foundation); + ok_pair->nominated = TRUE; } - } else { - if (p->mark_nominated_on_response_arrival) { + break; + case NICE_NOMINATION_MODE_AGGRESSIVE: + if (!p->nominated) { nice_debug ("Agent %p : marking pair %p (%s) as nominated " - "(%s nomination, control=0, mark_on_response=1).", - agent, ok_pair, ok_pair->foundation, - agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE ? - "aggressive" : "regular"); + "(aggressive nomination, control=1).", + agent, ok_pair, ok_pair->foundation); ok_pair->nominated = TRUE; } - } + break; + default: + /* Nothing to do */ + break; } - - if (ok_pair->nominated == TRUE) { - priv_update_selected_pair (agent, component, ok_pair); - priv_print_conn_check_lists (agent, G_STRFUNC, - ", got a nominated pair"); - - /* Do not step down to CONNECTED if we're already at state READY*/ - if (component->state != NICE_COMPONENT_STATE_READY) { - /* step: notify the client of a new component state (must be done - * before the possible check list state update step */ - agent_signal_component_state_change (agent, - stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); - } + } else { + if (p->mark_nominated_on_response_arrival) { + nice_debug ("Agent %p : marking pair %p (%s) as nominated " + "(%s nomination, control=0, mark_on_response=1).", + agent, ok_pair, ok_pair->foundation, + agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE ? + "aggressive" : "regular"); + ok_pair->nominated = TRUE; } + } + } - /* 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); + if (ok_pair->nominated == TRUE) { + priv_update_selected_pair (agent, component, ok_pair); + priv_print_conn_check_lists (agent, G_STRFUNC, + ", got a nominated pair"); - trans_found = TRUE; - } else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) { - /* case: role conflict error, need to restart with new role */ - nice_debug ("Agent %p : conncheck %p ROLE CONFLICT, restarting", agent, p); - - /* note: this res value indicates that the role of the peer - * agent has not changed after the tie-breaker comparison, so - * this is our role that must change. see ICE sect. 7.1.3.1 - * "Failure Cases". Our role might already have changed due to - * an earlier incoming request, but if not, change role now. - * - * Sect. 7.1.3.1 is not clear on this point, but we choose to - * put the candidate pair in the triggered check list even - * when the agent did not switch its role. The reason for this - * interpretation is that the reception of the stun reply, even - * an error reply, is a good sign that this pair will be - * valid, if we retry the check after the role of both peers - * has been fixed. - */ + /* Do not step down to CONNECTED if we're already at state READY*/ + if (component->state != NICE_COMPONENT_STATE_READY) + /* step: notify the client of a new component state (must be done + * before the possible check list state update step */ + agent_signal_component_state_change (agent, + stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); + } - if (p->stun_message.buffer != NULL) { - guint64 tie; - gboolean controlled_mode; + /* 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); + } else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) { + /* case: role conflict error, need to restart with new role */ + nice_debug ("Agent %p : conncheck %p ROLE CONFLICT, restarting", agent, p); + + /* note: this res value indicates that the role of the peer + * agent has not changed after the tie-breaker comparison, so + * this is our role that must change. see ICE sect. 7.1.3.1 + * "Failure Cases". Our role might already have changed due to + * an earlier incoming request, but if not, change role now. + * + * Sect. 7.1.3.1 is not clear on this point, but we choose to + * put the candidate pair in the triggered check list even + * when the agent did not switch its role. The reason for this + * interpretation is that the reception of the stun reply, even + * an error reply, is a good sign that this pair will be + * valid, if we retry the check after the role of both peers + * has been fixed. + */ - controlled_mode = (stun_message_find64 (&p->stun_message, - STUN_ATTRIBUTE_ICE_CONTROLLED, &tie) == - STUN_MESSAGE_RETURN_SUCCESS); + if (p->stun_message.buffer != NULL) { + guint64 tie; + gboolean controlled_mode; - priv_check_for_role_conflict (agent, controlled_mode); + controlled_mode = (stun_message_find64 (&p->stun_message, + STUN_ATTRIBUTE_ICE_CONTROLLED, &tie) == + STUN_MESSAGE_RETURN_SUCCESS); - p->stun_message.buffer = NULL; - p->stun_message.buffer_len = 0; - priv_add_pair_to_triggered_check_queue (agent, p); - } - trans_found = TRUE; - } else { - /* case: STUN error, the check STUN context was freed */ - nice_debug ("Agent %p : conncheck %p FAILED.", agent, p); - p->stun_message.buffer = NULL; - p->stun_message.buffer_len = 0; - trans_found = TRUE; - } + priv_check_for_role_conflict (agent, controlled_mode); + + p->stun_message.buffer = NULL; + p->stun_message.buffer_len = 0; + priv_add_pair_to_triggered_check_queue (agent, p); } + } else { + /* case: STUN error, the check STUN context was freed */ + nice_debug ("Agent %p : conncheck %p FAILED.", agent, p); + p->stun_message.buffer = NULL; + p->stun_message.buffer_len = 0; } + return TRUE; } - return trans_found; + return FALSE; } /* -- 2.13.6 From ce33747e6fc9ca59ea22d54e3b5a9a67b69d8141 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Mon, 26 Jun 2017 20:41:49 +0200 Subject: [PATCH 56/70] conncheck: make debug sentences more accurate We add a helper function to print the pair state in-extenso. Differential Revision: https://phabricator.freedesktop.org/D1759 --- agent/conncheck.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 874f7b1..9517ee1 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -108,6 +108,54 @@ priv_state_to_gchar (NiceCheckState state) } static const gchar * +priv_state_to_string (NiceCheckState state) +{ + switch (state) { + case NICE_CHECK_WAITING: + return "waiting"; + case NICE_CHECK_IN_PROGRESS: + return "in progress"; + case NICE_CHECK_SUCCEEDED: + return "succeeded"; + case NICE_CHECK_FAILED: + return "failed"; + case NICE_CHECK_FROZEN: + return "frozen"; + case NICE_CHECK_DISCOVERED: + return "discovered"; + default: + g_assert_not_reached (); + } +} + +static const gchar * +priv_ice_return_to_string (StunUsageIceReturn ice_return) +{ + switch (ice_return) { + case STUN_USAGE_ICE_RETURN_SUCCESS: + return "success"; + case STUN_USAGE_ICE_RETURN_ERROR: + return "error"; + case STUN_USAGE_ICE_RETURN_INVALID: + return "invalid"; + case STUN_USAGE_ICE_RETURN_ROLE_CONFLICT: + return "role conflict"; + case STUN_USAGE_ICE_RETURN_INVALID_REQUEST: + return "invalid request"; + case STUN_USAGE_ICE_RETURN_INVALID_METHOD: + return "invalid method"; + case STUN_USAGE_ICE_RETURN_MEMORY_ERROR: + return "memory error"; + case STUN_USAGE_ICE_RETURN_INVALID_ADDRESS: + return "invalid address"; + case STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS: + return "no mapped address"; + default: + g_assert_not_reached (); + } +} + +static const gchar * priv_candidate_type_to_string (NiceCandidateType type) { switch (type) { @@ -2614,7 +2662,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) nice_address_to_string (&pair->remote->addr, tmpbuf2); nice_debug ("Agent %p : STUN-CC REQ [%s]:%u --> [%s]:%u, socket=%u, " "pair=%s (c-id:%u), tie=%llu, username='%.*s' (%" G_GSIZE_FORMAT "), " - "password='%.*s' (%" G_GSIZE_FORMAT "), prio=%u, cont=%d.", agent, + "password='%.*s' (%" G_GSIZE_FORMAT "), prio=%u, %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, @@ -2622,7 +2670,8 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) (unsigned long long)agent->tie_breaker, (int) uname_len, uname, uname_len, (int) password_len, password, password_len, - pair->prflx_priority, controlling); + pair->prflx_priority, + controlling ? "controlling" : "controlled"); } if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { @@ -2867,8 +2916,8 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str p = p->succeeded_pair; } - nice_debug ("Agent %p : Found a matching pair %p (%s) (state=%c) ...", - agent, p, p->foundation, priv_state_to_gchar (p->state)); + nice_debug ("Agent %p : Found a matching pair %p (%s) (%s) ...", + agent, p, p->foundation, priv_state_to_string (p->state)); switch (p->state) { case NICE_CHECK_WAITING: @@ -3283,8 +3332,11 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre res = stun_usage_ice_conncheck_process (resp, &sockaddr.storage, &socklen, agent_to_ice_compatibility (agent)); - nice_debug ("Agent %p : stun_bind_process/conncheck for %p res %d " - "(controlling=%d).", agent, p, (int)res, agent->controlling_mode); + nice_debug ("Agent %p : stun_bind_process/conncheck for %p: " + "%s,res=%s.", + agent, p, + agent->controlling_mode ? "controlling" : "controlled", + priv_ice_return_to_string (res)); if (res == STUN_USAGE_ICE_RETURN_SUCCESS || res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { @@ -3370,7 +3422,7 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre case NICE_NOMINATION_MODE_REGULAR: if (p->use_candidate_on_next_check) { nice_debug ("Agent %p : marking pair %p (%s) as nominated " - "(regular nomination, control=1, " + "(regular nomination, controlling, " "use_cand_on_next_check=1).", agent, ok_pair, ok_pair->foundation); ok_pair->nominated = TRUE; @@ -3379,7 +3431,7 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre case NICE_NOMINATION_MODE_AGGRESSIVE: if (!p->nominated) { nice_debug ("Agent %p : marking pair %p (%s) as nominated " - "(aggressive nomination, control=1).", + "(aggressive nomination, controlling).", agent, ok_pair, ok_pair->foundation); ok_pair->nominated = TRUE; } @@ -3391,7 +3443,7 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre } else { if (p->mark_nominated_on_response_arrival) { nice_debug ("Agent %p : marking pair %p (%s) as nominated " - "(%s nomination, control=0, mark_on_response=1).", + "(%s nomination, controlled, mark_on_response=1).", agent, ok_pair, ok_pair->foundation, agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE ? "aggressive" : "regular"); -- 2.13.6 From e860948b5fe3a791119957f26045b8f5159baeff Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Mon, 26 Jun 2017 21:06:36 +0200 Subject: [PATCH 57/70] conncheck: use stun_timer_remainder less frequently We try to use stun_timer_remainder() less frequently, particularily in the debug messages, and favour of the next_tick value associated to the pair. Differential Revision: https://phabricator.freedesktop.org/D1760 --- agent/conncheck.c | 77 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 9517ee1..8075d4f 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -86,6 +86,17 @@ static int priv_timer_expired (GTimeVal *timer, GTimeVal *now) now->tv_sec >= timer->tv_sec; } +static unsigned int priv_timer_remainder (GTimeVal *timer, GTimeVal *now) +{ + unsigned int delay; + if (now->tv_sec > timer->tv_sec || + (now->tv_sec == timer->tv_sec && now->tv_usec > timer->tv_usec)) + return 0; + delay = (timer->tv_sec - now->tv_sec) * 1000; + delay += ((signed long)(timer->tv_usec - now->tv_usec)) / 1000; + return delay; +} + static gchar priv_state_to_gchar (NiceCheckState state) { @@ -180,10 +191,13 @@ priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar * { GSList *i, *k; guint j; + GTimeVal now; if (!nice_debug_is_verbose ()) return; + g_get_current_time (&now); + #define PRIORITY_LEN 32 nice_debug ("Agent %p : *** conncheck list DUMP (called from %s%s)", @@ -209,7 +223,8 @@ priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar * priv_candidate_type_to_string (pair->local->type), priv_candidate_type_to_string (pair->remote->type), timer->retransmissions, timer->max_retransmissions, - timer->delay - stun_timer_remainder (timer), timer->delay, + timer->delay - priv_timer_remainder (&pair->next_tick, &now), + timer->delay, local_addr, nice_address_get_port (&pair->local->addr), remote_addr, nice_address_get_port (&pair->remote->addr), priv_state_to_gchar (pair->state), @@ -445,8 +460,6 @@ priv_find_first_frozen_check_list (NiceAgent *agent) */ static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair *pair) { - g_get_current_time (&pair->next_tick); - g_time_val_add (&pair->next_tick, agent->timer_ta * 1000); pair->state = NICE_CHECK_IN_PROGRESS; nice_debug ("Agent %p : pair %p state IN_PROGRESS", agent, pair); conn_check_send (agent, pair); @@ -651,12 +664,15 @@ priv_conn_recheck_on_timeout (NiceAgent *agent, CandidateCheckPair *p) * * @return will return FALSE when no more pending timers. */ -static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent, GTimeVal *now) +static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent) { gboolean keep_timer_going = FALSE; GSList *i; CandidateCheckPair *pair; unsigned int timeout; + GTimeVal now; + + g_get_current_time (&now); /* step: process ongoing STUN transactions */ for (i = stream->conncheck_list; i ; i = i->next) { @@ -678,7 +694,7 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen continue; } - if (!priv_timer_expired (&p->next_tick, now)) + if (!priv_timer_expired (&p->next_tick, &now)) continue; switch (stun_timer_refresh (&p->timer)) { @@ -712,8 +728,6 @@ timer_timeout: priv_update_check_list_state_for_ready (agent, stream, component); break; case STUN_USAGE_TIMER_RETURN_RETRANSMIT: - timeout = stun_timer_remainder (&p->timer); - /* 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 @@ -730,9 +744,13 @@ timer_timeout: break; /* case: not ready, so schedule a new timeout */ + timeout = stun_timer_remainder (&p->timer); + nice_debug ("Agent %p :STUN transaction retransmitted on pair %p " - "(timeout %dms, delay=%dms, retrans=%d).", - agent, p, timeout, p->timer.delay, p->timer.retransmissions); + "(timer=%d/%d %d/%dms).", + agent, p, + p->timer.retransmissions, p->timer.max_retransmissions, + p->timer.delay - timeout, p->timer.delay); agent_socket_send (p->sockptr, &p->remote->addr, stun_message_length (&p->stun_message), @@ -740,7 +758,7 @@ timer_timeout: /* note: convert from milli to microseconds for g_time_val_add() */ - p->next_tick = *now; + p->next_tick = now; g_time_val_add (&p->next_tick, timeout * 1000); return TRUE; @@ -748,7 +766,7 @@ timer_timeout: timeout = stun_timer_remainder (&p->timer); /* note: convert from milli to microseconds for g_time_val_add() */ - p->next_tick = *now; + p->next_tick = now; g_time_val_add (&p->next_tick, timeout * 1000); keep_timer_going = TRUE; @@ -1001,9 +1019,6 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) CandidateCheckPair *pair = NULL; gboolean keep_timer_going = FALSE; GSList *i, *j; - GTimeVal now; - - g_get_current_time (&now); /* the conncheck really starts when we have built * a connection check list for each stream @@ -1047,7 +1062,7 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) */ for (i = agent->streams; i ; i = i->next) { NiceStream *stream = i->data; - if (priv_conn_check_tick_stream (stream, agent, &now)) + 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; @@ -2731,12 +2746,14 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) return -1; } - if (nice_socket_is_reliable(pair->sockptr)) - stun_timer_start_reliable(&pair->timer, agent->stun_reliable_timeout); - else { + if (nice_socket_is_reliable(pair->sockptr)) { + timeout = agent->stun_reliable_timeout; + stun_timer_start_reliable(&pair->timer, timeout); + } else { StunTimer *timer = &pair->timer; - if (pair->recheck_on_timeout) + 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 @@ -2751,17 +2768,24 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) * 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 - stun_timer_remainder (timer), timer->delay); - else - stun_timer_start (timer, - priv_compute_conncheck_timer (agent, stream), - agent->stun_max_retransmissions); + timer->delay - timeout, + timer->delay); + } else { + timeout = priv_compute_conncheck_timer (agent, stream); + stun_timer_start (timer, timeout, agent->stun_max_retransmissions); + } pair->recheck_on_timeout = FALSE; } + g_get_current_time (&pair->next_tick); + g_time_val_add (&pair->next_tick, 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 * check pair, until we discover a new local peer reflexive */ @@ -2796,11 +2820,6 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) ms_ice2_legacy_conncheck_send (&pair->stun_message, pair->sockptr, &pair->remote->addr); - timeout = stun_timer_remainder (&pair->timer); - /* note: convert from milli to microseconds for g_time_val_add() */ - g_get_current_time (&pair->next_tick); - g_time_val_add (&pair->next_tick, timeout * 1000); - return 0; } -- 2.13.6 From 36f306f4a95f1c2b3e9c584b5a645a78e231c020 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Mon, 26 Jun 2017 21:41:44 +0200 Subject: [PATCH 58/70] 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 --- agent/conncheck.c | 701 +++++++++++++++++++++++++++--------------------------- agent/conncheck.h | 21 +- 2 files changed, 361 insertions(+), 361 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 8075d4f..2a85738 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -189,8 +189,8 @@ priv_candidate_type_to_string (NiceCandidateType type) static void priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar *detail) { - GSList *i, *k; - guint j; + GSList *i, *k, *l; + guint j, m; GTimeVal now; if (!nice_debug_is_verbose ()) @@ -210,27 +210,34 @@ priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar * if (pair->component_id == j) { gchar local_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->remote->addr, remote_addr); nice_debug ("Agent %p : *** sc=%d/%d : pair %p : " - "f=%s t=%s:%s timer=%d/%d %d/%dms " - "[%s]:%u > [%s]:%u state=%c%s%s%s", + "f=%s t=%s:%s [%s]:%u > [%s]:%u state=%c%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), - 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), remote_addr, nice_address_get_port (&pair->remote->addr), priv_state_to_gchar (pair->state), pair->valid ? "V" : "", pair->nominated ? "N" : "", 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 } } +/* + * 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 -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; - 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); - if (p->stun_message.buffer != NULL) { - stun_message_id (&p->stun_message, id); + if (stun->message.buffer != NULL) { + stun_message_id (&stun->message, id); stun_agent_forget_transaction (&component->stun_agent, id); } +} - p->stun_message.buffer = NULL; - p->stun_message.buffer_len = 0; +static void +priv_free_stun_transaction (gpointer data) +{ + g_slice_free (StunTransaction, data); } /* - * Function that resubmits a new connection check, after a previous - * check in in-progress state got cancelled due to an incoming stun - * request matching this same pair + * Remove a STUN transaction from a pair, and forget it + * from the related component stun agent. * - * @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 -priv_conn_recheck_on_timeout (NiceAgent *agent, CandidateCheckPair *p) +static void +priv_remove_stun_transaction (CandidateCheckPair *pair, + StunTransaction *stun, NiceComponent *component) { - if (p->recheck_on_timeout) { - g_assert (p->state == NICE_CHECK_IN_PROGRESS); - /* this cancelled pair may have the flag 'mark nominated - * 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. - * - * this cancelled pair may also have the flag 'use candidate - * on next check' set, that we want to preserve too. - */ - nice_debug ("Agent %p : pair %p was cancelled, " - "triggering a new connection check", agent, p); - priv_add_pair_to_triggered_check_queue (agent, p); - return TRUE; - } - return FALSE; + priv_forget_stun_transaction (stun, component); + pair->stun_transactions = g_slist_remove (pair->stun_transactions, stun); + priv_free_stun_transaction (stun); +} + +/* + * Remove all STUN transactions from a pair, and forget them + * from the related component stun agent. + * + * @pair the pair the stun list should be cleared. + * @component the component containing the stun agent used to + * forget the stun transactions. + */ +static void +priv_free_all_stun_transactions (CandidateCheckPair *pair, + NiceComponent *component) +{ + if (component) + 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) static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agent) { gboolean keep_timer_going = FALSE; - GSList *i; + GSList *i, *j; CandidateCheckPair *pair; unsigned int timeout; GTimeVal now; @@ -679,39 +726,59 @@ static gboolean priv_conn_check_tick_stream (NiceStream *stream, NiceAgent *agen CandidateCheckPair *p = i->data; gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN]; NiceComponent *component; + StunTransaction *stun; + + if (p->stun_transactions == NULL) + continue; if (!agent_find_component (agent, p->stream_id, p->component_id, NULL, &component)) continue; - if (p->state != NICE_CHECK_IN_PROGRESS) - continue; + /* The first stun transaction of the list may eventually be + * retransmitted, other stun transactions just have their + * timer updated. + */ - if (p->stun_message.buffer == NULL) { - 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; + j = p->stun_transactions->next; + + /* process all stun transactions except the first one */ + while (j) { + StunTransaction *s = j->data; + GSList *next = j->next; + + if (priv_timer_expired (&s->next_tick, &now)) + switch (stun_timer_refresh (&s->timer)) { + case STUN_USAGE_TIMER_RETURN_TIMEOUT: + priv_remove_stun_transaction (p, s, 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; } - if (!priv_timer_expired (&p->next_tick, &now)) + if (p->state != NICE_CHECK_IN_PROGRESS) continue; - switch (stun_timer_refresh (&p->timer)) { - case STUN_USAGE_TIMER_RETURN_TIMEOUT: -timer_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; + /* 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 */ 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 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, tmpbuf1, nice_address_get_port (&p->local->addr), tmpbuf2, nice_address_get_port (&p->remote->addr)); @@ -732,42 +799,33 @@ timer_timeout: * 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_on_timeout) - goto timer_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; + if (!p->retransmit) + goto timer_return_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 " "(timer=%d/%d %d/%dms).", agent, p, - p->timer.retransmissions, p->timer.max_retransmissions, - p->timer.delay - timeout, p->timer.delay); + stun->timer.retransmissions, stun->timer.max_retransmissions, + stun->timer.delay - timeout, stun->timer.delay); agent_socket_send (p->sockptr, &p->remote->addr, - stun_message_length (&p->stun_message), - (gchar *)p->stun_buffer); - + stun_message_length (&stun->message), + (gchar *)stun->buffer); /* note: convert from milli to microseconds for g_time_val_add() */ - p->next_tick = now; - g_time_val_add (&p->next_tick, timeout * 1000); + stun->next_tick = now; + g_time_val_add (&stun->next_tick, timeout * 1000); return TRUE; 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() */ - p->next_tick = now; - g_time_val_add (&p->next_tick, timeout * 1000); + stun->next_tick = now; + g_time_val_add (&stun->next_tick, timeout * 1000); keep_timer_going = TRUE; break; @@ -948,7 +1006,6 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) g_assert (p->state == NICE_CHECK_SUCCEEDED); nice_debug ("Agent %p : restarting check %p with " "USE-CANDIDATE attrib (regular nomination)", agent, p); - p->recheck_on_timeout = FALSE; p->use_candidate_on_next_check = TRUE; priv_add_pair_to_triggered_check_queue (agent, p); keep_timer_going = TRUE; @@ -972,7 +1029,6 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) p->state == NICE_CHECK_DISCOVERED)) { nice_debug ("Agent %p : restarting check %p as the nominated pair.", agent, p); p->nominated = TRUE; - p->recheck_on_timeout = FALSE; priv_update_selected_pair (agent, component, p); priv_add_pair_to_triggered_check_queue (agent, p); keep_timer_going = TRUE; @@ -2186,7 +2242,6 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, } pair->prflx_priority = ensure_unique_priority (component, peer_reflexive_candidate_priority (agent, local)); - pair->retransmit_on_timeout = TRUE; stream->conncheck_list = g_slist_insert_sorted (stream->conncheck_list, pair, (GCompareFunc)conn_check_compare); @@ -2381,8 +2436,7 @@ static void conn_check_free_item (gpointer data) if (pair->agent) priv_remove_pair_from_triggered_check_queue (pair->agent, pair); - pair->stun_message.buffer = NULL; - pair->stun_message.buffer_len = 0; + priv_free_all_stun_transactions (pair, NULL); g_slice_free (CandidateCheckPair, pair); } @@ -2655,6 +2709,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) bool cand_use = controlling; size_t buffer_len; unsigned int timeout; + StunTransaction *stun; if (!agent_find_component (agent, pair->stream_id, pair->component_id, &stream, &component)) @@ -2718,13 +2773,13 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) if (uname_len == 0) { nice_debug ("Agent %p: no credentials found, cancelling conncheck", agent); - pair->stun_message.buffer = NULL; - pair->stun_message.buffer_len = 0; return -1; } + stun = priv_add_stun_transaction (pair); + 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, cand_use, controlling, pair->prflx_priority, agent->tie_breaker, @@ -2732,7 +2787,7 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) agent_to_ice_compatibility (agent)); nice_debug ("Agent %p: conncheck created %zd - %p", agent, buffer_len, - pair->stun_message.buffer); + stun->message.buffer); if (agent->compatibility == NICE_COMPATIBILITY_MSN || agent->compatibility == NICE_COMPATIBILITY_OC2007) { @@ -2741,50 +2796,20 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) if (buffer_len == 0) { nice_debug ("Agent %p: buffer is empty, cancelling conncheck", agent); - pair->stun_message.buffer = NULL; - pair->stun_message.buffer_len = 0; + priv_remove_stun_transaction (pair, stun, component); return -1; } if (nice_socket_is_reliable(pair->sockptr)) { 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 { - timeout = priv_compute_conncheck_timer (agent, stream); - stun_timer_start (timer, timeout, agent->stun_max_retransmissions); - } - pair->recheck_on_timeout = FALSE; + timeout = priv_compute_conncheck_timer (agent, stream); + stun_timer_start (&stun->timer, timeout, agent->stun_max_retransmissions); } - g_get_current_time (&pair->next_tick); - g_time_val_add (&pair->next_tick, timeout * 1000); + g_get_current_time (&stun->next_tick); + g_time_val_add (&stun->next_tick, 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 @@ -2814,10 +2839,10 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) } /* send the conncheck */ 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) - ms_ice2_legacy_conncheck_send (&pair->stun_message, pair->sockptr, + ms_ice2_legacy_conncheck_send (&stun->message, pair->sockptr, &pair->remote->addr); return 0; @@ -2881,8 +2906,7 @@ static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, gu (p->state == NICE_CHECK_WAITING && like_in_progress)) { if (highest_nominated_priority != 0 && p->priority < highest_nominated_priority) { - p->retransmit_on_timeout = FALSE; - p->recheck_on_timeout = FALSE; + p->retransmit = FALSE; nice_debug ("Agent %p : pair %p will not be retransmitted.", agent, p); } else { @@ -2938,9 +2962,9 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str nice_debug ("Agent %p : Found a matching pair %p (%s) (%s) ...", agent, p, p->foundation, priv_state_to_string (p->state)); - switch (p->state) { - case NICE_CHECK_WAITING: - case NICE_CHECK_FROZEN: + switch (p->state) { + case NICE_CHECK_WAITING: + case NICE_CHECK_FROZEN: nice_debug ("Agent %p : pair %p added for a triggered check.", agent, p); priv_add_pair_to_triggered_check_queue (agent, p); @@ -2952,19 +2976,30 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str * for that pair. The controlling role of this new check may * 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, * 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_on_timeout) { - nice_debug ("Agent %p : pair %p will be rechecked " - "on stun timer timeout.", agent, p); - /* this flag will determine the action at the retransmission - * timeout of the stun timer + if (!nice_socket_is_reliable (p->sockptr) && p->retransmit) { + 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) { + nice_debug ("Agent %p : pair %p added for a triggered check.", + agent, p); + priv_add_pair_to_triggered_check_queue (agent, p); + /* If the component for this pair is in failed state, move it + * back to connecting, and reinitiate the timers */ - p->recheck_on_timeout = TRUE; + if (component->state == NICE_COMPONENT_STATE_FAILED) { + agent_signal_component_state_change (agent, stream->id, + component->id, NICE_COMPONENT_STATE_CONNECTING); + conn_check_schedule_next (agent); + } } break; case NICE_CHECK_SUCCEEDED: @@ -2978,32 +3013,6 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str */ priv_update_check_list_state_for_ready (agent, stream, component); break; - case NICE_CHECK_FAILED: - /* 7.2.1.4 Triggered Checks - * 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.", - agent, p); - priv_add_pair_to_triggered_check_queue (agent, p); - /* If the component for this pair is in failed state, move it - * back to connecting, and reinitiate the timers - */ - if (component->state == NICE_COMPONENT_STATE_FAILED) { - agent_signal_component_state_change (agent, stream->id, - component->id, NICE_COMPONENT_STATE_CONNECTING); - conn_check_schedule_next (agent); - } - } - break; default: break; } @@ -3260,16 +3269,8 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * if (new_pair == p) p->valid = TRUE; 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_free_all_stun_transactions (p, component); nice_debug ("Agent %p : conncheck %p SUCCEEDED.", agent, p); nice_component_add_valid_candidate (component, remote_candidate); } @@ -3304,8 +3305,8 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * * Succeeded, RFC 5245, 7.1.3.2.3, "Updating Pair States" */ p->state = NICE_CHECK_SUCCEEDED; - p->recheck_on_timeout = FALSE; 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.", agent, p, new_pair); } @@ -3331,7 +3332,8 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre struct sockaddr addr; } sockaddr; socklen_t socklen = sizeof (sockaddr); - GSList *i; + GSList *i, *j; + guint k; StunUsageIceReturn res; StunTransactionId discovery_id; StunTransactionId response_id; @@ -3340,193 +3342,186 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; - if (p->stun_message.buffer == NULL) - continue; - - stun_message_id (&p->stun_message, discovery_id); - - if (memcmp (discovery_id, response_id, sizeof(StunTransactionId))) - continue; - - res = stun_usage_ice_conncheck_process (resp, - &sockaddr.storage, &socklen, - agent_to_ice_compatibility (agent)); - nice_debug ("Agent %p : stun_bind_process/conncheck for %p: " - "%s,res=%s.", - agent, p, - agent->controlling_mode ? "controlling" : "controlled", - priv_ice_return_to_string (res)); - - if (res == STUN_USAGE_ICE_RETURN_SUCCESS || - res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { - /* case: found a matching connectivity check request */ - - CandidateCheckPair *ok_pair = NULL; - - nice_debug ("Agent %p : conncheck %p MATCHED.", agent, p); - p->stun_message.buffer = NULL; - p->stun_message.buffer_len = 0; - - /* step: verify that response came from the same IP address we - * sent the original request to (see 7.1.2.1. "Failure - * Cases") */ - if (nice_address_equal (from, &p->remote->addr) == FALSE) { - - p->state = NICE_CHECK_FAILED; - if (nice_debug_is_enabled ()) { - gchar tmpbuf[INET6_ADDRSTRLEN]; - gchar tmpbuf2[INET6_ADDRSTRLEN]; - nice_debug ("Agent %p : conncheck %p FAILED" - " (mismatch of source address).", agent, p); - nice_address_to_string (&p->remote->addr, tmpbuf); - nice_address_to_string (from, tmpbuf2); - nice_debug ("Agent %p : '%s:%u' != '%s:%u'", agent, - tmpbuf, nice_address_get_port (&p->remote->addr), - tmpbuf2, nice_address_get_port (from)); - } - return TRUE; - } - - /* note: CONNECTED but not yet READY, see docs */ - - /* step: handle the possible case of a peer-reflexive - * candidate where the mapped-address in response does - * not match any local candidate, see 7.1.2.2.1 - * "Discovering Peer Reflexive Candidates" ICE ID-19) */ - - if (res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { - p->state = NICE_CHECK_SUCCEEDED; - p->valid = TRUE; - nice_debug ("Agent %p : Mapped address not found." - " conncheck %p SUCCEEDED.", agent, p); - nice_component_add_valid_candidate (component, p->remote); - } else - ok_pair = priv_process_response_check_for_reflexive (agent, - stream, component, p, sockptr, &sockaddr.addr, - 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 - */ - priv_conn_check_unfreeze_related (agent, stream, p); - - /* Note: this assignment helps to reduce the numbers of cases - * to be tested. If ok_pair and p refer to distinct pairs, it - * means that ok_pair is a discovered peer reflexive one, - * caused by the check made on pair p. In that case, the - * flags to be tested are on p, but the nominated flag will be - * set on ok_pair. When there's no discovered pair, p and - * ok_pair refer to the same pair. - * To summarize : p is a SUCCEEDED pair, ok_pair is a - * DISCOVERED, VALID, and eventually NOMINATED pair. - */ - if (!ok_pair) - ok_pair = p; - - /* step: updating nominated flag (ICE 7.1.2.2.4 "Updating the - Nominated Flag" (ID-19) */ - if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { - nice_debug ("Agent %p : Updating nominated flag (%s): " - "ok_pair=%p (%d/%d) p=%p (%d/%d) (ucnc/mnora)", - agent, p->local->transport == NICE_CANDIDATE_TRANSPORT_UDP ? - "UDP" : "TCP", - ok_pair, ok_pair->use_candidate_on_next_check, - ok_pair->mark_nominated_on_response_arrival, - p, p->use_candidate_on_next_check, - p->mark_nominated_on_response_arrival); - - if (agent->controlling_mode) { - switch (agent->nomination_mode) { - case NICE_NOMINATION_MODE_REGULAR: - if (p->use_candidate_on_next_check) { - nice_debug ("Agent %p : marking pair %p (%s) as nominated " - "(regular nomination, controlling, " - "use_cand_on_next_check=1).", - agent, ok_pair, ok_pair->foundation); - ok_pair->nominated = TRUE; - } - break; - case NICE_NOMINATION_MODE_AGGRESSIVE: - if (!p->nominated) { - nice_debug ("Agent %p : marking pair %p (%s) as nominated " - "(aggressive nomination, controlling).", - agent, ok_pair, ok_pair->foundation); - ok_pair->nominated = TRUE; - } - break; - default: - /* Nothing to do */ - break; - } - } else { - if (p->mark_nominated_on_response_arrival) { - nice_debug ("Agent %p : marking pair %p (%s) as nominated " - "(%s nomination, controlled, mark_on_response=1).", - agent, ok_pair, ok_pair->foundation, - agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE ? - "aggressive" : "regular"); - ok_pair->nominated = TRUE; - } - } - } - - if (ok_pair->nominated == TRUE) { - priv_update_selected_pair (agent, component, ok_pair); - priv_print_conn_check_lists (agent, G_STRFUNC, - ", got a nominated pair"); + for (j = p->stun_transactions, k = 0; j; j = j->next, k++) { + StunTransaction *stun = j->data; + + stun_message_id (&stun->message, discovery_id); + + if (memcmp (discovery_id, response_id, sizeof(StunTransactionId))) + continue; + + res = stun_usage_ice_conncheck_process (resp, + &sockaddr.storage, &socklen, + agent_to_ice_compatibility (agent)); + nice_debug ("Agent %p : stun_bind_process/conncheck for %p: " + "%s,res=%s,stun#=%d.", + agent, p, + agent->controlling_mode ? "controlling" : "controlled", + priv_ice_return_to_string (res), k); + + if (res == STUN_USAGE_ICE_RETURN_SUCCESS || + res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { + /* case: found a matching connectivity check request */ + + CandidateCheckPair *ok_pair = NULL; + + nice_debug ("Agent %p : conncheck %p MATCHED.", agent, p); + priv_remove_stun_transaction (p, stun, component); + + /* step: verify that response came from the same IP address we + * sent the original request to (see 7.1.2.1. "Failure + * Cases") */ + if (nice_address_equal (from, &p->remote->addr) == FALSE) { + candidate_check_pair_fail (stream, agent, p); + if (nice_debug_is_enabled ()) { + gchar tmpbuf[INET6_ADDRSTRLEN]; + gchar tmpbuf2[INET6_ADDRSTRLEN]; + nice_debug ("Agent %p : conncheck %p FAILED" + " (mismatch of source address).", agent, p); + nice_address_to_string (&p->remote->addr, tmpbuf); + nice_address_to_string (from, tmpbuf2); + nice_debug ("Agent %p : '%s:%u' != '%s:%u'", agent, + tmpbuf, nice_address_get_port (&p->remote->addr), + tmpbuf2, nice_address_get_port (from)); + } + return TRUE; + } - /* Do not step down to CONNECTED if we're already at state READY*/ - if (component->state != NICE_COMPONENT_STATE_READY) - /* step: notify the client of a new component state (must be done - * before the possible check list state update step */ - agent_signal_component_state_change (agent, - stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); - } + /* note: CONNECTED but not yet READY, see docs */ + + /* step: handle the possible case of a peer-reflexive + * candidate where the mapped-address in response does + * not match any local candidate, see 7.1.2.2.1 + * "Discovering Peer Reflexive Candidates" ICE ID-19) */ + + if (res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { + p->state = NICE_CHECK_SUCCEEDED; + p->valid = TRUE; + nice_debug ("Agent %p : Mapped address not found." + " conncheck %p SUCCEEDED.", agent, p); + nice_component_add_valid_candidate (component, p->remote); + } else + ok_pair = priv_process_response_check_for_reflexive (agent, + stream, component, p, sockptr, &sockaddr.addr, + 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 + */ + priv_conn_check_unfreeze_related (agent, stream, p); + + /* Note: this assignment helps to reduce the numbers of cases + * to be tested. If ok_pair and p refer to distinct pairs, it + * means that ok_pair is a discovered peer reflexive one, + * caused by the check made on pair p. In that case, the + * flags to be tested are on p, but the nominated flag will be + * set on ok_pair. When there's no discovered pair, p and + * ok_pair refer to the same pair. + * To summarize : p is a SUCCEEDED pair, ok_pair is a + * DISCOVERED, VALID, and eventually NOMINATED pair. + */ + if (!ok_pair) + ok_pair = p; + + /* step: updating nominated flag (ICE 7.1.2.2.4 "Updating the + Nominated Flag" (ID-19) */ + if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { + nice_debug ("Agent %p : Updating nominated flag (%s): " + "ok_pair=%p (%d/%d) p=%p (%d/%d) (ucnc/mnora)", + agent, p->local->transport == NICE_CANDIDATE_TRANSPORT_UDP ? + "UDP" : "TCP", + ok_pair, ok_pair->use_candidate_on_next_check, + ok_pair->mark_nominated_on_response_arrival, + p, p->use_candidate_on_next_check, + p->mark_nominated_on_response_arrival); + + if (agent->controlling_mode) { + switch (agent->nomination_mode) { + case NICE_NOMINATION_MODE_REGULAR: + if (p->use_candidate_on_next_check) { + nice_debug ("Agent %p : marking pair %p (%s) as nominated " + "(regular nomination, controlling, " + "use_cand_on_next_check=1).", + agent, ok_pair, ok_pair->foundation); + ok_pair->nominated = TRUE; + } + break; + case NICE_NOMINATION_MODE_AGGRESSIVE: + if (!p->nominated) { + nice_debug ("Agent %p : marking pair %p (%s) as nominated " + "(aggressive nomination, controlling).", + agent, ok_pair, ok_pair->foundation); + ok_pair->nominated = TRUE; + } + break; + default: + /* Nothing to do */ + break; + } + } else { + if (p->mark_nominated_on_response_arrival) { + nice_debug ("Agent %p : marking pair %p (%s) as nominated " + "(%s nomination, controlled, mark_on_response=1).", + agent, ok_pair, ok_pair->foundation, + agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE ? + "aggressive" : "regular"); + ok_pair->nominated = TRUE; + } + } + } - /* 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); - } else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) { - /* case: role conflict error, need to restart with new role */ - nice_debug ("Agent %p : conncheck %p ROLE CONFLICT, restarting", agent, p); - - /* note: this res value indicates that the role of the peer - * agent has not changed after the tie-breaker comparison, so - * this is our role that must change. see ICE sect. 7.1.3.1 - * "Failure Cases". Our role might already have changed due to - * an earlier incoming request, but if not, change role now. - * - * Sect. 7.1.3.1 is not clear on this point, but we choose to - * put the candidate pair in the triggered check list even - * when the agent did not switch its role. The reason for this - * interpretation is that the reception of the stun reply, even - * an error reply, is a good sign that this pair will be - * valid, if we retry the check after the role of both peers - * has been fixed. - */ + if (ok_pair->nominated == TRUE) { + priv_update_selected_pair (agent, component, ok_pair); + priv_print_conn_check_lists (agent, G_STRFUNC, + ", got a nominated pair"); + + /* Do not step down to CONNECTED if we're already at state READY*/ + if (component->state != NICE_COMPONENT_STATE_READY) + /* step: notify the client of a new component state (must be done + * before the possible check list state update step */ + agent_signal_component_state_change (agent, + stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); + } - if (p->stun_message.buffer != NULL) { + /* 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); + } else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) { guint64 tie; gboolean controlled_mode; - controlled_mode = (stun_message_find64 (&p->stun_message, + /* case: role conflict error, need to restart with new role */ + nice_debug ("Agent %p : conncheck %p ROLE CONFLICT, restarting", agent, p); + + /* note: this res value indicates that the role of the peer + * agent has not changed after the tie-breaker comparison, so + * this is our role that must change. see ICE sect. 7.1.3.1 + * "Failure Cases". Our role might already have changed due to + * an earlier incoming request, but if not, change role now. + * + * Sect. 7.1.3.1 is not clear on this point, but we choose to + * put the candidate pair in the triggered check list even + * when the agent did not switch its role. The reason for this + * interpretation is that the reception of the stun reply, even + * an error reply, is a good sign that this pair will be + * valid, if we retry the check after the role of both peers + * has been fixed. + */ + controlled_mode = (stun_message_find64 (&stun->message, STUN_ATTRIBUTE_ICE_CONTROLLED, &tie) == STUN_MESSAGE_RETURN_SUCCESS); priv_check_for_role_conflict (agent, controlled_mode); - - p->stun_message.buffer = NULL; - p->stun_message.buffer_len = 0; + priv_remove_stun_transaction (p, stun, component); priv_add_pair_to_triggered_check_queue (agent, p); + } else { + /* case: STUN error, the check STUN context was freed */ + nice_debug ("Agent %p : conncheck %p FAILED.", agent, p); + candidate_check_pair_fail (stream, agent, p); } - } else { - /* case: STUN error, the check STUN context was freed */ - nice_debug ("Agent %p : conncheck %p FAILED.", agent, p); - p->stun_message.buffer = NULL; - p->stun_message.buffer_len = 0; + return TRUE; } - return TRUE; } return FALSE; diff --git a/agent/conncheck.h b/agent/conncheck.h index 909d469..e16dc67 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -71,6 +71,15 @@ typedef enum } NiceCheckState; 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 { @@ -86,16 +95,12 @@ struct _CandidateCheckPair gboolean valid; gboolean use_candidate_on_next_check; gboolean mark_nominated_on_response_arrival; - gboolean recheck_on_timeout; - gboolean retransmit_on_timeout; - struct _CandidateCheckPair *discovered_pair; - struct _CandidateCheckPair *succeeded_pair; + gboolean retransmit; /* if the first stun request must be retransmitted */ + CandidateCheckPair *discovered_pair; + CandidateCheckPair *succeeded_pair; guint64 priority; guint32 prflx_priority; - GTimeVal next_tick; /* next tick timestamp */ - StunTimer timer; - uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6]; - StunMessage stun_message; + GSList *stun_transactions; /* a list of ongoing stun requests */ }; int conn_check_add_for_candidate (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidate *remote); -- 2.13.6 From 6fe64fdbc53ab87dffd79972f492665cff14c0a0 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 27 Jun 2017 11:01:14 +0200 Subject: [PATCH 59/70] conncheck: update the state of triggered checks pairs With this patch, we fix an ambiguity of some parts of the spec, when the document refers to in-progress pairs, that also concern pairs in the triggered checks list. The first cast is in section 7.1.2.5, "Updating the Nominated Flag", when the in-progress pair will be nominated on response arrival. This is handled in function priv_mark_pair_nominated(), when a pair is put to the triggered check list in reaction to a matching inbound stun request. Such a pair in priv_mark_pair_nominated() will _always_ be in the triggered check list, from the previously called function priv_schedule_triggered_check(). The second case is in section 8.1.2, "Updating State" when an in-progress pair stops its retransmission when another pair of higher priority is already nominated. This is handled by function priv_prune_pending_checks(). Until now, pairs enqueued in the triggered check list move transiently to state waiting, according to 7.2.1.4. But this state causes wrong decisions in the two previous cases, because such pairs should in fact rather be considered "like in-progress", to avoid discarding them inadvertantly. This patch update the state of the triggered check list pairs to in-progress. It allows to remove exception handling cited above: the code is a bit more simple, and allows some refactoring in priv_mark_pair_nominated() between RFC and compatibility modes. Differential Revision: https://phabricator.freedesktop.org/D1762 --- agent/conncheck.c | 96 +++++++++++++++---------------------------------------- 1 file changed, 25 insertions(+), 71 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 2a85738..628c708 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -244,16 +244,6 @@ priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar * } } -/* Verify if the pair is in the triggered checks list - */ - -static gboolean -priv_is_pair_in_triggered_check_queue (NiceAgent *agent, CandidateCheckPair *pair) -{ - g_assert (pair); - return (g_slist_find (agent->triggered_check_queue, pair) != NULL); -} - /* Add the pair to the triggered checks list, if not already present */ static void @@ -261,8 +251,8 @@ priv_add_pair_to_triggered_check_queue (NiceAgent *agent, CandidateCheckPair *pa { g_assert (pair); - pair->state = NICE_CHECK_WAITING; - nice_debug ("Agent %p : pair %p state WAITING", agent, pair); + pair->state = NICE_CHECK_IN_PROGRESS; + nice_debug ("Agent %p : pair %p state IN_PROGRESS", agent, pair); 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); @@ -841,12 +831,6 @@ timer_return_timeout: */ pair = priv_conn_check_find_next_waiting (stream->conncheck_list); if (pair) { - /* remove the pair from the triggered check list if needed. This - * may happen with the cancelled pair, that's just been added - * in state waiting to the triggered check list above in the - * same function. - */ - priv_remove_pair_from_triggered_check_queue (agent, pair); priv_print_conn_check_lists (agent, G_STRFUNC, ", got a pair in Waiting state"); priv_conn_check_initiate (agent, pair); @@ -1109,7 +1093,7 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) if (pair) { priv_print_conn_check_lists (agent, G_STRFUNC, ", got a pair from triggered check list"); - priv_conn_check_initiate (agent, pair); + conn_check_send (agent, pair); return TRUE; } @@ -2098,8 +2082,7 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice /* step: search for at least one nominated pair */ for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *pair = i->data; - if (pair->local == localcand && pair->remote == remotecand && - NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { + if (pair->local == localcand && pair->remote == remotecand) { /* ICE, 7.2.1.5. Updating the Nominated Flag */ /* note: TCP candidates typically produce peer reflexive * candidate, generating a "discovered" pair that can be @@ -2111,44 +2094,27 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice g_assert (pair->state == NICE_CHECK_DISCOVERED); } - if (pair->valid) { - nice_debug ("Agent %p : marking pair %p (%s) as nominated", - agent, pair, pair->foundation); - pair->nominated = TRUE; - priv_update_selected_pair (agent, component, pair); - /* Do not step down to CONNECTED if we're already at state READY*/ - if (component->state == NICE_COMPONENT_STATE_FAILED) - agent_signal_component_state_change (agent, - stream->id, component->id, NICE_COMPONENT_STATE_CONNECTING); - 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 */ - agent_signal_component_state_change (agent, - stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); - priv_update_check_list_state_for_ready (agent, stream, component); - } else if (pair->state == NICE_CHECK_IN_PROGRESS) { + /* If the state of this pair is In-Progress, [...] the resulting + * valid pair has its nominated flag set when the response + * arrives. + */ + if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) && + pair->state == NICE_CHECK_IN_PROGRESS) { pair->mark_nominated_on_response_arrival = TRUE; nice_debug ("Agent %p : pair %p (%s) is in-progress, " "will be nominated on response receipt.", agent, pair, pair->foundation); } - /* note: this case is not covered by the ICE spec, 7.2.1.5, - * "Updating the Nominated Flag", but a pair in waiting state - * deserves the same treatment than a pair in-progress. A pair - * can be in waiting state as the result of being enqueued in - * the triggered check list for example. - */ - if (pair->state == NICE_CHECK_WAITING) { - pair->mark_nominated_on_response_arrival = TRUE; - nice_debug ("Agent %p : pair %p (%s) is waiting, " - "will be nominated on response receipt.", + + if (pair->valid || + !NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { + nice_debug ("Agent %p : marking pair %p (%s) as nominated", agent, pair, pair->foundation); + pair->nominated = TRUE; } - } else if (pair->local == localcand && pair->remote == remotecand) { - nice_debug ("Agent %p : marking pair %p (%s) as nominated", agent, pair, pair->foundation); - pair->nominated = TRUE; + if (pair->valid) { - priv_update_selected_pair (agent, component, pair); + priv_update_selected_pair (agent, component, pair); /* Do not step down to CONNECTED if we're already at state READY*/ if (component->state == NICE_COMPONENT_STATE_FAILED) agent_signal_component_state_change (agent, @@ -2159,7 +2125,9 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); } - priv_update_check_list_state_for_ready (agent, stream, component); + + if (pair->nominated) + priv_update_check_list_state_for_ready (agent, stream, component); } } } @@ -2731,12 +2699,12 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) nice_address_to_string (&pair->local->addr, tmpbuf1); nice_address_to_string (&pair->remote->addr, tmpbuf2); nice_debug ("Agent %p : STUN-CC REQ [%s]:%u --> [%s]:%u, socket=%u, " - "pair=%s (c-id:%u), tie=%llu, username='%.*s' (%" G_GSIZE_FORMAT "), " + "pair=%p (c-id:%u), tie=%llu, username='%.*s' (%" G_GSIZE_FORMAT "), " "password='%.*s' (%" G_GSIZE_FORMAT "), prio=%u, %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, - pair->foundation, pair->component_id, + pair, pair->component_id, (unsigned long long)agent->tie_breaker, (int) uname_len, uname, uname_len, (int) password_len, password, password_len, @@ -2877,35 +2845,21 @@ static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, gu "is %" G_GUINT64_FORMAT, agent, highest_nominated_priority); /* step: cancel all FROZEN and WAITING pairs for the component */ - /* note: this case is not covered by the ICE spec, 8.1.2 - * "Updating States", but a pair in waiting state which will be - * nominated on response receipt should be treated the same way - * that an in-progress pair. A pair in waiting state and in - * the triggered check list should also be treated like an in-progress - * pair. - */ i = stream->conncheck_list; while (i) { CandidateCheckPair *p = i->data; GSList *next = i->next; if (p->component_id == component_id) { - gboolean like_in_progress = - p->mark_nominated_on_response_arrival || - priv_is_pair_in_triggered_check_queue (agent, p); - - if (p->state == NICE_CHECK_FROZEN || - (p->state == NICE_CHECK_WAITING && !like_in_progress)) { + if (p->state == NICE_CHECK_FROZEN || p->state == NICE_CHECK_WAITING) { nice_debug ("Agent %p : pair %p removed.", agent, p); conn_check_free_item (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 || - (p->state == NICE_CHECK_WAITING && like_in_progress)) { - if (highest_nominated_priority != 0 && - p->priority < highest_nominated_priority) { + else if (p->state == NICE_CHECK_IN_PROGRESS) { + if (p->priority < highest_nominated_priority) { p->retransmit = FALSE; nice_debug ("Agent %p : pair %p will not be retransmitted.", agent, p); -- 2.13.6 From 72dd26a3368d3506fe8faca7067a02784fb5f0fd Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Wed, 28 Jun 2017 12:06:48 +0200 Subject: [PATCH 60/70] conncheck: forgot to put a pair in triggered check list When a new pair is created from an unknown remote candidate, it should be enqueue for a triggered check, to allow it to be marked as nominated on response arrival in priv_mark_pair_nominated(). Creating it in waiting state is not sufficient since the update in priv_mark_pair_nominated() from previous commits. Differential Revision: https://phabricator.freedesktop.org/D1763 --- agent/conncheck.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 628c708..0e3ce88 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -2893,11 +2893,12 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str { GSList *i; NiceCandidate *local = NULL; + CandidateCheckPair *p; g_assert (remote_cand != NULL); for (i = stream->conncheck_list; i ; i = i->next) { - CandidateCheckPair *p = i->data; + p = i->data; if (p->component_id == component->id && p->remote == remote_cand && ((p->local->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE && @@ -2986,8 +2987,9 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str if (i) { nice_debug ("Agent %p : Adding a triggered check to conn.check list (local=%p).", agent, local); - priv_add_new_check_pair (agent, stream->id, component, + p = priv_add_new_check_pair (agent, stream->id, component, local, remote_cand, NICE_CHECK_WAITING); + priv_add_pair_to_triggered_check_queue (agent, p); return TRUE; } else { -- 2.13.6 From 14102d44449d2eb4148588ce54fa897fa13b87ad Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Sun, 2 Jul 2017 16:02:09 +0200 Subject: [PATCH 61/70] conncheck: change state before updating nominated pairs When a pair is nominated while in state failed, we first move back to state connecting, then we update the selected pair, and finally we move to state connected. --- agent/conncheck.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 0e3ce88..e584c0e 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -2114,11 +2114,11 @@ static void priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, Nice } if (pair->valid) { - priv_update_selected_pair (agent, component, pair); /* Do not step down to CONNECTED if we're already at state READY*/ 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); 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 */ -- 2.13.6 From 1a1803a45778000720c93d91060cedeb19124a27 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 12 Sep 2017 13:23:31 +0100 Subject: [PATCH 62/70] tests: Fix copyright dates in test-gstreamer.c This code is not 1000 years old. Signed-off-by: Philip Withnall --- tests/test-gstreamer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-gstreamer.c b/tests/test-gstreamer.c index 74d7133..a0be68e 100644 --- a/tests/test-gstreamer.c +++ b/tests/test-gstreamer.c @@ -1,7 +1,7 @@ /* * This file is part of the Nice GLib ICE library. * - * (C) 1015 Kurento. + * (C) 2015 Kurento. * Contact: Jose Antonio Santos Cadenas * * The contents of this file are subject to the Mozilla Public License Version -- 2.13.6 From 4c4834ab634f735145c8f758a22cbdd9cab79bac Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 12 Sep 2017 13:23:53 +0100 Subject: [PATCH 63/70] tests: Fix agent.h header inclusion in test-gstreamer.c Spotted by Lukas Gradl on the mailing list. Signed-off-by: Philip Withnall --- tests/test-gstreamer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-gstreamer.c b/tests/test-gstreamer.c index a0be68e..f060efc 100644 --- a/tests/test-gstreamer.c +++ b/tests/test-gstreamer.c @@ -34,7 +34,7 @@ */ #include -#include +#include "agent.h" #define RTP_HEADER_SIZE 12 #define RTP_PAYLOAD_SIZE 1024 -- 2.13.6 From fbdccf0c2787ebdc65fe13ac64bd25c829ea7972 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 3 Aug 2017 12:20:32 +0100 Subject: [PATCH 64/70] stun: Fix FD leak in test/utility code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://phabricator.freedesktop.org/T7798 Signed-off-by: Philip Withnall Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1819 --- stun/usages/bind.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/stun/usages/bind.c b/stun/usages/bind.c index d56790f..ee600a0 100644 --- a/stun/usages/bind.c +++ b/stun/usages/bind.c @@ -491,13 +491,15 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv, ret = stun_trans_create (&trans, SOCK_DGRAM, 0, srv, srvlen); if (ret != STUN_USAGE_TRANS_RETURN_SUCCESS) { stun_debug ("STUN transaction failed: couldn't create transport."); - return STUN_USAGE_BIND_RETURN_ERROR; + bind_ret = STUN_USAGE_BIND_RETURN_ERROR; + goto done; } val = stun_trans_send (&trans, req_buf, len); if (val < -1) { stun_debug ("STUN transaction failed: couldn't send request."); - return STUN_USAGE_BIND_RETURN_ERROR; + bind_ret = STUN_USAGE_BIND_RETURN_ERROR; + goto done; } stun_timer_start (&timer, STUN_TIMER_DEFAULT_TIMEOUT, @@ -514,14 +516,16 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv, switch (stun_timer_refresh (&timer)) { case STUN_USAGE_TIMER_RETURN_TIMEOUT: stun_debug ("STUN transaction failed: time out."); - return STUN_USAGE_BIND_RETURN_TIMEOUT; // fatal error! + bind_ret = STUN_USAGE_BIND_RETURN_TIMEOUT; // fatal error! + goto done; case STUN_USAGE_TIMER_RETURN_RETRANSMIT: stun_debug ("STUN transaction retransmitted (timeout %dms).", stun_timer_remainder (&timer)); val = stun_trans_send (&trans, req_buf, len); if (val < -1) { stun_debug ("STUN transaction failed: couldn't resend request."); - return STUN_USAGE_BIND_RETURN_ERROR; + bind_ret = STUN_USAGE_BIND_RETURN_ERROR; + goto done; } continue; case STUN_USAGE_TIMER_RETURN_SUCCESS: @@ -538,7 +542,10 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv, valid = stun_agent_validate (&agent, &msg, buf, val, NULL, NULL); if (valid == STUN_VALIDATION_UNKNOWN_ATTRIBUTE) - return STUN_USAGE_BIND_RETURN_ERROR; + { + bind_ret = STUN_USAGE_BIND_RETURN_ERROR; + goto done; + } if (valid != STUN_VALIDATION_SUCCESS) { ret = STUN_USAGE_TRANS_RETURN_RETRY; @@ -554,12 +561,16 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv, (struct sockaddr *) &alternate_server, alternate_server_len); if (ret != STUN_USAGE_TRANS_RETURN_SUCCESS) { - return STUN_USAGE_BIND_RETURN_ERROR; + bind_ret = STUN_USAGE_BIND_RETURN_ERROR; + goto done; } val = stun_trans_send (&trans, req_buf, len); if (val < -1) - return STUN_USAGE_BIND_RETURN_ERROR; + { + bind_ret = STUN_USAGE_BIND_RETURN_ERROR; + goto done; + } stun_timer_start (&timer, STUN_TIMER_DEFAULT_TIMEOUT, STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS); @@ -573,5 +584,9 @@ StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv, } while (ret == STUN_USAGE_TRANS_RETURN_RETRY); +done: + if (trans.fd != -1) + stun_trans_deinit (&trans); + return bind_ret; } -- 2.13.6 From 02216a6766caccb652387d5ee19686149eedbc93 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Tue, 21 Nov 2017 15:12:45 +0100 Subject: [PATCH 65/70] agent: prevent external role change while conncheck is running With this patch, we stash the controlling mode property change, and apply it safely, when it won't interfere with an ongoing conncheck running. According to RFC5245, sect 5.2. "Determining Role", the role is determined for a session, and persists unless an ICE is restarted. Differential Revision: https://phabricator.freedesktop.org/D1887 --- agent/agent-priv.h | 4 +++- agent/agent.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++-- tests/test-restart.c | 15 ++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/agent/agent-priv.h b/agent/agent-priv.h index 714ecff..7269be0 100644 --- a/agent/agent-priv.h +++ b/agent/agent-priv.h @@ -146,7 +146,7 @@ struct _NiceAgent NiceProxyType proxy_type; /* property: Proxy type */ gchar *proxy_username; /* property: Proxy username */ gchar *proxy_password; /* property: Proxy password */ - gboolean controlling_mode; /* property: controlling-mode */ + gboolean saved_controlling_mode;/* property: controlling-mode */ guint timer_ta; /* property: timer Ta */ guint max_conn_checks; /* property: max connectivity checks */ gboolean force_relay; /* property: force relay */ @@ -190,6 +190,8 @@ struct _NiceAgent gboolean use_ice_tcp; guint conncheck_timer_grace_period; /* ongoing delay before timer stop */ + gboolean controlling_mode; /* controlling mode used by the + conncheck */ /* XXX: add pointer to internal data struct for ABI-safe extensions */ }; diff --git a/agent/agent.c b/agent/agent.c index a4dcc0c..0773c53 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -405,6 +405,13 @@ nice_agent_class_init (NiceAgentClass *klass) 1, /* not a construct property, ignored */ G_PARAM_READWRITE)); + /** + * NiceAgent:controlling-mode: + * + * Whether the agent has the controlling role. This property should + * be modified before gathering candidates, any modification occuring + * later will be hold until ICE is restarted. + */ g_object_class_install_property (gobject_class, PROP_CONTROLLING_MODE, g_param_spec_boolean ( "controlling-mode", @@ -1107,6 +1114,47 @@ static void priv_generate_tie_breaker (NiceAgent *agent) } static void +priv_update_controlling_mode (NiceAgent *agent, gboolean value) +{ + gboolean update_controlling_mode; + GSList *i, *j; + + agent->saved_controlling_mode = value; + /* It is safe to update the agent controlling mode when all + * components are still in state disconnected. When we leave + * this state, the role must stay under the control of the + * conncheck algorithm exclusively, until the conncheck is + * eventually restarted. See RFC5245, sect 5.2. Determining Role + */ + if (agent->controlling_mode != agent->saved_controlling_mode) { + update_controlling_mode = TRUE; + for (i = agent->streams; + i && update_controlling_mode; i = i->next) { + NiceStream *stream = i->data; + for (j = stream->components; + j && update_controlling_mode; j = j->next) { + NiceComponent *component = j->data; + if (component->state > NICE_COMPONENT_STATE_DISCONNECTED) + update_controlling_mode = FALSE; + } + } + if (update_controlling_mode) { + agent->controlling_mode = agent->saved_controlling_mode; + nice_debug ("Agent %p : Property set, changing role to \"%s\".", + agent, agent->controlling_mode ? "controlling" : "controlled"); + } else { + nice_debug ("Agent %p : Property set, role switch requested " + "but conncheck already started.", agent); + nice_debug ("Agent %p : Property set, staying with role \"%s\" " + "until restart.", agent, + agent->controlling_mode ? "controlling" : "controlled"); + } + } else + nice_debug ("Agent %p : Property set, role is already \"%s\".", agent, + agent->controlling_mode ? "controlling" : "controlled"); +} + +static void nice_agent_init (NiceAgent *agent) { agent->next_candidate_id = 1; @@ -1115,6 +1163,7 @@ nice_agent_init (NiceAgent *agent) /* set defaults; not construct params, so set here */ agent->stun_server_port = DEFAULT_STUN_PORT; agent->controlling_mode = TRUE; + agent->saved_controlling_mode = TRUE; agent->max_conn_checks = NICE_AGENT_MAX_CONNECTIVITY_CHECKS_DEFAULT; agent->nomination_mode = NICE_NOMINATION_MODE_AGGRESSIVE; @@ -1213,7 +1262,7 @@ nice_agent_get_property ( break; case PROP_CONTROLLING_MODE: - g_value_set_boolean (value, agent->controlling_mode); + g_value_set_boolean (value, agent->saved_controlling_mode); break; case PROP_FULL_MODE: @@ -1422,7 +1471,7 @@ nice_agent_set_property ( break; case PROP_CONTROLLING_MODE: - agent->controlling_mode = g_value_get_boolean (value); + priv_update_controlling_mode (agent, g_value_get_boolean (value)); break; case PROP_FULL_MODE: @@ -4930,6 +4979,11 @@ nice_agent_restart ( /* step: regenerate tie-breaker value */ priv_generate_tie_breaker (agent); + /* step: reset controlling mode from the property value */ + agent->controlling_mode = agent->saved_controlling_mode; + nice_debug ("Agent %p : ICE restart, reset role to \"%s\".", + agent, agent->controlling_mode ? "controlling" : "controlled"); + for (i = agent->streams; i; i = i->next) { NiceStream *stream = i->data; diff --git a/tests/test-restart.c b/tests/test-restart.c index c2cbe9a..afc51b6 100644 --- a/tests/test-restart.c +++ b/tests/test-restart.c @@ -301,6 +301,11 @@ static int run_restart_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress * nice_agent_set_remote_candidates (lagent, ls_id, NICE_COMPONENT_TYPE_RTCP, cands); cdes.addr = laddr_rtcp; nice_agent_set_remote_candidates (ragent, rs_id, NICE_COMPONENT_TYPE_RTCP, cands); + /* This role switch request will be effective after restart. We test + * here that the role cannot be externally modified after conncheck + * has started. */ + g_object_set (G_OBJECT (ragent), "controlling-mode", TRUE, NULL); + g_assert (ragent->controlling_mode == FALSE); g_debug ("test-restart: Set properties, next running mainloop until connectivity checks succeed..."); @@ -329,10 +334,18 @@ static int run_restart_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress * global_ragent_read = 0; g_assert (nice_agent_send (lagent, ls_id, 1, 16, "1234567812345678") == 16); + /* Both agent have a distinct role at the end of the conncheck */ + g_assert (lagent->controlling_mode == TRUE); + g_assert (ragent->controlling_mode == FALSE); /* step: restart agents, exchange updated credentials */ tie_breaker = ragent->tie_breaker; nice_agent_restart (ragent); g_assert (tie_breaker != ragent->tie_breaker); + /* This role switch of ragent should be done now, and both agents + * have now the same role, which should generate a role conflict + * resolution situation */ + g_assert (lagent->controlling_mode == TRUE); + g_assert (ragent->controlling_mode == TRUE); nice_agent_restart (lagent); { gchar *ufrag = NULL, *password = NULL; @@ -375,6 +388,8 @@ static int run_restart_test (NiceAgent *lagent, NiceAgent *ragent, NiceAddress * /* note: verify binding requests were resent after restart */ g_assert (global_lagent_ibr_received == TRUE); g_assert (global_ragent_ibr_received == TRUE); + /* note: verify that a role switch occured for one of the agents */ + g_assert (ragent->controlling_mode != lagent->controlling_mode); g_debug ("test-restart: Ran mainloop, removing streams..."); -- 2.13.6 From c63349894b3fe974494453a883dfb5ad05df5a46 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Thu, 23 Nov 2017 18:31:31 +0100 Subject: [PATCH 66/70] Makefile: really enable debug for tests Differential Revision: https://phabricator.freedesktop.org/D1888 --- tests/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Makefile.am b/tests/Makefile.am index b623764..e94822d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -24,7 +24,7 @@ AM_CPPFLAGS = -DG_LOG_DOMAIN=\"libnice-tests\" AM_TESTS_ENVIRONMENT = \ G_MESSAGES_DEBUG=all \ - NICE_DEBUG=all; + NICE_DEBUG=all COMMON_LDADD = $(top_builddir)/agent/libagent.la $(top_builddir)/socket/libsocket.la $(GLIB_LIBS) $(GUPNP_LIBS) -- 2.13.6 From 17f30e4465efe9533799b02d6f95feeaf0f2748c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Par=C3=ADs?= Date: Wed, 8 Nov 2017 16:26:47 +0000 Subject: [PATCH 67/70] conncheck: do not require that all streams have a connection check list One or more streams might not have any connection check list if the number of streams differs from the peer agent. Differential Revision: https://phabricator.freedesktop.org/D1880 --- agent/conncheck.c | 23 ---- tests/Makefile.am | 3 + tests/test-different-number-streams.c | 208 ++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 23 deletions(-) create mode 100644 tests/test-different-number-streams.c diff --git a/agent/conncheck.c b/agent/conncheck.c index e584c0e..beb43c3 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -402,23 +402,6 @@ priv_conn_check_find_next_frozen (GSList *conn_check_list) } /* - * Returns the number of check lists of the agent - */ -static guint -priv_number_of_check_lists (NiceAgent *agent) -{ - guint n = 0; - GSList *i; - - for (i = agent->streams; i ; i = i->next) { - NiceStream *stream = i->data; - if (stream->conncheck_list != NULL) - n++; - } - return n; -} - -/* * Returns the number of active check lists of the agent */ static guint @@ -1060,12 +1043,6 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) gboolean keep_timer_going = FALSE; GSList *i, *j; - /* the conncheck really starts when we have built - * a connection check list for each stream - */ - if (priv_number_of_check_lists (agent) < g_slist_length (agent->streams)) - return TRUE; - /* configure the initial state of the check lists of the agent * as described in ICE spec, 5.7.4 * diff --git a/tests/Makefile.am b/tests/Makefile.am index e94822d..30d6f8e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -46,6 +46,7 @@ check_PROGRAMS = \ test-socket-is-based-on \ test-priority \ test-fullmode \ + test-different-number-streams \ test-restart \ test-fallback \ test-thread \ @@ -114,6 +115,8 @@ test_mainloop_LDADD = $(COMMON_LDADD) test_fullmode_LDADD = $(COMMON_LDADD) +test_different_number_streams_LDADD = $(COMMON_LDADD) + test_restart_LDADD = $(COMMON_LDADD) test_fallback_LDADD = $(COMMON_LDADD) diff --git a/tests/test-different-number-streams.c b/tests/test-different-number-streams.c new file mode 100644 index 0000000..7fd4763 --- /dev/null +++ b/tests/test-different-number-streams.c @@ -0,0 +1,208 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "agent.h" + +#include +#include + +#define ADD_2_STREAMS TRUE +#define USE_SECOND_STREAM TRUE + +static GMainLoop *global_mainloop = NULL; + +static guint global_components_ready = 0; +static guint global_components_ready_exit = 0; + +static gboolean timer_cb (gpointer pointer) +{ + g_debug ("test-different-number-streams:%s: %p", G_STRFUNC, pointer); + + /* signal status via a global variable */ + + /* note: should not be reached, abort */ + g_error ("ERROR: test has got stuck, aborting..."); + + return FALSE; +} + +static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer data) +{ + g_debug ("%p: gathering done (stream_id: %u)", agent, stream_id); +} + +static void cb_component_state_changed (NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer data) +{ + g_debug ("%p: component state changed (stream_id: %u, component_id: %u, state: %s)", + agent, stream_id, component_id, nice_component_state_to_string (state)); + + if (state == NICE_COMPONENT_STATE_READY) { + global_components_ready++; + } + + /* signal status via a global variable */ + if (global_components_ready == global_components_ready_exit) { + g_debug ("Components ready/failed achieved. Stopping mailoop"); + g_main_loop_quit (global_mainloop); + } +} + +static void set_candidates (NiceAgent *from, guint from_stream, + NiceAgent *to, guint to_stream, guint component) +{ + GSList *cands = NULL, *i; + + cands = nice_agent_get_local_candidates (from, from_stream, component); + nice_agent_set_remote_candidates (to, to_stream, component, cands); + + for (i = cands; i; i = i->next) + nice_candidate_free ((NiceCandidate *) i->data); + g_slist_free (cands); +} + +static void cb_nice_recv (NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer user_data) +{ + g_debug ("%p: recv (stream_id: %u, component_id: %u)", agent, stream_id, component_id); +} + +int main (void) +{ + NiceAgent *lagent, *ragent; + guint timer_id; + guint ls_id, rs_id_1, rs_id_2; + gchar *lufrag = NULL, *lpassword = NULL; + gchar *rufrag1 = NULL, *rpassword1 = NULL, *rufrag2 = NULL, *rpassword2 = NULL; + +#ifdef G_OS_WIN32 + WSADATA w; + + WSAStartup(0x0202, &w); +#endif + + global_mainloop = g_main_loop_new (NULL, FALSE); + + /* step: create the agents L and R */ + lagent = nice_agent_new (g_main_loop_get_context (global_mainloop), + NICE_COMPATIBILITY_GOOGLE); + g_debug ("lagent: %p", lagent); + nice_agent_set_software (lagent, "test-different-number-streams, Left Agent"); + g_object_set (G_OBJECT (lagent), "ice-tcp", FALSE, NULL); + g_object_set (G_OBJECT (lagent), "controlling-mode", TRUE, NULL); + g_object_set (G_OBJECT (lagent), "upnp", FALSE, NULL); + g_signal_connect (G_OBJECT (lagent), "candidate-gathering-done", + G_CALLBACK (cb_candidate_gathering_done), NULL); + g_signal_connect (G_OBJECT (lagent), "component-state-changed", + G_CALLBACK (cb_component_state_changed), NULL); + + ragent = nice_agent_new (g_main_loop_get_context (global_mainloop), + NICE_COMPATIBILITY_GOOGLE); + g_debug ("ragent: %p", ragent); + nice_agent_set_software (ragent, "test-different-number-streams, Right Agent"); + g_object_set (G_OBJECT (ragent), "ice-tcp", FALSE, NULL); + g_object_set (G_OBJECT (ragent), "controlling-mode", FALSE, NULL); + g_object_set (G_OBJECT (ragent), "upnp", FALSE, NULL); + g_signal_connect (G_OBJECT (ragent), "candidate-gathering-done", + G_CALLBACK (cb_candidate_gathering_done), NULL); + g_signal_connect (G_OBJECT (ragent), "component-state-changed", + G_CALLBACK (cb_component_state_changed), NULL); + + /* step: add a timer to catch state changes triggered by signals */ + timer_id = g_timeout_add (30000, timer_cb, NULL); + + ls_id = nice_agent_add_stream (lagent, 2); + g_assert (ls_id > 0); + nice_agent_get_local_credentials(lagent, ls_id, &lufrag, &lpassword); + + /* step: attach to mainloop (needed to register the fds) */ + nice_agent_attach_recv (lagent, ls_id, NICE_COMPONENT_TYPE_RTP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL); + nice_agent_attach_recv (lagent, ls_id, NICE_COMPONENT_TYPE_RTCP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL); + + global_components_ready_exit = 4; + + if (ADD_2_STREAMS) { + rs_id_1 = nice_agent_add_stream (ragent, 2); + g_assert (rs_id_1 > 0); + nice_agent_get_local_credentials(ragent, rs_id_1, &rufrag1, &rpassword1); + + rs_id_2 = nice_agent_add_stream (ragent, 2); + g_assert (rs_id_2 > 0); + nice_agent_get_local_credentials(ragent, rs_id_2, &rufrag2, &rpassword2); + + nice_agent_set_remote_credentials (ragent, rs_id_2, lufrag, lpassword); + nice_agent_set_remote_credentials (lagent, ls_id, rufrag2, rpassword2); + + g_assert (nice_agent_gather_candidates (lagent, ls_id) == TRUE); + g_assert (nice_agent_gather_candidates (ragent, rs_id_2) == TRUE); + g_assert (nice_agent_gather_candidates (ragent, rs_id_1) == TRUE); + + if (USE_SECOND_STREAM) { + set_candidates (ragent, rs_id_2, lagent, ls_id, NICE_COMPONENT_TYPE_RTP); + set_candidates (ragent, rs_id_2, lagent, ls_id, NICE_COMPONENT_TYPE_RTCP); + set_candidates (lagent, ls_id, ragent, rs_id_2, NICE_COMPONENT_TYPE_RTP); + set_candidates (lagent, ls_id, ragent, rs_id_2, NICE_COMPONENT_TYPE_RTCP); + } else { + set_candidates (ragent, rs_id_1, lagent, ls_id, NICE_COMPONENT_TYPE_RTP); + set_candidates (ragent, rs_id_1, lagent, ls_id, NICE_COMPONENT_TYPE_RTCP); + set_candidates (lagent, ls_id, ragent, rs_id_1, NICE_COMPONENT_TYPE_RTP); + set_candidates (lagent, ls_id, ragent, rs_id_1, NICE_COMPONENT_TYPE_RTCP); + } + + /* step: attach to mainloop (needed to register the fds) */ + nice_agent_attach_recv (ragent, rs_id_1, NICE_COMPONENT_TYPE_RTP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL); + nice_agent_attach_recv (ragent, rs_id_1, NICE_COMPONENT_TYPE_RTCP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL); + nice_agent_attach_recv (ragent, rs_id_2, NICE_COMPONENT_TYPE_RTP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL); + nice_agent_attach_recv (ragent, rs_id_2, NICE_COMPONENT_TYPE_RTCP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL); + } else { + rs_id_1 = nice_agent_add_stream (ragent, 2); + g_assert (rs_id_1 > 0); + nice_agent_get_local_credentials(ragent, rs_id_1, &rufrag1, &rpassword1); + + nice_agent_set_remote_credentials (ragent, rs_id_1, lufrag, lpassword); + nice_agent_set_remote_credentials (lagent, ls_id, rufrag1, rpassword1); + + g_assert (nice_agent_gather_candidates (lagent, ls_id) == TRUE); + g_assert (nice_agent_gather_candidates (ragent, rs_id_1) == TRUE); + + /* step: attach to mainloop (needed to register the fds) */ + nice_agent_attach_recv (ragent, rs_id_1, NICE_COMPONENT_TYPE_RTP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL); + nice_agent_attach_recv (ragent, rs_id_1, NICE_COMPONENT_TYPE_RTCP, + g_main_loop_get_context (global_mainloop), cb_nice_recv, NULL); + + set_candidates (ragent, rs_id_1, lagent, ls_id, NICE_COMPONENT_TYPE_RTP); + set_candidates (ragent, rs_id_1, lagent, ls_id, NICE_COMPONENT_TYPE_RTCP); + set_candidates (lagent, ls_id, ragent, rs_id_1, NICE_COMPONENT_TYPE_RTP); + set_candidates (lagent, ls_id, ragent, rs_id_1, NICE_COMPONENT_TYPE_RTCP); + } + + /* step: run the mainloop until connectivity checks succeed + * (see timer_cb() above) */ + g_main_loop_run (global_mainloop); + + g_free (lufrag); + g_free (lpassword); + g_free (rufrag1); + g_free (rpassword1); + g_free (rufrag2); + g_free (rpassword2); + g_object_unref (lagent); + g_object_unref (ragent); + + g_main_loop_unref (global_mainloop); + global_mainloop = NULL; + + g_source_remove (timer_id); + +#ifdef G_OS_WIN32 + WSACleanup(); +#endif + + return 0; +} -- 2.13.6 From 59fcf95d505c3995f858b826d10cd48321ed383e Mon Sep 17 00:00:00 2001 From: Youness Alaoui Date: Mon, 27 Nov 2017 17:07:02 -0500 Subject: [PATCH 68/70] turn: Add support for ALTERNATE_SERVER in OC2007 Compatibility The MS Office TURN servers will always return the MS_ALTERNATE_SERVER in allocation responses, and if they are not handled, we end up using the main turn server to send allocation requests that then get sent to the alternate server which will return the XOR_MAPPED_ADDRESS containing the IP address of the turn server that proxied the message instead of our own actual external IP. --- agent/conncheck.c | 14 ++++++++++++++ stun/usages/turn.c | 11 +++++++++++ stun/usages/turn.h | 4 ++++ 3 files changed, 29 insertions(+) diff --git a/agent/conncheck.c b/agent/conncheck.c index beb43c3..229c8b1 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -3764,6 +3764,20 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage * recv_realm = (uint8_t *) stun_message_find (resp, STUN_ATTRIBUTE_REALM, &recv_realm_len); + if ((agent->compatibility == NICE_COMPATIBILITY_OC2007 || + agent->compatibility == NICE_COMPATIBILITY_OC2007R2) && + alternatelen != sizeof(alternate)) { + NiceAddress alternate_addr; + + nice_address_set_from_sockaddr (&alternate_addr, &alternate.addr); + + if (!nice_address_equal (&alternate_addr, &d->server)) { + nice_address_set_from_sockaddr (&d->server, &alternate.addr); + nice_address_set_from_sockaddr (&d->turn->server, &alternate.addr); + + d->pending = FALSE; + } + } /* check for unauthorized error response */ if ((agent->compatibility == NICE_COMPATIBILITY_RFC5245 || agent->compatibility == NICE_COMPATIBILITY_OC2007 || diff --git a/stun/usages/turn.c b/stun/usages/turn.c index 3b94959..ec12642 100644 --- a/stun/usages/turn.c +++ b/stun/usages/turn.c @@ -300,6 +300,17 @@ StunUsageTurnReturn stun_usage_turn_process (StunMessage *msg, stun_debug (" STUN error message received (code: %d)", code); /* ALTERNATE-SERVER mechanism */ + if (compatibility == STUN_USAGE_TURN_COMPATIBILITY_OC2007 && + alternate_server && alternate_server_len && + stun_message_find_addr (msg, STUN_ATTRIBUTE_MS_ALTERNATE_SERVER, + alternate_server, + alternate_server_len) == STUN_MESSAGE_RETURN_SUCCESS) { + stun_debug ("Found alternate server"); + /* The ALTERNATE_SERVER will always be returned by the MS turn server. + * We need to check if the ALTERNATE_SERVER is the same as the current + * server to decide whether we need to switch servers or not. + */ + } if ((code / 100) == 3) { if (alternate_server && alternate_server_len) { if (stun_message_find_addr (msg, STUN_ATTRIBUTE_ALTERNATE_SERVER, diff --git a/stun/usages/turn.h b/stun/usages/turn.h index 7a2d4e6..83fa00a 100644 --- a/stun/usages/turn.h +++ b/stun/usages/turn.h @@ -256,6 +256,10 @@ size_t stun_usage_turn_create_permission (StunAgent *agent, StunMessage *msg, * Allocate request, in case the currently used TURN server is requesting the use * of an alternate server. This argument will only be filled if the return value * of the function is #STUN_USAGE_TURN_RETURN_ALTERNATE_SERVER + * In the case of @STUN_USAGE_TURN_COMPATIBILITY_OC2007 compatibility, the + * @alternate_server could be filled at any time, and should only be considered + * if the request was sent to a different server than the address returned + * in the @alternate_server field * @alternate_server_len: The length of @alternate_server * @bandwidth: A pointer to fill with the bandwidth the TURN server allocated us * @lifetime: A pointer to fill with the lifetime of the allocation -- 2.13.6 From 4172d48852ecd1c86cc7bd4665b23697603d1eed Mon Sep 17 00:00:00 2001 From: Youness Alaoui Date: Tue, 28 Nov 2017 15:14:11 -0500 Subject: [PATCH 69/70] discovery: Increase discovery_unsched_items whenever we restart a check The discovery_unsched_items is decremented every time a DiscoveryCandidate goes from non-pending to pending. So if we restart a check by setting pending to FALSE, we should re-increase the discovery_unsched_items. --- agent/conncheck.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/agent/conncheck.c b/agent/conncheck.c index 229c8b1..5b08311 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -3507,6 +3507,7 @@ static gboolean priv_map_reply_to_discovery_request (NiceAgent *agent, StunMessa d->server = niceaddr; d->pending = FALSE; + agent->discovery_unsched_items++; } else if (res == STUN_USAGE_BIND_RETURN_SUCCESS) { /* case: successful binding discovery, create a new local candidate */ @@ -3648,6 +3649,7 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage * nice_address_set_from_sockaddr (&d->turn->server, &alternate.addr); d->pending = FALSE; + agent->discovery_unsched_items++; } else if (res == STUN_USAGE_TURN_RETURN_RELAY_SUCCESS || res == STUN_USAGE_TURN_RETURN_MAPPED_SUCCESS) { /* case: successful allocate, create a new local candidate */ @@ -3776,6 +3778,7 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage * nice_address_set_from_sockaddr (&d->turn->server, &alternate.addr); d->pending = FALSE; + agent->discovery_unsched_items++; } } /* check for unauthorized error response */ @@ -3798,6 +3801,7 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage * d->stun_resp_msg.buffer = d->stun_resp_buffer; d->stun_resp_msg.buffer_len = sizeof(d->stun_resp_buffer); d->pending = FALSE; + agent->discovery_unsched_items++; } else { /* case: a real unauthorized error */ d->stun_message.buffer = NULL; -- 2.13.6 From fb2f1f77a31baa91968fc81c205f980b6913f403 Mon Sep 17 00:00:00 2001 From: Youness Alaoui Date: Tue, 28 Nov 2017 16:05:18 -0500 Subject: [PATCH 70/70] conncheck: handle alternate-server for turn relays differently If a relay gives us an alternate-server, we need to cancel and reset every candidate discovery attempt that uses the same server, to avoid ending up with one component on one server and the other component on another server (causing relay candidates with mismatched foundations). --- agent/conncheck.c | 56 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 5b08311..c8a4edf 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -3592,6 +3592,41 @@ priv_add_new_turn_refresh (CandidateDiscovery *cdisco, NiceCandidate *relay_cand return cand; } +static void priv_handle_turn_alternate_server (NiceAgent *agent, + CandidateDiscovery *disco, NiceAddress server, NiceAddress alternate) +{ + /* We need to cancel and reset all candidate discovery turn for the same + stream and type if there is an alternate server. Otherwise, we might end up + with two relay components on different servers, creating candidates with + unique foundations that only contain one component. + */ + GSList *i; + + for (i = agent->discovery_list; i; i = i->next) { + CandidateDiscovery *d = i->data; + + if (!d->done && + d->type == disco->type && + d->stream == disco->stream && + d->turn->type == disco->turn->type && + nice_address_equal (&d->server, &server)) { + gchar ip[INET6_ADDRSTRLEN]; + // Cancel the pending request to avoid a race condition with another + // component responding with another altenrate-server + d->stun_message.buffer = NULL; + d->stun_message.buffer_len = 0; + + nice_address_to_string (&server, ip); + nice_debug ("Agent %p : Cancelling and setting alternate server %s for " + "CandidateDiscovery %p", agent, ip, d); + d->server = alternate; + d->turn->server = alternate; + d->pending = FALSE; + agent->discovery_unsched_items++; + } + } +} + /* * Tries to match STUN reply in 'buf' to an existing STUN discovery * transaction. If found, a reply is sent. @@ -3644,12 +3679,11 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage * agent, d, (int)res); if (res == STUN_USAGE_TURN_RETURN_ALTERNATE_SERVER) { - /* handle alternate server */ - nice_address_set_from_sockaddr (&d->server, &alternate.addr); - nice_address_set_from_sockaddr (&d->turn->server, &alternate.addr); + NiceAddress addr; - d->pending = FALSE; - agent->discovery_unsched_items++; + /* handle alternate server */ + nice_address_set_from_sockaddr (&addr, &alternate.addr); + priv_handle_turn_alternate_server (agent, d, d->server, addr); } else if (res == STUN_USAGE_TURN_RETURN_RELAY_SUCCESS || res == STUN_USAGE_TURN_RETURN_MAPPED_SUCCESS) { /* case: successful allocate, create a new local candidate */ @@ -3769,16 +3803,12 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage * if ((agent->compatibility == NICE_COMPATIBILITY_OC2007 || agent->compatibility == NICE_COMPATIBILITY_OC2007R2) && alternatelen != sizeof(alternate)) { - NiceAddress alternate_addr; - - nice_address_set_from_sockaddr (&alternate_addr, &alternate.addr); + NiceAddress addr; - if (!nice_address_equal (&alternate_addr, &d->server)) { - nice_address_set_from_sockaddr (&d->server, &alternate.addr); - nice_address_set_from_sockaddr (&d->turn->server, &alternate.addr); + nice_address_set_from_sockaddr (&addr, &alternate.addr); - d->pending = FALSE; - agent->discovery_unsched_items++; + if (!nice_address_equal (&addr, &d->server)) { + priv_handle_turn_alternate_server (agent, d, d->server, addr); } } /* check for unauthorized error response */ -- 2.13.6 From db6166ee247a8f9fa4ebe2a08d223de73cbd2e96 Mon Sep 17 00:00:00 2001 From: Jozsef Vass Date: Mon, 8 Jan 2018 10:53:23 -0800 Subject: [PATCH 01/15] agent: Fixes incompatible pointer type warning on OSX. The variable tie is actually never read. --- agent/conncheck.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index c8a4edf..38a90cd 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -3421,7 +3421,7 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre states" and 8.1.2 "Updating States", ID-19) */ priv_update_check_list_state_for_ready (agent, stream, component); } else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) { - guint64 tie; + uint64_t tie; gboolean controlled_mode; /* case: role conflict error, need to restart with new role */ -- 2.14.3 From ae3e5acc775ee6c1701ff9a2404b14e4d5dd6c20 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Sun, 26 Nov 2017 17:13:19 +0100 Subject: [PATCH 02/15] conncheck: rework early stun requests handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this patch we simplify the code used to handle the incoming stun request when remote candidates or remote credentials have not been received yet. When the remote credentials is unknown, the stun request is stored in a list of incoming_checks for later processing, and no further processing is done, except responding to the request. When the remote credentials are received, the triggered checks for these incoming checks can now be queued, and the related pairs are created. If the remote candidates have not been received when the stun request on a valid local port arrives, a peer-reflexive remote candidate will be created. This candidate may need to be updated later when remote candidates are finally received, including candidate priority and foundation, and also related pairs. Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1889 --- agent/agent.c | 42 ++++++++++++-- agent/conncheck.c | 168 ++++++++++-------------------------------------------- agent/conncheck.h | 1 + 3 files changed, 66 insertions(+), 145 deletions(-) diff --git a/agent/agent.c b/agent/agent.c index 0773c53..49fc371 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -3300,6 +3300,28 @@ nice_agent_add_local_address (NiceAgent *agent, NiceAddress *addr) return TRUE; } +/* Recompute foundations of all candidate pairs from a given stream + * having a specific remote candidate + */ +static void priv_update_pair_foundations (NiceAgent *agent, + guint stream_id, NiceCandidate *remote) +{ + NiceStream *stream = agent_find_stream (agent, stream_id); + if (stream) { + GSList *i; + for (i = stream->conncheck_list; i; i = i->next) { + CandidateCheckPair *pair = i->data; + if (pair->remote == remote) { + g_snprintf (pair->foundation, + NICE_CANDIDATE_PAIR_MAX_FOUNDATION, "%s:%s", + pair->local->foundation, pair->remote->foundation); + nice_debug ("Agent %p : Updating pair %p foundation to '%s'", + agent, pair, pair->foundation); + } + } + } +} + static gboolean priv_add_remote_candidate ( NiceAgent *agent, guint stream_id, @@ -3331,8 +3353,7 @@ static gboolean priv_add_remote_candidate ( /* If it was a discovered remote peer reflexive candidate, then it should * be updated according to RFC 5245 section 7.2.1.3 */ - if (candidate && candidate->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE && - candidate->priority == priority) { + if (candidate && candidate->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) { nice_debug ("Agent %p : Updating existing peer-rfx remote candidate to %s", agent, _cand_type_to_sdp (type)); candidate->type = type; @@ -3375,6 +3396,13 @@ static gboolean priv_add_remote_candidate ( g_free (candidate->password); candidate->password = g_strdup (password); } + + /* since the type of the existing candidate may have changed, + * the pairs priority and foundation related to this candidate need + * to be recomputed. + */ + recalculate_pair_priorities (agent); + priv_update_pair_foundations (agent, stream_id, candidate); } else { /* case 2: add a new candidate */ @@ -3429,12 +3457,14 @@ static gboolean priv_add_remote_candidate ( if (foundation) g_strlcpy (candidate->foundation, foundation, NICE_CANDIDATE_MAX_FOUNDATION); - } - if (conn_check_add_for_candidate (agent, stream_id, component, candidate) < 0) { - goto errors; + /* We only create a pair when a candidate is new, and not when + * updating an existing one. + */ + if (conn_check_add_for_candidate (agent, stream_id, + component, candidate) < 0) + goto errors; } - return TRUE; errors: diff --git a/agent/conncheck.c b/agent/conncheck.c index 38a90cd..11ef9c9 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -1690,34 +1690,6 @@ gint conn_check_compare (const CandidateCheckPair *a, const CandidateCheckPair * return 0; } -/* - * Preprocesses a new connectivity check by going through list - * of a any stored early incoming connectivity checks from - * the remote peer. If a matching incoming check has been already - * received, update the state of the new outgoing check 'pair'. - * - * @param agent context pointer - * @param stream which stream (of the agent) - * @param component pointer to component object to which 'pair'has been added - * @param pair newly added connectivity check - */ -static void priv_preprocess_conn_check_pending_data (NiceAgent *agent, NiceStream *stream, NiceComponent *component, CandidateCheckPair *pair) -{ - GSList *i; - for (i = component->incoming_checks; i; i = i->next) { - IncomingCheck *icheck = i->data; - if (nice_address_equal (&icheck->from, &pair->remote->addr) && - icheck->local_socket == pair->sockptr) { - nice_debug ("Agent %p : Updating check %p with stored early-icheck %p, %p/%u/%u (agent/stream/component).", agent, pair, icheck, agent, stream->id, component->id); - priv_schedule_triggered_check (agent, stream, component, - icheck->local_socket, pair->remote); - if (icheck->use_candidate) - priv_mark_pair_nominated (agent, stream, component, pair->local, pair->remote); - } - } -} - - /* * Handle any processing steps for connectivity checks after * remote credentials have been set. This function handles @@ -1728,126 +1700,39 @@ static void priv_preprocess_conn_check_pending_data (NiceAgent *agent, NiceStrea */ void conn_check_remote_credentials_set(NiceAgent *agent, NiceStream *stream) { - GSList *j, *k, *l, *m, *n, *o; + GSList *j, *k, *l, *m; - for (j = stream->conncheck_list; j ; j = j->next) { - CandidateCheckPair *pair = j->data; - NiceComponent *component = - nice_stream_find_component_by_id (stream, pair->component_id); - gboolean match = FALSE; - - /* performn delayed processing of spec steps section 7.2.1.4, - and section 7.2.1.5 */ - priv_preprocess_conn_check_pending_data (agent, stream, component, pair); + for (j = stream->components; j ; j = j->next) { + NiceComponent *component = j->data; for (k = component->incoming_checks; k; k = k->next) { IncomingCheck *icheck = k->data; /* sect 7.2.1.3., "Learning Peer Reflexive Candidates", has to * be handled separately */ for (l = component->remote_candidates; l; l = l->next) { - NiceCandidate *cand = l->data; - if (nice_address_equal (&icheck->from, &cand->addr)) { - match = TRUE; - break; - } - } - if (match != TRUE) { - /* note: we have gotten an incoming connectivity check from - * an address that is not a known remote candidate */ - - NiceCandidate *local_candidate = NULL; - NiceCandidate *remote_candidate = NULL; - - if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE || - agent->compatibility == NICE_COMPATIBILITY_MSN || - agent->compatibility == NICE_COMPATIBILITY_OC2007) { - /* We need to find which local candidate was used */ - uint8_t uname[NICE_STREAM_MAX_UNAME]; - guint uname_len; - - nice_debug ("Agent %p: We have a peer-reflexive candidate in a " - "stored pending check", agent); - - for (m = component->remote_candidates; - m != NULL && remote_candidate == NULL; m = m->next) { - for (n = component->local_candidates; n; n = n->next) { - NiceCandidate *rcand = m->data; - NiceCandidate *lcand = n->data; - - uname_len = priv_create_username (agent, stream, - component->id, rcand, lcand, - uname, sizeof (uname), TRUE); - - stun_debug ("pending check, comparing usernames of len %d and %d, equal=%d", - icheck->username_len, uname_len, - icheck->username && uname_len == icheck->username_len && - memcmp (uname, icheck->username, icheck->username_len) == 0); - stun_debug_bytes (" first username: ", - icheck->username, - icheck->username? icheck->username_len : 0); - stun_debug_bytes (" second username: ", uname, uname_len); - - if (icheck->username && - uname_len == icheck->username_len && - memcmp (uname, icheck->username, icheck->username_len) == 0) { - local_candidate = lcand; - remote_candidate = rcand; - break; - } - } - } - } else { - for (l = component->local_candidates; l; l = l->next) { - NiceCandidate *cand = l->data; + 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; if (nice_address_equal (&cand->addr, &icheck->local_socket->addr)) { - local_candidate = cand; + lcand = cand; break; } } - } - - if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE && - local_candidate == NULL) { - /* if we couldn't match the username, then the matching remote - * candidate hasn't been received yet.. we must wait */ - nice_debug ("Agent %p : Username check failed. pending check has " - "to wait to be processed", agent); - } else { - NiceCandidate *candidate; - - nice_debug ("Agent %p : Discovered peer reflexive from early i-check", - agent); - candidate = - discovery_learn_remote_peer_reflexive_candidate (agent, - stream, - component, - icheck->priority, - &icheck->from, - icheck->local_socket, - local_candidate, remote_candidate); - if (candidate) { - if (local_candidate && - local_candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) - priv_conn_check_add_for_candidate_pair_matched (agent, - stream->id, component, local_candidate, candidate, NICE_CHECK_DISCOVERED); - else - conn_check_add_for_candidate (agent, stream->id, component, candidate); - - priv_schedule_triggered_check (agent, stream, component, - icheck->local_socket, candidate); - if (icheck->use_candidate) - priv_mark_pair_nominated (agent, stream, component, local_candidate, candidate); - } + 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); + break; } } } - } - - /* Once we process the pending checks, we should free them to avoid - * reprocessing them again if a dribble-mode set_remote_candidates - * is called */ - for (o = stream->components; o; o = o->next) { - NiceComponent *component = o->data; + /* Once we process the pending checks, we should free them to avoid + * reprocessing them again if a dribble-mode set_remote_candidates + * is called */ g_slist_free_full (component->incoming_checks, (GDestroyNotify) incoming_check_free); component->incoming_checks = NULL; @@ -2964,8 +2849,8 @@ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *str if (i) { nice_debug ("Agent %p : Adding a triggered check to conn.check list (local=%p).", agent, local); - p = priv_add_new_check_pair (agent, stream->id, component, - local, remote_cand, NICE_CHECK_WAITING); + 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); return TRUE; } @@ -3018,7 +2903,12 @@ static void priv_reply_to_conn_check (NiceAgent *agent, NiceStream *stream, ms_ice2_legacy_conncheck_send(msg, sockptr, toaddr); } - if (rcand) { + /* We react to this stun request when we have the remote credentials. + * When credentials are not yet known, this request is stored + * in incoming_checks for later processing when returning from this + * function. + */ + if (rcand && stream->remote_ufrag[0]) { priv_schedule_triggered_check (agent, stream, component, sockptr, rcand); if (use_candidate) priv_mark_pair_nominated (agent, stream, component, lcand, rcand); @@ -3114,7 +3004,7 @@ static CandidateCheckPair *priv_add_peer_reflexive_pair (NiceAgent *agent, guint * Recalculates priorities of all candidate pairs. This * is required after a conflict in ICE roles. */ -static void priv_recalculate_pair_priorities (NiceAgent *agent) +void recalculate_pair_priorities (NiceAgent *agent) { GSList *i, *j; @@ -3143,7 +3033,7 @@ static void priv_check_for_role_conflict (NiceAgent *agent, gboolean control) agent->controlling_mode = control; /* the pair priorities depend on the roles, so recalculation * is needed */ - priv_recalculate_pair_priorities (agent); + recalculate_pair_priorities (agent); } else nice_debug ("Agent %p : Role conflict, staying with role \"%s\".", diff --git a/agent/conncheck.h b/agent/conncheck.h index e16dc67..8cfe2d6 100644 --- a/agent/conncheck.h +++ b/agent/conncheck.h @@ -119,5 +119,6 @@ conn_check_prune_socket (NiceAgent *agent, NiceStream *stream, NiceComponent *co NiceSocket *sock); guint32 ensure_unique_priority (NiceComponent *component, guint32 priority); +void recalculate_pair_priorities (NiceAgent *agent); #endif /*_NICE_CONNCHECK_H */ -- 2.14.3 From 025d84b53bd4ffc0626dd25aa8351319f4d77944 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Sun, 26 Nov 2017 17:36:27 +0100 Subject: [PATCH 03/15] test-new-dribble: make credentials swap asymmetric MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the first case of test-new-dribble (standard-test) is updated, by making the credentials swap between the left and right agent asymmetric. Previously, ragent started to receive stun requests without initially knowing lagent candidates. Now, ragent also ignores lagent credentials. This modification allows to test changes introduced by the previous commit. Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1890 --- tests/test-new-dribble.c | 55 ++++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/tests/test-new-dribble.c b/tests/test-new-dribble.c index 3e60ae3..947f55d 100644 --- a/tests/test-new-dribble.c +++ b/tests/test-new-dribble.c @@ -269,7 +269,7 @@ static gpointer stun_thread_func (const gpointer user_data) return NULL; } -static void set_credentials (NiceAgent *lagent, guint lstream, +static void swap_credentials (NiceAgent *lagent, guint lstream, NiceAgent *ragent, guint rstream) { gchar *ufrag = NULL, *password = NULL; @@ -279,12 +279,6 @@ static void set_credentials (NiceAgent *lagent, guint lstream, g_free (ufrag); g_free (password); - - nice_agent_get_local_credentials (ragent, rstream, &ufrag, &password); - nice_agent_set_remote_credentials (lagent, lstream, ufrag, password); - - g_free (ufrag); - g_free (password); } static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer data) @@ -500,12 +494,10 @@ static void standard_test(NiceAgent *lagent, NiceAgent *ragent) g_cancellable_reset (global_cancellable); g_assert (ragent_candidate_gathering_done); - set_credentials (lagent, global_ls_id, ragent, global_rs_id); g_debug ("Setting local candidates of ragent as remote candidates of lagent"); - swap_candidates (ragent, global_rs_id, - lagent, global_ls_id, - TRUE); + swap_candidates (ragent, global_rs_id, lagent, global_ls_id, TRUE); + swap_credentials (ragent, global_rs_id, lagent, global_ls_id); while (!data_received) g_main_context_iteration (NULL, TRUE); @@ -514,9 +506,9 @@ static void standard_test(NiceAgent *lagent, NiceAgent *ragent) data_received); g_debug ("Setting local candidates of lagent as remote candidates of ragent"); - swap_candidates (lagent, global_ls_id, - ragent, global_rs_id, - FALSE); + swap_candidates (lagent, global_ls_id, ragent, global_rs_id, FALSE); + swap_credentials (lagent, global_ls_id, ragent, global_rs_id); + while (!lagent_candidate_gathering_done) g_main_context_iteration (NULL, TRUE); g_cancellable_reset (global_cancellable); @@ -557,22 +549,21 @@ static void bad_credentials_test(NiceAgent *lagent, NiceAgent *ragent) g_cancellable_reset (global_cancellable); g_assert (ragent_candidate_gathering_done); - swap_candidates (ragent, global_rs_id, - lagent, global_ls_id, - FALSE); + g_debug ("Setting local candidates of ragent as remote candidates of lagent"); + swap_candidates (ragent, global_rs_id, lagent, global_ls_id, FALSE); + while (global_lagent_state != NICE_COMPONENT_STATE_FAILED) g_main_context_iteration (NULL, TRUE); g_cancellable_reset (global_cancellable); // Set the correct credentials and swap candidates - set_credentials (lagent, global_ls_id, ragent, global_rs_id); - swap_candidates (ragent, global_rs_id, - lagent, global_ls_id, - FALSE); + g_debug ("Setting local candidates of ragent as remote candidates of lagent"); + swap_candidates (ragent, global_rs_id, lagent, global_ls_id, FALSE); + swap_credentials (lagent, global_ls_id, ragent, global_rs_id); - swap_candidates (lagent, global_ls_id, - ragent, global_rs_id, - FALSE); + g_debug ("Setting local candidates of lagent as remote candidates of ragent"); + swap_candidates (lagent, global_ls_id, ragent, global_rs_id, FALSE); + swap_credentials (ragent, global_rs_id, lagent, global_ls_id); while (!data_received) g_main_context_iteration (NULL, TRUE); @@ -628,15 +619,14 @@ static void bad_candidate_test(NiceAgent *lagent,NiceAgent *ragent) g_assert (global_lagent_state == NICE_COMPONENT_STATE_FAILED && !data_received); - set_credentials (lagent, global_ls_id, ragent, global_rs_id); - swap_candidates (ragent, global_rs_id, - lagent, global_ls_id, - FALSE); + g_debug ("Setting local candidates of ragent as remote candidates of lagent"); + swap_candidates (ragent, global_rs_id, lagent, global_ls_id, FALSE); + swap_credentials (ragent, global_rs_id, lagent, global_ls_id); - swap_candidates (lagent, global_ls_id, - ragent, global_rs_id, - FALSE); + g_debug ("Setting local candidates of lagent as remote candidates of ragent"); + swap_candidates (lagent, global_ls_id, ragent, global_rs_id, FALSE); + swap_credentials (lagent, global_ls_id, ragent, global_rs_id); while (!data_received) g_main_context_iteration (NULL, TRUE); @@ -655,7 +645,8 @@ static void new_candidate_test(NiceAgent *lagent, NiceAgent *ragent) g_debug ("test-dribblemode:%s", G_STRFUNC); init_test (lagent, ragent, TRUE); - set_credentials (lagent, global_ls_id, ragent, global_rs_id); + swap_credentials (lagent, global_ls_id, ragent, global_rs_id); + swap_credentials (ragent, global_rs_id, lagent, global_ls_id); nice_agent_gather_candidates (lagent, global_ls_id); while (!got_stun_packet) -- 2.14.3 From b5dd5e2aa72a68ac9f027bbcc22700db84a35677 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Sun, 26 Nov 2017 17:49:25 +0100 Subject: [PATCH 04/15] conncheck: the conncheck send function may fail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this patch, we put the pair in state failed if we cannot send the connection check, for example due to missing local credentials. Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1891 --- agent/conncheck.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 11ef9c9..9618c3a 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -442,7 +442,11 @@ static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair * { pair->state = NICE_CHECK_IN_PROGRESS; nice_debug ("Agent %p : pair %p state IN_PROGRESS", agent, pair); - conn_check_send (agent, pair); + if (conn_check_send (agent, pair)) { + pair->state = NICE_CHECK_FAILED; + nice_debug ("Agent %p : pair %p state FAILED", agent, pair); + return FALSE; + } return TRUE; } @@ -1070,7 +1074,11 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) if (pair) { priv_print_conn_check_lists (agent, G_STRFUNC, ", got a pair from triggered check list"); - conn_check_send (agent, pair); + if (conn_check_send (agent, pair)) { + pair->state = NICE_CHECK_FAILED; + nice_debug ("Agent %p : pair %p state FAILED", agent, pair); + return FALSE; + } return TRUE; } -- 2.14.3 From 47aa02885cda9ddf6e938f966a926be000611c5a Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Sun, 26 Nov 2017 18:10:12 +0100 Subject: [PATCH 05/15] conncheck: factorize pair state debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1892 --- agent/conncheck.c | 69 +++++++++++++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 9618c3a..00d02c5 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -123,22 +123,29 @@ priv_state_to_string (NiceCheckState state) { switch (state) { case NICE_CHECK_WAITING: - return "waiting"; + return "WAITING"; case NICE_CHECK_IN_PROGRESS: - return "in progress"; + return "IN_PROGRESS"; case NICE_CHECK_SUCCEEDED: - return "succeeded"; + return "SUCCEEDED"; case NICE_CHECK_FAILED: - return "failed"; + return "FAILED"; case NICE_CHECK_FROZEN: - return "frozen"; + return "FROZEN"; case NICE_CHECK_DISCOVERED: - return "discovered"; + return "DISCOVERED"; default: g_assert_not_reached (); } } +#define SET_PAIR_STATE( a, p, s ) G_STMT_START{\ + g_assert (p); \ + p->state = s; \ + nice_debug ("Agent %p : pair %p state %s (%s)", \ + a, p, priv_state_to_string (s), G_STRFUNC); \ +}G_STMT_END + static const gchar * priv_ice_return_to_string (StunUsageIceReturn ice_return) { @@ -251,8 +258,7 @@ priv_add_pair_to_triggered_check_queue (NiceAgent *agent, CandidateCheckPair *pa { g_assert (pair); - pair->state = NICE_CHECK_IN_PROGRESS; - nice_debug ("Agent %p : pair %p state IN_PROGRESS", agent, 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); @@ -440,11 +446,9 @@ priv_find_first_frozen_check_list (NiceAgent *agent) */ static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair *pair) { - pair->state = NICE_CHECK_IN_PROGRESS; - nice_debug ("Agent %p : pair %p state IN_PROGRESS", agent, pair); + SET_PAIR_STATE (agent, pair, NICE_CHECK_IN_PROGRESS); if (conn_check_send (agent, pair)) { - pair->state = NICE_CHECK_FAILED; - nice_debug ("Agent %p : pair %p state FAILED", agent, pair); + SET_PAIR_STATE (agent, pair, NICE_CHECK_FAILED); return FALSE; } return TRUE; @@ -495,8 +499,7 @@ static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent, NiceStream *str 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); - pair->state = NICE_CHECK_WAITING; - nice_debug ("Agent %p : pair %p state WAITING", agent, pair); + SET_PAIR_STATE (agent, pair, NICE_CHECK_WAITING); result = TRUE; } } @@ -535,8 +538,7 @@ static void priv_conn_check_unfreeze_related (NiceAgent *agent, NiceStream *stre strncmp (p->foundation, ok_check->foundation, NICE_CANDIDATE_PAIR_MAX_FOUNDATION) == 0) { nice_debug ("Agent %p : Unfreezing check %p (after successful check %p).", agent, p, ok_check); - p->state = NICE_CHECK_WAITING; - nice_debug ("Agent %p : pair %p state WAITING", agent, p); + SET_PAIR_STATE (agent, p, NICE_CHECK_WAITING); } } } @@ -559,8 +561,7 @@ static void priv_conn_check_unfreeze_related (NiceAgent *agent, NiceStream *stre 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); - p->state = NICE_CHECK_WAITING; - nice_debug ("Agent %p : pair %p state WAITING", agent, p); + SET_PAIR_STATE (agent, p, NICE_CHECK_WAITING); } } } else if (priv_is_checklist_frozen (s)) { @@ -576,8 +577,7 @@ static void priv_conn_check_unfreeze_related (NiceAgent *agent, NiceStream *stre 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); - p->state = NICE_CHECK_WAITING; - nice_debug ("Agent %p : pair %p state WAITING", agent, p); + SET_PAIR_STATE (agent, p, NICE_CHECK_WAITING); } } @@ -675,8 +675,7 @@ candidate_check_pair_fail (NiceStream *stream, NiceAgent *agent, CandidateCheckP 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); + SET_PAIR_STATE (agent, p, NICE_CHECK_FAILED); priv_free_all_stun_transactions (p, component); } @@ -841,8 +840,7 @@ timer_return_timeout: if (pair) { priv_print_conn_check_lists (agent, G_STRFUNC, ", got a pair in Frozen state"); - pair->state = NICE_CHECK_WAITING; - nice_debug ("Agent %p : pair %p state WAITING", agent, pair); + SET_PAIR_STATE (agent, pair, NICE_CHECK_WAITING); priv_conn_check_initiate (agent, pair); return TRUE; } @@ -1075,8 +1073,7 @@ static gboolean priv_conn_check_tick_unlocked (NiceAgent *agent) priv_print_conn_check_lists (agent, G_STRFUNC, ", got a pair from triggered check list"); if (conn_check_send (agent, pair)) { - pair->state = NICE_CHECK_FAILED; - nice_debug ("Agent %p : pair %p state FAILED", agent, pair); + SET_PAIR_STATE (agent, pair, NICE_CHECK_FAILED); return FALSE; } return TRUE; @@ -2067,8 +2064,8 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, g_snprintf (pair->foundation, NICE_CANDIDATE_PAIR_MAX_FOUNDATION, "%s:%s", local->foundation, remote->foundation); pair->priority = agent_candidate_pair_priority (agent, local, remote); - pair->state = initial_state; - nice_debug ("Agent %p : creating new pair %p state %d", agent, pair, initial_state); + nice_debug ("Agent %p : creating a new pair", agent); + SET_PAIR_STATE (agent, pair, initial_state); { gchar tmpbuf1[INET6_ADDRSTRLEN]; gchar tmpbuf2[INET6_ADDRSTRLEN]; @@ -2976,10 +2973,10 @@ static CandidateCheckPair *priv_add_peer_reflexive_pair (NiceAgent *agent, guint pair->local = local_cand; pair->remote = parent_pair->remote; pair->sockptr = local_cand->sockptr; - pair->state = NICE_CHECK_DISCOVERED; parent_pair->discovered_pair = pair; pair->succeeded_pair = parent_pair; - nice_debug ("Agent %p : new pair %p state DISCOVERED", agent, pair); + nice_debug ("Agent %p : creating a new pair", agent); + SET_PAIR_STATE (agent, pair, NICE_CHECK_DISCOVERED); { gchar tmpbuf1[INET6_ADDRSTRLEN]; gchar tmpbuf2[INET6_ADDRSTRLEN]; @@ -3099,10 +3096,9 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * */ if (new_pair == p) p->valid = TRUE; - p->state = NICE_CHECK_SUCCEEDED; + SET_PAIR_STATE (agent, p, NICE_CHECK_SUCCEEDED); 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_component_add_valid_candidate (component, remote_candidate); } else { @@ -3135,11 +3131,9 @@ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent * /* step: The agent sets the state of the pair that *generated* the check to * Succeeded, RFC 5245, 7.1.3.2.3, "Updating Pair States" */ - p->state = NICE_CHECK_SUCCEEDED; + SET_PAIR_STATE (agent, p, NICE_CHECK_SUCCEEDED); 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.", - agent, p, new_pair); } if (new_pair && new_pair->valid) @@ -3226,10 +3220,9 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre * "Discovering Peer Reflexive Candidates" ICE ID-19) */ if (res == STUN_USAGE_ICE_RETURN_NO_MAPPED_ADDRESS) { - p->state = NICE_CHECK_SUCCEEDED; + nice_debug ("Agent %p : Mapped address not found", agent); + SET_PAIR_STATE (agent, p, NICE_CHECK_SUCCEEDED); p->valid = TRUE; - nice_debug ("Agent %p : Mapped address not found." - " conncheck %p SUCCEEDED.", agent, p); nice_component_add_valid_candidate (component, p->remote); } else ok_pair = priv_process_response_check_for_reflexive (agent, -- 2.14.3 From 05f1e30239a448385709df0edd43ec3ac5173218 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Sun, 26 Nov 2017 19:31:39 +0100 Subject: [PATCH 06/15] conncheck: make debug more homonegeous MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1893 --- agent/conncheck.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 00d02c5..25bfd80 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -753,8 +753,8 @@ 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 " - "connectivity check %p", agent, p); + 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)); @@ -973,7 +973,7 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) p = p->succeeded_pair; } g_assert (p->state == NICE_CHECK_SUCCEEDED); - nice_debug ("Agent %p : restarting check %p with " + 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); @@ -996,7 +996,8 @@ priv_conn_check_tick_stream_nominate (NiceStream *stream, NiceAgent *agent) if (p->component_id == component->id && (p->state == NICE_CHECK_SUCCEEDED || p->state == NICE_CHECK_DISCOVERED)) { - nice_debug ("Agent %p : restarting check %p as the nominated pair.", agent, p); + 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); priv_add_pair_to_triggered_check_queue (agent, p); @@ -2081,7 +2082,9 @@ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, stream->conncheck_list = g_slist_insert_sorted (stream->conncheck_list, pair, (GCompareFunc)conn_check_compare); - nice_debug ("Agent %p : added a new conncheck %p with foundation of '%s' to list %u.", agent, pair, pair->foundation, stream_id); + 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); /* implement the hard upper limit for number of checks (see sect 5.7.3 ICE ID-19): */ @@ -2117,9 +2120,6 @@ static CandidateCheckPair *priv_conn_check_add_for_candidate_pair_matched ( { CandidateCheckPair *pair; - nice_debug ("Agent %p : Adding check pair between %s and %s for s%d/c%d", - agent, local->foundation, remote->foundation, - stream_id, component->id); pair = priv_add_new_check_pair (agent, stream_id, component, local, remote, initial_state); if (component->state == NICE_COMPONENT_STATE_CONNECTED || @@ -2997,7 +2997,8 @@ static CandidateCheckPair *priv_add_peer_reflexive_pair (NiceAgent *agent, guint pair->nominated = FALSE; pair->prflx_priority = ensure_unique_priority (component, peer_reflexive_candidate_priority (agent, local_cand)); - nice_debug ("Agent %p : added a new peer-discovered pair with foundation of '%s'.", agent, pair->foundation); + nice_debug ("Agent %p : added a new peer-discovered pair with " + "foundation '%s'.", agent, pair->foundation); stream->conncheck_list = g_slist_insert_sorted (stream->conncheck_list, pair, (GCompareFunc)conn_check_compare); @@ -3190,7 +3191,7 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre CandidateCheckPair *ok_pair = NULL; - nice_debug ("Agent %p : conncheck %p MATCHED.", agent, p); + nice_debug ("Agent %p : pair %p MATCHED.", agent, p); priv_remove_stun_transaction (p, stun, component); /* step: verify that response came from the same IP address we @@ -3201,7 +3202,7 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre if (nice_debug_is_enabled ()) { gchar tmpbuf[INET6_ADDRSTRLEN]; gchar tmpbuf2[INET6_ADDRSTRLEN]; - nice_debug ("Agent %p : conncheck %p FAILED" + nice_debug ("Agent %p : pair %p FAILED" " (mismatch of source address).", agent, p); nice_address_to_string (&p->remote->addr, tmpbuf); nice_address_to_string (from, tmpbuf2); @@ -3316,7 +3317,8 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre gboolean controlled_mode; /* 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 : Role conflict with pair %p, restarting", + agent, p); /* note: this res value indicates that the role of the peer * agent has not changed after the tie-breaker comparison, so @@ -3341,7 +3343,6 @@ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStre priv_add_pair_to_triggered_check_queue (agent, p); } else { /* case: STUN error, the check STUN context was freed */ - nice_debug ("Agent %p : conncheck %p FAILED.", agent, p); candidate_check_pair_fail (stream, agent, p); } return TRUE; @@ -4228,8 +4229,8 @@ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, agent_signal_initial_binding_request_received (agent, stream); if (remote_candidate == NULL) { - nice_debug ("Agent %p : No matching remote candidate for incoming check ->" - "peer-reflexive candidate.", agent); + nice_debug ("Agent %p : No matching remote candidate for incoming " + "check -> peer-reflexive candidate.", agent); remote_candidate = discovery_learn_remote_peer_reflexive_candidate ( agent, stream, component, priority, from, nicesock, local_candidate, @@ -4332,8 +4333,8 @@ conn_check_prune_socket (NiceAgent *agent, NiceStream *stream, NiceComponent *co if ((p->local != NULL && p->local->sockptr == sock) || (p->remote != NULL && p->remote->sockptr == sock) || (p->sockptr == sock)) { - nice_debug ("Agent %p : Retransmissions failed, giving up on " - "connectivity check %p", agent, p); + nice_debug ("Agent %p : Retransmissions failed, giving up on pair %p", + agent, p); candidate_check_pair_fail (stream, agent, p); conn_check_free_item (p); stream->conncheck_list = g_slist_delete_link (stream->conncheck_list, l); -- 2.14.3 From 00dfcc6a625e6c6ed758a4b3b4d0858113508a2f Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Mon, 27 Nov 2017 23:56:17 +0100 Subject: [PATCH 07/15] socket: ping the stun server address on the right socket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verify the compatibility of the socket domain with the stun server IP address, before sending a request. Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1894 --- agent/agent.c | 12 +++++++----- agent/conncheck.c | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/agent/agent.c b/agent/agent.c index 49fc371..3306378 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -3062,11 +3062,13 @@ nice_agent_gather_candidates ( if (nice_address_set_from_string (&stun_server, agent->stun_server_ip)) { nice_address_set_port (&stun_server, agent->stun_server_port); - priv_add_new_candidate_discovery_stun (agent, - host_candidate->sockptr, - stun_server, - stream, - cid); + if (nice_address_ip_version (&host_candidate->addr) == + nice_address_ip_version (&stun_server)) + priv_add_new_candidate_discovery_stun (agent, + host_candidate->sockptr, + stun_server, + stream, + cid); } } diff --git a/agent/conncheck.c b/agent/conncheck.c index 25bfd80..4d91f41 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -1459,7 +1459,9 @@ static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent) for (k = component->local_candidates; k; k = k->next) { NiceCandidate *candidate = (NiceCandidate *) k->data; if (candidate->type == NICE_CANDIDATE_TYPE_HOST && - candidate->transport == NICE_CANDIDATE_TRANSPORT_UDP) { + candidate->transport == NICE_CANDIDATE_TRANSPORT_UDP && + nice_address_ip_version (&candidate->addr) == + nice_address_ip_version (&stun_server)) { /* send the conncheck */ nice_debug ("Agent %p : resending STUN on %s to keep the " "candidate alive.", agent, candidate->foundation); -- 2.14.3 From ea05a3d51990d17bbe25984eb5730849f46bfae0 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Wed, 29 Nov 2017 11:04:04 +0100 Subject: [PATCH 08/15] conncheck: dont fail a stream with a empty conncheck list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 17f30e4, we may have a stream with an empty conncheck list, and such a stream obviously should not be tested for failed components. Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1895 --- agent/conncheck.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 4d91f41..0ebe7e9 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -1832,6 +1832,9 @@ static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStre * must be fetched before entering the loop*/ guint c, components = stream->n_components; + if (stream->conncheck_list == NULL) + return; + for (i = agent->discovery_list; i; i = i->next) { CandidateDiscovery *d = i->data; @@ -1846,8 +1849,8 @@ static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStre /* note: iterate the conncheck list for each component separately */ for (c = 0; c < components; c++) { - NiceComponent *comp = NULL; - if (!agent_find_component (agent, stream->id, c+1, NULL, &comp)) + NiceComponent *component = NULL; + if (!agent_find_component (agent, stream->id, c+1, NULL, &component)) continue; nominated = 0; @@ -1873,7 +1876,7 @@ static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStre * Set the component to FAILED only if it actually had remote candidates * that failed.. */ if (completed && nominated == 0 && - comp != NULL && comp->remote_candidates != NULL) + component != NULL && component->remote_candidates != NULL) agent_signal_component_state_change (agent, stream->id, (c + 1), /* component-id */ -- 2.14.3 From a9ac0487b0d1708d780d7c0b7a2206c71a8c7163 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Thu, 30 Nov 2017 20:11:22 +0100 Subject: [PATCH 09/15] discovery: ignore all non-relay local candidates when relay is forced MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tcp server reflexive discovered local candidates must be ignored when force_relay is set. Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1899 --- agent/discovery.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agent/discovery.c b/agent/discovery.c index 4cc99c2..e2142a2 100644 --- a/agent/discovery.c +++ b/agent/discovery.c @@ -688,7 +688,8 @@ discovery_discover_tcp_server_reflexive_candidates ( caddr = c->addr; nice_address_set_port (&caddr, 0); - if (c->transport != NICE_CANDIDATE_TRANSPORT_UDP && + if (agent->force_relay == FALSE && + c->transport != NICE_CANDIDATE_TRANSPORT_UDP && c->type == NICE_CANDIDATE_TYPE_HOST && nice_address_equal (&base_addr, &caddr)) { nice_address_set_port (address, nice_address_get_port (&c->addr)); -- 2.14.3 From 5a644f459dc75c80dfb19c7772f74e37a0258771 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Mon, 11 Dec 2017 08:50:33 +0100 Subject: [PATCH 10/15] agent: make candidate username and password immutable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this patch we prevent the username and the password of a candidate to be modified during a session, as required by the RFC, sect 9.1.2. This is also needed from a memory management point of view, because the password string pointer may be recorded in the components stun agent sent_ids[] struct key member, and freeing these values there may cause an use-after-free condition, when an inbound stun is received from this candidate. This behavior has been observed with pidgin, xmpp, and farstream when a same remote candidates are "updated" several times, even if the credentials don't change in this case. Reviewed-by: Olivier Crête Differential Revision: https://phabricator.freedesktop.org/D1917 --- agent/agent.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/agent/agent.c b/agent/agent.c index 3306378..dbece3b 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -3388,15 +3388,22 @@ static gboolean priv_add_remote_candidate ( * this is essential to overcome a race condition where we might receive * a valid binding request from a valid candidate that wasn't yet added to * our list of candidates.. this 'update' will make the peer-rflx a - * server-rflx/host candidate again and restore that user/pass it needed - * to have in the first place */ + * server-rflx/host candidate again */ if (username) { - g_free (candidate->username); - candidate->username = g_strdup (username); + if (candidate->username == NULL) + candidate->username = g_strdup (username); + else if (g_strcmp0 (username, candidate->username)) + nice_debug ("Agent %p : Candidate username '%s' is not allowed " + "to change to '%s' now (ICE restart only).", agent, + candidate->username, username); } if (password) { - g_free (candidate->password); - candidate->password = g_strdup (password); + if (candidate->password == NULL) + candidate->password = g_strdup (password); + else if (g_strcmp0 (password, candidate->password)) + nice_debug ("Agent %p : candidate password '%s' is not allowed " + "to change to '%s' now (ICE restart only).", agent, + candidate->password, password); } /* since the type of the existing candidate may have changed, -- 2.14.3 From 54fb03427ebc13413cd1ddd5d9e91c1751eac0cb Mon Sep 17 00:00:00 2001 From: Jakub Adam Date: Sat, 3 Feb 2018 23:59:20 +0100 Subject: [PATCH 11/15] discovery: ignore bogus Skype for Business srflx addresses If main SfB TURN server sends our allocation request to an alternate server, the response will have XOR_MAPPED_ADDRESS containing the IP address of the turn server that proxied the message instead of our own actual external IP. Before we create server reflexive candidates upon receiving an allocate response, check that the TURN port got assigned on the same server we sent out allocate request to. Otherwise, the request was proxied and XOR_MAPPED_ADDRESS contains a bogus value we should ignore. Issue introduced by 59fcf95d505c3995f858b826d10cd48321ed383e. Differential Revision: https://phabricator.freedesktop.org/D1949 --- agent/conncheck.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/agent/conncheck.c b/agent/conncheck.c index 0ebe7e9..19729c2 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -3587,9 +3587,13 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage * NiceAddress niceaddr; NiceCandidate *relay_cand; + nice_address_set_from_sockaddr (&niceaddr, &relayaddr.addr); + if (res == STUN_USAGE_TURN_RETURN_MAPPED_SUCCESS) { + NiceAddress mappedniceaddr; + /* We also received our mapped address */ - nice_address_set_from_sockaddr (&niceaddr, &sockaddr.addr); + nice_address_set_from_sockaddr (&mappedniceaddr, &sockaddr.addr); /* TCP or TLS TURNS means the server-reflexive address was * on a TCP connection, which cannot be used for server-reflexive @@ -3601,21 +3605,28 @@ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage * d->agent, d->stream->id, d->component->id, - &niceaddr, + &mappedniceaddr, NICE_CANDIDATE_TRANSPORT_UDP, d->nicesock, FALSE); } - if (d->agent->use_ice_tcp) - discovery_discover_tcp_server_reflexive_candidates ( - d->agent, - d->stream->id, - d->component->id, - &niceaddr, - d->nicesock); + if (d->agent->use_ice_tcp) { + if ((agent->compatibility == NICE_COMPATIBILITY_OC2007 || + agent->compatibility == NICE_COMPATIBILITY_OC2007R2) && + !nice_address_equal_no_port (&niceaddr, &d->turn->server)) { + nice_debug("TURN port got allocated on an alternate server, " + "ignoring bogus srflx address"); + } else { + discovery_discover_tcp_server_reflexive_candidates ( + d->agent, + d->stream->id, + d->component->id, + &mappedniceaddr, + d->nicesock); + } + } } - nice_address_set_from_sockaddr (&niceaddr, &relayaddr.addr); if (nice_socket_is_reliable (d->nicesock)) { relay_cand = discovery_add_relay_candidate ( d->agent, -- 2.14.3 From 922ee4e61b4d9c6b403933f4a3261e67589d5099 Mon Sep 17 00:00:00 2001 From: Jakub Adam Date: Wed, 19 Apr 2017 14:17:04 +0200 Subject: [PATCH 12/15] agent: don't require "reliable" be TRUE in order to use "ice-tcp" Setting writable socket callbacks doesn't have to be limited to reliable agents. TCP sockets need the callback in any case for correct operation and calling nice_socket_set_writable_callback() on a NiceSocket that has UDP as its base has no effect. Differential Revision: https://phabricator.freedesktop.org/D1726 --- agent/agent.c | 11 ++++------- agent/conncheck.c | 5 ++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/agent/agent.c b/agent/agent.c index dbece3b..89e3514 100644 --- a/agent/agent.c +++ b/agent/agent.c @@ -2547,9 +2547,8 @@ priv_add_new_candidate_discovery_turn (NiceAgent *agent, if (nicesock == NULL) return; - if (agent->reliable) - nice_socket_set_writable_callback (nicesock, _tcp_sock_is_writable, - component); + nice_socket_set_writable_callback (nicesock, _tcp_sock_is_writable, component); + if (turn->type == NICE_RELAY_TYPE_TURN_TLS && agent->compatibility == NICE_COMPATIBILITY_GOOGLE) { nicesock = nice_pseudossl_socket_new (nicesock, @@ -3033,10 +3032,8 @@ nice_agent_gather_candidates ( found_local_address = TRUE; nice_address_set_port (addr, 0); - - if (agent->reliable) - nice_socket_set_writable_callback (host_candidate->sockptr, - _tcp_sock_is_writable, component); + nice_socket_set_writable_callback (host_candidate->sockptr, + _tcp_sock_is_writable, component); #ifdef HAVE_GUPNP if (agent->upnp_enabled && agent->upnp && diff --git a/agent/conncheck.c b/agent/conncheck.c index 19729c2..64a3cb8 100644 --- a/agent/conncheck.c +++ b/agent/conncheck.c @@ -2669,9 +2669,8 @@ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) pair->sockptr = new_socket; _priv_set_socket_tos (agent, pair->sockptr, stream2->tos); - if (agent->reliable) - nice_socket_set_writable_callback (pair->sockptr, - _tcp_sock_is_writable, component2); + nice_socket_set_writable_callback (pair->sockptr, _tcp_sock_is_writable, + component2); nice_component_attach_socket (component2, new_socket); } -- 2.14.3 From e6217f8eba6ea17d90eac67ef5fa5412fbf10dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Fri, 4 May 2018 15:44:05 +0200 Subject: [PATCH 13/15] Ignore function case warnings This makes GLib usage annoying as it makes GSourceFunc casts invalid. --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index 16988ad..36bd622 100644 --- a/configure.ac +++ b/configure.ac @@ -136,6 +136,7 @@ AS_IF([test "x$enable_compile_warnings" != "xno" -a \ NICE_ADD_FLAG([-Wcast-align]) NICE_ADD_FLAG([-Wformat-nonliteral]) NICE_ADD_FLAG([-Wformat-security]) + NICE_ADD_FLAG([-Wno-cast-function-type]) ]) AS_IF([test "$enable_compile_warnings" = "yes" -o \ "$enable_compile_warnings" = "maximum" -o \ -- 2.14.3 From 3a9d92818b4c2f083e26fe164a1be82212bd4061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Fri, 4 May 2018 16:44:45 +0200 Subject: [PATCH 14/15] stund: Pass the right length for ipv6 --- stun/tools/stund.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/stun/tools/stund.c b/stun/tools/stund.c index addc4fa..c148e51 100644 --- a/stun/tools/stund.c +++ b/stun/tools/stund.c @@ -100,6 +100,8 @@ int listen_socket (int fam, int type, int proto, unsigned int port) struct sockaddr_in6 in6; struct sockaddr_storage storage; } addr; + int len; + if (fd == -1) { perror ("Error opening IP port"); @@ -118,6 +120,7 @@ int listen_socket (int fam, int type, int proto, unsigned int port) { case AF_INET: addr.in.sin_port = htons (port); + len = sizeof (struct sockaddr_in); break; case AF_INET6: @@ -125,13 +128,14 @@ int listen_socket (int fam, int type, int proto, unsigned int port) setsockopt (fd, SOL_IPV6, IPV6_V6ONLY, &yes, sizeof (yes)); #endif addr.in6.sin6_port = htons (port); + len = sizeof (struct sockaddr_in6); break; default: assert (0); /* should never be reached */ } - if (bind (fd, &addr.addr, sizeof (struct sockaddr))) + if (bind (fd, &addr.addr, len)) { perror ("Error opening IP port"); goto error; -- 2.14.3 From 34d60446ddfcdb98f2543611151ef8fbc5be4805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Fri, 4 May 2018 16:50:45 +0200 Subject: [PATCH 15/15] stund: Pass sockaddr_storage size for both families --- stun/tools/stund.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/stun/tools/stund.c b/stun/tools/stund.c index c148e51..00a0881 100644 --- a/stun/tools/stund.c +++ b/stun/tools/stund.c @@ -100,15 +100,12 @@ int listen_socket (int fam, int type, int proto, unsigned int port) struct sockaddr_in6 in6; struct sockaddr_storage storage; } addr; - int len; if (fd == -1) { perror ("Error opening IP port"); return -1; } - if (fd < 3) - goto error; memset (&addr, 0, sizeof (addr)); addr.storage.ss_family = fam; @@ -120,7 +117,6 @@ int listen_socket (int fam, int type, int proto, unsigned int port) { case AF_INET: addr.in.sin_port = htons (port); - len = sizeof (struct sockaddr_in); break; case AF_INET6: @@ -128,14 +124,13 @@ int listen_socket (int fam, int type, int proto, unsigned int port) setsockopt (fd, SOL_IPV6, IPV6_V6ONLY, &yes, sizeof (yes)); #endif addr.in6.sin6_port = htons (port); - len = sizeof (struct sockaddr_in6); break; default: assert (0); /* should never be reached */ } - if (bind (fd, &addr.addr, len)) + if (bind (fd, &addr.addr, sizeof (struct sockaddr_storage))) { perror ("Error opening IP port"); goto error; @@ -192,7 +187,7 @@ static int dgram_process (int sock, StunAgent *oldagent, StunAgent *newagent) StunValidationStatus validation; StunAgent *agent = NULL; - addr_len = sizeof (struct sockaddr_in); + addr_len = sizeof (struct sockaddr_storage); len = recvfrom (sock, buf, sizeof(buf), 0, &addr.addr, &addr_len); if (len == (size_t)-1) return -1; -- 2.14.3