1469 lines
45 KiB
C
1469 lines
45 KiB
C
/*
|
|
* This file is part of the Nice GLib ICE library.
|
|
*
|
|
* (C) 2008-2009 Collabora Ltd.
|
|
* Contact: Youness Alaoui
|
|
* (C) 2007-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:
|
|
* Youness Alaoui, Collabora Ltd.
|
|
* Kai Vehmanen, Nokia
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of the
|
|
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
|
|
* case the provisions of LGPL are applicable instead of those above. If you
|
|
* wish to allow use of your version of this file only under the terms of the
|
|
* LGPL and not to allow others to use your version of this file under the
|
|
* MPL, indicate your decision by deleting the provisions above and replace
|
|
* them with the notice and other provisions required by the LGPL. If you do
|
|
* not delete the provisions above, a recipient may use your version of this
|
|
* file under either the MPL or the LGPL.
|
|
*/
|
|
|
|
/*
|
|
* @file discovery.c
|
|
* @brief ICE candidate discovery functions
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "debug.h"
|
|
|
|
#include "agent.h"
|
|
#include "agent-priv.h"
|
|
#include "component.h"
|
|
#include "discovery.h"
|
|
#include "stun/usages/bind.h"
|
|
#include "stun/usages/turn.h"
|
|
#include "socket.h"
|
|
|
|
/*
|
|
* Frees the CandidateDiscovery structure pointed to
|
|
* by 'user data'. Compatible with g_slist_free_full().
|
|
*/
|
|
static void discovery_free_item (CandidateDiscovery *cand)
|
|
{
|
|
if (cand->turn)
|
|
turn_server_unref (cand->turn);
|
|
|
|
g_slice_free (CandidateDiscovery, cand);
|
|
}
|
|
|
|
/*
|
|
* Frees all discovery related resources for the agent.
|
|
*/
|
|
void discovery_free (NiceAgent *agent)
|
|
{
|
|
g_slist_free_full (agent->discovery_list,
|
|
(GDestroyNotify) discovery_free_item);
|
|
agent->discovery_list = NULL;
|
|
agent->discovery_unsched_items = 0;
|
|
|
|
if (agent->discovery_timer_source != NULL) {
|
|
g_source_destroy (agent->discovery_timer_source);
|
|
g_source_unref (agent->discovery_timer_source);
|
|
agent->discovery_timer_source = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Prunes the list of discovery processes for items related
|
|
* to stream 'stream_id'.
|
|
*
|
|
* @return TRUE on success, FALSE on a fatal error
|
|
*/
|
|
void discovery_prune_stream (NiceAgent *agent, guint stream_id)
|
|
{
|
|
GSList *i;
|
|
|
|
for (i = agent->discovery_list; i ; ) {
|
|
CandidateDiscovery *cand = i->data;
|
|
GSList *next = i->next;
|
|
|
|
if (cand->stream_id == stream_id) {
|
|
agent->discovery_list = g_slist_remove (agent->discovery_list, cand);
|
|
discovery_free_item (cand);
|
|
}
|
|
i = next;
|
|
}
|
|
|
|
if (agent->discovery_list == NULL) {
|
|
/* noone using the timer anymore, clean it up */
|
|
discovery_free (agent);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Prunes the list of discovery processes for items related
|
|
* to socket @sock.
|
|
*
|
|
* @return TRUE on success, FALSE on a fatal error
|
|
*/
|
|
void discovery_prune_socket (NiceAgent *agent, NiceSocket *sock)
|
|
{
|
|
GSList *i;
|
|
|
|
for (i = agent->discovery_list; i ; ) {
|
|
CandidateDiscovery *discovery = i->data;
|
|
GSList *next = i->next;
|
|
|
|
if (discovery->nicesock == sock) {
|
|
agent->discovery_list = g_slist_remove (agent->discovery_list, discovery);
|
|
discovery_free_item (discovery);
|
|
}
|
|
i = next;
|
|
}
|
|
|
|
if (agent->discovery_list == NULL) {
|
|
/* noone using the timer anymore, clean it up */
|
|
discovery_free (agent);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Frees a CandidateRefresh and calls destroy callback if it has been set.
|
|
*/
|
|
void refresh_free (NiceAgent *agent, CandidateRefresh *cand)
|
|
{
|
|
nice_debug ("Agent %p : Freeing candidate refresh %p", agent, cand);
|
|
|
|
agent->refresh_list = g_slist_remove (agent->refresh_list, cand);
|
|
agent->pruning_refreshes = g_slist_remove (agent->pruning_refreshes, cand);
|
|
|
|
if (cand->timer_source != NULL) {
|
|
g_source_destroy (cand->timer_source);
|
|
g_clear_pointer (&cand->timer_source, g_source_unref);
|
|
}
|
|
|
|
if (cand->tick_source) {
|
|
g_source_destroy (cand->tick_source);
|
|
g_clear_pointer (&cand->tick_source, g_source_unref);
|
|
}
|
|
|
|
if (cand->destroy_source) {
|
|
g_source_destroy (cand->destroy_source);
|
|
g_source_unref (cand->destroy_source);
|
|
}
|
|
|
|
if (cand->destroy_cb) {
|
|
cand->destroy_cb (cand->destroy_cb_data);
|
|
}
|
|
|
|
g_slice_free (CandidateRefresh, cand);
|
|
}
|
|
|
|
static gboolean on_refresh_remove_timeout (NiceAgent *agent,
|
|
CandidateRefresh *cand)
|
|
{
|
|
switch (stun_timer_refresh (&cand->timer)) {
|
|
case STUN_USAGE_TIMER_RETURN_TIMEOUT:
|
|
{
|
|
StunTransactionId id;
|
|
|
|
nice_debug ("Agent %p : TURN deallocate for refresh %p timed out",
|
|
agent, cand);
|
|
|
|
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:
|
|
nice_debug ("Agent %p : Retransmitting TURN deallocate for refresh %p",
|
|
agent, cand);
|
|
|
|
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,
|
|
"TURN deallocate retransmission", stun_timer_remainder (&cand->timer),
|
|
(NiceTimeoutLockedCallback) on_refresh_remove_timeout, cand);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/*
|
|
* Closes the port associated with the candidate refresh on the TURN server by
|
|
* sending a refresh request that has zero lifetime. After a response is
|
|
* received or the request times out, 'cand' gets freed and 'cb' is called.
|
|
*/
|
|
static gboolean refresh_remove_async (NiceAgent *agent, gpointer pointer)
|
|
{
|
|
uint8_t *username;
|
|
gsize username_len;
|
|
uint8_t *password;
|
|
gsize password_len;
|
|
size_t buffer_len = 0;
|
|
CandidateRefresh *cand = (CandidateRefresh *) pointer;
|
|
NiceCandidateImpl *c = (NiceCandidateImpl *) cand->candidate;
|
|
StunUsageTurnCompatibility turn_compat = agent_to_turn_compatibility (agent);
|
|
|
|
nice_debug ("Agent %p : Sending request to remove TURN allocation "
|
|
"for refresh %p", agent, cand);
|
|
|
|
if (cand->timer_source != NULL) {
|
|
g_source_destroy (cand->timer_source);
|
|
g_source_unref (cand->timer_source);
|
|
cand->timer_source = NULL;
|
|
}
|
|
|
|
g_source_destroy (cand->destroy_source);
|
|
g_source_unref (cand->destroy_source);
|
|
cand->destroy_source = NULL;
|
|
|
|
username = (uint8_t *)c->turn->username;
|
|
username_len = (size_t) strlen (c->turn->username);
|
|
password = (uint8_t *)c->turn->password;
|
|
password_len = (size_t) strlen (c->turn->password);
|
|
|
|
if (turn_compat == STUN_USAGE_TURN_COMPATIBILITY_MSN ||
|
|
turn_compat == STUN_USAGE_TURN_COMPATIBILITY_OC2007) {
|
|
username = c->turn->decoded_username;
|
|
password = c->turn->decoded_password;
|
|
username_len = c->turn->decoded_username_len;
|
|
password_len = c->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, 0,
|
|
username, username_len,
|
|
password, password_len,
|
|
agent_to_turn_compatibility (agent));
|
|
|
|
if (buffer_len > 0) {
|
|
agent_socket_send (cand->nicesock, &cand->server, buffer_len,
|
|
(gchar *)cand->stun_buffer);
|
|
|
|
stun_timer_start (&cand->timer, agent->stun_initial_timeout,
|
|
agent->stun_max_retransmissions);
|
|
|
|
agent_timeout_add_with_context (agent, &cand->tick_source,
|
|
"TURN deallocate retransmission", stun_timer_remainder (&cand->timer),
|
|
(NiceTimeoutLockedCallback) on_refresh_remove_timeout, cand);
|
|
}
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
typedef struct {
|
|
NiceAgent *agent;
|
|
gpointer user_data;
|
|
guint items_to_free;
|
|
NiceTimeoutLockedCallback cb;
|
|
} RefreshPruneAsyncData;
|
|
|
|
static void on_refresh_removed (RefreshPruneAsyncData *data)
|
|
{
|
|
if (data->items_to_free == 0 || --(data->items_to_free) == 0) {
|
|
data->cb (data->agent, data->user_data);
|
|
g_free (data);
|
|
}
|
|
}
|
|
|
|
static void refresh_prune_async (NiceAgent *agent, GSList *refreshes,
|
|
NiceTimeoutLockedCallback function, gpointer user_data)
|
|
{
|
|
RefreshPruneAsyncData *data = g_new0 (RefreshPruneAsyncData, 1);
|
|
GSList *it;
|
|
guint timeout = 0;
|
|
|
|
data->agent = agent;
|
|
data->user_data = user_data;
|
|
data->cb = function;
|
|
|
|
for (it = refreshes; it; it = it->next) {
|
|
CandidateRefresh *cand = it->data;
|
|
|
|
if (cand->disposing)
|
|
continue;
|
|
|
|
agent->pruning_refreshes = g_slist_append (agent->pruning_refreshes, cand);
|
|
|
|
timeout += agent->timer_ta;
|
|
cand->disposing = TRUE;
|
|
cand->destroy_cb = (GDestroyNotify) on_refresh_removed;
|
|
cand->destroy_cb_data = data;
|
|
|
|
agent_timeout_add_with_context(agent, &cand->destroy_source,
|
|
"TURN refresh remove async", timeout, refresh_remove_async, cand);
|
|
|
|
++data->items_to_free;
|
|
}
|
|
|
|
if (data->items_to_free == 0) {
|
|
/* Stream doesn't have any refreshes to remove. Invoke our callback once to
|
|
* schedule client's callback function. */
|
|
on_refresh_removed (data);
|
|
}
|
|
}
|
|
|
|
void refresh_prune_agent_async (NiceAgent *agent,
|
|
NiceTimeoutLockedCallback function, gpointer user_data)
|
|
{
|
|
refresh_prune_async (agent, agent->refresh_list, function, user_data);
|
|
}
|
|
|
|
/*
|
|
* Removes the candidate refreshes related to 'stream' and asynchronously
|
|
* closes the associated port allocations on TURN server. Invokes 'function'
|
|
* when the process finishes.
|
|
*/
|
|
void refresh_prune_stream_async (NiceAgent *agent, NiceStream *stream,
|
|
NiceTimeoutLockedCallback function)
|
|
{
|
|
GSList *refreshes = NULL;
|
|
GSList *i;
|
|
|
|
for (i = agent->refresh_list; i ; i = i->next) {
|
|
CandidateRefresh *cand = i->data;
|
|
|
|
/* Don't free the candidate refresh to the currently selected local candidate
|
|
* unless the whole pair is being destroyed.
|
|
*/
|
|
if (cand->stream_id == stream->id) {
|
|
refreshes = g_slist_append (refreshes, cand);
|
|
}
|
|
}
|
|
|
|
refresh_prune_async (agent, refreshes, function, stream);
|
|
g_slist_free (refreshes);
|
|
}
|
|
|
|
/*
|
|
* Removes the candidate refreshes related to 'candidate'. The function does not
|
|
* close any associated port allocations on TURN server. Its purpose is in
|
|
* situations when an error is detected in socket communication that prevents
|
|
* sending more requests to the server.
|
|
*/
|
|
void refresh_prune_candidate (NiceAgent *agent, NiceCandidateImpl *candidate)
|
|
{
|
|
GSList *i;
|
|
|
|
for (i = agent->refresh_list; i;) {
|
|
GSList *next = i->next;
|
|
CandidateRefresh *refresh = i->data;
|
|
|
|
if (refresh->candidate == candidate) {
|
|
refresh_free(agent, refresh);
|
|
}
|
|
|
|
i = next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Removes the candidate refreshes related to 'candidate' and asynchronously
|
|
* closes the associated port allocations on TURN server. Invokes 'function'
|
|
* when the process finishes.
|
|
*/
|
|
void refresh_prune_candidate_async (NiceAgent *agent,
|
|
NiceCandidateImpl *candidate, NiceTimeoutLockedCallback function)
|
|
{
|
|
GSList *refreshes = NULL;
|
|
GSList *i;
|
|
|
|
for (i = agent->refresh_list; i; i = i->next) {
|
|
CandidateRefresh *refresh = i->data;
|
|
|
|
if (refresh->candidate == candidate) {
|
|
refreshes = g_slist_append (refreshes, refresh);
|
|
}
|
|
}
|
|
|
|
refresh_prune_async (agent, refreshes, function, candidate);
|
|
g_slist_free (refreshes);
|
|
}
|
|
|
|
/*
|
|
* Removes the candidate refreshes related to 'nicesock'.
|
|
*/
|
|
void refresh_prune_socket (NiceAgent *agent, NiceSocket *nicesock)
|
|
{
|
|
GSList *i;
|
|
|
|
for (i = agent->refresh_list; i;) {
|
|
GSList *next = i->next;
|
|
CandidateRefresh *refresh = i->data;
|
|
|
|
if (refresh->nicesock == nicesock)
|
|
refresh_free(agent, refresh);
|
|
|
|
i = next;
|
|
}
|
|
|
|
for (i = agent->pruning_refreshes; i;) {
|
|
GSList *next = i->next;
|
|
CandidateRefresh *refresh = i->data;
|
|
|
|
if (refresh->nicesock == nicesock)
|
|
refresh_free(agent, refresh);
|
|
|
|
i = next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Adds a new local candidate. Implements the candidate pruning
|
|
* defined in ICE spec section 4.1.3 "Eliminating Redundant
|
|
* Candidates" (ID-19).
|
|
*/
|
|
static gboolean priv_add_local_candidate_pruned (NiceAgent *agent, guint stream_id, NiceComponent *component, NiceCandidate *candidate)
|
|
{
|
|
GSList *i;
|
|
|
|
g_assert (candidate != NULL);
|
|
|
|
for (i = component->local_candidates; i ; i = i->next) {
|
|
NiceCandidate *c = i->data;
|
|
|
|
if (nice_address_equal (&c->base_addr, &candidate->base_addr) &&
|
|
nice_address_equal (&c->addr, &candidate->addr) &&
|
|
c->transport == candidate->transport) {
|
|
nice_debug ("Agent %p : s%d/c%d : cand %p redundant, ignoring.",
|
|
agent, stream_id, component->id, candidate);
|
|
return FALSE;
|
|
}
|
|
|
|
if (c->type == NICE_CANDIDATE_TYPE_RELAYED &&
|
|
candidate->type == NICE_CANDIDATE_TYPE_RELAYED &&
|
|
c->transport == candidate->transport &&
|
|
nice_address_equal_no_port (&c->addr, &candidate->addr)) {
|
|
nice_debug ("Agent %p : s%d/c%d : relay cand %p redundant, ignoring.",
|
|
agent, stream_id, component->id, candidate);
|
|
return FALSE;
|
|
}
|
|
|
|
if (c->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE &&
|
|
candidate->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE &&
|
|
c->transport == candidate->transport &&
|
|
nice_address_equal_no_port (&c->addr, &candidate->addr)) {
|
|
nice_debug ("Agent %p : s%d/c%d : srflx cand %p redundant, ignoring.",
|
|
agent, stream_id, component->id, candidate);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
component->local_candidates = g_slist_append (component->local_candidates,
|
|
candidate);
|
|
conn_check_add_for_local_candidate(agent, stream_id, component, candidate);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static guint priv_highest_remote_foundation (NiceComponent *component)
|
|
{
|
|
GSList *i;
|
|
guint highest = 1;
|
|
gchar foundation[NICE_CANDIDATE_MAX_FOUNDATION];
|
|
|
|
for (highest = 1;; highest++) {
|
|
gboolean taken = FALSE;
|
|
|
|
g_snprintf (foundation, NICE_CANDIDATE_MAX_FOUNDATION, "remote%u",
|
|
highest);
|
|
for (i = component->remote_candidates; i; i = i->next) {
|
|
NiceCandidate *cand = i->data;
|
|
if (strncmp (foundation, cand->foundation,
|
|
NICE_CANDIDATE_MAX_FOUNDATION) == 0) {
|
|
taken = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!taken)
|
|
return highest;
|
|
}
|
|
|
|
g_return_val_if_reached (highest);
|
|
}
|
|
|
|
/* From RFC 5245 section 4.1.3:
|
|
*
|
|
* for reflexive and relayed candidates, the STUN or TURN servers
|
|
* used to obtain them have the same IP address.
|
|
*/
|
|
static gboolean
|
|
priv_compare_turn_servers (TurnServer *turn1, TurnServer *turn2)
|
|
{
|
|
if (turn1 == turn2)
|
|
return TRUE;
|
|
if (turn1 == NULL || turn2 == NULL)
|
|
return FALSE;
|
|
|
|
return nice_address_equal_no_port (&turn1->server, &turn2->server);
|
|
}
|
|
|
|
/*
|
|
* Assings a foundation to the candidate.
|
|
*
|
|
* Implements the mechanism described in ICE sect
|
|
* 4.1.1.3 "Computing Foundations" (ID-19).
|
|
*/
|
|
static void priv_assign_foundation (NiceAgent *agent, NiceCandidate *candidate)
|
|
{
|
|
GSList *i, *j, *k;
|
|
NiceCandidateImpl *c = (NiceCandidateImpl *) candidate;
|
|
|
|
for (i = agent->streams; i; i = i->next) {
|
|
NiceStream *stream = i->data;
|
|
for (j = stream->components; j; j = j->next) {
|
|
NiceComponent *component = j->data;
|
|
for (k = component->local_candidates; k; k = k->next) {
|
|
NiceCandidateImpl *n = k->data;
|
|
|
|
/* note: candidate must not be on the local candidate list */
|
|
g_assert (c != n);
|
|
|
|
if (candidate->type != n->c.type)
|
|
continue;
|
|
|
|
if (candidate->transport != n->c.transport)
|
|
continue;
|
|
|
|
if (candidate->type == NICE_CANDIDATE_TYPE_RELAYED &&
|
|
!nice_address_equal_no_port (&candidate->addr, &n->c.addr))
|
|
continue;
|
|
|
|
/* The base of a relayed candidate is that candidate itself, see
|
|
* sect 5.1.1.2. (Server Reflexive and Relayed Candidates) or
|
|
* ICE spec (RFC8445). It allows the relayed candidate from the
|
|
* same TURN server to share the same foundation.
|
|
*/
|
|
if (candidate->type != NICE_CANDIDATE_TYPE_RELAYED &&
|
|
!nice_address_equal_no_port (&candidate->base_addr, &n->c.base_addr))
|
|
continue;
|
|
|
|
if (candidate->type == NICE_CANDIDATE_TYPE_RELAYED &&
|
|
!priv_compare_turn_servers (c->turn, n->turn))
|
|
continue;
|
|
|
|
if (candidate->type == NICE_CANDIDATE_TYPE_RELAYED &&
|
|
agent->compatibility == NICE_COMPATIBILITY_GOOGLE)
|
|
/* note: currently only one STUN server per stream at a
|
|
* time is supported, so there is no need to check
|
|
* for candidates that would otherwise share the
|
|
* foundation, but have different STUN servers */
|
|
continue;
|
|
|
|
g_strlcpy (candidate->foundation, n->c.foundation,
|
|
NICE_CANDIDATE_MAX_FOUNDATION);
|
|
if (n->c.username) {
|
|
g_free (candidate->username);
|
|
candidate->username = g_strdup (n->c.username);
|
|
}
|
|
if (n->c.password) {
|
|
g_free (candidate->password);
|
|
candidate->password = g_strdup (n->c.password);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_snprintf (candidate->foundation, NICE_CANDIDATE_MAX_FOUNDATION,
|
|
"%u", agent->next_candidate_id++);
|
|
}
|
|
|
|
static void priv_assign_remote_foundation (NiceAgent *agent, NiceCandidate *candidate)
|
|
{
|
|
GSList *i, *j, *k;
|
|
guint next_remote_id;
|
|
NiceComponent *component = NULL;
|
|
|
|
for (i = agent->streams; i; i = i->next) {
|
|
NiceStream *stream = i->data;
|
|
for (j = stream->components; j; j = j->next) {
|
|
NiceComponent *c = j->data;
|
|
|
|
if (c->id == candidate->component_id)
|
|
component = c;
|
|
|
|
for (k = c->remote_candidates; k; k = k->next) {
|
|
NiceCandidate *n = k->data;
|
|
|
|
/* note: candidate must not on the remote candidate list */
|
|
g_assert (candidate != n);
|
|
|
|
if (candidate->type == n->type &&
|
|
candidate->transport == n->transport &&
|
|
candidate->stream_id == n->stream_id &&
|
|
nice_address_equal_no_port (&candidate->addr, &n->addr)) {
|
|
/* note: No need to check for STUN/TURN servers, as these candidate
|
|
* will always be peer reflexive, never relayed or serve reflexive.
|
|
*/
|
|
g_strlcpy (candidate->foundation, n->foundation,
|
|
NICE_CANDIDATE_MAX_FOUNDATION);
|
|
if (n->username) {
|
|
g_free (candidate->username);
|
|
candidate->username = g_strdup (n->username);
|
|
}
|
|
if (n->password) {
|
|
g_free (candidate->password);
|
|
candidate->password = g_strdup (n->password);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (component) {
|
|
next_remote_id = priv_highest_remote_foundation (component);
|
|
g_snprintf (candidate->foundation, NICE_CANDIDATE_MAX_FOUNDATION,
|
|
"remote%u", next_remote_id);
|
|
}
|
|
}
|
|
|
|
|
|
static
|
|
void priv_generate_candidate_credentials (NiceAgent *agent,
|
|
NiceCandidate *candidate)
|
|
{
|
|
|
|
if (agent->compatibility == NICE_COMPATIBILITY_MSN ||
|
|
agent->compatibility == NICE_COMPATIBILITY_OC2007) {
|
|
guchar username[32];
|
|
guchar password[16];
|
|
|
|
g_free (candidate->username);
|
|
g_free (candidate->password);
|
|
|
|
nice_rng_generate_bytes (agent->rng, 32, (gchar *)username);
|
|
nice_rng_generate_bytes (agent->rng, 16, (gchar *)password);
|
|
|
|
candidate->username = g_base64_encode (username, 32);
|
|
candidate->password = g_base64_encode (password, 16);
|
|
|
|
} else if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE) {
|
|
gchar username[16];
|
|
|
|
g_free (candidate->username);
|
|
g_free (candidate->password);
|
|
candidate->password = NULL;
|
|
|
|
nice_rng_generate_bytes_print (agent->rng, 16, (gchar *)username);
|
|
|
|
candidate->username = g_strndup (username, 16);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
static gboolean
|
|
priv_local_host_candidate_duplicate_port (NiceAgent *agent,
|
|
NiceCandidate *candidate, gboolean accept_duplicate)
|
|
{
|
|
GSList *i, *j, *k;
|
|
|
|
if (candidate->transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE)
|
|
return FALSE;
|
|
|
|
for (i = agent->streams; i; i = i->next) {
|
|
NiceStream *stream = i->data;
|
|
|
|
for (j = stream->components; j; j = j->next) {
|
|
NiceComponent *component = j->data;
|
|
|
|
for (k = component->local_candidates; k; k = k->next) {
|
|
NiceCandidate *c = k->data;
|
|
|
|
if (candidate->transport == c->transport &&
|
|
nice_address_ip_version (&candidate->addr) ==
|
|
nice_address_ip_version (&c->addr) &&
|
|
nice_address_get_port (&candidate->addr) ==
|
|
nice_address_get_port (&c->addr)) {
|
|
|
|
if (accept_duplicate && candidate->stream_id == stream->id &&
|
|
candidate->component_id == component->id) {
|
|
/* We accept it anyway, but with a warning! */
|
|
gchar ip[NICE_ADDRESS_STRING_LEN];
|
|
gchar ip2[NICE_ADDRESS_STRING_LEN];
|
|
|
|
nice_address_to_string (&candidate->addr, ip);
|
|
nice_address_to_string (&c->addr, ip2);
|
|
nice_debug ("Agent %p: s%d/c%d: host candidate %s:[%s]:%u "
|
|
" will use the same port as %s:[%s]:%u", agent,
|
|
stream->id, component->id,
|
|
nice_candidate_transport_to_string (candidate->transport),
|
|
ip, nice_address_get_port (&candidate->addr),
|
|
nice_candidate_transport_to_string (c->transport),
|
|
ip2, nice_address_get_port (&c->addr));
|
|
return FALSE;
|
|
}
|
|
{
|
|
gchar ip[NICE_ADDRESS_STRING_LEN];
|
|
gchar ip2[NICE_ADDRESS_STRING_LEN];
|
|
|
|
nice_address_to_string (&candidate->addr, ip);
|
|
nice_address_to_string (&c->addr, ip2);
|
|
nice_debug ("Agent %p: s%d/c%d: host candidate %s:[%s]:%u "
|
|
" has the same port as %s:[%s]:%u from s%d/c%d", agent,
|
|
candidate->stream_id, candidate->component_id,
|
|
nice_candidate_transport_to_string (candidate->transport),
|
|
ip, nice_address_get_port (&candidate->addr),
|
|
nice_candidate_transport_to_string (c->transport),
|
|
ip2, nice_address_get_port (&c->addr),
|
|
stream->id, component->id);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Creates a local host candidate for 'component_id' of stream
|
|
* 'stream_id'.
|
|
*
|
|
* @return pointer to the created candidate, or NULL on error
|
|
*/
|
|
HostCandidateResult discovery_add_local_host_candidate (
|
|
NiceAgent *agent,
|
|
guint stream_id,
|
|
guint component_id,
|
|
NiceAddress *address,
|
|
NiceCandidateTransport transport,
|
|
gboolean accept_duplicate,
|
|
NiceCandidateImpl **outcandidate)
|
|
{
|
|
NiceCandidate *candidate;
|
|
NiceCandidateImpl *c;
|
|
NiceComponent *component;
|
|
NiceStream *stream;
|
|
NiceSocket *nicesock = NULL;
|
|
HostCandidateResult res = HOST_CANDIDATE_FAILED;
|
|
GError *error = NULL;
|
|
|
|
if (!agent_find_component (agent, stream_id, component_id, &stream, &component))
|
|
return res;
|
|
|
|
candidate = nice_candidate_new (NICE_CANDIDATE_TYPE_HOST);
|
|
c = (NiceCandidateImpl *) candidate;
|
|
candidate->transport = transport;
|
|
candidate->stream_id = stream_id;
|
|
candidate->component_id = component_id;
|
|
candidate->addr = *address;
|
|
candidate->base_addr = *address;
|
|
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE) {
|
|
candidate->priority = nice_candidate_jingle_priority (candidate);
|
|
} else if (agent->compatibility == NICE_COMPATIBILITY_MSN ||
|
|
agent->compatibility == NICE_COMPATIBILITY_OC2007) {
|
|
candidate->priority = nice_candidate_msn_priority (candidate);
|
|
} else if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) {
|
|
candidate->priority = nice_candidate_ms_ice_priority (candidate,
|
|
agent->reliable, FALSE);
|
|
} else {
|
|
candidate->priority = nice_candidate_ice_priority (candidate,
|
|
agent->reliable, FALSE);
|
|
}
|
|
|
|
priv_generate_candidate_credentials (agent, candidate);
|
|
priv_assign_foundation (agent, candidate);
|
|
|
|
/* note: candidate username and password are left NULL as stream
|
|
level ufrag/password are used */
|
|
if (transport == NICE_CANDIDATE_TRANSPORT_UDP) {
|
|
nicesock = nice_udp_bsd_socket_new (address, &error);
|
|
} else if (transport == NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE) {
|
|
nicesock = nice_tcp_active_socket_new (agent->main_context, address);
|
|
} else if (transport == NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE) {
|
|
nicesock = nice_tcp_passive_socket_new (agent->main_context, address, &error);
|
|
} else {
|
|
/* TODO: Add TCP-SO */
|
|
}
|
|
if (!nicesock) {
|
|
if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE))
|
|
res = HOST_CANDIDATE_DUPLICATE_PORT;
|
|
else
|
|
res = HOST_CANDIDATE_CANT_CREATE_SOCKET;
|
|
g_clear_error (&error);
|
|
goto errors;
|
|
}
|
|
|
|
c->sockptr = nicesock;
|
|
candidate->addr = nicesock->addr;
|
|
candidate->base_addr = nicesock->addr;
|
|
|
|
if (priv_local_host_candidate_duplicate_port (agent, candidate, accept_duplicate)) {
|
|
res = HOST_CANDIDATE_DUPLICATE_PORT;
|
|
goto errors;
|
|
}
|
|
|
|
if (!priv_add_local_candidate_pruned (agent, stream_id, component,
|
|
candidate)) {
|
|
res = HOST_CANDIDATE_REDUNDANT;
|
|
goto errors;
|
|
}
|
|
|
|
_priv_set_socket_tos (agent, nicesock, stream->tos);
|
|
nice_component_attach_socket (component, nicesock);
|
|
|
|
*outcandidate = c;
|
|
|
|
return HOST_CANDIDATE_SUCCESS;
|
|
|
|
errors:
|
|
nice_candidate_free (candidate);
|
|
if (nicesock)
|
|
nice_socket_free (nicesock);
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Creates a server reflexive candidate for 'component_id' of stream
|
|
* 'stream_id'.
|
|
*
|
|
* @return pointer to the created candidate, or NULL on error
|
|
*/
|
|
void
|
|
discovery_add_server_reflexive_candidate (
|
|
NiceAgent *agent,
|
|
guint stream_id,
|
|
guint component_id,
|
|
NiceAddress *address,
|
|
NiceCandidateTransport transport,
|
|
NiceSocket *base_socket,
|
|
const NiceAddress *server_address,
|
|
gboolean nat_assisted)
|
|
{
|
|
NiceCandidate *candidate;
|
|
NiceCandidateImpl *c;
|
|
NiceComponent *component;
|
|
NiceStream *stream;
|
|
gboolean result = FALSE;
|
|
|
|
if (!agent_find_component (agent, stream_id, component_id, &stream, &component))
|
|
return;
|
|
|
|
candidate = nice_candidate_new (NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE);
|
|
c = (NiceCandidateImpl *) candidate;
|
|
candidate->transport = transport;
|
|
candidate->stream_id = stream_id;
|
|
candidate->component_id = component_id;
|
|
candidate->addr = *address;
|
|
|
|
/* step: link to the base candidate+socket */
|
|
c->sockptr = base_socket;
|
|
candidate->base_addr = base_socket->addr;
|
|
|
|
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE) {
|
|
candidate->priority = nice_candidate_jingle_priority (candidate);
|
|
} else if (agent->compatibility == NICE_COMPATIBILITY_MSN ||
|
|
agent->compatibility == NICE_COMPATIBILITY_OC2007) {
|
|
candidate->priority = nice_candidate_msn_priority (candidate);
|
|
} else if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) {
|
|
candidate->priority = nice_candidate_ms_ice_priority (candidate,
|
|
agent->reliable, nat_assisted);
|
|
} else {
|
|
candidate->priority = nice_candidate_ice_priority (candidate,
|
|
agent->reliable, nat_assisted);
|
|
}
|
|
|
|
if (server_address != NULL)
|
|
c->stun_server = nice_address_dup (server_address);
|
|
|
|
priv_generate_candidate_credentials (agent, candidate);
|
|
priv_assign_foundation (agent, candidate);
|
|
|
|
result = priv_add_local_candidate_pruned (agent, stream_id, component, candidate);
|
|
if (result) {
|
|
agent_signal_new_candidate (agent, candidate);
|
|
}
|
|
else {
|
|
/* error: duplicate candidate */
|
|
nice_candidate_free (candidate), candidate = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Creates a server reflexive candidate for 'component_id' of stream
|
|
* 'stream_id' for each TCP_PASSIVE and TCP_ACTIVE candidates for each
|
|
* base address.
|
|
*
|
|
* @return pointer to the created candidate, or NULL on error
|
|
*/
|
|
void
|
|
discovery_discover_tcp_server_reflexive_candidates (
|
|
NiceAgent *agent,
|
|
guint stream_id,
|
|
guint component_id,
|
|
NiceAddress *address,
|
|
NiceSocket *base_socket,
|
|
const NiceAddress *server_addr)
|
|
{
|
|
NiceComponent *component;
|
|
NiceStream *stream;
|
|
NiceAddress base_addr = base_socket->addr;
|
|
GSList *i;
|
|
|
|
if (!agent_find_component (agent, stream_id, component_id, &stream, &component))
|
|
return;
|
|
|
|
nice_address_set_port (&base_addr, 0);
|
|
for (i = component->local_candidates; i; i = i ->next) {
|
|
NiceCandidate *c = i->data;
|
|
NiceAddress caddr;
|
|
|
|
caddr = c->addr;
|
|
nice_address_set_port (&caddr, 0);
|
|
if (agent->force_relay == FALSE &&
|
|
c->transport != NICE_CANDIDATE_TRANSPORT_UDP &&
|
|
c->type == NICE_CANDIDATE_TYPE_HOST &&
|
|
nice_address_equal (&base_addr, &caddr)) {
|
|
nice_address_set_port (address, nice_address_get_port (&c->addr));
|
|
discovery_add_server_reflexive_candidate (
|
|
agent,
|
|
stream_id,
|
|
component_id,
|
|
address,
|
|
c->transport,
|
|
((NiceCandidateImpl *) c)->sockptr,
|
|
server_addr,
|
|
FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Creates a server reflexive candidate for 'component_id' of stream
|
|
* 'stream_id'.
|
|
*
|
|
* @return pointer to the created candidate, or NULL on error
|
|
*/
|
|
NiceCandidateImpl *
|
|
discovery_add_relay_candidate (
|
|
NiceAgent *agent,
|
|
guint stream_id,
|
|
guint component_id,
|
|
NiceAddress *address,
|
|
NiceCandidateTransport transport,
|
|
NiceSocket *base_socket,
|
|
TurnServer *turn,
|
|
uint32_t *lifetime)
|
|
{
|
|
NiceCandidate *candidate;
|
|
NiceCandidateImpl *c;
|
|
NiceComponent *component;
|
|
NiceStream *stream;
|
|
NiceSocket *relay_socket = NULL;
|
|
|
|
if (!agent_find_component (agent, stream_id, component_id, &stream, &component))
|
|
return NULL;
|
|
|
|
candidate = nice_candidate_new (NICE_CANDIDATE_TYPE_RELAYED);
|
|
c = (NiceCandidateImpl *) candidate;
|
|
candidate->transport = transport;
|
|
candidate->stream_id = stream_id;
|
|
candidate->component_id = component_id;
|
|
candidate->addr = *address;
|
|
c->turn = turn_server_ref (turn);
|
|
|
|
/* step: link to the base candidate+socket */
|
|
relay_socket = nice_udp_turn_socket_new (agent->main_context, address,
|
|
base_socket, &turn->server,
|
|
turn->username, turn->password,
|
|
agent_to_turn_socket_compatibility (agent));
|
|
if (!relay_socket)
|
|
goto errors;
|
|
|
|
c->sockptr = relay_socket;
|
|
candidate->base_addr = base_socket->addr;
|
|
|
|
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE) {
|
|
candidate->priority = nice_candidate_jingle_priority (candidate);
|
|
} else if (agent->compatibility == NICE_COMPATIBILITY_MSN ||
|
|
agent->compatibility == NICE_COMPATIBILITY_OC2007) {
|
|
candidate->priority = nice_candidate_msn_priority (candidate);
|
|
} else if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) {
|
|
candidate->priority = nice_candidate_ms_ice_priority (candidate,
|
|
agent->reliable, FALSE);
|
|
} else {
|
|
candidate->priority = nice_candidate_ice_priority (candidate,
|
|
agent->reliable, FALSE);
|
|
}
|
|
|
|
priv_generate_candidate_credentials (agent, candidate);
|
|
|
|
/* Google uses the turn username as the candidate username */
|
|
if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE) {
|
|
g_free (candidate->username);
|
|
candidate->username = g_strdup (turn->username);
|
|
}
|
|
|
|
priv_assign_foundation (agent, candidate);
|
|
|
|
if (!priv_add_local_candidate_pruned (agent, stream_id, component, candidate)) {
|
|
if (lifetime)
|
|
*lifetime = 0;
|
|
return c;
|
|
}
|
|
|
|
nice_component_attach_socket (component, relay_socket);
|
|
agent_signal_new_candidate (agent, candidate);
|
|
|
|
return c;
|
|
|
|
errors:
|
|
nice_candidate_free (candidate);
|
|
if (relay_socket)
|
|
nice_socket_free (relay_socket);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Creates a peer reflexive candidate for 'component_id' of stream
|
|
* 'stream_id'.
|
|
*
|
|
* @return pointer to the created candidate, or NULL on error
|
|
*/
|
|
NiceCandidate*
|
|
discovery_add_peer_reflexive_candidate (
|
|
NiceAgent *agent,
|
|
guint stream_id,
|
|
guint component_id,
|
|
guint32 priority,
|
|
NiceAddress *address,
|
|
NiceSocket *base_socket,
|
|
NiceCandidate *local,
|
|
NiceCandidate *remote)
|
|
{
|
|
NiceCandidate *candidate;
|
|
NiceCandidateImpl *c;
|
|
NiceComponent *component;
|
|
NiceStream *stream;
|
|
gboolean result;
|
|
|
|
if (!agent_find_component (agent, stream_id, component_id, &stream, &component))
|
|
return NULL;
|
|
|
|
candidate = nice_candidate_new (NICE_CANDIDATE_TYPE_PEER_REFLEXIVE);
|
|
c = (NiceCandidateImpl *) candidate;
|
|
if (local)
|
|
candidate->transport = local->transport;
|
|
else if (remote)
|
|
candidate->transport = conn_check_match_transport (remote->transport);
|
|
else {
|
|
if (base_socket->type == NICE_SOCKET_TYPE_UDP_BSD ||
|
|
base_socket->type == NICE_SOCKET_TYPE_UDP_TURN)
|
|
candidate->transport = NICE_CANDIDATE_TRANSPORT_UDP;
|
|
else
|
|
candidate->transport = NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE;
|
|
}
|
|
candidate->stream_id = stream_id;
|
|
candidate->component_id = component_id;
|
|
candidate->addr = *address;
|
|
c->sockptr = base_socket;
|
|
candidate->base_addr = base_socket->addr;
|
|
/* We don't ensure priority uniqueness in this case, since the
|
|
* discovered candidate receives the same priority than its
|
|
* parent pair, by design, RFC 5245, sect 7.1.3.2.1.
|
|
* Discovering Peer Reflexive Candidates (the priority from the
|
|
* STUN Request)
|
|
*/
|
|
candidate->priority = priority;
|
|
priv_assign_foundation (agent, candidate);
|
|
|
|
if ((agent->compatibility == NICE_COMPATIBILITY_MSN ||
|
|
agent->compatibility == NICE_COMPATIBILITY_OC2007) &&
|
|
remote && local) {
|
|
guchar *new_username = NULL;
|
|
guchar *decoded_local = NULL;
|
|
guchar *decoded_remote = NULL;
|
|
gsize local_size;
|
|
gsize remote_size;
|
|
g_free(candidate->username);
|
|
g_free(candidate->password);
|
|
|
|
decoded_local = g_base64_decode (local->username, &local_size);
|
|
decoded_remote = g_base64_decode (remote->username, &remote_size);
|
|
|
|
new_username = g_new0(guchar, local_size + remote_size);
|
|
memcpy(new_username, decoded_local, local_size);
|
|
memcpy(new_username + local_size, decoded_remote, remote_size);
|
|
|
|
candidate->username = g_base64_encode (new_username, local_size + remote_size);
|
|
g_free(new_username);
|
|
g_free(decoded_local);
|
|
g_free(decoded_remote);
|
|
|
|
candidate->password = g_strdup(local->password);
|
|
} else if (local) {
|
|
g_free(candidate->username);
|
|
g_free(candidate->password);
|
|
|
|
candidate->username = g_strdup(local->username);
|
|
candidate->password = g_strdup(local->password);
|
|
}
|
|
|
|
result = priv_add_local_candidate_pruned (agent, stream_id, component, candidate);
|
|
if (result != TRUE) {
|
|
/* error: memory allocation, or duplicate candidate */
|
|
nice_candidate_free (candidate), candidate = NULL;
|
|
}
|
|
|
|
return candidate;
|
|
}
|
|
|
|
|
|
/*
|
|
* Adds a new peer reflexive candidate to the list of known
|
|
* remote candidates. The candidate is however not paired with
|
|
* existing local candidates.
|
|
*
|
|
* See ICE sect 7.2.1.3 "Learning Peer Reflexive Candidates" (ID-19).
|
|
*
|
|
* @return pointer to the created candidate, or NULL on error
|
|
*/
|
|
NiceCandidate *discovery_learn_remote_peer_reflexive_candidate (
|
|
NiceAgent *agent,
|
|
NiceStream *stream,
|
|
NiceComponent *component,
|
|
guint32 priority,
|
|
const NiceAddress *remote_address,
|
|
NiceSocket *nicesock,
|
|
NiceCandidate *local,
|
|
NiceCandidate *remote)
|
|
{
|
|
NiceCandidate *candidate;
|
|
NiceCandidateImpl *c;
|
|
|
|
candidate = nice_candidate_new (NICE_CANDIDATE_TYPE_PEER_REFLEXIVE);
|
|
c = (NiceCandidateImpl *) candidate;
|
|
|
|
candidate->addr = *remote_address;
|
|
candidate->base_addr = *remote_address;
|
|
if (remote)
|
|
candidate->transport = remote->transport;
|
|
else if (local)
|
|
candidate->transport = conn_check_match_transport (local->transport);
|
|
else {
|
|
if (nicesock->type == NICE_SOCKET_TYPE_UDP_BSD ||
|
|
nicesock->type == NICE_SOCKET_TYPE_UDP_TURN)
|
|
candidate->transport = NICE_CANDIDATE_TRANSPORT_UDP;
|
|
else
|
|
candidate->transport = NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE;
|
|
}
|
|
c->sockptr = nicesock;
|
|
candidate->stream_id = stream->id;
|
|
candidate->component_id = component->id;
|
|
|
|
/* if the check didn't contain the PRIORITY attribute, then the priority will
|
|
* be 0, which is invalid... */
|
|
if (priority != 0) {
|
|
candidate->priority = priority;
|
|
} else if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE) {
|
|
candidate->priority = nice_candidate_jingle_priority (candidate);
|
|
} else if (agent->compatibility == NICE_COMPATIBILITY_MSN ||
|
|
agent->compatibility == NICE_COMPATIBILITY_OC2007) {
|
|
candidate->priority = nice_candidate_msn_priority (candidate);
|
|
} else if (agent->compatibility == NICE_COMPATIBILITY_OC2007R2) {
|
|
candidate->priority = nice_candidate_ms_ice_priority (candidate,
|
|
agent->reliable, FALSE);
|
|
} else {
|
|
candidate->priority = nice_candidate_ice_priority (candidate,
|
|
agent->reliable, FALSE);
|
|
}
|
|
|
|
priv_assign_remote_foundation (agent, candidate);
|
|
|
|
if ((agent->compatibility == NICE_COMPATIBILITY_MSN ||
|
|
agent->compatibility == NICE_COMPATIBILITY_OC2007) &&
|
|
remote && local) {
|
|
guchar *new_username = NULL;
|
|
guchar *decoded_local = NULL;
|
|
guchar *decoded_remote = NULL;
|
|
gsize local_size;
|
|
gsize remote_size;
|
|
g_free(candidate->username);
|
|
g_free (candidate->password);
|
|
|
|
decoded_local = g_base64_decode (local->username, &local_size);
|
|
decoded_remote = g_base64_decode (remote->username, &remote_size);
|
|
|
|
new_username = g_new0(guchar, local_size + remote_size);
|
|
memcpy(new_username, decoded_remote, remote_size);
|
|
memcpy(new_username + remote_size, decoded_local, local_size);
|
|
|
|
candidate->username = g_base64_encode (new_username, local_size + remote_size);
|
|
g_free(new_username);
|
|
g_free(decoded_local);
|
|
g_free(decoded_remote);
|
|
|
|
candidate->password = g_strdup(remote->password);
|
|
} else if (remote) {
|
|
g_free (candidate->username);
|
|
g_free (candidate->password);
|
|
candidate->username = g_strdup(remote->username);
|
|
candidate->password = g_strdup(remote->password);
|
|
}
|
|
|
|
/* note: candidate username and password are left NULL as stream
|
|
level ufrag/password are used */
|
|
|
|
component->remote_candidates = g_slist_append (component->remote_candidates,
|
|
candidate);
|
|
|
|
agent_signal_new_remote_candidate (agent, candidate);
|
|
|
|
return candidate;
|
|
}
|
|
|
|
/*
|
|
* Timer callback that handles scheduling new candidate discovery
|
|
* processes (paced by the Ta timer), and handles running of the
|
|
* existing discovery processes.
|
|
*
|
|
* This function is designed for the g_timeout_add() interface.
|
|
*
|
|
* @return will return FALSE when no more pending timers.
|
|
*/
|
|
static gboolean priv_discovery_tick_unlocked (NiceAgent *agent)
|
|
{
|
|
CandidateDiscovery *cand;
|
|
GSList *i;
|
|
int not_done = 0; /* note: track whether to continue timer */
|
|
int need_pacing = 0;
|
|
size_t buffer_len = 0;
|
|
|
|
{
|
|
static int tick_counter = 0;
|
|
if (tick_counter++ % 50 == 0)
|
|
nice_debug ("Agent %p : discovery tick #%d with list %p (1)", agent, tick_counter, agent->discovery_list);
|
|
}
|
|
|
|
for (i = agent->discovery_list; i ; i = i->next) {
|
|
cand = i->data;
|
|
|
|
if (cand->pending != TRUE) {
|
|
cand->pending = TRUE;
|
|
|
|
if (agent->discovery_unsched_items)
|
|
--agent->discovery_unsched_items;
|
|
|
|
if (nice_debug_is_enabled ()) {
|
|
gchar tmpbuf[INET6_ADDRSTRLEN];
|
|
nice_address_to_string (&cand->server, tmpbuf);
|
|
nice_debug ("Agent %p : discovery - scheduling cand type %u addr %s.",
|
|
agent, cand->type, tmpbuf);
|
|
}
|
|
if (nice_address_is_valid (&cand->server) &&
|
|
(cand->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE ||
|
|
cand->type == NICE_CANDIDATE_TYPE_RELAYED)) {
|
|
|
|
if (cand->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE) {
|
|
buffer_len = stun_usage_bind_create (&cand->stun_agent,
|
|
&cand->stun_message, cand->stun_buffer, sizeof(cand->stun_buffer));
|
|
} else if (cand->type == NICE_CANDIDATE_TYPE_RELAYED) {
|
|
uint8_t *username = (uint8_t *)cand->turn->username;
|
|
gsize username_len = strlen (cand->turn->username);
|
|
uint8_t *password = (uint8_t *)cand->turn->password;
|
|
gsize password_len = strlen (cand->turn->password);
|
|
StunUsageTurnCompatibility turn_compat =
|
|
agent_to_turn_compatibility (agent);
|
|
|
|
if (turn_compat == STUN_USAGE_TURN_COMPATIBILITY_MSN ||
|
|
turn_compat == STUN_USAGE_TURN_COMPATIBILITY_OC2007) {
|
|
username = cand->turn->decoded_username;
|
|
password = cand->turn->decoded_password;
|
|
username_len = cand->turn->decoded_username_len;
|
|
password_len = cand->turn->decoded_password_len;
|
|
}
|
|
|
|
buffer_len = stun_usage_turn_create (&cand->stun_agent,
|
|
&cand->stun_message, cand->stun_buffer, sizeof(cand->stun_buffer),
|
|
cand->stun_resp_msg.buffer == NULL ? NULL : &cand->stun_resp_msg,
|
|
STUN_USAGE_TURN_REQUEST_PORT_NORMAL,
|
|
-1, -1,
|
|
username, username_len,
|
|
password, password_len,
|
|
turn_compat);
|
|
}
|
|
|
|
if (buffer_len > 0 &&
|
|
agent_socket_send (cand->nicesock, &cand->server, buffer_len,
|
|
(gchar *)cand->stun_buffer) >= 0) {
|
|
/* case: success, start waiting for the result */
|
|
if (nice_socket_is_reliable (cand->nicesock)) {
|
|
stun_timer_start_reliable (&cand->timer, agent->stun_reliable_timeout);
|
|
} else {
|
|
stun_timer_start (&cand->timer,
|
|
agent->stun_initial_timeout,
|
|
agent->stun_max_retransmissions);
|
|
}
|
|
|
|
cand->next_tick = g_get_monotonic_time ();
|
|
++need_pacing;
|
|
} else {
|
|
/* case: error in starting discovery, start the next discovery */
|
|
nice_debug ("Agent %p : Error starting discovery, skipping the item.",
|
|
agent);
|
|
cand->done = TRUE;
|
|
cand->stun_message.buffer = NULL;
|
|
cand->stun_message.buffer_len = 0;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
/* allocate relayed candidates */
|
|
g_assert_not_reached ();
|
|
|
|
++not_done; /* note: new discovery scheduled */
|
|
}
|
|
|
|
if (need_pacing)
|
|
break;
|
|
|
|
if (cand->done != TRUE) {
|
|
gint64 now = g_get_monotonic_time ();
|
|
|
|
if (cand->stun_message.buffer == NULL) {
|
|
nice_debug ("Agent %p : STUN discovery was cancelled, marking discovery done.", agent);
|
|
cand->done = TRUE;
|
|
}
|
|
else if (now >= cand->next_tick) {
|
|
switch (stun_timer_refresh (&cand->timer)) {
|
|
case STUN_USAGE_TIMER_RETURN_TIMEOUT:
|
|
{
|
|
/* Time out */
|
|
/* case: error, abort processing */
|
|
StunTransactionId id;
|
|
|
|
stun_message_id (&cand->stun_message, id);
|
|
stun_agent_forget_transaction (&cand->stun_agent, id);
|
|
|
|
cand->done = TRUE;
|
|
cand->stun_message.buffer = NULL;
|
|
cand->stun_message.buffer_len = 0;
|
|
nice_debug ("Agent %p : bind discovery timed out, aborting discovery item.", agent);
|
|
break;
|
|
}
|
|
case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
|
|
{
|
|
/* case: not ready complete, so schedule next timeout */
|
|
unsigned int timeout = stun_timer_remainder (&cand->timer);
|
|
|
|
stun_debug ("STUN transaction retransmitted (timeout %dms).",
|
|
timeout);
|
|
|
|
/* retransmit */
|
|
agent_socket_send (cand->nicesock, &cand->server,
|
|
stun_message_length (&cand->stun_message),
|
|
(gchar *)cand->stun_buffer);
|
|
|
|
/* note: convert from milli to microseconds for g_time_val_add() */
|
|
cand->next_tick = now + (timeout * 1000);
|
|
|
|
++not_done; /* note: retry later */
|
|
++need_pacing;
|
|
break;
|
|
}
|
|
case STUN_USAGE_TIMER_RETURN_SUCCESS:
|
|
{
|
|
unsigned int timeout = stun_timer_remainder (&cand->timer);
|
|
|
|
cand->next_tick = now + (timeout * 1000);
|
|
|
|
++not_done; /* note: retry later */
|
|
break;
|
|
}
|
|
default:
|
|
/* Nothing to do. */
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
++not_done; /* note: discovery not expired yet */
|
|
}
|
|
}
|
|
|
|
if (need_pacing)
|
|
break;
|
|
}
|
|
|
|
if (not_done == 0) {
|
|
nice_debug ("Agent %p : Candidate gathering FINISHED, stopping discovery timer.", agent);
|
|
|
|
discovery_free (agent);
|
|
|
|
agent_gathering_done (agent);
|
|
|
|
/* note: no pending timers, return FALSE to stop timer */
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean priv_discovery_tick_agent_locked (NiceAgent *agent,
|
|
gpointer pointer)
|
|
{
|
|
gboolean ret;
|
|
|
|
ret = priv_discovery_tick_unlocked (agent);
|
|
if (ret == FALSE) {
|
|
if (agent->discovery_timer_source != NULL) {
|
|
g_source_destroy (agent->discovery_timer_source);
|
|
g_source_unref (agent->discovery_timer_source);
|
|
agent->discovery_timer_source = NULL;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Initiates the candidate discovery process by starting
|
|
* the necessary timers.
|
|
*
|
|
* @pre agent->discovery_list != NULL // unsched discovery items available
|
|
*/
|
|
void discovery_schedule (NiceAgent *agent)
|
|
{
|
|
g_assert (agent->discovery_list != NULL);
|
|
|
|
if (agent->discovery_unsched_items > 0) {
|
|
|
|
if (agent->discovery_timer_source == NULL) {
|
|
/* step: run first iteration immediately */
|
|
gboolean res = priv_discovery_tick_unlocked (agent);
|
|
if (res == TRUE) {
|
|
agent_timeout_add_with_context (agent, &agent->discovery_timer_source,
|
|
"Candidate discovery tick", agent->timer_ta,
|
|
priv_discovery_tick_agent_locked, NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|