/* * This file is part of the Nice GLib ICE library. * * (C) 2006-2009 Collabora Ltd. * Contact: Youness Alaoui * (C) 2006-2009 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: * Kai Vehmanen, Nokia * Youness Alaoui, Collabora Ltd. * Dafydd Harries, Collabora Ltd. * * 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. */ /* * @file conncheck.c * @brief ICE connectivity checks */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include "debug.h" #include "agent.h" #include "agent-priv.h" #include "conncheck.h" #include "discovery.h" #include "stun/stun5389.h" #include "stun/usages/ice.h" #include "stun/usages/bind.h" #include "stun/usages/turn.h" static void priv_update_check_list_failed_components (NiceAgent *agent, NiceStream *stream); static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, NiceComponent *component); static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *local_socket, NiceCandidate *remote_cand); static gboolean 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, uint8_t *dest, guint dest_len, gboolean inbound); static size_t priv_get_password (NiceAgent *agent, NiceStream *stream, NiceCandidate *remote, uint8_t **password); static void candidate_check_pair_fail (NiceStream *stream, NiceAgent *agent, CandidateCheckPair *p); static void candidate_check_pair_free (NiceAgent *agent, CandidateCheckPair *pair); 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_conn_keepalive_tick_agent_locked (NiceAgent *agent, gpointer pointer); static void priv_schedule_next (NiceAgent *agent); static gint64 priv_timer_remainder (gint64 timer, gint64 now) { if (now >= timer) return 0; return (timer - now) / 1000; } static gchar priv_state_to_gchar (NiceCheckState state) { switch (state) { case NICE_CHECK_WAITING: return 'W'; case NICE_CHECK_IN_PROGRESS: return 'I'; case NICE_CHECK_SUCCEEDED: return 'S'; case NICE_CHECK_FAILED: return 'F'; case NICE_CHECK_FROZEN: return 'Z'; case NICE_CHECK_DISCOVERED: return 'D'; default: g_assert_not_reached (); } } 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 (); } } #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) { 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_socket_type_to_string (NiceSocketType type) { switch (type) { case NICE_SOCKET_TYPE_UDP_BSD: return "udp"; case NICE_SOCKET_TYPE_TCP_BSD: return "tcp"; case NICE_SOCKET_TYPE_PSEUDOSSL: return "ssl"; case NICE_SOCKET_TYPE_HTTP: return "http"; case NICE_SOCKET_TYPE_SOCKS5: return "socks"; case NICE_SOCKET_TYPE_UDP_TURN: return "udp-turn"; case NICE_SOCKET_TYPE_UDP_TURN_OVER_TCP: return "tcp-turn"; case NICE_SOCKET_TYPE_TCP_ACTIVE: return "tcp-act"; case NICE_SOCKET_TYPE_TCP_PASSIVE: return "tcp-pass"; case NICE_SOCKET_TYPE_TCP_SO: return "tcp-so"; default: g_assert_not_reached (); } } /* * Dump the component list of incoming checks */ static void print_component_incoming_checks (NiceAgent *agent, NiceStream *stream, NiceComponent *component) { GList *i; for (i = component->incoming_checks.head; i; i = i->next) { IncomingCheck *icheck = i->data; gchar tmpbuf1[INET6_ADDRSTRLEN] = {0}; gchar tmpbuf2[INET6_ADDRSTRLEN] = {0}; nice_address_to_string (&icheck->local_socket->addr, tmpbuf1); nice_address_to_string (&icheck->from, tmpbuf2); nice_debug ("Agent %p : *** sc=%d/%d : icheck %p : " "sock %s [%s]:%u > [%s]:%u, use_cand %u", agent, stream->id, component->id, icheck, priv_socket_type_to_string (icheck->local_socket->type), tmpbuf1, nice_address_get_port (&icheck->local_socket->addr), tmpbuf2, nice_address_get_port (&icheck->from), icheck->use_candidate); } } /* * Dump the conncheck lists of the agent */ static void priv_print_conn_check_lists (NiceAgent *agent, const gchar *where, const gchar *detail) { GSList *i, *k, *l; guint j, m; gint64 now; if (!nice_debug_is_verbose ()) return; now = g_get_monotonic_time (); #define PRIORITY_LEN 32 nice_debug ("Agent %p : *** conncheck list DUMP (called from %s%s)", agent, where, detail ? detail : ""); nice_debug ("Agent %p : *** agent nomination mode %s, %s", agent, agent->nomination_mode == NICE_NOMINATION_MODE_AGGRESSIVE ? "aggressive" : "regular", agent->controlling_mode ? "controlling" : "controlled"); for (i = agent->streams; i ; i = i->next) { NiceStream *stream = i->data; for (j = 1; j <= stream->n_components; j++) { NiceComponent *component; for (k = stream->conncheck_list; k ; k = k->next) { CandidateCheckPair *pair = k->data; if (pair->component_id == j) { gchar local_addr[INET6_ADDRSTRLEN]; gchar remote_addr[INET6_ADDRSTRLEN]; gchar priority[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE]; nice_address_to_string (&pair->local->addr, local_addr); nice_address_to_string (&pair->remote->addr, remote_addr); nice_candidate_pair_priority_to_string (pair->priority, priority); nice_debug ("Agent %p : *** sc=%d/%d : pair %p : " "f=%s t=%s:%s sock=%s " "%s:[%s]:%u > %s:[%s]:%u prio=%s/%08x state=%c%s%s%s%s", agent, pair->stream_id, pair->component_id, pair, pair->foundation, nice_candidate_type_to_string (pair->local->type), nice_candidate_type_to_string (pair->remote->type), priv_socket_type_to_string (pair->sockptr->type), nice_candidate_transport_to_string (pair->local->transport), local_addr, nice_address_get_port (&pair->local->addr), nice_candidate_transport_to_string (pair->remote->transport), remote_addr, nice_address_get_port (&pair->remote->addr), priority, pair->stun_priority, priv_state_to_gchar (pair->state), pair->valid ? "V" : "", pair->nominated ? "N" : "", pair->use_candidate_on_next_check ? "C" : "", g_slist_find (agent->triggered_check_queue, pair) ? "T" : ""); for (l = pair->stun_transactions, m = 0; l; l = l->next, m++) { StunTransaction *stun = l->data; nice_debug ("Agent %p : *** sc=%d/%d : pair %p : " "stun#=%d timer=%d/%d %" G_GINT64_FORMAT "/%dms buf=%p %s", agent, pair->stream_id, pair->component_id, pair, m, stun->timer.retransmissions, stun->timer.max_retransmissions, stun->timer.delay - priv_timer_remainder (stun->next_tick, now), stun->timer.delay, stun->message.buffer, (m == 0 && pair->retransmit) ? "(R)" : ""); } } } if (agent_find_component (agent, stream->id, j, NULL, &component)) print_component_incoming_checks (agent, stream, component); } } } /* Add the pair to the triggered checks list, if not already present */ static void priv_add_pair_to_triggered_check_queue (NiceAgent *agent, CandidateCheckPair *pair) { g_assert (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); priv_schedule_next (agent); } } /* Remove the pair from the triggered checks list */ static void priv_remove_pair_from_triggered_check_queue (NiceAgent *agent, CandidateCheckPair *pair) { g_assert (pair); agent->triggered_check_queue = g_slist_remove (agent->triggered_check_queue, pair); } /* Get the pair from the triggered checks list */ static CandidateCheckPair * priv_get_pair_from_triggered_check_queue (NiceAgent *agent) { CandidateCheckPair *pair = NULL; if (agent->triggered_check_queue) { pair = (CandidateCheckPair *)agent->triggered_check_queue->data; priv_remove_pair_from_triggered_check_queue (agent, pair); } return pair; } /* * Finds the next connectivity check in WAITING state. */ static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check_list) { GSList *i; /* 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) return p; } return NULL; } /* * Initiates a new connectivity check for a ICE candidate pair. * * @return TRUE on success, FALSE on error */ static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair *pair) { SET_PAIR_STATE (agent, pair, NICE_CHECK_IN_PROGRESS); if (conn_check_send (agent, pair)) { NiceStream *stream; NiceComponent *component; if (!agent_find_component (agent, pair->stream_id, pair->component_id, &stream, &component)) { nice_debug ("Could not find stream or component in conn_check_initiate"); SET_PAIR_STATE (agent, pair, NICE_CHECK_FAILED); return FALSE; } candidate_check_pair_fail (stream, agent, pair); conn_check_update_check_list_state_for_ready (agent, stream, component); return FALSE; } return TRUE; } /* * Unfreezes the next connectivity check in the list. Follows the * algorithm defined in sect 6.1.2.6 (Computing Candidate Pair States) * and sect 6.1.4.2 (Performing Connectivity Checks) of the ICE spec * (RFC8445) * * Note that this algorithm is slightly simplified compared to previous * version of the spec (RFC5245), and this new version is now * idempotent. * * @return TRUE on success, and FALSE if no frozen candidates were found. */ static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent) { GSList *i, *j; GSList *foundation_list = NULL; gboolean result = FALSE; /* While a pair in state waiting exists, we do nothing */ for (i = agent->streams; i ; i = i->next) { NiceStream *s = i->data; for (j = s->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; if (p->state == NICE_CHECK_WAITING) return TRUE; } } /* When there are no more pairs in waiting state, we unfreeze some * pairs, so that we get a single waiting pair per foundation. */ for (i = agent->streams; i ; i = i->next) { NiceStream *s = i->data; for (j = s->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; if (g_slist_find_custom (foundation_list, p->foundation, (GCompareFunc)strcmp)) continue; if (p->state == NICE_CHECK_FROZEN) { nice_debug ("Agent %p : Pair %p with s/c-id %u/%u (%s) unfrozen.", agent, p, p->stream_id, p->component_id, p->foundation); SET_PAIR_STATE (agent, p, NICE_CHECK_WAITING); foundation_list = g_slist_prepend (foundation_list, p->foundation); result = TRUE; } } } g_slist_free (foundation_list); /* We dump the conncheck list when something interesting happened, ie * when we unfroze some pairs. */ if (result) priv_print_conn_check_lists (agent, G_STRFUNC, NULL); return result; } /* * Unfreezes the related connectivity check in the list after * check 'success_check' has successfully completed. * * See sect 7.2.5.3.3 (Updating Candidate Pair States) of ICE spec (RFC8445). * * Note that this algorithm is slightly simplified compared to previous * version of the spec (RFC5245) * * @param agent context * @param pair a pair, whose connectivity check has just succeeded * */ void conn_check_unfreeze_related (NiceAgent *agent, CandidateCheckPair *pair) { GSList *i, *j; gboolean result = FALSE; g_assert (pair); g_assert (pair->state == NICE_CHECK_SUCCEEDED); for (i = agent->streams; i ; i = i->next) { NiceStream *s = i->data; for (j = s->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; /* The states for all other Frozen candidates pairs in all * checklists with the same foundation is set to waiting */ if (p->state == NICE_CHECK_FROZEN && strncmp (p->foundation, pair->foundation, NICE_CANDIDATE_PAIR_MAX_FOUNDATION) == 0) { nice_debug ("Agent %p : Unfreezing check %p " "(after successful check %p).", agent, p, pair); SET_PAIR_STATE (agent, p, NICE_CHECK_WAITING); result = TRUE; } } } /* We dump the conncheck list when something interesting happened, ie * when we unfroze some pairs. */ if (result) priv_print_conn_check_lists (agent, G_STRFUNC, NULL); } /* * Unfreezes this connectivity check if its foundation is the same than * the foundation of an already succeeded pair. * * See sect 7.2.5.3.3 (Updating Candidate Pair States) of ICE spec (RFC8445). * * @param agent context * @param pair a pair, whose state is frozen * */ static void priv_conn_check_unfreeze_maybe (NiceAgent *agent, CandidateCheckPair *pair) { GSList *i, *j; gboolean result = FALSE; g_assert (pair); g_assert (pair->state == NICE_CHECK_FROZEN); for (i = agent->streams; i ; i = i->next) { NiceStream *s = i->data; for (j = s->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; if (p->state == NICE_CHECK_SUCCEEDED && strncmp (p->foundation, pair->foundation, NICE_CANDIDATE_PAIR_MAX_FOUNDATION) == 0) { nice_debug ("Agent %p : Unfreezing check %p " "(after successful check %p).", agent, pair, p); SET_PAIR_STATE (agent, pair, NICE_CHECK_WAITING); result = TRUE; } } } /* We dump the conncheck list when something interesting happened, ie * when we unfroze some pairs. */ if (result) priv_print_conn_check_lists (agent, G_STRFUNC, NULL); } guint conn_check_stun_transactions_count (NiceAgent *agent) { GSList *i, *j; guint count = 0; for (i = agent->streams; i ; i = i->next) { NiceStream *s = i->data; for (j = s->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; if (p->stun_transactions) count += g_slist_length (p->stun_transactions); } } return count; } /* * 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 priv_forget_stun_transaction (gpointer data, gpointer user_data) { StunTransaction *stun = data; NiceComponent *component = user_data; StunTransactionId id; if (stun->message.buffer != NULL) { stun_message_id (&stun->message, id); stun_agent_forget_transaction (&component->stun_agent, id); } } static void priv_free_stun_transaction (gpointer data) { g_slice_free (StunTransaction, data); } /* * Remove a STUN transaction from a pair, and forget it * from the related component stun agent. * * @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 void priv_remove_stun_transaction (CandidateCheckPair *pair, StunTransaction *stun, NiceComponent *component) { priv_forget_stun_transaction (stun, component); pair->stun_transactions = g_slist_remove (pair->stun_transactions, stun); priv_free_stun_transaction (stun); if (pair->stun_transactions == NULL) pair->retransmit = FALSE; } /* * 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; pair->retransmit = FALSE; } 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); SET_PAIR_STATE (agent, p, NICE_CHECK_FAILED); priv_free_all_stun_transactions (p, component); /* Ensure related succeeded-discovered pairs change to state failed * simultaneously, to avoid leaving dangling pointers if one is freeed * while the other is still in the conncheck list. Such transition is * very rare, and only occurs in regular nomination mode, when the * network conditions change between the time the pair is initially * discovered and the time it is rechecked with the use-candidate * flag. */ if (p->discovered_pair != NULL) { nice_debug ("Agent %p : related discovered pair %p of pair %p " "will fail too.", agent, p->discovered_pair, p); SET_PAIR_STATE (agent, p->discovered_pair, NICE_CHECK_FAILED); } } /* * Helper function for connectivity check timer callback that * runs through the stream specific part of the state machine. * * @param agent context pointer * @param stream which stream (of the agent) * @return will return TRUE if a new stun request has been sent */ static gboolean priv_conn_check_tick_stream (NiceAgent *agent, NiceStream *stream) { gboolean pair_failed = FALSE; GSList *i, *j; unsigned int timeout; gint64 now; now = g_get_monotonic_time (); /* step: process ongoing STUN transactions */ for (i = stream->conncheck_list; i ; i = i->next) { CandidateCheckPair *p = i->data; gchar tmpbuf1[INET6_ADDRSTRLEN], tmpbuf2[INET6_ADDRSTRLEN]; NiceComponent *component; guint index = 0, remaining = 0; if (p->stun_transactions == NULL) continue; if (!agent_find_component (agent, p->stream_id, p->component_id, NULL, &component)) continue; j = p->stun_transactions; while (j) { StunTransaction *stun = j->data; GSList *next = j->next; if (now < stun->next_tick) remaining++; else switch (stun_timer_refresh (&stun->timer)) { case STUN_USAGE_TIMER_RETURN_TIMEOUT: timer_return_timeout: priv_remove_stun_transaction (p, stun, component); break; case STUN_USAGE_TIMER_RETURN_RETRANSMIT: /* case: retransmission stopped, due to the nomination of * a pair with a higher priority than this in-progress pair, * ICE spec, sect 8.1.2 "Updating States", item 2.2 */ if (!p->retransmit || index > 0) goto timer_return_timeout; /* case: not ready, so schedule a new timeout */ timeout = stun_timer_remainder (&stun->timer); nice_debug ("Agent %p :STUN transaction retransmitted on pair %p " "(timer=%d/%d %d/%dms).", agent, p, stun->timer.retransmissions, stun->timer.max_retransmissions, stun->timer.delay - timeout, stun->timer.delay); agent_socket_send (p->sockptr, &p->remote->addr, stun_message_length (&stun->message), (gchar *)stun->buffer); /* note: convert from milli to microseconds for g_time_val_add() */ stun->next_tick = now + timeout * 1000; return TRUE; case STUN_USAGE_TIMER_RETURN_SUCCESS: timeout = stun_timer_remainder (&stun->timer); /* note: convert from milli to microseconds for g_time_val_add() */ stun->next_tick = now + timeout * 1000; remaining++; break; default: g_assert_not_reached(); break; } j = next; index++; } if (remaining == 0) { nice_address_to_string (&p->local->addr, tmpbuf1); nice_address_to_string (&p->remote->addr, tmpbuf2); nice_debug ("Agent %p : Retransmissions failed, giving up on pair %p", agent, p); nice_debug ("Agent %p : Failed pair is [%s]:%u --> [%s]:%u", agent, tmpbuf1, nice_address_get_port (&p->local->addr), tmpbuf2, nice_address_get_port (&p->remote->addr)); candidate_check_pair_fail (stream, agent, p); pair_failed = TRUE; /* perform a check if a transition state from connected to * ready can be performed. This may happen here, when the last * in-progress pair has expired its retransmission count * in priv_conn_check_tick_stream(), which is a condition to * make the transition connected to ready. */ conn_check_update_check_list_state_for_ready (agent, stream, component); } } if (pair_failed) priv_print_conn_check_lists (agent, G_STRFUNC, ", retransmission failed"); return FALSE; } static gboolean priv_conn_check_ordinary_check (NiceAgent *agent, NiceStream *stream) { CandidateCheckPair *pair; gboolean stun_sent = FALSE; /* step: perform an ordinary check, sec 6.1.4.2 point 3. (Performing * Connectivity Checks) of ICE spec (RFC8445) * note: This code is executed when the triggered checks list is * empty, and when no STUN message has been sent (pacing constraint) */ pair = priv_conn_check_find_next_waiting (stream->conncheck_list); if (pair == NULL) { /* step: there is no candidate in waiting state, try to unfreeze * some pairs and retry, sect 6.1.4.2 point 2. (Performing Connectivity * Checks) of ICE spec (RFC8445) */ priv_conn_check_unfreeze_next (agent); pair = priv_conn_check_find_next_waiting (stream->conncheck_list); } if (pair) { stun_sent = priv_conn_check_initiate (agent, pair); priv_print_conn_check_lists (agent, G_STRFUNC, ", initiated an ordinary connection check"); } return stun_sent; } static gboolean priv_conn_check_triggered_check (NiceAgent *agent, NiceStream *stream) { CandidateCheckPair *pair; gboolean stun_sent = FALSE; /* step: perform a test from the triggered checks list, * sect 6.1.4.2 point 1. (Performing Connectivity Checks) of ICE * spec (RFC8445) */ pair = priv_get_pair_from_triggered_check_queue (agent); if (pair) { stun_sent = priv_conn_check_initiate (agent, pair); priv_print_conn_check_lists (agent, G_STRFUNC, ", initiated a connection check from triggered check list"); } return stun_sent; } static gboolean priv_conn_check_tick_stream_nominate (NiceAgent *agent, NiceStream *stream) { gboolean keep_timer_going = FALSE; /* s_xxx counters are stream-wide */ guint s_inprogress = 0; guint s_succeeded = 0; guint s_discovered = 0; guint s_nominated = 0; guint s_waiting_for_nomination = 0; guint s_valid = 0; guint s_frozen = 0; guint s_waiting = 0; CandidateCheckPair *other_stream_pair = NULL; GSList *i, *j; /* Search for a nominated pair (or selected to be nominated pair) * from another stream. */ for (i = agent->streams; i ; i = i->next) { NiceStream *s = i->data; if (s->id == stream->id) continue; for (j = s->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; if (p->nominated || (p->use_candidate_on_next_check && p->state != NICE_CHECK_FAILED)) { other_stream_pair = p; break; } } if (other_stream_pair) break; } /* we compute some stream-wide counter values */ for (i = stream->conncheck_list; i ; i = i->next) { CandidateCheckPair *p = i->data; if (p->state == NICE_CHECK_FROZEN) s_frozen++; else if (p->state == NICE_CHECK_IN_PROGRESS) s_inprogress++; else if (p->state == NICE_CHECK_WAITING) s_waiting++; else if (p->state == NICE_CHECK_SUCCEEDED) s_succeeded++; else if (p->state == NICE_CHECK_DISCOVERED) s_discovered++; if (p->valid) s_valid++; if ((p->state == NICE_CHECK_SUCCEEDED || p->state == NICE_CHECK_DISCOVERED) && p->nominated) s_nominated++; else if ((p->state == NICE_CHECK_SUCCEEDED || p->state == NICE_CHECK_DISCOVERED) && !p->nominated) s_waiting_for_nomination++; } /* note: keep the timer going as long as there is work to be done */ if (s_inprogress) keep_timer_going = TRUE; if (s_nominated < stream->n_components && s_waiting_for_nomination) { if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { if (agent->nomination_mode == NICE_NOMINATION_MODE_REGULAR && 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 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, * and limiting the time spent waiting for in-progress connections * checks until they finally fail. */ for (i = stream->components; i; i = i->next) { NiceComponent *component = i->data; CandidateCheckPair *other_component_pair = NULL; CandidateCheckPair *this_component_pair = NULL; NiceCandidate *lcand1 = NULL; NiceCandidate *rcand1 = NULL; NiceCandidate *lcand2, *rcand2; gboolean already_done = FALSE; gboolean found_other_component_pair = FALSE; gboolean found_other_stream_pair = FALSE; gboolean first_nomination = FALSE; gboolean stopping_criterion; /* p_xxx counters are component-wide */ guint p_valid = 0; guint p_frozen = 0; guint p_waiting = 0; guint p_inprogress = 0; guint p_host_host_valid = 0; /* we compute some component-wide counter values */ for (j = stream->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; if (p->component_id == component->id) { /* verify that the choice of the pair to be nominated * has not already been done */ if (p->use_candidate_on_next_check) already_done = TRUE; if (p->state == NICE_CHECK_FROZEN) 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++; } } if (already_done) continue; /* Search for a nominated pair (or selected to be nominated pair) * from another component of this stream. */ for (j = stream->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; if (p->component_id == component->id) continue; if (p->nominated || (p->use_candidate_on_next_check && p->state != NICE_CHECK_FAILED)) { other_component_pair = p; break; } } if (other_stream_pair == NULL && other_component_pair == NULL) first_nomination = TRUE; /* We choose a pair to be nominated in the list of valid * pairs. * * this pair will be the one with the highest priority, * when we don't have other nominated pairs in other * components and in other streams * * this pair will be a pair compatible with another nominated * pair from another component if we found one. * * else this pair will be a pair compatible with another * nominated pair from another stream if we found one. * */ for (j = stream->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; /* note: highest priority item selected (list always sorted) */ if (p->component_id == component->id && !p->nominated && !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); if (this_component_pair == NULL) /* highest priority pair */ this_component_pair = p; lcand1 = p->local; rcand1 = p->remote; if (first_nomination) /* use the highest priority pair */ break; if (other_component_pair) { lcand2 = other_component_pair->local; rcand2 = other_component_pair->remote; } if (other_component_pair && lcand1->transport == lcand2->transport && nice_address_equal_no_port (&lcand1->addr, &lcand2->addr) && nice_address_equal_no_port (&rcand1->addr, &rcand2->addr)) { /* else continue the research with lower priority * pairs, compatible with a nominated pair of * another component */ this_component_pair = p; found_other_component_pair = TRUE; break; } if (other_stream_pair) { lcand2 = other_stream_pair->local; rcand2 = other_stream_pair->remote; } if (other_stream_pair && other_component_pair == NULL && lcand1->transport == lcand2->transport && nice_address_equal_no_port (&lcand1->addr, &lcand2->addr) && nice_address_equal_no_port (&rcand1->addr, &rcand2->addr)) { /* else continue the research with lower priority * pairs, compatible with a nominated pair of * another stream */ this_component_pair = p; found_other_stream_pair = TRUE; break; } } } /* No valid pair for this component */ if (this_component_pair == NULL) continue; /* The stopping criterion tries to select a set of pairs of * the same kind (transport/type) for all components of a * stream, and for all streams, when possible (see last * paragraph). * * When no stream has nominated a pair yet, we apply the * following criterion : * - stop if we have a valid host-host pair * - or stop if we have at least "some* (2 in the current * implementation) valid pairs, and select the best one * - or stop if the conncheck cannot evolve more * * Else when the stream has a nominated pair in another * component we apply this criterion: * - stop if we have a valid pair of the same kind than this * other nominated pair. * - or stop if the conncheck cannot evolve more * * Else when another stream has a nominated pair we apply the * following criterion: * - stop if we have a valid pair of the same kind than the * other nominated pair. * - or stop if the conncheck cannot evolve more * * When no further evolution of the conncheck is possible, we * prefer to select the best valid pair we have, *even* if it * is not compatible with the transport of another stream of * component. We think it's still a better choice than marking * this component 'failed'. */ stopping_criterion = FALSE; if (first_nomination && p_host_host_valid > 0) { stopping_criterion = TRUE; nice_debug ("Agent %p : stopping criterion: " "valid host-host pair", agent); } else if (first_nomination && p_valid >= NICE_MIN_NUMBER_OF_VALID_PAIRS) { stopping_criterion = TRUE; nice_debug ("Agent %p : stopping criterion: " "*some* valid pairs", agent); } else if (found_other_component_pair) { stopping_criterion = TRUE; nice_debug ("Agent %p : stopping criterion: " "matching pair in another component", agent); } else if (found_other_stream_pair) { stopping_criterion = TRUE; nice_debug ("Agent %p : stopping criterion: " "matching pair in another stream", agent); } else if (p_waiting == 0 && p_inprogress == 0 && p_frozen == 0) { stopping_criterion = TRUE; nice_debug ("Agent %p : stopping criterion: " "no more pairs to check", agent); } if (!stopping_criterion) continue; /* when the stopping criterion is reached, we add the * selected pair for this component to the triggered checks * list */ nice_debug ("Agent %p : restarting check of %s:%s pair %p with " "USE-CANDIDATE attrib (regular nomination) for " "stream %d component %d", agent, nice_candidate_transport_to_string ( this_component_pair->local->transport), nice_candidate_transport_to_string ( this_component_pair->remote->transport), this_component_pair, stream->id, component->id); this_component_pair->use_candidate_on_next_check = TRUE; priv_add_pair_to_triggered_check_queue (agent, this_component_pair); keep_timer_going = TRUE; } } } else if (agent->controlling_mode) { for (i = stream->components; i; i = i->next) { NiceComponent *component = i->data; for (j = stream->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; /* note: highest priority item selected (list always sorted) */ if (p->component_id == component->id && (p->state == NICE_CHECK_SUCCEEDED || p->state == NICE_CHECK_DISCOVERED)) { nice_debug ("Agent %p : restarting check of pair %p as the " "nominated pair.", agent, p); p->nominated = TRUE; conn_check_update_selected_pair (agent, component, p); priv_add_pair_to_triggered_check_queue (agent, p); keep_timer_going = TRUE; break; /* move to the next component */ } } } } } if (stream->tick_counter++ % 50 == 0) nice_debug ("Agent %p : stream %u: timer tick #%u: %u frozen, " "%u in-progress, %u waiting, %u succeeded, %u discovered, " "%u nominated, %u waiting-for-nom, %u valid", agent, stream->id, stream->tick_counter, s_frozen, s_inprogress, s_waiting, s_succeeded, s_discovered, s_nominated, s_waiting_for_nomination, s_valid); return keep_timer_going; } static void conn_check_stop (NiceAgent *agent) { if (agent->conncheck_timer_source == NULL) return; g_source_destroy (agent->conncheck_timer_source); g_source_unref (agent->conncheck_timer_source); agent->conncheck_timer_source = NULL; agent->conncheck_ongoing_idle_delay = 0; } /* * Timer callback that handles initiating and managing connectivity * checks (paced by the Ta timer). * * This function is designed for the g_timeout_add() interface. * * @return will return FALSE when no more pending timers. */ static gboolean priv_conn_check_tick_agent_locked (NiceAgent *agent, gpointer user_data) { gboolean keep_timer_going = FALSE; gboolean stun_sent = FALSE; GSList *i; /* step: process triggered checks * these steps are ordered by priority, since a single stun request * is sent per callback, we process the important steps first. * * perform a single stun request per timer callback, * to respect stun pacing */ for (i = agent->streams; i && !stun_sent; i = i->next) { NiceStream *stream = i->data; stun_sent = priv_conn_check_triggered_check (agent, stream); } /* step: process ongoing STUN transactions */ for (i = agent->streams; i && !stun_sent; i = i->next) { NiceStream *stream = i->data; stun_sent = priv_conn_check_tick_stream (agent, stream); } /* step: process ordinary checks */ for (i = agent->streams; i && !stun_sent; i = i->next) { NiceStream *stream = i->data; stun_sent = priv_conn_check_ordinary_check (agent, stream); } if (stun_sent) keep_timer_going = TRUE; /* step: try to nominate a pair */ for (i = agent->streams; i; i = i->next) { NiceStream *stream = i->data; if (priv_conn_check_tick_stream_nominate (agent, stream)) keep_timer_going = TRUE; } /* note: we provide a grace period before declaring a component as * failed. Components marked connected, and then ready follow another * code path, and are not concerned by this grace period. */ if (!keep_timer_going && agent->conncheck_ongoing_idle_delay == 0) nice_debug ("Agent %p : waiting %d msecs before checking " "for failed components.", agent, agent->idle_timeout); if (keep_timer_going) agent->conncheck_ongoing_idle_delay = 0; else agent->conncheck_ongoing_idle_delay += agent->timer_ta; /* step: stop timer if no work left */ if (!keep_timer_going && agent->conncheck_ongoing_idle_delay >= agent->idle_timeout) { nice_debug ("Agent %p : checking for failed components now.", agent); for (i = agent->streams; i; i = i->next) { NiceStream *stream = i->data; priv_update_check_list_failed_components (agent, stream); } nice_debug ("Agent %p : %s: stopping conncheck timer", agent, G_STRFUNC); priv_print_conn_check_lists (agent, G_STRFUNC, ", conncheck timer stopped"); /* Stopping the timer so destroy the source.. this will allow the timer to be reset if we get a set_remote_candidates after this point */ conn_check_stop (agent); /* XXX: what to signal, is all processing now really done? */ nice_debug ("Agent %p : changing conncheck state to COMPLETED.", agent); return FALSE; } return TRUE; } static gboolean priv_conn_remote_consent_tick_agent_locked ( NiceAgent *agent, gpointer pointer) { CandidatePair *pair = (CandidatePair *) pointer; guint64 consent_timeout = 0; guint64 now; if (pair->remote_consent.tick_source) { g_source_destroy (pair->remote_consent.tick_source); g_source_unref (pair->remote_consent.tick_source); } pair->remote_consent.tick_source = NULL; if (agent->consent_freshness) { consent_timeout = NICE_AGENT_TIMER_CONSENT_TIMEOUT * 1000; } else { consent_timeout = NICE_AGENT_TIMER_KEEPALIVE_TIMEOUT* 1000; } now = g_get_monotonic_time(); if (now - pair->remote_consent.last_received > consent_timeout) { guint64 time_since = now - pair->remote_consent.last_received; pair->remote_consent.have = FALSE; nice_debug ("Agent %p : pair %p consent for stream/component %u/%u timed " "out! -> FAILED. Last consent received: %" G_GUINT64_FORMAT ".%" G_GUINT64_FORMAT "s ago", agent, pair, pair->keepalive.stream_id, pair->keepalive.component_id, time_since / G_USEC_PER_SEC, time_since % G_USEC_PER_SEC); agent_signal_component_state_change (agent, pair->keepalive.stream_id, pair->keepalive.component_id, NICE_COMPONENT_STATE_FAILED); } else { guint64 delay = (consent_timeout - (now - pair->remote_consent.last_received)) / 1000; nice_debug ("Agent %p : pair %p rechecking consent in %" G_GUINT64_FORMAT ".%03" G_GUINT64_FORMAT "s", agent, pair, delay / 1000, delay % 1000); agent_timeout_add_with_context (agent, &pair->remote_consent.tick_source, "Pair remote consent", delay, priv_conn_remote_consent_tick_agent_locked, pair); } return FALSE; } static guint32 peer_reflexive_candidate_priority (NiceAgent *agent, NiceCandidate *local_candidate) { NiceCandidate *candidate_priority = nice_candidate_new (NICE_CANDIDATE_TYPE_PEER_REFLEXIVE); guint32 priority; candidate_priority->transport = local_candidate->transport; candidate_priority->component_id = local_candidate->component_id; candidate_priority->base_addr = local_candidate->addr; if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE) { priority = nice_candidate_jingle_priority (candidate_priority); } else if (agent->compatibility == NICE_COMPATIBILITY_MSN || agent->compatibility == NICE_COMPATIBILITY_OC2007) { priority = nice_candidate_msn_priority (candidate_priority); } else if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) { priority = nice_candidate_ms_ice_priority (candidate_priority, agent->reliable, FALSE); } else { priority = nice_candidate_ice_priority (candidate_priority, agent->reliable, FALSE); } nice_candidate_free (candidate_priority); return priority; } /* Returns the priority of a local candidate of type peer-reflexive that * would be learned as a consequence of a check from this local * candidate. See RFC 5245, section 7.1.2.1. "PRIORITY and USE-CANDIDATE". * RFC 5245 is more explanatory than RFC 8445 on this detail. * * Apply to local candidates of type host only, because candidates of type * relay are supposed to have a public IP address, that wont generate * a peer-reflexive address. Server-reflexive candidates are not * concerned too, because no STUN request is sent with a local candidate * of this type. */ static guint32 stun_request_priority (NiceAgent *agent, NiceCandidate *local_candidate) { if (local_candidate->type == NICE_CANDIDATE_TYPE_HOST) return peer_reflexive_candidate_priority (agent, local_candidate); else return local_candidate->priority; } static void ms_ice2_legacy_conncheck_send(StunMessage *msg, NiceSocket *sock, const NiceAddress *remote_addr) { uint32_t *fingerprint_attr; uint32_t fingerprint_orig; uint16_t fingerprint_len; size_t buffer_len; if (msg->agent->ms_ice2_send_legacy_connchecks == FALSE) { return; } fingerprint_attr = (uint32_t *)stun_message_find (msg, STUN_ATTRIBUTE_FINGERPRINT, &fingerprint_len); if (fingerprint_attr == NULL) { nice_debug ("FINGERPRINT not found."); return; } if (fingerprint_len != sizeof (fingerprint_orig)) { nice_debug ("Unexpected FINGERPRINT length %u.", fingerprint_len); return; } memcpy (&fingerprint_orig, fingerprint_attr, sizeof (fingerprint_orig)); buffer_len = stun_message_length (msg); *fingerprint_attr = stun_fingerprint (msg->buffer, buffer_len, TRUE); agent_socket_send (sock, remote_addr, buffer_len, (gchar *)msg->buffer); memcpy (fingerprint_attr, &fingerprint_orig, sizeof (fingerprint_orig)); } /* * Timer callback that handles initiating and managing connectivity * checks (paced by the Ta timer). * * This function is designed for the g_timeout_add() interface. * * @return will return FALSE when no more pending timers. */ static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent) { GSList *i, *j, *k; int errors = 0; size_t buf_len = 0; guint64 now; guint64 min_next_tick; guint64 next_timer_tick; now = g_get_monotonic_time (); if (agent->consent_freshness) { min_next_tick = now + 1000 * NICE_AGENT_TIMER_MIN_CONSENT_INTERVAL; } else { min_next_tick = now + 1000 * NICE_AGENT_TIMER_TR_DEFAULT; } /* case 1: session established and media flowing * (ref ICE sect 11 "Keepalives" RFC-8445) * TODO: without RFC 7675 (consent freshness), keepalives should be sent * only when no packet has been sent on that pair in the last Tr seconds, * and not unconditionally. */ for (i = agent->streams; i; i = i->next) { NiceStream *stream = i->data; for (j = stream->components; j; j = j->next) { NiceComponent *component = j->data; if (component->selected_pair.local != NULL) { CandidatePair *p = &component->selected_pair; /* Disable keepalive checks on TCP candidates unless explicitly enabled */ if (p->local->c.transport != NICE_CANDIDATE_TRANSPORT_UDP && !NICE_AGENT_DO_KEEPALIVE_CONNCHECKS (agent)) continue; if (p->keepalive.next_tick) { if (p->keepalive.next_tick < min_next_tick) min_next_tick = p->keepalive.next_tick; if (now < p->keepalive.next_tick) continue; } if (NICE_AGENT_DO_KEEPALIVE_CONNCHECKS (agent)) { uint8_t uname[NICE_STREAM_MAX_UNAME]; size_t uname_len = priv_create_username (agent, agent_find_stream (agent, stream->id), component->id, (NiceCandidate *) p->remote, (NiceCandidate *) p->local, uname, sizeof (uname), FALSE); uint8_t *password = NULL; size_t password_len = priv_get_password (agent, agent_find_stream (agent, stream->id), (NiceCandidate *) p->remote, &password); uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6]; StunMessage stun_message; if (uname_len > 0) { if (nice_debug_is_enabled ()) { gchar tmpbuf[INET6_ADDRSTRLEN]; nice_address_to_string (&p->remote->c.addr, tmpbuf); nice_debug ("Agent %p : Keepalive STUN-CC REQ to '%s:%u', " "(c-id:%u), username='%.*s' (%" G_GSIZE_FORMAT "), " "password='%.*s' (%" G_GSIZE_FORMAT "), priority=%08x.", agent, tmpbuf, nice_address_get_port (&p->remote->c.addr), component->id, (int) uname_len, uname, uname_len, (int) password_len, password, password_len, p->stun_priority); } buf_len = stun_usage_ice_conncheck_create (&component->stun_agent, &stun_message, stun_buffer, sizeof(stun_buffer), uname, uname_len, password, password_len, agent->controlling_mode, agent->controlling_mode, p->stun_priority, agent->tie_breaker, NULL, agent_to_ice_compatibility (agent)); nice_debug ("Agent %p: conncheck created %zd - %p", agent, buf_len, stun_message.buffer); if (buf_len > 0) { /* random range over 0.8 -> 1.2 as specified in RFC7675 */ double modifier = g_random_double() * 0.4 + 0.8; guint64 delay = 1000 * MAX((guint64) ((NICE_AGENT_TIMER_CONSENT_DEFAULT) * modifier), NICE_AGENT_TIMER_MIN_CONSENT_INTERVAL); p->keepalive.stream_id = stream->id; p->keepalive.component_id = component->id; p->keepalive.next_tick = now + delay; if (p->remote_consent.have) { if (p->remote_consent.last_received == 0) { p->remote_consent.last_received = g_get_monotonic_time(); } priv_conn_remote_consent_tick_agent_locked (agent, p); } agent->media_after_tick = FALSE; /* send the conncheck */ agent_socket_send (p->local->sockptr, &p->remote->c.addr, buf_len, (gchar *) stun_buffer); next_timer_tick = now + agent->timer_ta * 1000; goto done; } else { ++errors; } } } else { uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6]; StunMessage stun_message; buf_len = stun_usage_bind_keepalive (&component->stun_agent, &stun_message, stun_buffer, sizeof(stun_buffer)); if (buf_len > 0) { agent_socket_send (p->local->sockptr, &p->remote->c.addr, buf_len, (gchar *) stun_buffer); p->keepalive.next_tick = now + 1000 * NICE_AGENT_TIMER_TR_DEFAULT; if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) { ms_ice2_legacy_conncheck_send (&stun_message, p->local->sockptr, &p->remote->c.addr); } if (nice_debug_is_enabled ()) { gchar tmpbuf[INET6_ADDRSTRLEN]; nice_address_to_string (&p->local->c.base_addr, tmpbuf); nice_debug ("Agent %p : resending STUN to keep the " "selected base address %s:%u alive in s%d/c%d.", agent, tmpbuf, nice_address_get_port (&p->local->c.base_addr), stream->id, component->id); } next_timer_tick = now + agent->timer_ta * 1000; goto done; } else { ++errors; } } } } } /* case 2: connectivity establishment ongoing * (ref ICE sect 5.1.1.4 "Keeping Candidates Alive" RFC-8445) */ for (i = agent->streams; i; i = i->next) { NiceStream *stream = i->data; for (j = stream->components; j; j = j->next) { NiceComponent *component = j->data; if (component->state < NICE_COMPONENT_STATE_CONNECTED && agent->stun_server_ip) { NiceAddress stun_server; if (nice_address_set_from_string (&stun_server, agent->stun_server_ip)) { StunAgent stun_agent; uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE_IPV6]; StunMessage stun_message; size_t buffer_len = 0; nice_address_set_port (&stun_server, agent->stun_server_port); nice_agent_init_stun_agent (agent, &stun_agent); buffer_len = stun_usage_bind_create (&stun_agent, &stun_message, stun_buffer, sizeof(stun_buffer)); for (k = component->local_candidates; k; k = k->next) { NiceCandidateImpl *candidate = (NiceCandidateImpl *) k->data; if (candidate->c.type == NICE_CANDIDATE_TYPE_HOST && candidate->c.transport == NICE_CANDIDATE_TRANSPORT_UDP && nice_address_ip_version (&candidate->c.addr) == nice_address_ip_version (&stun_server)) { if (candidate->keepalive_next_tick) { if (candidate->keepalive_next_tick < min_next_tick) min_next_tick = candidate->keepalive_next_tick; if (now < candidate->keepalive_next_tick) continue; } /* send the conncheck */ if (nice_debug_is_enabled ()) { gchar tmpbuf[INET6_ADDRSTRLEN]; nice_address_to_string (&candidate->c.addr, tmpbuf); nice_debug ("Agent %p : resending STUN to keep the local " "candidate %s:%u alive in s%d/c%d.", agent, tmpbuf, nice_address_get_port (&candidate->c.addr), stream->id, component->id); } agent_socket_send (candidate->sockptr, &stun_server, buffer_len, (gchar *)stun_buffer); candidate->keepalive_next_tick = now + 1000 * NICE_AGENT_TIMER_TR_DEFAULT; next_timer_tick = now + agent->timer_ta * 1000; goto done; } } } } } } next_timer_tick = min_next_tick; done: if (errors) { nice_debug ("Agent %p : %s: stopping keepalive timer", agent, G_STRFUNC); return FALSE; } if (agent->keepalive_timer_source) { g_source_destroy (agent->keepalive_timer_source); g_source_unref (agent->keepalive_timer_source); agent->keepalive_timer_source = NULL; } agent_timeout_add_with_context (agent, &agent->keepalive_timer_source, "Connectivity keepalive timeout", (next_timer_tick - now)/ 1000, priv_conn_keepalive_tick_agent_locked, NULL); return TRUE; } static gboolean priv_conn_keepalive_tick_agent_locked (NiceAgent *agent, gpointer pointer) { gboolean ret; ret = priv_conn_keepalive_tick_unlocked (agent); if (ret == FALSE) { if (agent->keepalive_timer_source) { g_source_destroy (agent->keepalive_timer_source); g_source_unref (agent->keepalive_timer_source); agent->keepalive_timer_source = NULL; } } return ret; } static gboolean priv_turn_allocate_refresh_retransmissions_tick_agent_locked ( NiceAgent *agent, gpointer pointer) { CandidateRefresh *cand = (CandidateRefresh *) pointer; g_source_destroy (cand->tick_source); g_source_unref (cand->tick_source); cand->tick_source = NULL; switch (stun_timer_refresh (&cand->timer)) { case STUN_USAGE_TIMER_RETURN_TIMEOUT: { /* Time out */ StunTransactionId id; stun_message_id (&cand->stun_message, id); stun_agent_forget_transaction (&cand->stun_agent, id); refresh_free (agent, cand); break; } case STUN_USAGE_TIMER_RETURN_RETRANSMIT: /* Retransmit */ agent_socket_send (cand->nicesock, &cand->server, stun_message_length (&cand->stun_message), (gchar *)cand->stun_buffer); /* fall through */ case STUN_USAGE_TIMER_RETURN_SUCCESS: agent_timeout_add_with_context (agent, &cand->tick_source, "Candidate TURN refresh", stun_timer_remainder (&cand->timer), priv_turn_allocate_refresh_retransmissions_tick_agent_locked, cand); break; default: /* Nothing to do. */ break; } return G_SOURCE_REMOVE; } static void priv_turn_allocate_refresh_tick_unlocked (NiceAgent *agent, CandidateRefresh *cand) { uint8_t *username; gsize username_len; uint8_t *password; gsize password_len; size_t buffer_len = 0; StunUsageTurnCompatibility turn_compat = agent_to_turn_compatibility (agent); username = (uint8_t *)cand->candidate->turn->username; username_len = (size_t) strlen (cand->candidate->turn->username); password = (uint8_t *)cand->candidate->turn->password; password_len = (size_t) strlen (cand->candidate->turn->password); if (turn_compat == STUN_USAGE_TURN_COMPATIBILITY_MSN || turn_compat == STUN_USAGE_TURN_COMPATIBILITY_OC2007) { username = cand->candidate->turn->decoded_username; password = cand->candidate->turn->decoded_password; username_len = cand->candidate->turn->decoded_username_len; password_len = cand->candidate->turn->decoded_password_len; } buffer_len = stun_usage_turn_create_refresh (&cand->stun_agent, &cand->stun_message, cand->stun_buffer, sizeof(cand->stun_buffer), cand->stun_resp_msg.buffer == NULL ? NULL : &cand->stun_resp_msg, cand->disposing ? 0 : -1, username, username_len, password, password_len, turn_compat); nice_debug ("Agent %p : Sending allocate Refresh %zd", agent, buffer_len); if (cand->tick_source != NULL) { g_source_destroy (cand->tick_source); g_source_unref (cand->tick_source); cand->tick_source = NULL; } if (buffer_len > 0) { stun_timer_start (&cand->timer, agent->stun_initial_timeout, agent->stun_max_retransmissions); /* send the refresh */ agent_socket_send (cand->nicesock, &cand->server, buffer_len, (gchar *)cand->stun_buffer); agent_timeout_add_with_context (agent, &cand->tick_source, "Candidate TURN refresh", stun_timer_remainder (&cand->timer), priv_turn_allocate_refresh_retransmissions_tick_agent_locked, cand); } } /* * Timer callback that handles refreshing TURN allocations * * This function is designed for the g_timeout_add() interface. * * @return will return FALSE when no more pending timers. */ static gboolean priv_turn_allocate_refresh_tick_agent_locked (NiceAgent *agent, gpointer pointer) { CandidateRefresh *cand = (CandidateRefresh *) pointer; priv_turn_allocate_refresh_tick_unlocked (agent, cand); return G_SOURCE_REMOVE; } /* * Initiates the next pending connectivity check. */ static void priv_schedule_next (NiceAgent *agent) { if (agent->discovery_unsched_items > 0) nice_debug ("Agent %p : WARN: starting conn checks before local candidate gathering is finished.", agent); /* step: schedule timer if not running yet */ if (agent->conncheck_timer_source == NULL) { agent_timeout_add_with_context (agent, &agent->conncheck_timer_source, "Connectivity check schedule", agent->timer_ta, priv_conn_check_tick_agent_locked, NULL); } /* step: also start the keepalive timer */ if (agent->keepalive_timer_source == NULL) { agent_timeout_add_with_context (agent, &agent->keepalive_timer_source, "Connectivity keepalive timeout", agent->timer_ta, priv_conn_keepalive_tick_agent_locked, NULL); } } /* * Compares two connectivity check items. Checkpairs are sorted * in descending priority order, with highest priority item at * the start of the list. */ gint conn_check_compare (const CandidateCheckPair *a, const CandidateCheckPair *b) { if (a->priority > b->priority) return -1; else if (a->priority < b->priority) return 1; return 0; } /* Find a transport compatible with a given socket. * * Returns TRUE when a matching transport can be guessed from * the type of the socket in an unambiguous way. */ static gboolean nice_socket_has_compatible_transport (NiceSocket *socket, NiceCandidateTransport *transport) { gboolean found = TRUE; g_assert (socket); g_assert (transport); switch (socket->type) { case NICE_SOCKET_TYPE_TCP_BSD: if (nice_tcp_bsd_socket_get_passive_parent (socket)) *transport = NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE; else *transport = NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE; break; case NICE_SOCKET_TYPE_TCP_PASSIVE: *transport = NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE; break; case NICE_SOCKET_TYPE_TCP_ACTIVE: *transport = NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE; break; case NICE_SOCKET_TYPE_UDP_BSD: *transport = NICE_CANDIDATE_TRANSPORT_UDP; break; default: found = FALSE; } return found; } /* Test if a local socket and a local candidate are compatible. This * function does supplementary tests when the address and port are not * sufficient to give a unique candidate. We try to avoid comparing * directly the sockptr value, when possible, to rely on objective * properties of the candidate and the socket instead, and we also * choose to ignore the conncheck list for the same reason. */ static gboolean local_candidate_and_socket_compatible (NiceAgent *agent, NiceCandidate *lcand, NiceSocket *socket) { gboolean ret = TRUE; NiceCandidateTransport transport; NiceCandidateImpl *lc = (NiceCandidateImpl *) lcand; g_assert (socket); g_assert (lcand); if (nice_socket_has_compatible_transport (socket, &transport)) { ret = (lcand->transport == transport); /* tcp-active discovered peer-reflexive local candidate, where * socket is the tcp connect related socket */ if (ret && transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE && nice_address_get_port (&lcand->addr) > 0) ret = (lc->sockptr == socket); } else if (socket->type == NICE_SOCKET_TYPE_UDP_TURN) /* Socket of type udp-turn will match a unique local candidate * by its sockptr value. An an udp-turn socket doesn't carry enough * information when base socket is udp-turn-over-tcp to disambiguate * between a tcp-act and a tcp-pass local candidate. */ ret = (lc->sockptr == socket); return ret; } /* Test if a local socket and a remote candidate are compatible. * This function is very close to its local candidate counterpart, * the difference is that we also use information from the local * candidate we may have identified previously. This is needed * to disambiguate the transport of the candidate with a socket * of type udp-turn. * */ static gboolean remote_candidate_and_socket_compatible (NiceAgent *agent, NiceCandidate *lcand, NiceCandidate *rcand, NiceSocket *socket) { gboolean ret = TRUE; NiceCandidateTransport transport; g_assert (socket); g_assert (rcand); if (nice_socket_has_compatible_transport (socket, &transport)) ret = (conn_check_match_transport (rcand->transport) == transport); /* This supplementary test with the local candidate is needed with * socket of type udp-turn, the type doesn't allow to disambiguate * between a tcp-pass and a tcp-act remote candidate */ if (lcand && ret) ret = (conn_check_match_transport (lcand->transport) == rcand->transport); return ret; } void conn_check_remote_candidates_set(NiceAgent *agent, NiceStream *stream, NiceComponent *component) { GList *i; GSList *j; NiceCandidate *lcand = NULL, *rcand = NULL; nice_debug ("Agent %p : conn_check_remote_candidates_set %u %u", agent, stream->id, component->id); if (stream->remote_ufrag[0] == 0) return; if (component->incoming_checks.head) nice_debug ("Agent %p : credentials have been set, " "we can process incoming checks", agent); for (i = component->incoming_checks.head; i;) { IncomingCheck *icheck = i->data; GList *i_next = i->next; nice_debug ("Agent %p : replaying icheck=%p (sock=%p)", agent, icheck, icheck->local_socket); /* sect 7.2.1.3., "Learning Peer Reflexive Candidates", has to * be handled separately */ for (j = component->local_candidates; j; j = j->next) { NiceCandidate *cand = j->data; NiceAddress *addr; if (cand->type == NICE_CANDIDATE_TYPE_RELAYED) addr = &cand->addr; else addr = &cand->base_addr; if (nice_address_equal (&icheck->local_socket->addr, addr) && local_candidate_and_socket_compatible (agent, cand, icheck->local_socket)) { lcand = cand; break; } } if (lcand == NULL) { for (j = component->local_candidates; j; j = j->next) { NiceCandidate *cand = j->data; NiceAddress *addr = &cand->base_addr; /* tcp-active (not peer-reflexive discovered) local candidate, where * socket is the tcp connect related socket */ if (nice_address_equal_no_port (&icheck->local_socket->addr, addr) && nice_address_get_port (&cand->addr) == 0 && cand->transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE && local_candidate_and_socket_compatible (agent, cand, icheck->local_socket)) { lcand = cand; break; } } } g_assert (lcand != NULL); for (j = component->remote_candidates; j; j = j->next) { NiceCandidate *cand = j->data; if (nice_address_equal (&cand->addr, &icheck->from) && remote_candidate_and_socket_compatible (agent, lcand, cand, icheck->local_socket)) { rcand = cand; break; } } if (lcand->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) { CandidateCheckPair *pair = NULL; for (j = stream->conncheck_list; j; j = j->next) { CandidateCheckPair *p = j->data; if (lcand == p->local && rcand == p->remote) { pair = p; break; } } if (pair == NULL) priv_conn_check_add_for_candidate_pair_matched (agent, stream->id, component, lcand, rcand, NICE_CHECK_WAITING); } priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, rcand); if (icheck->use_candidate) priv_mark_pair_nominated (agent, stream, component, lcand, rcand); if (icheck->username) g_free (icheck->username); g_slice_free (IncomingCheck, icheck); g_queue_delete_link (&component->incoming_checks, i); i = i_next; } } /* * Handle any processing steps for connectivity checks after * remote credentials have been set. This function handles * the special case where answerer has sent us connectivity * checks before the answer (containing credentials information), * reaches us. The special case is documented in RFC 5245 sect 7.2. * ). */ void conn_check_remote_credentials_set(NiceAgent *agent, NiceStream *stream) { GSList *j; for (j = stream->components; j ; j = j->next) { NiceComponent *component = j->data; conn_check_remote_candidates_set(agent, stream, component); } } /* * Enforces the upper limit for connectivity checks by dropping * lower-priority pairs as described RFC 8445 section 6.1.2.5. See also * conn_check_add_for_candidate(). * Returns TRUE if the pair in argument is one of the deleted pairs. */ static gboolean priv_limit_conn_check_list_size (NiceAgent *agent, NiceStream *stream, CandidateCheckPair *pair) { guint valid = 0; guint cancelled = 0; gboolean deleted = FALSE; GSList *item = stream->conncheck_list; while (item) { CandidateCheckPair *p = item->data; GSList *next = item->next; valid++; /* We remove lower-priority pairs, but only the ones that can be * safely discarded without breaking an ongoing conncheck process. * This only includes pairs that are in the frozen state (those * initially added when remote candidates are received) or in failed * state. Pairs in any other state play a role in the conncheck, and * there removal may lead to a failing conncheck that would succeed * otherwise. * * We also remove failed pairs from the list unconditionally. */ if ((valid > agent->max_conn_checks && p->state == NICE_CHECK_FROZEN) || p->state == NICE_CHECK_FAILED) { if (p == pair) deleted = TRUE; nice_debug ("Agent %p : pair %p removed.", agent, p); candidate_check_pair_free (agent, p); stream->conncheck_list = g_slist_delete_link (stream->conncheck_list, item); cancelled++; } item = next; } if (cancelled > 0) nice_debug ("Agent %p : Pruned %d pairs. " "Conncheck list has %d elements left. " "Maximum connchecks allowed : %d", agent, cancelled, valid - cancelled, agent->max_conn_checks); return deleted; } /* * Changes the selected pair for the component if 'pair' * has higher priority than the currently selected pair. See * RFC 8445 sect 8.1.1. "Nominating Pairs" */ void conn_check_update_selected_pair (NiceAgent *agent, NiceComponent *component, CandidateCheckPair *pair) { CandidatePair cpair = { 0, }; g_assert (component); g_assert (pair); /* pair is expected to have the nominated flag */ g_assert (pair->nominated); if (pair->priority > component->selected_pair.priority) { gchar priority[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE]; nice_candidate_pair_priority_to_string (pair->priority, priority); nice_debug ("Agent %p : changing SELECTED PAIR for component %u: %s:%s " "(prio:%s).", agent, component->id, pair->local->foundation, pair->remote->foundation, priority); cpair.local = (NiceCandidateImpl *) pair->local; cpair.remote = (NiceCandidateImpl *) pair->remote; cpair.priority = pair->priority; cpair.stun_priority = pair->stun_priority; cpair.remote_consent.have = TRUE; nice_component_update_selected_pair (agent, component, &cpair); priv_conn_keepalive_tick_unlocked (agent); agent_signal_new_selected_pair (agent, pair->stream_id, component->id, pair->local, pair->remote); } } /* * Updates the check list state. * * Implements parts of the algorithm described in * ICE sect 8.1.2. "Updating States" (RFC 5245): if for any * component, all checks have been completed and have * 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*/ 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; /* There is still discovery ogoing for this stream, * so don't fail any of it's candidates. */ if (d->stream_id == stream->id && !d->done) return; } if (agent->discovery_list != NULL) return; /* note: iterate the conncheck list for each component separately */ for (c = 0; c < components; c++) { NiceComponent *component = NULL; if (!agent_find_component (agent, stream->id, c+1, NULL, &component)) continue; nominated = 0; completed = TRUE; for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; g_assert (p->stream_id == stream->id); if (p->component_id == (c + 1)) { if (p->nominated) ++nominated; if (p->state != NICE_CHECK_FAILED && p->state != NICE_CHECK_SUCCEEDED && p->state != NICE_CHECK_DISCOVERED) completed = FALSE; } } /* 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 (completed && nominated == 0 && component != NULL && component->remote_candidates != NULL) agent_signal_component_state_change (agent, stream->id, (c + 1), /* component-id */ NICE_COMPONENT_STATE_FAILED); } } /* * Updates the check list state for a stream component. * * Implements the algorithm described in ICE sect 8.1.2 * "Updating States" (ID-19) as it applies to checks of * a certain component. If there are any nominated pairs, * ICE processing may be concluded, and component state is * changed to READY. * * Sends a component state changesignal via 'agent'. */ void conn_check_update_check_list_state_for_ready (NiceAgent *agent, NiceStream *stream, NiceComponent *component) { GSList *i; guint valid = 0, nominated = 0; g_assert (component); /* step: search for at least one nominated pair */ for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; if (p->component_id == component->id) { if (p->valid) { ++valid; if (p->nominated == TRUE) { ++nominated; } } } } if (nominated > 0) { /* Only go to READY if no checks are left in progress. If there are * any that are kept, then this function will be called again when the * conncheck tick timer finishes them all */ if (priv_prune_pending_checks (agent, stream, component) == 0) { /* Continue through the states to give client code a nice * logical progression. See http://phabricator.freedesktop.org/D218 for * discussion. */ if (component->state < NICE_COMPONENT_STATE_CONNECTING || 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_CONNECTED) agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_READY); } } nice_debug ("Agent %p : conn.check list status: %u nominated, %u valid, c-id %u.", agent, nominated, valid, component->id); } /* * The remote party has signalled that the candidate pair * described by 'component' and 'remotecand' is nominated * for use. * return TRUE if at least one matching pair is found and got nominated (or marked to be nominated on response_arrival). */ static gboolean priv_mark_pair_nominated (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceCandidate *localcand, NiceCandidate *remotecand) { GSList *i; gboolean res = FALSE; g_assert (component); if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) && agent->controlling_mode) return res; if (nice_debug_is_verbose()) { gchar remote_str[INET6_ADDRSTRLEN]; gchar local_str[INET6_ADDRSTRLEN]; nice_address_to_string(&remotecand->addr, remote_str); nice_address_to_string(&localcand->addr, local_str); nice_debug ("Agent %p : *** priv_mark_pair_nominated: local candidate %p [%s]:%u, remote candidate %p [%s]:%u", agent, localcand, local_str, nice_address_get_port (&localcand->addr), remotecand, remote_str, nice_address_get_port (&remotecand->addr)); } /* step: search for at least one nominated pair */ for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *pair = i->data; if (nice_debug_is_verbose()) { gchar remote_str[INET6_ADDRSTRLEN]; gchar local_str[INET6_ADDRSTRLEN]; nice_address_to_string(&pair->remote->addr, remote_str); nice_address_to_string(&pair->local->addr, local_str); nice_debug ("Agent %p : *** priv_mark_pair_nominated: conncheck pair %p, state %u, valid %u, nom %u, disc p %p: local candidate %p [%s]:%u, remote candidate %p [%s]:%u", agent, pair, pair->state, pair->valid, pair->nominated, pair->discovered_pair, pair->local, local_str, nice_address_get_port (&pair->local->addr), pair->remote, remote_str, nice_address_get_port (&pair->remote->addr)); } 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 * nominated. */ if (pair->state == NICE_CHECK_SUCCEEDED && pair->discovered_pair != NULL) { nice_debug ("Agent %p : priv_mark_pair_nominated: conncheck pair %p - replace with discovered pair %p", agent, pair, pair->discovered_pair); pair = pair->discovered_pair; g_assert (pair->state == NICE_CHECK_DISCOVERED); } /* If the received Binding request triggered a new check to be * enqueued in the triggered-check queue (Section 7.3.1.4), once * the check is sent and if it generates a successful response, * and generates a valid pair, the agent sets the nominated flag * of the pair to true */ if (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent)) { if (g_slist_find (agent->triggered_check_queue, pair) || pair->state == NICE_CHECK_IN_PROGRESS) { /* This pair is not always in the triggered check list, for * example if it is in-progress with a lower priority than an * already nominated pair. Is that case, it is not rescheduled * for a connection check, see function * priv_schedule_triggered_check(), case NICE_CHECK_IN_PROGRESS. */ pair->mark_nominated_on_response_arrival = TRUE; res = TRUE; nice_debug ("Agent %p : pair %p (%s) is %s, " "will be nominated on response receipt.", agent, pair, pair->foundation, priv_state_to_string (pair->state)); } } 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; } if (pair->valid) { /* 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); conn_check_update_selected_pair (agent, component, pair); if (component->state == NICE_COMPONENT_STATE_CONNECTING) /* step: notify the client of a new component state (must be done * before the possible check list state update step */ agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); } if (pair->nominated) { conn_check_update_check_list_state_for_ready (agent, stream, component); res = TRUE; } } } return res; } /* * Creates a new connectivity check pair and adds it to * the agent's list of checks. */ static CandidateCheckPair *priv_add_new_check_pair (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidateImpl *local, NiceCandidateImpl *remote, NiceCheckState initial_state) { NiceStream *stream; CandidateCheckPair *pair; guint64 priority; g_assert (local != NULL); g_assert (remote != NULL); priority = agent_candidate_pair_priority (agent, (NiceCandidate *) local, (NiceCandidate *) remote); if (component->selected_pair.priority && priority < component->selected_pair.priority) { gchar prio1[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE]; gchar prio2[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE]; nice_candidate_pair_priority_to_string (priority, prio1); nice_candidate_pair_priority_to_string (component->selected_pair.priority, prio2); nice_debug ("Agent %p : do not create a pair that would have a priority " "%s lower than selected pair priority %s.", agent, prio1, prio2); return NULL; } stream = agent_find_stream (agent, stream_id); pair = g_slice_new0 (CandidateCheckPair); pair->stream_id = stream_id; pair->component_id = component->id; pair->local = (NiceCandidate *) local; pair->remote = (NiceCandidate *) remote; /* note: we use the remote sockptr only in the case * of TCP transport */ if (local->c.transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE && remote->c.type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) pair->sockptr = remote->sockptr; else pair->sockptr = local->sockptr; g_snprintf (pair->foundation, NICE_CANDIDATE_PAIR_MAX_FOUNDATION, "%s:%s", local->c.foundation, remote->c.foundation); pair->priority = agent_candidate_pair_priority (agent, (NiceCandidate *) local, (NiceCandidate *) remote); nice_debug ("Agent %p : creating a new pair", agent); SET_PAIR_STATE (agent, pair, initial_state); { gchar tmpbuf1[INET6_ADDRSTRLEN]; gchar tmpbuf2[INET6_ADDRSTRLEN]; nice_address_to_string (&pair->local->addr, tmpbuf1); nice_address_to_string (&pair->remote->addr, tmpbuf2); nice_debug ("Agent %p : new pair %p : [%s]:%u --> [%s]:%u", agent, pair, tmpbuf1, nice_address_get_port (&pair->local->addr), tmpbuf2, nice_address_get_port (&pair->remote->addr)); } pair->stun_priority = stun_request_priority (agent, (NiceCandidate *) local); stream->conncheck_list = g_slist_insert_sorted (stream->conncheck_list, pair, (GCompareFunc)conn_check_compare); priv_schedule_next (agent); nice_debug ("Agent %p : added a new pair %p with foundation '%s' and " "transport %s:%s to stream %u component %u", agent, pair, pair->foundation, nice_candidate_transport_to_string (pair->local->transport), nice_candidate_transport_to_string (pair->remote->transport), stream_id, component->id); if (initial_state == NICE_CHECK_FROZEN) priv_conn_check_unfreeze_maybe (agent, pair); /* implement the hard upper limit for number of checks (see sect 5.7.3 ICE ID-19): */ if (agent->compatibility == NICE_COMPATIBILITY_RFC5245) { if (priv_limit_conn_check_list_size (agent, stream, pair)) return NULL; } return pair; } NiceCandidateTransport conn_check_match_transport (NiceCandidateTransport transport) { switch (transport) { case NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE: return NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE; break; case NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE: return NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE; break; case NICE_CANDIDATE_TRANSPORT_TCP_SO: case NICE_CANDIDATE_TRANSPORT_UDP: default: return transport; break; } } static CandidateCheckPair *priv_conn_check_add_for_candidate_pair_matched ( NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidate *local, NiceCandidate *remote, NiceCheckState initial_state) { CandidateCheckPair *pair; pair = priv_add_new_check_pair (agent, stream_id, component, (NiceCandidateImpl *) local, (NiceCandidateImpl *) remote, initial_state); if (pair) { if (component->state == NICE_COMPONENT_STATE_CONNECTED || component->state == NICE_COMPONENT_STATE_READY) { agent_signal_component_state_change (agent, stream_id, component->id, NICE_COMPONENT_STATE_CONNECTED); } else { agent_signal_component_state_change (agent, stream_id, component->id, NICE_COMPONENT_STATE_CONNECTING); } } return pair; } static gboolean _is_linklocal_to_non_linklocal (NiceAddress *laddr, NiceAddress *raddr) { return nice_address_is_linklocal (laddr) != nice_address_is_linklocal (raddr); } gboolean conn_check_add_for_candidate_pair (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidate *local, NiceCandidate *remote) { gboolean ret = FALSE; g_assert (local != NULL); g_assert (remote != NULL); /* note: do not create pairs where the local candidate is a srv-reflexive * or peer-reflexive (ICE 6.1.2.4. "Pruning the pairs" RFC 8445) */ if ((agent->compatibility == NICE_COMPATIBILITY_RFC5245 || agent->compatibility == NICE_COMPATIBILITY_WLM2009 || agent->compatibility == NICE_COMPATIBILITY_OC2007R2) && (local->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE || local->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE)) { return FALSE; } /* note: do not create pairs where local candidate has TCP passive transport * (ice-tcp-13 6.2. "Forming the Check Lists") */ if (local->transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) { return FALSE; } /* note: match pairs only if transport and address family are the same * and make sure link-local stay link-local */ if (local->transport == conn_check_match_transport (remote->transport) && local->addr.s.addr.sa_family == remote->addr.s.addr.sa_family && !_is_linklocal_to_non_linklocal (&local->addr, &remote->addr)) { if (priv_conn_check_add_for_candidate_pair_matched (agent, stream_id, component, local, remote, NICE_CHECK_FROZEN)) ret = TRUE; } return ret; } /* * Forms new candidate pairs by matching the new remote candidate * 'remote_cand' with all existing local candidates of 'component'. * Implements the logic described in ICE sect 5.7.1. "Forming Candidate * Pairs" (ID-19). * * @param agent context * @param component pointer to the component * @param remote remote candidate to match with * * @return number of checks added, negative on fatal errors */ int conn_check_add_for_candidate (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidate *remote) { GSList *i; int added = 0; int ret = 0; 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 (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) && remote->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) { return added; } for (i = component->local_candidates; i ; i = i->next) { NiceCandidate *local = i->data; if (agent->force_relay && local->type != NICE_CANDIDATE_TYPE_RELAYED) continue; ret = conn_check_add_for_candidate_pair (agent, stream_id, component, local, remote); if (ret) { ++added; } } return added; } /* * Forms new candidate pairs by matching the new local candidate * 'local_cand' with all existing remote candidates of 'component'. * * @param agent context * @param component pointer to the component * @param local local candidate to match with * * @return number of checks added, negative on fatal errors */ int conn_check_add_for_local_candidate (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidate *local) { GSList *i; int added = 0; int ret = 0; 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 (NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) && local->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) { return added; } for (i = component->remote_candidates; i ; i = i->next) { NiceCandidate *remote = i->data; ret = conn_check_add_for_candidate_pair (agent, stream_id, component, local, remote); if (ret) { ++added; } } return added; } /* * Frees the CandidateCheckPair structure pointer to * by 'user data'. Compatible with GDestroyNotify. */ static void candidate_check_pair_free (NiceAgent *agent, CandidateCheckPair *pair) { priv_remove_pair_from_triggered_check_queue (agent, pair); priv_free_all_stun_transactions (pair, NULL); g_slice_free (CandidateCheckPair, pair); } /* * Frees all resources of all connectivity checks. */ void conn_check_free (NiceAgent *agent) { GSList *i; for (i = agent->streams; i; i = i->next) { NiceStream *stream = i->data; if (stream->conncheck_list) { GSList *item; nice_debug ("Agent %p, freeing conncheck_list of stream %p", agent, stream); for (item = stream->conncheck_list; item; item = item->next) candidate_check_pair_free (agent, item->data); g_slist_free (stream->conncheck_list); stream->conncheck_list = NULL; } } conn_check_stop (agent); } /* * Prunes the list of connectivity checks for items related * to stream 'stream_id'. * * @return TRUE on success, FALSE on a fatal error */ void conn_check_prune_stream (NiceAgent *agent, NiceStream *stream) { GSList *i; gboolean keep_going = FALSE; if (stream->conncheck_list) { GSList *item; nice_debug ("Agent %p, freeing conncheck_list of stream %p", agent, stream); for (item = stream->conncheck_list; item; item = item->next) candidate_check_pair_free (agent, item->data); g_slist_free (stream->conncheck_list); stream->conncheck_list = NULL; } for (i = agent->streams; i; i = i->next) { NiceStream *s = i->data; if (s->conncheck_list) { keep_going = TRUE; break; } } if (!keep_going) conn_check_stop (agent); } /* * Fills 'dest' with a username string for use in an outbound connectivity * checks. No more than 'dest_len' characters (including terminating * NULL) is ever written to the 'dest'. */ static size_t priv_gen_username (NiceAgent *agent, guint component_id, gchar *remote, gchar *local, uint8_t *dest, guint dest_len) { guint len = 0; gsize remote_len = strlen (remote); gsize local_len = strlen (local); if (remote_len > 0 && local_len > 0) { if (agent->compatibility == NICE_COMPATIBILITY_RFC5245 && dest_len >= remote_len + local_len + 1) { memcpy (dest, remote, remote_len); len += remote_len; memcpy (dest + len, ":", 1); len++; memcpy (dest + len, local, local_len); len += local_len; } else if ((agent->compatibility == NICE_COMPATIBILITY_WLM2009 || agent->compatibility == NICE_COMPATIBILITY_OC2007R2) && dest_len >= remote_len + local_len + 4 ) { memcpy (dest, remote, remote_len); len += remote_len; memcpy (dest + len, ":", 1); len++; memcpy (dest + len, local, local_len); len += local_len; if (len % 4 != 0) { memset (dest + len, 0, 4 - (len % 4)); len += 4 - (len % 4); } } else if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE && dest_len >= remote_len + local_len) { memcpy (dest, remote, remote_len); len += remote_len; memcpy (dest + len, local, local_len); len += local_len; } else if (agent->compatibility == NICE_COMPATIBILITY_MSN || agent->compatibility == NICE_COMPATIBILITY_OC2007) { gchar component_str[10]; guchar *local_decoded = NULL; guchar *remote_decoded = NULL; gsize local_decoded_len; gsize remote_decoded_len; gsize total_len; int padding; g_snprintf (component_str, sizeof(component_str), "%d", component_id); local_decoded = g_base64_decode (local, &local_decoded_len); remote_decoded = g_base64_decode (remote, &remote_decoded_len); total_len = remote_decoded_len + local_decoded_len + 3 + 2*strlen (component_str); padding = 4 - (total_len % 4); if (dest_len >= total_len + padding) { guchar pad_char[1] = {0}; int i; memcpy (dest, remote_decoded, remote_decoded_len); len += remote_decoded_len; memcpy (dest + len, ":", 1); len++; memcpy (dest + len, component_str, strlen (component_str)); len += strlen (component_str); memcpy (dest + len, ":", 1); len++; memcpy (dest + len, local_decoded, local_decoded_len); len += local_decoded_len; memcpy (dest + len, ":", 1); len++; memcpy (dest + len, component_str, strlen (component_str));; len += strlen (component_str); for (i = 0; i < padding; i++) { memcpy (dest + len, pad_char, 1); len++; } } g_free (local_decoded); g_free (remote_decoded); } } return len; } /* * Fills 'dest' with a username string for use in an outbound connectivity * checks. No more than 'dest_len' characters (including terminating * NULL) is ever written to the 'dest'. */ static size_t priv_create_username (NiceAgent *agent, NiceStream *stream, guint component_id, NiceCandidate *remote, NiceCandidate *local, uint8_t *dest, guint dest_len, gboolean inbound) { gchar *local_username = NULL; gchar *remote_username = NULL; if (remote && remote->username) { remote_username = remote->username; } if (local && local->username) { local_username = local->username; } if (stream) { if (remote_username == NULL) { remote_username = stream->remote_ufrag; } if (local_username == NULL) { local_username = stream->local_ufrag; } } if (local_username && remote_username) { if (inbound) { return priv_gen_username (agent, component_id, local_username, remote_username, dest, dest_len); } else { return priv_gen_username (agent, component_id, remote_username, local_username, dest, dest_len); } } return 0; } /* * Returns a password string for use in an outbound connectivity * check. */ static size_t priv_get_password (NiceAgent *agent, NiceStream *stream, NiceCandidate *remote, uint8_t **password) { if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE) return 0; if (remote && remote->password) { *password = (uint8_t *)remote->password; return strlen (remote->password); } if (stream) { *password = (uint8_t *)stream->remote_password; return strlen (stream->remote_password); } return 0; } /* Implement the computation specific in RFC 8445 section 14 */ static unsigned int priv_compute_conncheck_timer (NiceAgent *agent, NiceStream *stream) { GSList *i, *j; guint waiting_and_in_progress = 0; unsigned int rto = 0; /* we can compute precisely the number of pairs in-progress or * waiting for all streams, instead of limiting the value to one * stream, and multiplying it by the number of active streams. * Since RFC8445, this number of waiting and in-progress pairs * if maxed by the number of different foundations in the conncheck * list. */ for (i = agent->streams; i ; i = i->next) { NiceStream *s = i->data; for (j = s->conncheck_list; j ; j = j->next) { CandidateCheckPair *p = j->data; if (p->state == NICE_CHECK_IN_PROGRESS || p->state == NICE_CHECK_WAITING) waiting_and_in_progress++; } } rto = agent->timer_ta * waiting_and_in_progress; nice_debug ("Agent %p : timer set to %dms, " "waiting+in_progress=%d", agent, MAX (rto, STUN_TIMER_DEFAULT_TIMEOUT), waiting_and_in_progress); return MAX (rto, STUN_TIMER_DEFAULT_TIMEOUT); } /* * Sends a connectivity check over candidate pair 'pair'. * * @return zero on success, non-zero on error */ int conn_check_send (NiceAgent *agent, CandidateCheckPair *pair) { /* note: following information is supplied: * - username (for USERNAME attribute) * - password (for MESSAGE-INTEGRITY) * - priority (for PRIORITY) * - ICE-CONTROLLED/ICE-CONTROLLING (for role conflicts) * - USE-CANDIDATE (if sent by the controlling agent) */ uint8_t uname[NICE_STREAM_MAX_UNAME]; NiceStream *stream; NiceComponent *component; gsize uname_len; uint8_t *password = NULL; uint8_t *free_password = NULL; gsize password_len; bool controlling = agent->controlling_mode; /* XXX: add API to support different nomination modes: */ 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)) return -1; uname_len = priv_create_username (agent, stream, pair->component_id, pair->remote, pair->local, uname, sizeof (uname), FALSE); password_len = priv_get_password (agent, stream, pair->remote, &password); if (password != NULL && (agent->compatibility == NICE_COMPATIBILITY_MSN || agent->compatibility == NICE_COMPATIBILITY_OC2007)) { free_password = password = g_base64_decode ((gchar *) password, &password_len); } if (nice_debug_is_enabled ()) { gchar tmpbuf1[INET6_ADDRSTRLEN]; gchar tmpbuf2[INET6_ADDRSTRLEN]; 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=%p (c-id:%u), tie=%llu, username='%.*s' (%" G_GSIZE_FORMAT "), " "password='%.*s' (%" G_GSIZE_FORMAT "), prio=%08x, %s.", agent, tmpbuf1, nice_address_get_port (&pair->local->addr), tmpbuf2, nice_address_get_port (&pair->remote->addr), pair->sockptr->fileno ? g_socket_get_fd(pair->sockptr->fileno) : -1, pair, pair->component_id, (unsigned long long)agent->tie_breaker, (int) uname_len, uname, uname_len, (int) password_len, password, password_len, pair->stun_priority, controlling ? "controlling" : "controlled"); } 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) { nice_debug ("Agent %p: no credentials found, cancelling conncheck", agent); g_free (free_password); return -1; } stun = priv_add_stun_transaction (pair); buffer_len = stun_usage_ice_conncheck_create (&component->stun_agent, &stun->message, stun->buffer, sizeof(stun->buffer), uname, uname_len, password, password_len, cand_use, controlling, pair->stun_priority, agent->tie_breaker, pair->local->foundation, agent_to_ice_compatibility (agent)); nice_debug ("Agent %p: conncheck created %zd - %p", agent, buffer_len, stun->message.buffer); g_free (free_password); if (buffer_len == 0) { nice_debug ("Agent %p: buffer is empty, cancelling conncheck", agent); 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(&stun->timer, timeout); } else { timeout = priv_compute_conncheck_timer (agent, stream); stun_timer_start (&stun->timer, timeout, agent->stun_max_retransmissions); } stun->next_tick = g_get_monotonic_time () + timeout * 1000; /* TCP-ACTIVE candidate must create a new socket before sending * by connecting to the peer. The new socket is stored in the candidate * 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) { nice_debug ("Agent %p: add to tcp-act socket %p a new " "tcp connect socket %p on pair %p in s/c %d/%d", agent, pair->sockptr, new_socket, pair, stream->id, component->id); pair->sockptr = new_socket; _priv_set_socket_tos (agent, pair->sockptr, stream2->tos); nice_socket_set_writable_callback (pair->sockptr, _tcp_sock_is_writable, component2); nice_component_attach_socket (component2, new_socket); } else { priv_remove_stun_transaction (pair, stun, component); return -1; } } } /* send the conncheck */ if (agent_socket_send (pair->sockptr, &pair->remote->addr, buffer_len, (gchar *)stun->buffer) < 0) { priv_remove_stun_transaction (pair, stun, component); return -1; } if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) ms_ice2_legacy_conncheck_send (&stun->message, pair->sockptr, &pair->remote->addr); return 0; } /* * Implemented the pruning steps described in ICE sect 8.1.2 * "Updating States" (ID-19) after a pair has been nominated. * * @see conn_check_update_check_list_state_failed_components() */ static guint priv_prune_pending_checks (NiceAgent *agent, NiceStream *stream, NiceComponent *component) { GSList *i; guint64 priority; guint in_progress = 0; guint triggered_check = 0; gchar prio[NICE_CANDIDATE_PAIR_PRIORITY_MAX_SIZE]; nice_debug ("Agent %p: Pruning pending checks for s%d/c%d", agent, stream->id, component->id); /* Called when we have at least one selected pair */ priority = component->selected_pair.priority; g_assert (priority > 0); nice_candidate_pair_priority_to_string (priority, prio); nice_debug ("Agent %p : selected pair priority is %s", agent, prio); i = stream->conncheck_list; while (i) { CandidateCheckPair *p = i->data; GSList *next = i->next; if (p->component_id != component->id) { i = next; continue; } /* We do not remove a pair from the conncheck list if it is also in * the triggered check queue. This is not what suggests the ICE * spec, but it proved to be more robust in the aggressive * nomination scenario, precisely because these pairs may have the * use-candidate flag set, and the peer agent may already have * selected such one. */ if (g_slist_find (agent->triggered_check_queue, p) && p->state != NICE_CHECK_IN_PROGRESS) { if (p->priority < priority) { nice_debug ("Agent %p : pair %p removed.", agent, p); candidate_check_pair_free (agent, p); stream->conncheck_list = g_slist_delete_link(stream->conncheck_list, i); } else triggered_check++; } /* step: cancel all FROZEN and WAITING pairs for the component */ else if (p->state == NICE_CHECK_FROZEN || p->state == NICE_CHECK_WAITING) { nice_debug ("Agent %p : pair %p removed.", agent, p); candidate_check_pair_free (agent, p); stream->conncheck_list = g_slist_delete_link(stream->conncheck_list, i); } /* note: a SHOULD level req. in ICE 8.1.2. "Updating States" (ID-19) */ else if (p->state == NICE_CHECK_IN_PROGRESS) { if (p->priority < priority) { priv_remove_pair_from_triggered_check_queue (agent, p); if (p->retransmit) { p->retransmit = FALSE; nice_debug ("Agent %p : pair %p will not be retransmitted.", agent, p); } } else { /* We must keep the higher priority pairs running because if a udp * packet was lost, we might end up using a bad candidate */ nice_candidate_pair_priority_to_string (p->priority, prio); nice_debug ("Agent %p : pair %p kept IN_PROGRESS because priority " "%s is higher than priority of best nominated pair.", agent, p, prio); /* We may also have to enable the retransmit flag of pairs with * a higher priority than the first nominated pair */ if (!p->retransmit && p->stun_transactions) { p->retransmit = TRUE; nice_debug ("Agent %p : pair %p will be retransmitted.", agent, p); } in_progress++; } } i = next; } return in_progress + triggered_check; } /* * Schedules a triggered check after a successfully inbound * connectivity check. Implements ICE sect 7.2.1.4 "Triggered Checks" (ID-19). * * @param agent self pointer * @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 */ static gboolean priv_schedule_triggered_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *local_socket, NiceCandidate *remote_cand) { GSList *i; NiceCandidate *local = NULL; CandidateCheckPair *p; g_assert (remote_cand != NULL); nice_debug ("Agent %p : scheduling triggered check with socket=%p " "and remote cand=%p.", agent, local_socket, remote_cand); for (i = stream->conncheck_list; i ; i = i->next) { p = i->data; if (p->component_id == component->id && p->remote == remote_cand && p->sockptr == local_socket) { /* If we match with a peer-reflexive discovered pair, we * use the parent succeeded pair instead */ if (p->succeeded_pair != NULL) { g_assert (p->state == NICE_CHECK_DISCOVERED); p = p->succeeded_pair; } 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: 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_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. * * When another pair, with a higher priority is already * nominated, so there's no reason to recheck this pair, * since it can in no way replace the nominated one. */ if (p->priority > component->selected_pair.priority) { nice_debug ("Agent %p : pair %p added for a triggered check.", agent, p); priv_add_pair_to_triggered_check_queue (agent, p); } break; case NICE_CHECK_FAILED: if (p->priority > component->selected_pair.priority) { nice_debug ("Agent %p : pair %p added for a triggered check.", agent, p); priv_add_pair_to_triggered_check_queue (agent, p); /* 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); /* If the component if in ready state, move it back to * connected as this failed pair with a higher priority * than the nominated pair requires to pursue the * conncheck */ else if (component->state == NICE_COMPONENT_STATE_READY) agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_CONNECTED); } break; case NICE_CHECK_SUCCEEDED: nice_debug ("Agent %p : nothing to do for pair %p.", agent, p); break; default: break; } /* note: the spec says the we SHOULD retransmit in-progress * checks immediately, but we won't do that now */ return TRUE; } } for (i = component->local_candidates; i ; i = i->next) { NiceCandidateImpl *lc = i->data; local = i->data; if (lc->sockptr == local_socket) break; } if (i) { nice_debug ("Agent %p : Adding a triggered check to conn.check list (local=%p).", agent, local); p = priv_conn_check_add_for_candidate_pair_matched (agent, stream->id, component, local, remote_cand, NICE_CHECK_WAITING); if (p) priv_add_pair_to_triggered_check_queue (agent, p); return TRUE; } else { nice_debug ("Agent %p : Didn't find a matching pair for triggered check (remote-cand=%p).", agent, remote_cand); return FALSE; } } /* * Sends a reply to a successfully received STUN connectivity * check request. Implements parts of the ICE spec section 7.2 (STUN * Server Procedures). * * @param agent context pointer * @param stream which stream (of the agent) * @param component which component (of the stream) * @param rcand remote candidate from which the request came, if NULL, * the response is sent immediately but no other processing is done * @param toaddr address to which reply is sent * @param socket the socket over which the request came * @param rbuf_len length of STUN message to send * @param msg the STUN message to send * @param use_candidate whether the request had USE_CANDIDATE attribute * * @pre (rcand == NULL || nice_address_equal(rcand->addr, toaddr) == TRUE) */ static void priv_reply_to_conn_check (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceCandidate *lcand, NiceCandidate *rcand, const NiceAddress *toaddr, NiceSocket *sockptr, size_t rbuf_len, StunMessage *msg, gboolean use_candidate) { g_assert (rcand == NULL || nice_address_equal(&rcand->addr, toaddr) == TRUE); if (nice_debug_is_enabled ()) { gchar tmpbuf[INET6_ADDRSTRLEN]; StunTransactionId id; nice_address_to_string (toaddr, tmpbuf); /* get stun message transaction id and convert it to hex. */ stun_message_id(msg, id); nice_debug ("Agent %p : STUN-CC RESP to '%s:%u', socket=%u, len=%u, cand=%p (c-id:%u)," " use-cand=%d, " "transactionId=%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx", agent, tmpbuf, nice_address_get_port (toaddr), sockptr->fileno ? g_socket_get_fd(sockptr->fileno) : -1, (unsigned)rbuf_len, rcand, component->id, (int)use_candidate, id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); } agent_socket_send (sockptr, toaddr, rbuf_len, (const gchar*)msg->buffer); if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) { ms_ice2_legacy_conncheck_send(msg, sockptr, toaddr); } /* 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); } } /* * Stores information of an incoming STUN connectivity check * for later use. This is only needed when a check is received * before we get information about the remote candidates (via * SDP or other signaling means). * * @return pointer to created pending check, zero on error */ static IncomingCheck *priv_store_pending_check (NiceAgent *agent, NiceComponent *component, const NiceAddress *from, NiceSocket *sockptr, uint8_t *username, uint16_t username_len, uint32_t priority, gboolean use_candidate) { IncomingCheck *icheck = NULL; guint max_incoming_checks = agent->max_conn_checks * 2; nice_debug ("Agent %p : Storing pending check.", agent); icheck = g_slice_new0 (IncomingCheck); icheck->from = *from; icheck->local_socket = sockptr; icheck->priority = priority; icheck->use_candidate = use_candidate; icheck->username_len = username_len; icheck->username = NULL; if (username_len > 0) icheck->username = g_memdup (username, username_len); g_queue_push_tail (&component->incoming_checks, icheck); if (g_queue_get_length (&component->incoming_checks) >= max_incoming_checks) { IncomingCheck *old_icheck = g_queue_pop_head (&component->incoming_checks); g_free (old_icheck->username); g_slice_free (IncomingCheck, old_icheck); nice_debug ("Agent %p : WARN: Over %d early checks, dropping the oldest", agent, max_incoming_checks); } return icheck; } /* * Adds a new pair, discovered from an incoming STUN response, to * the connectivity check list. * * @return created pair, or NULL on fatal (memory allocation) errors */ static CandidateCheckPair *priv_add_peer_reflexive_pair (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidateImpl *local_cand, CandidateCheckPair *parent_pair) { CandidateCheckPair *pair = g_slice_new0 (CandidateCheckPair); NiceStream *stream = agent_find_stream (agent, stream_id); pair->stream_id = stream_id; pair->component_id = component->id;; pair->local = (NiceCandidate *) local_cand; pair->remote = parent_pair->remote; pair->sockptr = local_cand->sockptr; parent_pair->discovered_pair = pair; pair->succeeded_pair = parent_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]; nice_address_to_string (&pair->local->addr, tmpbuf1); nice_address_to_string (&pair->remote->addr, tmpbuf2); nice_debug ("Agent %p : new pair %p : [%s]:%u --> [%s]:%u", agent, pair, tmpbuf1, nice_address_get_port (&pair->local->addr), tmpbuf2, nice_address_get_port (&pair->remote->addr)); } g_snprintf (pair->foundation, NICE_CANDIDATE_PAIR_MAX_FOUNDATION, "%s:%s", local_cand->c.foundation, parent_pair->remote->foundation); if (agent->controlling_mode == TRUE) pair->priority = nice_candidate_pair_priority (pair->local->priority, pair->remote->priority); else pair->priority = nice_candidate_pair_priority (pair->remote->priority, pair->local->priority); pair->nominated = parent_pair->nominated; /* the peer-reflexive priority used in stun request is copied from * the parent succeeded pair. This value is not required for discovered * pair, that won't emit stun requests themselves, but may be used when * such pair becomes the selected pair, and when keepalive stun are emitted, * using the sockptr and stun_priority values from the succeeded pair. */ pair->stun_priority = parent_pair->stun_priority; nice_debug ("Agent %p : added a new peer-discovered pair %p with " "foundation '%s' and transport %s:%s to stream %u component %u", agent, pair, pair->foundation, nice_candidate_transport_to_string (pair->local->transport), nice_candidate_transport_to_string (pair->remote->transport), stream_id, component->id); stream->conncheck_list = g_slist_insert_sorted (stream->conncheck_list, pair, (GCompareFunc)conn_check_compare); return pair; } /* * Recalculates priorities of all candidate pairs. This * is required after a conflict in ICE roles. */ void recalculate_pair_priorities (NiceAgent *agent) { GSList *i, *j; for (i = agent->streams; i; i = i->next) { NiceStream *stream = i->data; for (j = stream->conncheck_list; j; j = j->next) { CandidateCheckPair *p = j->data; p->priority = agent_candidate_pair_priority (agent, p->local, p->remote); } stream->conncheck_list = g_slist_sort (stream->conncheck_list, (GCompareFunc)conn_check_compare); } } /* * Change the agent role if different from 'control'. Can be * initiated both by handling of incoming connectivity checks, * and by processing the responses to checks sent by us. */ 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 \"%s\".", agent, control ? "controlling" : "controlled"); agent->controlling_mode = control; /* the pair priorities depend on the roles, so recalculation * is needed */ recalculate_pair_priorities (agent); } else nice_debug ("Agent %p : Role conflict, staying with role \"%s\".", agent, control ? "controlling" : "controlled"); } /* * Checks whether the mapped address in connectivity check response * matches any of the known local candidates. If not, apply the * mechanism for "Discovering Peer Reflexive Candidates" ICE ID-19) * * @param agent context pointer * @param stream which stream (of the agent) * @param component which component (of the stream) * @param p the connectivity check pair for which we got a response * @param socketptr socket used to send the reply * @param mapped_sockaddr mapped address in the response * * @return pointer to a candidate pair, found in conncheck list or newly created */ static CandidateCheckPair *priv_process_response_check_for_reflexive(NiceAgent *agent, NiceStream *stream, NiceComponent *component, CandidateCheckPair *p, NiceSocket *sockptr, struct sockaddr *mapped_sockaddr, NiceCandidate *local_candidate, NiceCandidate *remote_candidate) { CandidateCheckPair *new_pair = NULL; NiceAddress mapped; GSList *i; NiceCandidate *local_cand = NULL; nice_address_set_from_sockaddr (&mapped, mapped_sockaddr); for (i = component->local_candidates; i; i = i->next) { NiceCandidate *cand = i->data; if (nice_address_equal (&mapped, &cand->addr) && local_candidate_and_socket_compatible (agent, cand, sockptr)) { local_cand = cand; break; } } /* The mapped address allows to look for a previously discovered * peer reflexive local candidate, and its related pair. This * new_pair will be marked 'Valid', while the pair 'p' of the * initial stun request will be marked 'Succeeded' * * In the case of a tcp-act/tcp-pass pair 'p', where the local * candidate is of type tcp-act, and its port number is zero, a * conncheck on this pair *always* leads to the creation of a * discovered peer-reflexive tcp-act local candidate. */ for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *pair = i->data; if (local_cand == pair->local && remote_candidate == pair->remote) { new_pair = pair; break; } } 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; else /* this new_pair distinct from p may also be in state failed (if * the related succeeded pair p was in state failed previously, but * retriggered a successful check a bit later), so we force its * state back to discovered there. */ SET_PAIR_STATE (agent, new_pair, NICE_CHECK_DISCOVERED); 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_component_add_valid_candidate (agent, component, remote_candidate); } else { if (local_cand == NULL && !agent->force_relay) { /* step: find a new local candidate, see RFC 5245 7.1.3.2.1. * "Discovering Peer Reflexive Candidates" * * The priority equal to the value of the PRIORITY attribute * in the Binding request is taken from the "parent" pair p */ local_cand = discovery_add_peer_reflexive_candidate (agent, stream->id, component->id, p->stun_priority, &mapped, sockptr, local_candidate, remote_candidate); nice_debug ("Agent %p : added a new peer-reflexive local candidate %p " "with transport %s", agent, local_cand, nice_candidate_transport_to_string (local_cand->transport)); } /* step: add a new discovered pair (see RFC 5245 7.1.3.2.2 "Constructing a Valid Pair") */ if (local_cand) new_pair = priv_add_peer_reflexive_pair (agent, stream->id, component, (NiceCandidateImpl *) 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" */ SET_PAIR_STATE (agent, p, NICE_CHECK_SUCCEEDED); priv_remove_pair_from_triggered_check_queue (agent, p); priv_free_all_stun_transactions (p, component); } if (new_pair && new_pair->valid) nice_component_add_valid_candidate (agent, component, remote_candidate); return new_pair; } /* * Tries to match STUN reply in 'buf' to an existing STUN connectivity * check transaction. If found, the reply is processed. Implements * section 7.1.2 "Processing the Response" of ICE spec (ID-19). * * @return TRUE if a matching transaction is found */ static gboolean priv_map_reply_to_conn_check_request (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *sockptr, const NiceAddress *from, NiceCandidate *local_candidate, NiceCandidate *remote_candidate, StunMessage *resp) { union { struct sockaddr_storage storage; struct sockaddr addr; } sockaddr; socklen_t socklen = sizeof (sockaddr); GSList *i, *j; guint k; StunUsageIceReturn res; StunTransactionId discovery_id; StunTransactionId response_id; stun_message_id (resp, response_id); for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; 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 : pair %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 : pair %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)); } conn_check_update_check_list_state_for_ready (agent, stream, component); return TRUE; } if (remote_candidate == NULL) { candidate_check_pair_fail (stream, agent, p); if (nice_debug_is_enabled ()) { nice_debug ("Agent %p : pair %p FAILED " "(got a matching pair without a known remote candidate).", agent, p); } conn_check_update_check_list_state_for_ready (agent, stream, component); 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) { nice_debug ("Agent %p : Mapped address not found", agent); SET_PAIR_STATE (agent, p, NICE_CHECK_SUCCEEDED); p->valid = TRUE; nice_component_add_valid_candidate (agent, 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 * See sect 7.2.5.3.3 (Updating Candidate Pair States) of * ICE spec (RFC8445). */ conn_check_unfreeze_related (agent, p); /* Note: this assignment helps to reduce the numbers of cases * to be tested. If ok_pair and p refer to distinct pairs, it * 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) { conn_check_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); } /* step: update pair states (ICE 7.1.2.2.3 "Updating pair states" and 8.1.2 "Updating States", ID-19) */ conn_check_update_check_list_state_for_ready (agent, stream, component); } else if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) { uint64_t tie; gboolean controlled_mode; if (!p->retransmit) { nice_debug ("Agent %p : Role conflict with pair %p, not restarting", agent, p); return TRUE; } /* case: role conflict error, need to restart with new role */ nice_debug ("Agent %p : Role conflict with pair %p, restarting", agent, p); /* 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); 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 */ candidate_check_pair_fail (stream, agent, p); conn_check_update_check_list_state_for_ready (agent, stream, component); } return TRUE; } } return FALSE; } /* * Tries to match STUN reply in 'buf' to an existing STUN discovery * transaction. If found, a reply is sent. * * @return TRUE if a matching transaction is found */ static gboolean priv_map_reply_to_discovery_request (NiceAgent *agent, StunMessage *resp, const NiceAddress *server_address) { union { struct sockaddr_storage storage; struct sockaddr addr; } sockaddr; socklen_t socklen = sizeof (sockaddr); union { struct sockaddr_storage storage; struct sockaddr addr; } alternate; socklen_t alternatelen = sizeof (sockaddr); GSList *i; StunUsageBindReturn res; gboolean trans_found = FALSE; StunTransactionId discovery_id; StunTransactionId response_id; stun_message_id (resp, response_id); for (i = agent->discovery_list; i && trans_found != TRUE; i = i->next) { CandidateDiscovery *d = i->data; if (d->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE && d->stun_message.buffer) { stun_message_id (&d->stun_message, discovery_id); if (memcmp (discovery_id, response_id, sizeof(StunTransactionId)) == 0) { res = stun_usage_bind_process (resp, &sockaddr.addr, &socklen, &alternate.addr, &alternatelen); nice_debug ("Agent %p : stun_bind_process/disc for %p res %d.", agent, d, (int)res); if (res == STUN_USAGE_BIND_RETURN_ALTERNATE_SERVER) { /* handle alternate server */ NiceAddress niceaddr; nice_address_set_from_sockaddr (&niceaddr, &alternate.addr); 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 */ if (!agent->force_relay) { NiceAddress niceaddr; nice_address_set_from_sockaddr (&niceaddr, &sockaddr.addr); discovery_add_server_reflexive_candidate ( agent, d->stream_id, d->component_id, &niceaddr, NICE_CANDIDATE_TRANSPORT_UDP, d->nicesock, server_address, FALSE); if (agent->use_ice_tcp) discovery_discover_tcp_server_reflexive_candidates ( agent, d->stream_id, d->component_id, &niceaddr, d->nicesock, server_address); } d->stun_message.buffer = NULL; d->stun_message.buffer_len = 0; d->done = TRUE; trans_found = TRUE; } else if (res == STUN_USAGE_BIND_RETURN_ERROR) { /* case: STUN error, the check STUN context was freed */ d->stun_message.buffer = NULL; d->stun_message.buffer_len = 0; d->done = TRUE; trans_found = TRUE; } } } } return trans_found; } static guint priv_calc_turn_timeout (guint lifetime) { if (lifetime > 120) return lifetime - 60; else return lifetime / 2; } static void priv_add_new_turn_refresh (NiceAgent *agent, CandidateDiscovery *cdisco, NiceCandidateImpl *relay_cand, guint lifetime) { CandidateRefresh *cand; if (cdisco->turn->type == NICE_RELAY_TYPE_TURN_TLS && (agent->compatibility == NICE_COMPATIBILITY_OC2007 || agent->compatibility == NICE_COMPATIBILITY_OC2007R2)) return; cand = g_slice_new0 (CandidateRefresh); cand->candidate = relay_cand; cand->nicesock = cdisco->nicesock; cand->server = cdisco->server; cand->stream_id = cdisco->stream_id; cand->component_id = cdisco->component_id; memcpy (&cand->stun_agent, &cdisco->stun_agent, sizeof(StunAgent)); /* Use previous stun response for authentication credentials */ if (cdisco->stun_resp_msg.buffer != NULL) { memcpy(cand->stun_resp_buffer, cdisco->stun_resp_buffer, sizeof(cand->stun_resp_buffer)); memcpy(&cand->stun_resp_msg, &cdisco->stun_resp_msg, sizeof(StunMessage)); cand->stun_resp_msg.buffer = cand->stun_resp_buffer; cand->stun_resp_msg.agent = &cand->stun_agent; cand->stun_resp_msg.key = NULL; } if (lifetime > 0) { agent->refresh_list = g_slist_append (agent->refresh_list, cand); nice_debug ("Agent %p : Adding new refresh candidate %p with timeout %d", agent, cand, priv_calc_turn_timeout (lifetime)); /* step: also start the refresh timer */ /* refresh should be sent 1 minute before it expires */ agent_timeout_add_seconds_with_context (agent, &cand->timer_source, "Candidate TURN refresh", priv_calc_turn_timeout (lifetime), priv_turn_allocate_refresh_tick_agent_locked, cand); nice_debug ("timer source is : %p", cand->timer_source); } else { agent->pruning_refreshes = g_slist_append (agent->pruning_refreshes, cand); nice_debug ("Agent %p : Sending request to remove TURN allocation " "for refresh %p", agent, cand); cand->disposing = TRUE; priv_turn_allocate_refresh_tick_unlocked (agent, cand); if (relay_cand->sockptr) nice_socket_free (relay_cand->sockptr); nice_candidate_free ((NiceCandidate *)relay_cand); } return; } 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_id == disco->stream_id && 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 on s%d/c%d", agent, ip, d, d->stream_id, d->component_id); d->server = alternate; d->turn->server = alternate; d->pending = FALSE; agent->discovery_unsched_items++; if (d->turn->type == NICE_RELAY_TYPE_TURN_TCP || d->turn->type == NICE_RELAY_TYPE_TURN_TLS) { NiceStream *stream; NiceComponent *component; if (!agent_find_component (agent, d->stream_id, d->component_id, &stream, &component)) { nice_debug ("Could not find stream or component in " "priv_handle_turn_alternate_server"); continue; } d->nicesock = agent_create_tcp_turn_socket (agent, stream, component, d->nicesock, &d->server, d->turn->type, nice_socket_is_reliable (d->nicesock)); nice_component_attach_socket (component, d->nicesock); } } } } /* * Tries to match STUN reply in 'buf' to an existing STUN discovery * transaction. If found, a reply is sent. * * @return TRUE if a matching transaction is found */ static gboolean priv_map_reply_to_relay_request (NiceAgent *agent, StunMessage *resp) { union { struct sockaddr_storage storage; struct sockaddr addr; } sockaddr; socklen_t socklen = sizeof (sockaddr); union { struct sockaddr_storage storage; struct sockaddr addr; } alternate; socklen_t alternatelen = sizeof (alternate); union { struct sockaddr_storage storage; struct sockaddr addr; } relayaddr; socklen_t relayaddrlen = sizeof (relayaddr); uint32_t lifetime; uint32_t bandwidth; GSList *i; StunUsageTurnReturn res; gboolean trans_found = FALSE; StunTransactionId discovery_id; StunTransactionId response_id; stun_message_id (resp, response_id); for (i = agent->discovery_list; i && trans_found != TRUE; i = i->next) { CandidateDiscovery *d = i->data; if (d->type == NICE_CANDIDATE_TYPE_RELAYED && d->stun_message.buffer) { stun_message_id (&d->stun_message, discovery_id); if (memcmp (discovery_id, response_id, sizeof(StunTransactionId)) == 0) { res = stun_usage_turn_process (resp, &relayaddr.storage, &relayaddrlen, &sockaddr.storage, &socklen, &alternate.storage, &alternatelen, &bandwidth, &lifetime, agent_to_turn_compatibility (agent)); nice_debug ("Agent %p : stun_turn_process/disc for %p res %d.", agent, d, (int)res); if (res == STUN_USAGE_TURN_RETURN_ALTERNATE_SERVER) { NiceAddress addr; /* handle alternate server */ nice_address_set_from_sockaddr (&addr, &alternate.addr); priv_handle_turn_alternate_server (agent, d, d->server, addr); trans_found = TRUE; } else if (res == STUN_USAGE_TURN_RETURN_RELAY_SUCCESS || res == STUN_USAGE_TURN_RETURN_MAPPED_SUCCESS) { /* case: successful allocate, create a new local candidate */ NiceAddress niceaddr; NiceCandidateImpl *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 (&mappedniceaddr, &sockaddr.addr); /* TCP or TLS TURNS means the server-reflexive address was * on a TCP connection, which cannot be used for server-reflexive * discovery of candidates. */ if (d->turn->type == NICE_RELAY_TYPE_TURN_UDP && !agent->force_relay) { discovery_add_server_reflexive_candidate ( agent, d->stream_id, d->component_id, &mappedniceaddr, NICE_CANDIDATE_TRANSPORT_UDP, d->nicesock, &niceaddr, FALSE); } if (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 ( agent, d->stream_id, d->component_id, &mappedniceaddr, d->nicesock, &niceaddr); } } } if (nice_socket_is_reliable (d->nicesock)) { relay_cand = discovery_add_relay_candidate ( agent, d->stream_id, d->component_id, &niceaddr, NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE, d->nicesock, d->turn, &lifetime); if (relay_cand) { if (agent->compatibility == NICE_COMPATIBILITY_OC2007 || agent->compatibility == NICE_COMPATIBILITY_OC2007R2) { nice_udp_turn_socket_set_ms_realm(relay_cand->sockptr, &d->stun_message); nice_udp_turn_socket_set_ms_connection_id(relay_cand->sockptr, resp); } priv_add_new_turn_refresh (agent, d, relay_cand, lifetime); } relay_cand = discovery_add_relay_candidate ( agent, d->stream_id, d->component_id, &niceaddr, NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE, d->nicesock, d->turn, &lifetime); } else { relay_cand = discovery_add_relay_candidate ( agent, d->stream_id, d->component_id, &niceaddr, NICE_CANDIDATE_TRANSPORT_UDP, d->nicesock, d->turn, &lifetime); } if (relay_cand) { if (d->stun_resp_msg.buffer) nice_udp_turn_socket_cache_realm_nonce (relay_cand->sockptr, &d->stun_resp_msg); if (agent->compatibility == NICE_COMPATIBILITY_OC2007 || agent->compatibility == NICE_COMPATIBILITY_OC2007R2) { /* These data are needed on TURN socket when sending requests, * but never reach nice_turn_socket_parse_recv() where it could * be read directly, as the socket does not exist when allocate * response arrives to _nice_agent_recv(). We must set them right * after socket gets created in discovery_add_relay_candidate(), * so we are doing it here. */ nice_udp_turn_socket_set_ms_realm(relay_cand->sockptr, &d->stun_message); nice_udp_turn_socket_set_ms_connection_id(relay_cand->sockptr, resp); } priv_add_new_turn_refresh (agent, d, relay_cand, lifetime); } d->stun_message.buffer = NULL; d->stun_message.buffer_len = 0; d->done = TRUE; trans_found = TRUE; } else if (res == STUN_USAGE_TURN_RETURN_ERROR) { int code = -1; uint8_t *sent_realm = NULL; uint8_t *recv_realm = NULL; uint16_t sent_realm_len = 0; uint16_t recv_realm_len = 0; sent_realm = (uint8_t *) stun_message_find (&d->stun_message, STUN_ATTRIBUTE_REALM, &sent_realm_len); 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 addr; nice_address_set_from_sockaddr (&addr, &alternate.addr); if (!nice_address_equal (&addr, &d->server)) { priv_handle_turn_alternate_server (agent, d, d->server, addr); } } /* check for unauthorized error response */ if ((agent->compatibility == NICE_COMPATIBILITY_RFC5245 || agent->compatibility == NICE_COMPATIBILITY_OC2007 || agent->compatibility == NICE_COMPATIBILITY_OC2007R2) && stun_message_get_class (resp) == STUN_ERROR && stun_message_find_error (resp, &code) == STUN_MESSAGE_RETURN_SUCCESS && recv_realm != NULL && recv_realm_len > 0) { if (code == STUN_ERROR_STALE_NONCE || (code == STUN_ERROR_UNAUTHORIZED && !(recv_realm_len == sent_realm_len && sent_realm != NULL && memcmp (sent_realm, recv_realm, sent_realm_len) == 0))) { d->stun_resp_msg = *resp; memcpy (d->stun_resp_buffer, resp->buffer, stun_message_length (resp)); 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; d->stun_message.buffer_len = 0; d->done = TRUE; } } else if (d->pending) { /* case: STUN error, the check STUN context was freed */ d->stun_message.buffer = NULL; d->stun_message.buffer_len = 0; d->done = TRUE; } trans_found = TRUE; } } } } return trans_found; } /* * Tries to match STUN reply in 'buf' to an existing STUN discovery * transaction. If found, a reply is sent. * * @return TRUE if a matching transaction is found */ static gboolean priv_map_reply_to_relay_refresh (NiceAgent *agent, StunMessage *resp) { uint32_t lifetime; GSList *i; StunUsageTurnReturn res; gboolean trans_found = FALSE; StunTransactionId refresh_id; StunTransactionId response_id; stun_message_id (resp, response_id); for (i = agent->refresh_list; i && trans_found != TRUE;) { CandidateRefresh *cand = i->data; GSList *next = i->next; if (!cand->disposing && cand->stun_message.buffer) { stun_message_id (&cand->stun_message, refresh_id); if (memcmp (refresh_id, response_id, sizeof(StunTransactionId)) == 0) { res = stun_usage_turn_refresh_process (resp, &lifetime, agent_to_turn_compatibility (agent)); nice_debug ("Agent %p : stun_turn_refresh_process for %p res %d with lifetime %u.", agent, cand, (int)res, lifetime); if (res == STUN_USAGE_TURN_RETURN_RELAY_SUCCESS) { /* refresh should be sent 1 minute before it expires */ agent_timeout_add_seconds_with_context (agent, &cand->timer_source, "Candidate TURN refresh", priv_calc_turn_timeout (lifetime), priv_turn_allocate_refresh_tick_agent_locked, cand); g_source_destroy (cand->tick_source); g_source_unref (cand->tick_source); cand->tick_source = NULL; trans_found = TRUE; } else if (res == STUN_USAGE_TURN_RETURN_ERROR) { int code = -1; uint8_t *sent_realm = NULL; uint8_t *recv_realm = NULL; uint16_t sent_realm_len = 0; uint16_t recv_realm_len = 0; sent_realm = (uint8_t *) stun_message_find (&cand->stun_message, STUN_ATTRIBUTE_REALM, &sent_realm_len); recv_realm = (uint8_t *) stun_message_find (resp, STUN_ATTRIBUTE_REALM, &recv_realm_len); /* check for unauthorized error response */ if (agent->compatibility == NICE_COMPATIBILITY_RFC5245 && stun_message_get_class (resp) == STUN_ERROR && stun_message_find_error (resp, &code) == STUN_MESSAGE_RETURN_SUCCESS && recv_realm != NULL && recv_realm_len > 0) { if (code == STUN_ERROR_STALE_NONCE || (code == STUN_ERROR_UNAUTHORIZED && !(recv_realm_len == sent_realm_len && sent_realm != NULL && memcmp (sent_realm, recv_realm, sent_realm_len) == 0))) { cand->stun_resp_msg = *resp; memcpy (cand->stun_resp_buffer, resp->buffer, stun_message_length (resp)); cand->stun_resp_msg.buffer = cand->stun_resp_buffer; cand->stun_resp_msg.buffer_len = sizeof(cand->stun_resp_buffer); priv_turn_allocate_refresh_tick_unlocked (agent, cand); } else { /* case: a real unauthorized error */ refresh_free (agent, cand); } } else { /* case: STUN error, the check STUN context was freed */ refresh_free (agent, cand); } trans_found = TRUE; } } } i = next; } return trans_found; } static gboolean priv_map_reply_to_relay_remove (NiceAgent *agent, StunMessage *resp) { StunTransactionId response_id; GSList *i; stun_message_id (resp, response_id); for (i = agent->refresh_list; i; i = i->next) { CandidateRefresh *cand = i->data; StunTransactionId request_id; StunUsageTurnReturn res; uint32_t lifetime; if (!cand->disposing || !cand->stun_message.buffer) { continue; } stun_message_id (&cand->stun_message, request_id); if (memcmp (request_id, response_id, sizeof(StunTransactionId)) == 0) { res = stun_usage_turn_refresh_process (resp, &lifetime, agent_to_turn_compatibility (agent)); nice_debug ("Agent %p : priv_map_reply_to_relay_remove for %p res %d " "with lifetime %u.", agent, cand, res, lifetime); if (res != STUN_USAGE_TURN_RETURN_INVALID) { refresh_free (agent, cand); return TRUE; } } } return FALSE; } static gboolean priv_map_reply_to_keepalive_conncheck (NiceAgent *agent, NiceComponent *component, StunMessage *resp) { guint64 now = g_get_monotonic_time(); nice_debug ("Agent %p : Keepalive for selected pair %p received.", agent, &component->selected_pair); /* timeout is checked even if consent_freshness is disabled */ component->selected_pair.remote_consent.last_received = now; return TRUE; } typedef struct { NiceAgent *agent; NiceStream *stream; NiceComponent *component; uint8_t *password; } conncheck_validater_data; static bool conncheck_stun_validater (StunAgent *agent, StunMessage *message, uint8_t *username, uint16_t username_len, uint8_t **password, size_t *password_len, void *user_data) { conncheck_validater_data *data = (conncheck_validater_data*) user_data; GSList *i; gchar *ufrag = NULL; gsize ufrag_len; gboolean msn_msoc_nice_compatibility = data->agent->compatibility == NICE_COMPATIBILITY_MSN || data->agent->compatibility == NICE_COMPATIBILITY_OC2007; if (data->agent->compatibility == NICE_COMPATIBILITY_OC2007 && stun_message_get_class (message) == STUN_RESPONSE) i = data->component->remote_candidates; else i = data->component->local_candidates; for (; i; i = i->next) { NiceCandidate *cand = i->data; ufrag = NULL; if (cand->username) ufrag = cand->username; else ufrag = data->stream->local_ufrag; ufrag_len = ufrag? strlen (ufrag) : 0; if (ufrag && msn_msoc_nice_compatibility) ufrag = (gchar *)g_base64_decode (ufrag, &ufrag_len); if (ufrag == NULL) continue; stun_debug ("Comparing username/ufrag of len %d and %" G_GSIZE_FORMAT ", equal=%d", username_len, ufrag_len, username_len >= ufrag_len ? memcmp (username, ufrag, ufrag_len) : 0); stun_debug_bytes (" username: ", username, username_len); stun_debug_bytes (" ufrag: ", ufrag, ufrag_len); if (ufrag_len > 0 && username_len >= ufrag_len && memcmp (username, ufrag, ufrag_len) == 0) { gchar *pass = NULL; if (cand->password) pass = cand->password; else if (data->stream && data->stream->local_password[0]) pass = data->stream->local_password; if (pass) { *password = (uint8_t *) pass; *password_len = strlen (pass); if (msn_msoc_nice_compatibility) { gsize pass_len; data->password = g_base64_decode (pass, &pass_len); *password = data->password; *password_len = pass_len; } } if (msn_msoc_nice_compatibility) g_free (ufrag); stun_debug ("Found valid username, returning password: '%s'", *password); return TRUE; } if (msn_msoc_nice_compatibility) g_free (ufrag); } return FALSE; } /* * handle RENOMINATION stun attribute * @return TRUE if nomination changed. FALSE otherwise */ static gboolean conn_check_handle_renomination (NiceAgent *agent, NiceStream *stream, NiceComponent *component, StunMessage *req, NiceCandidate *remote_candidate, NiceCandidate *local_candidate, IncomingCheck *pending_check) { GSList *lst; if (!agent->controlling_mode && NICE_AGENT_IS_COMPATIBLE_WITH_RFC5245_OR_OC2007R2 (agent) && agent->support_renomination && remote_candidate && local_candidate) { uint32_t nom_value = 0; uint16_t nom_len = 0; const void *value = stun_message_find (req, STUN_ATTRIBUTE_NOMINATION, &nom_len); if (nom_len == 0) { return FALSE; } if (nom_len == 4) { memcpy (&nom_value, value, 4); nom_value = ntohl (nom_value); } else { nice_debug ("Agent %p : received NOMINATION attr with incorrect octet length %u, expected 4 bytes", agent, nom_len); return FALSE; } if (nice_debug_is_enabled ()) { gchar remote_str[INET6_ADDRSTRLEN]; nice_address_to_string(&remote_candidate->addr, remote_str); nice_debug ("Agent %p : received NOMINATION attr for remote candidate [%s]:%u, value is %u", agent, remote_str, nice_address_get_port (&remote_candidate->addr), nom_value); } /* * If another pair is SELECTED, change this pair's priority to be greater than * selected pair's priority so this pair gets SELECTED! */ if (component->selected_pair.priority && component->selected_pair.remote && component->selected_pair.remote != (NiceCandidateImpl *) remote_candidate && component->selected_pair.local && component->selected_pair.local != (NiceCandidateImpl *) local_candidate) { for (lst = stream->conncheck_list; lst; lst = lst->next) { CandidateCheckPair *pair = lst->data; if (pair->local == local_candidate && pair->remote == remote_candidate) { if (pair->valid) { pair->priority = component->selected_pair.priority + 1; } break; } } } if (!priv_mark_pair_nominated (agent, stream, component, local_candidate, remote_candidate)) { /* No matching pair in conn check list. It means that we are probably handling incoming conn check, * so triggered check (pending_check) will be performed in future once we have credentials and remote candidates. * Constructed pair needs to be nominated then, so set use_candidate for pending check. */ if (nice_debug_is_enabled ()) { gchar remote_str[INET6_ADDRSTRLEN]; nice_address_to_string(&remote_candidate->addr, remote_str); nice_debug ("Agent %p : no matching pair nominated while handling NOMINATION attr for " "remote candidate [%s]:%u, pending check: %p - %s", agent, remote_str, nice_address_get_port (&remote_candidate->addr), pending_check, pending_check ? "set use_candidate" : "skip"); } if (pending_check) pending_check->use_candidate = TRUE; } return TRUE; } return FALSE; } /* * Processing an incoming STUN message. * * @param agent self pointer * @param stream stream the packet is related to * @param component component the packet is related to * @param nicesock socket from which the packet was received * @param from address of the sender * @param buf message contents * @param buf message length * * @pre contents of 'buf' is a STUN message * * @return XXX (what FALSE means exactly?) */ gboolean conn_check_handle_inbound_stun (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *nicesock, const NiceAddress *from, gchar *buf, guint len) { union { struct sockaddr_storage storage; struct sockaddr addr; } sockaddr; uint8_t rbuf[MAX_STUN_DATAGRAM_PAYLOAD]; ssize_t res; size_t rbuf_len = sizeof (rbuf); bool control = agent->controlling_mode; uint8_t uname[NICE_STREAM_MAX_UNAME]; guint uname_len; uint8_t *username; uint16_t username_len; StunMessage req; StunMessage msg; StunValidationStatus valid; conncheck_validater_data validater_data = {agent, stream, component, NULL}; GSList *i, *j; NiceCandidate *remote_candidate = NULL; NiceCandidate *remote_candidate2 = NULL; NiceCandidate *local_candidate = NULL; gboolean discovery_msg = FALSE; IncomingCheck *pending_check = NULL; nice_address_copy_to_sockaddr (from, &sockaddr.addr); /* note: contents of 'buf' already validated, so it is * a valid and fully received STUN message */ if (nice_debug_is_enabled ()) { gchar tmpbuf[INET6_ADDRSTRLEN]; nice_address_to_string (from, tmpbuf); nice_debug ("Agent %p: inbound STUN packet for %u/%u (stream/component) from [%s]:%u (%u octets) :", agent, stream->id, component->id, tmpbuf, nice_address_get_port (from), len); } /* note: ICE 7.2. "STUN Server Procedures" (ID-19) */ valid = stun_agent_validate (&component->stun_agent, &req, (uint8_t *) buf, len, conncheck_stun_validater, &validater_data); /* Check for discovery candidates stun agents */ if (valid == STUN_VALIDATION_BAD_REQUEST || valid == STUN_VALIDATION_UNMATCHED_RESPONSE) { for (i = agent->discovery_list; i; i = i->next) { CandidateDiscovery *d = i->data; if (d->stream_id == stream->id && d->component_id == component->id && d->nicesock == nicesock) { valid = stun_agent_validate (&d->stun_agent, &req, (uint8_t *) buf, len, conncheck_stun_validater, &validater_data); if (valid == STUN_VALIDATION_UNMATCHED_RESPONSE) continue; discovery_msg = TRUE; break; } } } /* Check for relay refresh stun agents */ if (valid == STUN_VALIDATION_BAD_REQUEST || valid == STUN_VALIDATION_UNMATCHED_RESPONSE) { for (i = agent->refresh_list; i; i = i->next) { CandidateRefresh *r = i->data; nice_debug_verbose ("Comparing r.sid=%u to sid=%u, r.cid=%u to cid=%u and %p and %p to %p", r->stream_id, stream->id, r->component_id, component->id, r->nicesock, r->candidate->sockptr, nicesock); if (r->stream_id == stream->id && r->component_id == component->id && (r->nicesock == nicesock || r->candidate->sockptr == nicesock)) { valid = stun_agent_validate (&r->stun_agent, &req, (uint8_t *) buf, len, conncheck_stun_validater, &validater_data); nice_debug ("Validating gave %d", valid); if (valid == STUN_VALIDATION_UNMATCHED_RESPONSE) continue; discovery_msg = TRUE; break; } } } g_free (validater_data.password); if (valid == STUN_VALIDATION_NOT_STUN || valid == STUN_VALIDATION_INCOMPLETE_STUN || valid == STUN_VALIDATION_BAD_REQUEST) { nice_debug ("Agent %p : Incorrectly multiplexed STUN message ignored.", agent); return FALSE; } if (valid == STUN_VALIDATION_UNKNOWN_REQUEST_ATTRIBUTE) { nice_debug ("Agent %p : Unknown mandatory attributes in message.", agent); if (agent->compatibility != NICE_COMPATIBILITY_MSN && agent->compatibility != NICE_COMPATIBILITY_OC2007) { rbuf_len = stun_agent_build_unknown_attributes_error (&component->stun_agent, &msg, rbuf, rbuf_len, &req); if (rbuf_len != 0) agent_socket_send (nicesock, from, rbuf_len, (const gchar*)rbuf); } return TRUE; } if (valid == STUN_VALIDATION_UNAUTHORIZED) { nice_debug ("Agent %p : Integrity check failed.", agent); if (stun_agent_init_error (&component->stun_agent, &msg, rbuf, rbuf_len, &req, STUN_ERROR_UNAUTHORIZED)) { rbuf_len = stun_agent_finish_message (&component->stun_agent, &msg, NULL, 0); if (rbuf_len > 0 && agent->compatibility != NICE_COMPATIBILITY_MSN && agent->compatibility != NICE_COMPATIBILITY_OC2007) agent_socket_send (nicesock, from, rbuf_len, (const gchar*)rbuf); } return TRUE; } if (valid == STUN_VALIDATION_UNAUTHORIZED_BAD_REQUEST) { nice_debug ("Agent %p : Integrity check failed - bad request.", agent); if (stun_agent_init_error (&component->stun_agent, &msg, rbuf, rbuf_len, &req, STUN_ERROR_BAD_REQUEST)) { rbuf_len = stun_agent_finish_message (&component->stun_agent, &msg, NULL, 0); if (rbuf_len > 0 && agent->compatibility != NICE_COMPATIBILITY_MSN && agent->compatibility != NICE_COMPATIBILITY_OC2007) agent_socket_send (nicesock, from, rbuf_len, (const gchar*)rbuf); } return TRUE; } if (valid == STUN_VALIDATION_FORBIDDEN) { CandidatePair *pair = &component->selected_pair; gchar tmpbuf[INET6_ADDRSTRLEN]; nice_address_to_string (from, tmpbuf); nice_debug ("Agent %p : received 403: 'Forbidden' for %u/%u (stream/component) from [%s]:%u", agent, stream->id, component->id, tmpbuf, nice_address_get_port (from)); for (i = stream->conncheck_list; i; i = i->next) { CandidateCheckPair *p = i->data; if (nice_address_equal (from, &p->remote->addr)) { candidate_check_pair_fail (stream, agent, p); } } /* if the pair was selected, it is no longer useful */ if (nice_address_equal (from, &pair->remote->c.addr)) { pair->remote_consent.have = FALSE; nice_debug ("Agent %p : pair %p lost consent for %u/%u (stream/component)", agent, pair, stream->id, component->id); /* explicit revocation received, we don't need to time out anymore */ if (pair->remote_consent.tick_source) { g_source_destroy (pair->remote_consent.tick_source); g_source_unref (pair->remote_consent.tick_source); pair->remote_consent.tick_source = NULL; } agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_FAILED); } return TRUE; } username = (uint8_t *) stun_message_find (&req, STUN_ATTRIBUTE_USERNAME, &username_len); for (i = component->local_candidates; i; i = i->next) { NiceCandidate *cand = i->data; NiceAddress *addr; if (cand->type == NICE_CANDIDATE_TYPE_RELAYED) addr = &cand->addr; else addr = &cand->base_addr; if (nice_address_equal (&nicesock->addr, addr) && local_candidate_and_socket_compatible (agent, cand, nicesock)) { local_candidate = cand; break; } } for (i = component->remote_candidates; i; i = i->next) { NiceCandidate *cand = i->data; if (nice_address_equal (from, &cand->addr) && remote_candidate_and_socket_compatible (agent, local_candidate, cand, nicesock)) { remote_candidate = cand; break; } } 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 */ for (i = component->remote_candidates; i != NULL && remote_candidate2 == NULL; i = i->next) { for (j = component->local_candidates; j; j = j->next) { gboolean inbound = TRUE; NiceCandidate *rcand = i->data; NiceCandidate *lcand = j->data; /* If we receive a response, then the username is local:remote */ if (agent->compatibility != NICE_COMPATIBILITY_MSN) { if (stun_message_get_class (&req) == STUN_REQUEST || stun_message_get_class (&req) == STUN_INDICATION) { inbound = TRUE; } else { inbound = FALSE; } } uname_len = priv_create_username (agent, stream, component->id, rcand, lcand, uname, sizeof (uname), inbound); stun_debug ("Comparing usernames of size %d and %d: %d", username_len, uname_len, username && uname_len == username_len && memcmp (username, uname, uname_len) == 0); stun_debug_bytes (" First username: ", username, username ? username_len : 0); stun_debug_bytes (" Second uname: ", uname, uname_len); if (username && uname_len == username_len && memcmp (uname, username, username_len) == 0) { local_candidate = lcand; remote_candidate2 = rcand; break; } } } } if (component->remote_candidates && agent->compatibility == NICE_COMPATIBILITY_GOOGLE && local_candidate == NULL && discovery_msg == FALSE) { /* if we couldn't match the username and the stun agent has IGNORE_CREDENTIALS then we have an integrity check failing. This could happen with the race condition of receiving connchecks before the remote candidates are added. Just drop the message, and let the retransmissions make it work. */ nice_debug ("Agent %p : Username check failed.", agent); return TRUE; } /* This is most likely caused by a second response to a request which * already has received a valid reply. */ if (valid == STUN_VALIDATION_UNMATCHED_RESPONSE) { nice_debug ("Agent %p : Valid STUN response for which we don't have a request, ignoring", agent); return TRUE; } if (valid != STUN_VALIDATION_SUCCESS) { nice_debug ("Agent %p : STUN message is unsuccessful %d, ignoring", agent, valid); return FALSE; } agent->media_after_tick = TRUE; if (stun_message_get_class (&req) == STUN_REQUEST) { if ( agent->compatibility == NICE_COMPATIBILITY_MSN || agent->compatibility == NICE_COMPATIBILITY_OC2007) { if (local_candidate && remote_candidate2) { gsize key_len; if (agent->compatibility == NICE_COMPATIBILITY_MSN) { username = (uint8_t *) stun_message_find (&req, STUN_ATTRIBUTE_USERNAME, &username_len); uname_len = priv_create_username (agent, stream, component->id, remote_candidate2, local_candidate, uname, sizeof (uname), FALSE); memcpy (username, uname, MIN (uname_len, username_len)); req.key = g_base64_decode ((gchar *) remote_candidate2->password, &key_len); req.key_len = key_len; } else if (agent->compatibility == NICE_COMPATIBILITY_OC2007) { req.key = g_base64_decode ((gchar *) local_candidate->password, &key_len); req.key_len = key_len; } } else { nice_debug ("Agent %p : received MSN incoming check from unknown remote candidate. " "Ignoring request", agent); return TRUE; } } if (!component->have_local_consent) { /* RFC 7675: return forbidden to all authenticated requests if we should * signal lost consent */ nice_debug("Agent %p : returning FORBIDDEN on stream/component %u/%u " "for lost local consent", agent, stream->id, component->id); if (stun_agent_init_error (&component->stun_agent, &msg, rbuf, rbuf_len, &req, STUN_ERROR_FORBIDDEN)) { rbuf_len = stun_agent_finish_message (&component->stun_agent, &msg, NULL, 0); if (rbuf_len > 0 && agent->compatibility != NICE_COMPATIBILITY_MSN && agent->compatibility != NICE_COMPATIBILITY_OC2007) { agent_socket_send (nicesock, from, rbuf_len, (const gchar*) rbuf); } return TRUE; } } rbuf_len = sizeof (rbuf); res = stun_usage_ice_conncheck_create_reply (&component->stun_agent, &req, &msg, rbuf, &rbuf_len, &sockaddr.storage, sizeof (sockaddr), &control, agent->tie_breaker, agent_to_ice_compatibility (agent)); if ( agent->compatibility == NICE_COMPATIBILITY_MSN || agent->compatibility == NICE_COMPATIBILITY_OC2007) { g_free (req.key); } if (res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) priv_check_for_role_conflict (agent, control); if (res == STUN_USAGE_ICE_RETURN_SUCCESS || res == STUN_USAGE_ICE_RETURN_ROLE_CONFLICT) { /* case 1: valid incoming request, send a reply/error */ bool use_candidate = stun_usage_ice_conncheck_use_candidate (&req); uint32_t priority = stun_usage_ice_conncheck_priority (&req); if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE || agent->compatibility == NICE_COMPATIBILITY_MSN || agent->compatibility == NICE_COMPATIBILITY_OC2007) use_candidate = TRUE; if (stream->initial_binding_request_received != TRUE) 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); 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 && stream->remote_ufrag[0]) { 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, remote_candidate, NICE_CHECK_WAITING); else conn_check_add_for_candidate (agent, stream->id, component, remote_candidate); } } nice_component_add_valid_candidate (agent, component, remote_candidate); priv_reply_to_conn_check (agent, stream, component, local_candidate, remote_candidate, from, nicesock, rbuf_len, &msg, use_candidate); 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 * immediately but postpone all other processing until * we get information about the remote candidates */ /* step: send a reply immediately but postpone other processing */ pending_check = priv_store_pending_check (agent, component, from, nicesock, username, username_len, priority, use_candidate); priv_print_conn_check_lists (agent, G_STRFUNC, ", icheck stored"); } } else { nice_debug ("Agent %p : Invalid STUN packet, ignoring... %s", agent, strerror(errno)); return FALSE; } } else { /* case 2: not a new request, might be a reply... */ gboolean trans_found = FALSE; /* note: ICE sect 7.1.2. "Processing the Response" (ID-19) */ /* step: let's try to match the response to an existing check context */ if (trans_found != TRUE) trans_found = priv_map_reply_to_conn_check_request (agent, stream, component, nicesock, from, local_candidate, remote_candidate, &req); /* step: let's try to match the response to an existing discovery */ if (trans_found != TRUE) trans_found = priv_map_reply_to_discovery_request (agent, &req, from); /* step: let's try to match the response to an existing turn allocate */ if (trans_found != TRUE) trans_found = priv_map_reply_to_relay_request (agent, &req); /* step: let's try to match the response to an existing turn refresh */ if (trans_found != TRUE) trans_found = priv_map_reply_to_relay_refresh (agent, &req); if (trans_found != TRUE) trans_found = priv_map_reply_to_relay_remove (agent, &req); /* step: let's try to match the response to an existing keepalive conncheck */ if (trans_found != TRUE) trans_found = priv_map_reply_to_keepalive_conncheck (agent, component, &req); if (trans_found != TRUE) nice_debug ("Agent %p : Unable to match to an existing transaction, " "probably a keepalive.", agent); } /* RENOMINATION attribute support */ conn_check_handle_renomination(agent, stream, component, &req, remote_candidate, local_candidate, pending_check); return TRUE; } /* Remove all pointers to the given @sock from the connection checking process. * These are entirely NiceCandidates pointed to from various places. */ void conn_check_prune_socket (NiceAgent *agent, NiceStream *stream, NiceComponent *component, NiceSocket *sock) { GSList *l; gboolean pair_failed = FALSE; gboolean selected_pair_failed = FALSE; guint p_nominated = 0, p_count = 0; if (component->selected_pair.local && component->selected_pair.local->sockptr == sock) { nice_debug ("Agent %p: Selected pair socket %p has been destroyed, " "declaring failed", agent, sock); selected_pair_failed = TRUE; if (component->state == NICE_COMPONENT_STATE_READY) agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_FAILED); else if (component->state == NICE_COMPONENT_STATE_CONNECTED) agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_CONNECTING); } /* Prune from the candidate check pairs. */ for (l = stream->conncheck_list; l != NULL;) { CandidateCheckPair *p = l->data; GSList *next = l->next; if (p->component_id != component->id) { l = next; continue; } if (selected_pair_failed && !p->retransmit && p->stun_transactions) p->retransmit = TRUE; if ((p->local != NULL && ((NiceCandidateImpl*) p->local)->sockptr == sock) || (p->remote != NULL && ((NiceCandidateImpl*)p->remote)->sockptr == sock) || (p->sockptr == sock)) { nice_debug ("Agent %p : Retransmissions failed, giving up on pair %p", agent, p); if (component->selected_pair.local == ((NiceCandidateImpl *)p->local) && component->selected_pair.remote == ((NiceCandidateImpl *)p->remote)) selected_pair_failed = TRUE; candidate_check_pair_fail (stream, agent, p); candidate_check_pair_free (agent, p); stream->conncheck_list = g_slist_delete_link (stream->conncheck_list, l); pair_failed = TRUE; } else { p_count++; if (p->nominated) p_nominated++; } l = next; } if (pair_failed) { if (p_count == 0) agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_FAILED); else if (p_nominated == 0) { if (component->state == NICE_COMPONENT_STATE_READY) agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_FAILED); else if (component->state == NICE_COMPONENT_STATE_CONNECTED) agent_signal_component_state_change (agent, stream->id, component->id, NICE_COMPONENT_STATE_CONNECTING); } } /* outside of the previous loop, because it may * remove pairs from the conncheck list */ if (pair_failed) conn_check_update_check_list_state_for_ready (agent, stream, component); }