commit 27273c2710853f2b11b105799be1dc3c88ebf589 Author: CentOS Sources Date: Tue Sep 29 14:08:44 2020 +0000 import libnice-0.1.14-7.20180504git34d6044.el8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b299db --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/libnice-0.1.14.tar.gz diff --git a/.libnice.metadata b/.libnice.metadata new file mode 100644 index 0000000..b1e7238 --- /dev/null +++ b/.libnice.metadata @@ -0,0 +1 @@ +bc33999b547c14d40be42e67aa813b0d87bbd98f SOURCES/libnice-0.1.14.tar.gz diff --git a/SOURCES/libnice-0.1.14-85-g34d6044.patch b/SOURCES/libnice-0.1.14-85-g34d6044.patch new file mode 100644 index 0000000..541d010 --- /dev/null +++ b/SOURCES/libnice-0.1.14-85-g34d6044.patch @@ -0,0 +1,11963 @@ +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 + diff --git a/SOURCES/libnice-0.1.14-tests-i686.patch b/SOURCES/libnice-0.1.14-tests-i686.patch new file mode 100644 index 0000000..6558549 --- /dev/null +++ b/SOURCES/libnice-0.1.14-tests-i686.patch @@ -0,0 +1,48 @@ +From 3f8364b41207d8c26d3d3be518a7d9ebf4243b92 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Fri, 9 Feb 2018 18:01:57 +0100 +Subject: [PATCH] tests: make them compile on i686 + +--- + tests/test-pseudotcp-fuzzy.c | 4 ++-- + tests/test-pseudotcp.c | 2 +- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/tests/test-pseudotcp-fuzzy.c b/tests/test-pseudotcp-fuzzy.c +index 4a714e6..030c03f 100644 +--- a/tests/test-pseudotcp-fuzzy.c ++++ b/tests/test-pseudotcp-fuzzy.c +@@ -129,7 +129,7 @@ write_to_sock (PseudoTcpSocket *sock) + total += wlen; + total_read += wlen; + if (wlen < (gint) len) { +- g_debug ("seeking %ld from %lu", wlen - len, ftell (in)); ++ g_debug ("seeking %ld from %lu", (long) wlen - len, ftell (in)); + fseek (in, wlen - len, SEEK_CUR); + g_assert (!feof (in)); + g_debug ("Socket queue full after %d bytes written", total); +@@ -355,7 +355,7 @@ static void adjust_clock (PseudoTcpSocket *sock) + + if (pseudo_tcp_socket_get_next_clock (sock, &timeout)) { + timeout -= g_get_monotonic_time () / 1000; +- g_debug ("Socket %p: Adjusting clock to %ld ms", sock, timeout); ++ g_debug ("Socket %p: Adjusting clock to %ld ms", sock, (long) timeout); + if (sock == left) { + if (left_clock != 0) + g_source_remove (left_clock); +diff --git a/tests/test-pseudotcp.c b/tests/test-pseudotcp.c +index 1a8391a..584a0d0 100644 +--- a/tests/test-pseudotcp.c ++++ b/tests/test-pseudotcp.c +@@ -81,7 +81,7 @@ static void write_to_sock (PseudoTcpSocket *sock) + total += wlen; + total_read += wlen; + if (wlen < (gint) len) { +- g_debug ("seeking %ld from %lu", wlen - len, ftell (in)); ++ g_debug ("seeking %ld from %lu", (long) wlen - len, ftell (in)); + fseek (in, wlen - len, SEEK_CUR); + g_assert (!feof (in)); + g_debug ("Socket queue full after %d bytes written", total); +-- +2.13.6 + diff --git a/SOURCES/libnice-0.1.14-tests-koji.patch b/SOURCES/libnice-0.1.14-tests-koji.patch new file mode 100644 index 0000000..997e712 --- /dev/null +++ b/SOURCES/libnice-0.1.14-tests-koji.patch @@ -0,0 +1,36 @@ +From 527c30ba453753e75d3d31be29a277ea6adc17c0 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Fri, 9 Feb 2018 19:10:20 +0100 +Subject: [PATCH] tests: make the test-suite more verbose + +... and skip test-gstreamer if user's home is not /builddir (a heuristic +to detect mock) because multicast traffic is blocked on Koji buildhosts. +--- + configure.ac | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/configure.ac b/configure.ac +index 16988ad..b7b74fa 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -12,7 +12,7 @@ AC_CANONICAL_TARGET + + AC_CONFIG_SRCDIR([agent/agent.c]) + AC_CONFIG_HEADERS([config.h]) +-AM_INIT_AUTOMAKE([1.12 -Wall -Wno-portability subdir-objects]) ++AM_INIT_AUTOMAKE([1.12 -Wall -Wno-portability subdir-objects serial-tests]) + + AC_CONFIG_FILES([ + Makefile +@@ -263,7 +263,7 @@ AC_SUBST(gstplugindir) + AC_SUBST(gstplugin010dir) + + AM_CONDITIONAL(WITH_GSTREAMER, test "$with_gstreamer" = yes) +-AM_CONDITIONAL(HAVE_GST_CHECK, test "$have_gst_check" = yes) ++AM_CONDITIONAL(HAVE_GST_CHECK, test "$have_gst_check" = yes -a "$HOME" != /builddir) + AM_CONDITIONAL(WITH_GSTREAMER010, test "$with_gstreamer010" = yes) + + GUPNP_IGD_REQUIRED=0.2.4 +-- +2.13.6 + diff --git a/SOURCES/libnice-0.1.14-turn-verify.patch b/SOURCES/libnice-0.1.14-turn-verify.patch new file mode 100644 index 0000000..2ce2f86 --- /dev/null +++ b/SOURCES/libnice-0.1.14-turn-verify.patch @@ -0,0 +1,32 @@ +From e4d92bf96d0bb64df35790e5b49c58bfa6e9fbcc Mon Sep 17 00:00:00 2001 +From: Jakub Adam +Date: Thu, 20 Apr 2017 06:47:00 +0200 +Subject: [PATCH] component: accept TURN in + nice_component_verify_remote_candidate() + +When TURN is in operation, agent_recv_message_unlocked() may receive from +NiceSocket with type = NICE_SOCKET_TYPE_UDP_TURN. Such messages were always +dropped due to failed nice_component_verify_remote_candidate(). + +Bug: https://phabricator.freedesktop.org/D1727 +--- + agent/component.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/agent/component.c b/agent/component.c +index 6eee90e..3e8a7a6 100644 +--- a/agent/component.c ++++ b/agent/component.c +@@ -1510,7 +1510,8 @@ nice_component_verify_remote_candidate (NiceComponent *component, + (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) && ++ cand->transport == NICE_CANDIDATE_TRANSPORT_UDP || ++ nicesock->type == NICE_SOCKET_TYPE_UDP_TURN) && + nice_address_equal (address, &cand->addr)) { + /* fast return if it's already the first */ + if (item == component->valid_candidates) +-- +2.14.3 + diff --git a/SPECS/libnice.spec b/SPECS/libnice.spec new file mode 100644 index 0000000..f27e02f --- /dev/null +++ b/SPECS/libnice.spec @@ -0,0 +1,348 @@ +# disable building of plugin for gstreamer 0.10 +%bcond_with gst010 + +%global upstream_date 20180504 +%global upstream_rnum 85 +%global upstream_hash 34d6044 + +Name: libnice +Version: 0.1.14 +Release: 7.%{upstream_date}git%{upstream_hash}%{?dist} +Summary: GLib ICE implementation + +Group: System Environment/Libraries +License: LGPLv2 and MPLv1.1 +URL: https://nice.freedesktop.org/wiki/ +Source0: https://nice.freedesktop.org/releases/%{name}-%{version}.tar.gz +Patch1: libnice-0.1.14-%{upstream_rnum}-g%{upstream_hash}.patch + +# make tests compile on i686 +Patch2: libnice-0.1.14-tests-i686.patch + +# make tests pass in Koji +Patch3: libnice-0.1.14-tests-koji.patch + +# component: accept TURN in nice_component_verify_remote_candidate() (#1541646) +Patch4: libnice-0.1.14-turn-verify.patch + +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: glib2-devel +BuildRequires: gnutls-devel >= 2.12.0 +BuildRequires: gobject-introspection-devel +%if %{with gst010} +BuildRequires: gstreamer-devel +BuildRequires: gstreamer-plugins-base-devel +%endif +BuildRequires: gstreamer1-devel >= 0.11.91 +BuildRequires: gstreamer1-plugins-base-devel >= 0.11.91 +BuildRequires: gupnp-igd-devel >= 0.1.2 + + +%description +%{name} is an implementation of the IETF draft Interactive Connectivity +Establishment standard (ICE). ICE is useful for applications that want to +establish peer-to-peer UDP data streams. It automates the process of traversing +NATs and provides security against some attacks. Existing standards that use +ICE include the Session Initiation Protocol (SIP) and Jingle, XMPP extension +for audio/video calls. + + +%if %{with gst010} +%package gstreamer +Summary: GStreamer plugin for %{name} +Group: Development/Libraries +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description gstreamer +The %{name}-gstreamer package contains a gstreamer 0.10 plugin for %{name}. +%endif + + +%package gstreamer1 +Summary: GStreamer plugin for %{name} +Group: Development/Libraries +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description gstreamer1 +The %{name}-gstreamer1 package contains a gstreamer 1.0 plugin for %{name}. + + +%package devel +Summary: Development files for %{name} +Group: Development/Libraries +Requires: %{name}%{?_isa} = %{version}-%{release} +Requires: glib2-devel +Requires: pkgconfig + +%description devel +The %{name}-devel package contains libraries and header files for +developing applications that use %{name}. + + +%prep +%setup -q +%patch1 -p1 +%patch2 -p1 +%patch3 -p1 +%patch4 -p1 +chmod 0755 scripts/valgrind-test-driver + +# disable test-new-dribble, which sometimes hangs indefinitely, and +# test-send-recv, which fails in Koji due to insufficiently configured network +sed -e 's/test-new-dribble/#&/' \ + -e 's/test-send-recv/#&/' \ + -i tests/Makefile.am + +autoreconf -fiv + + +%build +%configure --enable-compile-warnings=yes --disable-static \ +%if %{with gst010} + --with-gstreamer-0.10 +%else + --without-gstreamer-0.10 +%endif +sed -i 's|^hardcode_libdir_flag_spec=.*|hardcode_libdir_flag_spec=""|g' libtool +sed -i 's|^runpath_var=LD_RUN_PATH|runpath_var=DIE_RPATH_DIE|g' libtool +make %{?_smp_mflags} V=1 + + +%install +make install DESTDIR=$RPM_BUILD_ROOT +find $RPM_BUILD_ROOT -name '*.la' -exec rm -f {} ';' + + +%check +# Temporarily make the upstream test-suite run on Intel arches only because we +# are getting random crashes in Koji on secondary arches but I have not been +# able to reproduce them locally so far. +%ifarch x86_64 %{ix86} +export LD_LIBRARY_PATH="$PWD/nice/.libs" +make check +%endif + + +%post -p /sbin/ldconfig + + +%postun -p /sbin/ldconfig + + +%files +%doc NEWS README +%license COPYING COPYING.LGPL COPYING.MPL +%{_bindir}/stunbdc +%{_bindir}/stund +%{_libdir}/*.so.* +%{_libdir}/girepository-1.0/Nice-0.1.typelib + + +%if %{with gst010} +%files gstreamer +%{_libdir}/gstreamer-0.10/libgstnice010.so +%endif + + +%files gstreamer1 +%{_libdir}/gstreamer-1.0/libgstnice.so + + +%files devel +%{_includedir}/* +%{_libdir}/*.so +%{_libdir}/pkgconfig/nice.pc +%{_datadir}/gtk-doc/html/%{name}/ +%{_datadir}/gir-1.0/Nice-0.1.gir + + +%changelog +* Mon May 07 2018 Kamil Dudka - 0.1.14-7.20180504git34d6044 +- component: accept TURN in nice_component_verify_remote_candidate() (#1541646) +- update to 0.1.14-85-g34d6044 (#1541646) + +* Mon Apr 16 2018 Kamil Dudka - 0.1.14-6.20171128gitfb2f1f7 +- temporarily make the upstream test-suite run on Intel arches only +- disable test-send-recv, which fails in Koji + +* Fri Mar 16 2018 Kamil Dudka - 0.1.14-5.20171128gitfb2f1f7 +- do not build with -Werror by default +- make the build more verbose + +* Fri Feb 09 2018 Kamil Dudka - 0.1.14-4.20171128gitfb2f1f7 +- enable make check again +- make tests pass in Koji +- disable test-new-dribble that sometimes hangs indefinitely +- make tests compile on i686 +- make the package build on armv7hl +- make the package build on Fedora 28 +- avoid build failure if gstreamer-plugins-base-devel is installed +- move autoreconf invocation to %%prep +- use Name Version Release that explicitly identifies an SCM snapshot (#1541646) + +* Fri Feb 09 2018 Stefan Becker - 0.1.14-3 +- update to 0.1.14-70-gfb2f1f7 with alternate server fixes for SIPE +- add autoreconf build step +- remove examples subpackage as examples are no longer installed + +* Wed Feb 07 2018 Fedora Release Engineering - 0.1.14-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Mon Jan 29 2018 Stefan Becker - 0.1.14-1 +- Update to 0.1.14 + +* Wed Jan 24 2018 Tomas Hoger - 0.1.13-11 +- Add conditional for building with(out) gst010 / GStreamer 0.10 support. +- Disable gst010 plugin by default. + +* Thu Aug 03 2017 Fedora Release Engineering - 0.1.13-10 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Wed Jul 26 2017 Fedora Release Engineering - 0.1.13-9 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Fri Feb 10 2017 Fedora Release Engineering - 0.1.13-8 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Thu Jul 21 2016 Than Ngo - 0.1.13-7 +- Rebuilt for glibc: Revert sendmsg/recvmsg ABI changes + +* Fri Jun 10 2016 David Woodhouse - 0.1.13-6 +- More updates from libnice git; use-after-free fixes + +* Mon Jun 06 2016 David Woodhouse - 0.1.13-5 +- Wholesale update to git HEAD, which fixes SIPE again. + +* Fri May 20 2016 David Woodhouse - 0.1.13-4 +- Backport patch to fix SIPE audio disconnections (#1337051) +- Fix candidate gathering with IPV6 tentative addresses (#1337412) + +* Thu Feb 04 2016 Fedora Release Engineering - 0.1.13-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Wed Jun 17 2015 Fedora Release Engineering - 0.1.13-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Wed Apr 29 2015 Kalev Lember - 0.1.13-1 +- Update to 0.1.13 +- Tighten dependencies with the _isa macro + +* Tue Apr 21 2015 Kalev Lember - 0.1.11-1 +- Update to 0.1.11 +- Use license macro for COPYING files + +* Mon Mar 02 2015 David Woodhouse - 0.1.10-1 +- Update to 0.1.10 + +* Sat Feb 21 2015 Till Maas - 0.1.8-2 +- Rebuilt for Fedora 23 Change + https://fedoraproject.org/wiki/Changes/Harden_all_packages_with_position-independent_code + +* Sun Oct 26 2014 Kalev Lember - 0.1.8-1 +- Update to 0.1.8 +- Build with gobject introspection support + +* Sun Aug 17 2014 Fedora Release Engineering - 0.1.4-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Sat Jun 07 2014 Fedora Release Engineering - 0.1.4-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Sat Aug 03 2013 Fedora Release Engineering - 0.1.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Mon May 13 2013 Brian Pepple - 0.1.4-1 +- Update to 0.1.4. +- Add examples subpackage. + +* Fri May 10 2013 Matthias Clasen - 0.1.3-3 +- Split the gstreamer plugins off in subpackages + +* Thu Feb 14 2013 Fedora Release Engineering - 0.1.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild + +* Fri Sep 14 2012 Brian Pepple - 0.1.3-1 +- Update to 0.1.3. +- Add BR on gstreamer1 packages. + +* Thu Jul 19 2012 Fedora Release Engineering - 0.1.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Mon Apr 23 2012 Brian Pepple - 0.1.2-1 +- Update to 0.1.2. + +* Mon Jan 16 2012 Brian Pepple - 0.1.1-3 +- Rebuild for new gupnp-idg. + +* Sun Jan 08 2012 Brian Pepple - 0.1.1-2 +- Rebuild for new gcc. + +* Wed Dec 7 2011 Brian Pepple - 0.1.1-1 +- Update to 0.1.1. +- Drop ppc64 patch. Fixed upstream. + +* Tue Aug 16 2011 David Woodhouse - 0.1.0-5 +- Apply portability patch to nice/Makefile.in too. I hate autocrap. + +* Tue Aug 16 2011 David Woodhouse - 0.1.0-4 +- Fix non-portable symbol checks in nice/Makefile.am + +* Fri Jun 17 2011 Peter Robinson - 0.1.0-3 +- rebuild for new gupnp/gssdp + +* Tue Feb 08 2011 Fedora Release Engineering - 0.1.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Wed Jan 26 2011 Brian Pepple - 0.1.0-1 +- Update to 0.1.0. +- Enable make check. +- Drop buildroot and clean section. No longer needed. + +* Wed Aug 4 2010 Brian Pepple - 0.0.13-1 +- Update to 0.0.13. + +* Wed May 19 2010 Brian Pepple - 0.0.12-1 +- Update to 0.0.12. + +* Fri Mar 19 2010 Brian Pepple - 0.0.11-1 +- Update to 0.0.11. + +* Wed Dec 16 2009 Brian Pepple - 0.0.10-2 +- Rebuild for new gupnp-igd. + +* Mon Nov 9 2009 Brian Pepple - 0.0.10-1 +- Update to 0.0.10. + +* Thu Sep 17 2009 Bastien Nocera 0.0.9-2 +- Rebuild for new gupnp + +* Sun Aug 2 2009 Brian Pepple - 0.0.9-1 +- Update to 0.0.9. +- Drop sha1 patch. Fixed upstream. + +* Fri Jul 24 2009 Fedora Release Engineering - 0.0.8-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild + +* Tue Jul 21 2009 Warren Togami - 0.0.8-2 +- stun sha1 patch from upstream to make it work at all + +* Sun Jun 21 2009 Brian Pepple - 0.0.8-1 +- Update to 0.0.8. + +* Sun Jun 14 2009 Brian Pepple - 0.0.7-1 +- Update to 0.0.7. +- Add BR on gupnp-igd-devel. + +* Mon Apr 13 2009 Brian Pepple - 0.0.6-1 +- Update to 0.0.6. + +* Wed Mar 18 2009 Brian Pepple - 0.0.5-1 +- Update to 0.0.5. + +* Wed Feb 25 2009 Fedora Release Engineering - 0.0.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild + +* Sat Dec 27 2008 Brian Pepple - 0.0.4-1 +- Initial Fedora spec. +