You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rtpengine/daemon/ice.c

1541 lines
42 KiB

#include "ice.h"
#include <glib.h>
#include <sys/time.h>
#include <unistd.h>
#include "str.h"
#include "call.h"
#include "helpers.h"
#include "log.h"
#include "obj.h"
#include "stun.h"
#include "poller.h"
#include "log_funcs.h"
#include "timerthread.h"
#include "call_interfaces.h"
#if __DEBUG
#define ICE_DEBUG 1
#else
#define ICE_DEBUG 0
#endif
#if ICE_DEBUG
#define __DBG(x...) ilogs(ice, LOG_DEBUG, x)
#else
#define __DBG(x...) ilogs(internals, LOG_DEBUG, x)
#endif
#define PAIR_FORMAT STR_FORMAT_M ":" STR_FORMAT_M ":%lu"
#define PAIR_FMT(p) \
STR_FMT_M(&(p)->local_intf->ice_foundation), \
STR_FMT_M(&(p)->remote_candidate->foundation), \
(p)->remote_candidate->component_id
struct sdp_fragment {
ng_buffer *ngbuf;
int64_t received;
sdp_streams_q streams;
sdp_ng_flags flags;
};
static void __ice_agent_free(struct ice_agent *);
static void create_random_ice_string(call_t *call, str *s, int len);
static void __do_ice_checks(struct ice_agent *ag);
static struct ice_candidate_pair *__pair_lookup(struct ice_agent *, struct ice_candidate *cand,
const struct local_intf *ifa);
static void __recalc_pair_prios(struct ice_agent *ag);
static void __role_change(struct ice_agent *ag, int new_controlling);
static void __get_complete_components(candidate_pair_q *out, struct ice_agent *ag, GTree *t, unsigned int);
static void __agent_schedule(struct ice_agent *ag, int64_t);
static void __agent_schedule_abs(struct ice_agent *ag, int64_t tv);
static void __agent_deschedule(struct ice_agent *ag);
static void __ice_agent_free_components(struct ice_agent *ag);
static void __agent_shutdown(struct ice_agent *ag);
static void ice_agents_timer_run(void *);
static uint64_t tie_breaker;
static struct timerthread ice_agents_timer_thread;
static const char ice_chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const unsigned int ice_type_preferences[] = {
[ICT_UNKNOWN] = 0,
[ICT_HOST] = 126,
[ICT_SRFLX] = 100,
[ICT_PRFLX] = 110,
[ICT_RELAY] = 0,
};
const char * const ice_type_strings[] = {
[ICT_UNKNOWN] = "unknown",
[ICT_HOST] = "host",
[ICT_SRFLX] = "srflx",
[ICT_PRFLX] = "prflx",
[ICT_RELAY] = "relay",
};
TYPED_GHASHTABLE_LOOKUP_INSERT(fragments_ht, NULL, fragment_q_new)
static void ice_update_media_streams(struct call_monologue *ml, sdp_streams_q *streams,
sdp_ng_flags *flags)
{
for (__auto_type l = streams->head; l; l = l->next) {
struct stream_params *sp = l->data;
struct call_media *media = NULL;
if (sp->media_id.len)
media = t_hash_table_lookup(ml->media_ids, &sp->media_id);
else if (sp->index > 0) {
unsigned int arr_idx = sp->index - 1;
if (arr_idx < ml->medias->len)
media = ml->medias->pdata[arr_idx];
}
if (!media) {
ilogs(ice, LOG_WARN, "No matching media for trickle ICE update found");
continue;
}
if (!media->ice_agent) {
ilogs(ice, LOG_WARN, "Media for trickle ICE update is not ICE-enabled");
continue;
}
if (!MEDIA_ISSET(media, TRICKLE_ICE)) {
ilogs(ice, LOG_WARN, "Media for trickle ICE update is not trickle-ICE-enabled");
continue;
}
ice_update(media->ice_agent, sp, false);
}
}
static void fragment_free(struct sdp_fragment *frag) {
sdp_streams_clear(&frag->streams);
call_ng_free_flags(&frag->flags);
obj_put(frag->ngbuf);
g_free(frag);
}
static void queue_sdp_fragment(ng_buffer *ngbuf, call_t *call, str *key, sdp_streams_q *streams, sdp_ng_flags *flags) {
ilog(LOG_DEBUG, "Queuing up SDP fragment for " STR_FORMAT_M "/" STR_FORMAT_M,
STR_FMT_M(&flags->call_id), STR_FMT_M(&flags->from_tag));
struct sdp_fragment *frag = g_new0(__typeof(*frag), 1);
frag->received = rtpe_now;
frag->ngbuf = obj_get(ngbuf);
if (streams) {
frag->streams = *streams;
t_queue_init(streams);
}
frag->flags = *flags;
ZERO(*flags);
fragment_q *frags = fragments_ht_lookup_insert(call->sdp_fragments, call_str_dup(key));
t_queue_push_tail(frags, frag);
}
bool trickle_ice_update(ng_buffer *ngbuf, call_t *call, sdp_ng_flags *flags,
sdp_streams_q *streams)
{
if (!flags->fragment)
return false;
struct call_monologue *ml = call_get_monologue(call, &flags->from_tag);
if (!ml) {
queue_sdp_fragment(ngbuf, call, &flags->from_tag, streams, flags);
return true;
}
ice_update_media_streams(ml, streams, flags);
return true;
}
#define MAX_FRAG_AGE 3000000
void dequeue_sdp_fragments(struct call_monologue *monologue) {
call_t *call = monologue->call;
fragment_q *frags = NULL;
t_hash_table_steal_extended(call->sdp_fragments, &monologue->tag, NULL, &frags);
if (!frags)
return;
// we own the queue now
struct sdp_fragment *frag;
while ((frag = t_queue_pop_head(frags))) {
if (rtpe_now - frag->received > MAX_FRAG_AGE)
goto next;
ilog(LOG_DEBUG, "Dequeuing SDP fragment for " STR_FORMAT_M "/" STR_FORMAT_M,
STR_FMT_M(&call->callid), STR_FMT_M(&monologue->tag));
ice_update_media_streams(monologue, &frag->streams, &frag->flags);
next:
fragment_free(frag);
}
t_queue_free(frags);
}
static gboolean fragment_check_cleanup(str *key, fragment_q *frags, void *p) {
bool all = GPOINTER_TO_INT(p);
if (!key || !frags)
return TRUE;
while (frags->length) {
struct sdp_fragment *frag = frags->head->data;
if (!all && rtpe_now - frag->received <= MAX_FRAG_AGE)
break;
t_queue_pop_head(frags);
fragment_free(frag);
}
if (!frags->length) {
t_queue_free(frags);
return TRUE;
}
return FALSE;
}
void ice_fragments_cleanup(fragments_ht ht, bool all) {
t_hash_table_foreach_remove(ht, fragment_check_cleanup, GINT_TO_POINTER(all));
}
enum ice_candidate_type ice_candidate_type(const str *s) {
int i;
for (i = 1; i < G_N_ELEMENTS(ice_type_strings); i++) {
if (!str_cmp(s, ice_type_strings[i]))
return i;
}
return ICT_UNKNOWN;
}
bool ice_has_related(enum ice_candidate_type t) {
if (t == ICT_HOST)
return false;
/* ignoring ICT_UNKNOWN */
return true;
}
static uint64_t __ice_pair_priority(const struct local_intf *ifa, struct ice_candidate *cand,
int controlling)
{
uint64_t g, d;
g = ice_priority(ICT_HOST, ifa->unique_id, cand->component_id);
d = cand->priority;
if (!controlling) {
uint64_t t = g;
g = d;
d = t;
}
return (MIN(g,d) << 32) + (MAX(g,d) << 1) + (g > d ? 1 : 0);
}
static void __do_ice_pair_priority(struct ice_candidate_pair *pair) {
pair->pair_priority = __ice_pair_priority(pair->local_intf, pair->remote_candidate,
AGENT_ISSET(pair->agent, CONTROLLING));
}
static void __new_stun_transaction(struct ice_candidate_pair *pair) {
struct ice_agent *ag = pair->agent;
t_hash_table_remove(ag->transaction_hash, pair->stun_transaction);
random_string((void *) pair->stun_transaction, sizeof(pair->stun_transaction));
t_hash_table_insert(ag->transaction_hash, pair->stun_transaction, pair);
}
/* agent must be locked */
static void __all_pairs_list(struct ice_agent *ag) {
t_queue_clear(&ag->all_pairs_list);
rtpe_g_tree_get_values(&ag->all_pairs_list.q, ag->all_pairs);
}
static void __tree_coll_callback(void *oo, void *nn) {
struct ice_candidate_pair *o = oo, *n = nn;
ilogs(ice, LOG_WARN | LOG_FLAG_LIMIT, "Priority collision between candidate pairs " PAIR_FORMAT " and "
PAIR_FORMAT " - ICE will likely fail",
PAIR_FMT(o), PAIR_FMT(n));
}
/* agent must be locked */
static struct ice_candidate_pair *__pair_candidate(stream_fd *sfd, struct ice_agent *ag,
struct ice_candidate *cand)
{
struct ice_candidate_pair *pair;
if (sfd->socket.family != cand->endpoint.address.family)
return NULL;
pair = g_new0(__typeof(*pair), 1);
pair->agent = ag;
pair->remote_candidate = cand;
pair->local_intf = sfd->local_intf;
pair->sfd = sfd;
if (cand->component_id != 1)
PAIR_SET(pair, FROZEN);
__do_ice_pair_priority(pair);
__new_stun_transaction(pair);
t_queue_push_tail(&ag->candidate_pairs, pair);
t_hash_table_insert(ag->pair_hash, pair, pair);
rtpe_g_tree_insert_coll(ag->all_pairs, pair, pair, __tree_coll_callback);
ilogs(ice, LOG_DEBUG, "Created candidate pair "PAIR_FORMAT" between %s and %s%s%s, type %s", PAIR_FMT(pair),
sockaddr_print_buf(&sfd->socket.local.address),
FMT_M(endpoint_print_buf(&cand->endpoint)),
ice_candidate_type_str(cand->type));
return pair;
}
static unsigned int __pair_hash(const struct ice_candidate_pair *pair) {
return GPOINTER_TO_UINT(pair->local_intf) ^ GPOINTER_TO_UINT(pair->remote_candidate);
}
static int __pair_equal(const struct ice_candidate_pair *A, const struct ice_candidate_pair *B) {
return A->local_intf == B->local_intf
&& A->remote_candidate == B->remote_candidate;
}
static unsigned int __cand_hash(const struct ice_candidate *cand) {
return endpoint_hash(&cand->endpoint) ^ cand->component_id;
}
static int __cand_equal(const struct ice_candidate *A, const struct ice_candidate *B) {
return endpoint_eq(&A->endpoint, &B->endpoint)
&& A->component_id == B->component_id;
}
static unsigned int __found_hash(const struct ice_candidate *cand) {
return str_hash(&cand->foundation) ^ cand->component_id;
}
static int __found_equal(const struct ice_candidate *A, const struct ice_candidate *B) {
return str_equal(&A->foundation, &B->foundation)
&& A->component_id == B->component_id;
}
static unsigned int __trans_hash(const uint32_t *tp) {
return tp[0] ^ tp[1] ^ tp[2];
}
static int __trans_equal(const uint32_t *A, const uint32_t *B) {
return A[0] == B[0] && A[1] == B[1] && A[2] == B[2];
}
static int __pair_prio_cmp(const void *a, const void *b) {
const struct ice_candidate_pair *A = a, *B = b;
/* highest priority first */
if (A->pair_priority < B->pair_priority)
return 1;
if (A->pair_priority > B->pair_priority)
return -1;
/* lowest component first */
if (A->remote_candidate->component_id < B->remote_candidate->component_id)
return -1;
if (A->remote_candidate->component_id > B->remote_candidate->component_id)
return 1;
/* highest local preference first, which is lowest unique_id first */
if (A->local_intf->unique_id < B->local_intf->unique_id)
return -1;
if (A->local_intf->unique_id > B->local_intf->unique_id)
return 1;
return 0;
}
TYPED_GHASHTABLE_IMPL(candidate_ht, __cand_hash, __cand_equal, NULL, NULL)
TYPED_GHASHTABLE_IMPL(candidate_pair_ht, __pair_hash, __pair_equal, NULL, NULL)
TYPED_GHASHTABLE_IMPL(foundation_ht, __found_hash, __found_equal, NULL, NULL)
TYPED_GHASHTABLE_IMPL(priority_ht, g_direct_hash, g_direct_equal, NULL, NULL)
TYPED_GHASHTABLE_IMPL(transaction_ht, __trans_hash, __trans_equal, NULL, NULL)
static void __ice_agent_initialize(struct ice_agent *ag) {
struct call_media *media = ag->media;
call_t *call = ag->call;
ag->candidate_hash = candidate_ht_new();
ag->cand_prio_hash = priority_ht_new();
ag->pair_hash = candidate_pair_ht_new();
ag->transaction_hash = transaction_ht_new();
ag->foundation_hash = foundation_ht_new();
atomic64_set_na(&ag->agent_flags, 0);
bf_copy(&ag->agent_flags, ICE_AGENT_CONTROLLING, &media->media_flags, MEDIA_FLAG_ICE_CONTROLLING);
bf_copy(&ag->agent_flags, ICE_AGENT_LITE_SELF, &media->media_flags, MEDIA_FLAG_ICE_LITE_SELF);
ag->logical_intf = media->logical_intf;
ag->desired_family = media->desired_family;
ag->nominated_pairs = g_tree_new(__pair_prio_cmp);
ag->valid_pairs = g_tree_new(__pair_prio_cmp);
ag->succeeded_pairs = g_tree_new(__pair_prio_cmp);
ag->all_pairs = g_tree_new(__pair_prio_cmp);
create_random_ice_string(call, &ag->ufrag[1], 8);
create_random_ice_string(call, &ag->pwd[1], 26);
atomic64_set_na(&ag->last_activity, rtpe_now);
}
static struct ice_agent *__ice_agent_new(struct call_media *media) {
struct ice_agent *ag;
call_t *call = media->call;
ag = obj_alloc0(struct ice_agent, __ice_agent_free);
ag->tt_obj.tt = &ice_agents_timer_thread;
ag->tt_obj.thread = &ice_agents_timer_thread.threads[0]; // there's only one thread
ag->call = obj_get(call);
ag->media = media;
mutex_init(&ag->lock);
__ice_agent_initialize(ag);
return ag;
}
/* called with the call lock held in W */
void ice_agent_init(struct ice_agent **agp, struct call_media *media) {
struct ice_agent *ag;
if (*agp)
ag = *agp;
else
*agp = ag = __ice_agent_new(media);
}
static int __copy_cand(call_t *call, struct ice_candidate *dst, const struct ice_candidate *src) {
int eq = (dst->priority == src->priority);
*dst = *src;
dst->foundation = call_str_cpy(&src->foundation);
return eq ? 0 : 1;
}
static void __ice_reset(struct ice_agent *ag) {
__agent_deschedule(ag);
AGENT_CLEAR3(ag, COMPLETED, NOMINATING, USABLE);
__ice_agent_free_components(ag);
ZERO(ag->active_components);
ag->start_nominating = 0;
ag->tt_obj.last_run = 0;
__ice_agent_initialize(ag);
}
/* if the other side did a restart */
static void __ice_restart(struct ice_agent *ag) {
ilogs(ice, LOG_DEBUG, "ICE restart detected, resetting ICE agent");
ag->ufrag[0] = STR_NULL;
ag->pwd[0] = STR_NULL;
ag->ufrag[1] = STR_NULL;
ag->pwd[1] = STR_NULL;
__ice_reset(ag);
}
/* if we're doing a restart */
void ice_restart(struct ice_agent *ag) {
ilogs(ice, LOG_DEBUG, "Restarting ICE and resetting ICE agent");
ag->ufrag[1] = STR_NULL;
ag->pwd[1] = STR_NULL;
__ice_reset(ag);
}
/* called with the call lock held in W, hence agent doesn't need to be locked */
void ice_update(struct ice_agent *ag, struct stream_params *sp, bool allow_reset) {
struct ice_candidate *cand, *dup;
struct call_media *media;
call_t *call;
int recalc = 0;
unsigned int comps;
struct packet_stream *components[MAX_COMPONENTS], *ps;
candidate_q *candidates;
stream_fd *sfd;
if (!ag)
return;
log_info_ice_agent(ag);
atomic64_set_na(&ag->last_activity, rtpe_now);
media = ag->media;
call = media->call;
__role_change(ag, MEDIA_ISSET(media, ICE_CONTROLLING));
if (sp) {
if (ice_is_restart(ag, sp)) {
if (!allow_reset)
ilog(LOG_WARN, "ICE restart detected, but reset not allowed at this point");
else
__ice_restart(ag);
}
/* update remote info */
if (sp->ice_ufrag.s)
ag->ufrag[0] = call_str_cpy(&sp->ice_ufrag);
if (sp->ice_pwd.s)
ag->pwd[0] = call_str_cpy(&sp->ice_pwd);
candidates = &sp->ice_candidates;
}
else /* this is a dummy update in case rtcp-mux has changed */
candidates = &ag->remote_candidates;
/* get our component streams */
ZERO(components);
comps = 0;
for (__auto_type l = media->streams.head; l; l = l->next)
components[comps++] = l->data;
if (comps == 2 && (MEDIA_ISSET(media, RTCP_MUX) || !proto_is_rtp(media->protocol)))
components[1] = NULL;
comps = 0;
for (__auto_type l = candidates->head; l; l = l->next) {
if (ag->remote_candidates.length >= MAX_ICE_CANDIDATES) {
ilogs(ice, LOG_WARNING, "Maxmimum number of ICE candidates exceeded");
break;
}
cand = l->data;
/* skip invalid */
if (!cand->component_id || cand->component_id > G_N_ELEMENTS(components))
continue;
ps = components[cand->component_id - 1];
if (ps) /* only count active components */
comps = MAX(comps, cand->component_id);
dup = t_hash_table_lookup(ag->candidate_hash, cand);
if (!sp && dup) /* this isn't a real update, so only check pairings */
goto pair;
/* check for duplicates */
if (dup) {
/* if this is peer reflexive, we've learned it through STUN.
* otherwise it's simply one we've seen before. */
if (dup->type == ICT_PRFLX) {
ilogs(ice, LOG_DEBUG, "Replacing previously learned prflx ICE candidate with "
STR_FORMAT_M ":%lu", STR_FMT_M(&cand->foundation),
cand->component_id);
}
else {
/* if the new one has higher priority then the old one, then we
* update it, otherwise we just drop it */
if (cand->priority <= dup->priority) {
ilogs(ice, LOG_DEBUG, "Dropping new ICE candidate " STR_FORMAT_M
" in favour of "
STR_FORMAT_M ":%lu",
STR_FMT_M(&cand->foundation),
STR_FMT_M(&dup->foundation), cand->component_id);
continue;
}
ilogs(ice, LOG_DEBUG, "Replacing known ICE candidate " STR_FORMAT_M " with higher "
"priority "
STR_FORMAT_M ":%lu",
STR_FMT_M(&dup->foundation),
STR_FMT_M(&cand->foundation), cand->component_id);
}
/* priority and foundation may change */
t_hash_table_remove(ag->foundation_hash, dup);
recalc += __copy_cand(call, dup, cand);
}
else {
ilogs(ice, LOG_DEBUG, "Learning new ICE candidate " STR_FORMAT_M ":%lu",
STR_FMT_M(&cand->foundation), cand->component_id);
dup = g_new(__typeof(*dup), 1);
__copy_cand(call, dup, cand);
t_hash_table_insert(ag->candidate_hash, dup, dup);
t_hash_table_insert(ag->cand_prio_hash, GUINT_TO_POINTER(dup->priority), dup);
t_queue_push_tail(&ag->remote_candidates, dup);
}
t_hash_table_insert(ag->foundation_hash, dup, dup);
pair:
if (!ps)
continue;
for (__auto_type k = ps->sfds.head; k; k = k->next) {
sfd = k->data;
/* skip duplicates here also */
if (__pair_lookup(ag, dup, sfd->local_intf))
continue;
__pair_candidate(sfd, ag, dup);
}
}
if (comps)
ag->active_components = comps;
if (!ag->active_components) {
/* determine components for tricke-ice case */
comps = 2;
if (!components[1])
comps = 1;
ag->active_components = comps;
}
/* if we're here, we can start our ICE checks */
if (recalc)
__recalc_pair_prios(ag);
else
__all_pairs_list(ag);
if (comps)
__do_ice_checks(ag);
else
__agent_shutdown(ag);
log_info_pop();
}
static void ice_candidate_free(struct ice_candidate *p) {
g_free(p);
}
void ice_candidates_free(candidate_q *q) {
t_queue_clear_full(q, ice_candidate_free);
}
static void ice_candidate_pair_free(struct ice_candidate_pair *p) {
g_free(p);
}
static void ice_candidate_pairs_free(candidate_pair_q *q) {
t_queue_clear_full(q, ice_candidate_pair_free);
}
/* call must be locked */
void ice_shutdown(struct ice_agent **agp) {
struct ice_agent *ag;
if (!agp) {
ilogs(ice, LOG_ERR, "ice agp is NULL");
return ;
}
ag = *agp;
if (!ag)
return;
__agent_deschedule(ag);
*agp = NULL;
obj_put(&ag->tt_obj);
}
static void __ice_agent_free_components(struct ice_agent *ag) {
if (!ag) {
ilogs(ice, LOG_ERR, "ice ag is NULL");
return;
}
t_queue_clear(&ag->triggered);
t_hash_table_destroy(ag->candidate_hash);
t_hash_table_destroy(ag->cand_prio_hash);
t_hash_table_destroy(ag->pair_hash);
t_hash_table_destroy(ag->transaction_hash);
t_hash_table_destroy(ag->foundation_hash);
g_tree_destroy(ag->all_pairs);
t_queue_clear(&ag->all_pairs_list);
g_tree_destroy(ag->nominated_pairs);
g_tree_destroy(ag->succeeded_pairs);
g_tree_destroy(ag->valid_pairs);
ice_candidates_free(&ag->remote_candidates);
ice_candidate_pairs_free(&ag->candidate_pairs);
}
static void __ice_agent_free(struct ice_agent *ag) {
if (!ag) {
ilogs(ice, LOG_ERR, "ice ag is NULL");
return;
}
__DBG("freeing ice_agent");
__ice_agent_free_components(ag);
mutex_destroy(&ag->lock);
obj_put(ag->call);
}
static void __agent_schedule(struct ice_agent *ag, int64_t usec) {
int64_t nxt;
nxt = rtpe_now;
nxt += usec;
__agent_schedule_abs(ag, nxt);
}
static void __agent_schedule_abs(struct ice_agent *ag, int64_t tv) {
int64_t nxt;
int64_t diff;
if (!ag)
return;
nxt = tv;
struct timerthread_thread *tt = ag->tt_obj.thread;
mutex_lock(&tt->lock);
if (ag->tt_obj.last_run) {
/* make sure we don't run more often than we should */
diff = nxt - ag->tt_obj.last_run;
if (diff < TIMER_RUN_INTERVAL * 1000)
nxt += TIMER_RUN_INTERVAL * 1000 - diff;
}
timerthread_obj_schedule_abs_nl(&ag->tt_obj, nxt);
mutex_unlock(&tt->lock);
}
static void __agent_deschedule(struct ice_agent *ag) {
if (ag)
timerthread_obj_deschedule(&ag->tt_obj);
}
void ice_init(void) {
random_string((void *) &tie_breaker, sizeof(tie_breaker));
timerthread_init(&ice_agents_timer_thread, 1, ice_agents_timer_run);
}
void ice_free(void) {
timerthread_free(&ice_agents_timer_thread);
}
static void __fail_pair(struct ice_candidate_pair *pair) {
ilogs(ice, LOG_DEBUG, "Setting ICE candidate pair "PAIR_FORMAT" as failed", PAIR_FMT(pair));
PAIR_SET(pair, FAILED);
PAIR_CLEAR(pair, IN_PROGRESS);
}
/* agent must NOT be locked, but call must be locked in R */
static void __do_ice_check(struct ice_candidate_pair *pair) {
stream_fd *sfd = pair->sfd;
struct ice_agent *ag = pair->agent;
uint32_t prio, transact[3];
if (AGENT_ISSET(ag, LITE_SELF))
PAIR_SET(pair, SUCCEEDED);
if (PAIR_ISSET(pair, SUCCEEDED) && !PAIR_ISSET(pair, TO_USE))
return;
if (!ag->pwd[0].s)
return;
prio = ice_priority(ICT_PRFLX, pair->local_intf->unique_id,
pair->remote_candidate->component_id);
mutex_lock(&ag->lock);
pair->retransmit = rtpe_now;
if (!PAIR_SET(pair, IN_PROGRESS)) {
PAIR_CLEAR2(pair, FROZEN, FAILED);
pair->retransmit_ms = STUN_RETRANSMIT_INTERVAL;
pair->retransmits = 0;
}
else if (pair->retransmits > STUN_MAX_RETRANSMITS) {
__fail_pair(pair);
mutex_unlock(&ag->lock);
return;
}
else {
pair->retransmit_ms *= 2;
pair->retransmits++;
}
pair->retransmit += pair->retransmit_ms * 1000; // XXX convert to micro
__agent_schedule_abs(pair->agent, pair->retransmit);
memcpy(transact, pair->stun_transaction, sizeof(transact));
pair->was_controlling = AGENT_ISSET(ag, CONTROLLING);
pair->was_nominated = PAIR_ISSET(pair, TO_USE);
mutex_unlock(&ag->lock);
ilogs(ice, LOG_DEBUG, "Sending %sICE/STUN request for candidate pair "PAIR_FORMAT" from %s to %s%s%s",
PAIR_ISSET(pair, TO_USE) ? "nominating " : "",
PAIR_FMT(pair), sockaddr_print_buf(&pair->local_intf->spec->local_address.addr),
FMT_M(endpoint_print_buf(&pair->remote_candidate->endpoint)));
stun_binding_request(&pair->remote_candidate->endpoint, transact, &ag->pwd[0], ag->ufrag,
AGENT_ISSET(ag, CONTROLLING), tie_breaker,
prio, &sfd->socket,
PAIR_ISSET(pair, TO_USE));
}
static int __component_find(const void *a, const void *b) {
const struct ice_candidate_pair *A = a;
unsigned int comp = GPOINTER_TO_UINT(b);
if (A->remote_candidate->component_id == comp)
return TRUE;
return FALSE;
}
static struct ice_candidate_pair *__get_pair_by_component(GTree *t, unsigned int component) {
return rtpe_g_tree_find_first(t, __component_find, GUINT_TO_POINTER(component));
}
static void __get_pairs_by_component(candidate_pair_q *out, GTree *t, unsigned int component) {
rtpe_g_tree_find_all(&out->q, t, __component_find, GUINT_TO_POINTER(component));
}
static void __get_complete_succeeded_pairs(candidate_pair_q *out, struct ice_agent *ag) {
__get_complete_components(out, ag, ag->succeeded_pairs, ICE_PAIR_SUCCEEDED);
}
static void __get_complete_valid_pairs(candidate_pair_q *out, struct ice_agent *ag) {
__get_complete_components(out, ag, ag->valid_pairs, ICE_PAIR_VALID);
}
static void __nominate_pairs(struct ice_agent *ag) {
candidate_pair_q complete;
struct ice_candidate_pair *pair;
ilogs(ice, LOG_DEBUG, "Start nominating ICE pairs");
AGENT_SET(ag, NOMINATING);
ag->start_nominating = 0;
__get_complete_succeeded_pairs(&complete, ag);
for (__auto_type l = complete.head; l; l = l->next) {
pair = l->data;
ilogs(ice, LOG_DEBUG, "Nominating ICE pair "PAIR_FORMAT, PAIR_FMT(pair));
PAIR_CLEAR(pair, IN_PROGRESS);
PAIR_SET2(pair, NOMINATED, TO_USE);
pair->retransmits = 0;
__new_stun_transaction(pair);
t_queue_push_tail(&ag->triggered, pair);
}
t_queue_clear(&complete);
}
/* call must be locked R or W, agent must not be locked */
static void __do_ice_checks(struct ice_agent *ag) {
struct ice_candidate_pair *pair, *highest = NULL, *frozen = NULL, *valid;
stream_fd *sfd;
GQueue retransmits = G_QUEUE_INIT;
int64_t next_run = 0;
int have_more = 0;
if (!ag) {
ilogs(ice, LOG_ERR, "ice ag is NULL");
return;
}
if (!ag->pwd[0].s)
return;
atomic64_set_na(&ag->last_activity, rtpe_now);
__DBG("running checks, call "STR_FORMAT" tag "STR_FORMAT"", STR_FMT(&ag->call->callid),
STR_FMT(&ag->media->monologue->tag));
mutex_lock(&ag->lock);
/* check if we're done and should start nominating pairs */
if (AGENT_ISSET(ag, CONTROLLING) && !AGENT_ISSET(ag, NOMINATING) && ag->start_nominating) {
if (rtpe_now >= ag->start_nominating)
__nominate_pairs(ag);
next_run = timeval_us(timeval_lowest(timeval_from_us(next_run), timeval_from_us(ag->start_nominating)));
}
/* triggered checks are preferred */
pair = t_queue_pop_head(&ag->triggered);
if (pair) {
__DBG("running triggered check on " PAIR_FORMAT, PAIR_FMT(pair));
PAIR_CLEAR(pair, TRIGGERED);
next_run = rtpe_now;
goto check;
}
/* find the highest-priority non-frozen non-in-progress pair */
for (__auto_type l = ag->all_pairs_list.head; l; l = l->next) {
pair = l->data;
__DBG("considering checking " PAIR_FORMAT, PAIR_FMT(pair));
/* skip dead streams */
sfd = pair->sfd;
if (!sfd || !sfd->stream || !sfd->stream->selected_sfd)
continue;
if (PAIR_ISSET(pair, FAILED))
continue;
if (PAIR_ISSET(pair, SUCCEEDED) && !PAIR_ISSET(pair, TO_USE))
continue;
valid = __get_pair_by_component(ag->valid_pairs, pair->remote_candidate->component_id);
if (PAIR_ISSET(pair, IN_PROGRESS)) {
/* handle retransmits */
/* but only if our priority is lower than any valid pair */
if (valid && valid->pair_priority > pair->pair_priority)
continue;
if (pair->retransmit <= rtpe_now)
g_queue_push_tail(&retransmits, pair); /* can't run check directly
due to locks */
else
next_run = timeval_us(timeval_lowest(timeval_from_us(next_run), timeval_from_us(pair->retransmit)));
continue;
}
/* don't do anything else if we already have a valid pair */
if (valid)
continue;
/* or if we're in or past the final phase */
if (AGENT_ISSET2(ag, NOMINATING, COMPLETED))
continue;
have_more = 1;
/* remember the first frozen pair in case we find nothing else */
if (PAIR_ISSET(pair, FROZEN)) {
__DBG("pair " PAIR_FORMAT " is frozen", PAIR_FMT(pair));
if (!frozen)
frozen = pair;
continue;
}
if (!highest)
highest = pair;
}
if (highest) {
pair = highest;
__DBG("checking highest priority pair " PAIR_FORMAT, PAIR_FMT(pair));
}
else if (frozen) {
pair = frozen;
__DBG("checking highest priority frozen pair " PAIR_FORMAT, PAIR_FMT(pair));
}
else
pair = NULL;
check:
mutex_unlock(&ag->lock);
if (pair)
__do_ice_check(pair);
while ((pair = g_queue_pop_head(&retransmits)))
__do_ice_check(pair);
/* determine when to run next */
if (have_more)
__agent_schedule(ag, 0);
else if (next_run)
__agent_schedule_abs(ag, next_run); /* for retransmits */
}
static void __agent_shutdown(struct ice_agent *ag) {
ilogs(ice, LOG_DEBUG, "Shutting down ICE agent (nothing to do)");
__agent_deschedule(ag);
}
/* agent must be locked for these */
static struct ice_candidate *__cand_lookup(struct ice_agent *ag, const endpoint_t *sin,
unsigned int component)
{
struct ice_candidate d;
d.endpoint = *sin;
d.component_id = component;
return t_hash_table_lookup(ag->candidate_hash, &d);
}
static struct ice_candidate *__foundation_lookup(struct ice_agent *ag, const str *foundation,
unsigned int component)
{
struct ice_candidate d;
d.foundation = *foundation;
d.component_id = component;
return t_hash_table_lookup(ag->foundation_hash, &d);
}
static struct ice_candidate_pair *__pair_lookup(struct ice_agent *ag, struct ice_candidate *cand,
const struct local_intf *ifa)
{
struct ice_candidate_pair p;
p.local_intf = ifa;
p.remote_candidate = cand;
return t_hash_table_lookup(ag->pair_hash, &p);
}
static void __cand_ice_foundation(call_t *call, struct ice_candidate *cand) {
char buf[64];
int len;
len = sprintf(buf, "%x%x%x", endpoint_hash(&cand->endpoint),
cand->type, GPOINTER_TO_UINT(cand->transport));
cand->foundation = call_str_cpy_len(buf, len);
}
/* agent must be locked */
static struct ice_candidate_pair *__learned_candidate(struct ice_agent *ag, stream_fd *sfd,
const endpoint_t *src, unsigned long priority)
{
struct ice_candidate *cand, *old_cand;
struct ice_candidate_pair *pair;
call_t *call = ag->call;
struct packet_stream *ps = sfd->stream;
cand = g_new0(__typeof(*cand), 1);
cand->component_id = ps->component;
cand->transport = sfd->local_intf->spec->local_address.type; // XXX add socket type into socket_t?
cand->priority = priority;
cand->endpoint = *src;
cand->type = ICT_PRFLX;
// check if we've already learned another candidate that belongs to this one. use the priority number
// together with the component to guess a matching other candidate.
unsigned long prio_base = priority + ps->component;
struct ice_candidate *known_cand = NULL;
for (unsigned int comp = 1; comp <= ag->active_components; comp++) {
if (comp == ps->component)
continue;
unsigned long prio = prio_base - comp;
known_cand = t_hash_table_lookup(ag->cand_prio_hash, GUINT_TO_POINTER(prio));
if (known_cand)
break;
}
if (known_cand) {
// got one. use the previously learned generated ICE foundation string also for this one:
cand->foundation = known_cand->foundation;
}
else {
// make new:
__cand_ice_foundation(call, cand);
}
old_cand = __foundation_lookup(ag, &cand->foundation, ps->component);
if (old_cand && old_cand->priority > priority) {
/* this is possible if two distinct requests are received from the same NAT IP
* address, but from different ports. we cannot distinguish such candidates and
* will drop the one with the lower priority */
g_free(cand);
pair = __pair_lookup(ag, old_cand, sfd->local_intf);
if (pair)
goto out; /* nothing to do */
cand = old_cand;
goto pair;
}
t_queue_push_tail(&ag->remote_candidates, cand);
t_hash_table_insert(ag->candidate_hash, cand, cand);
t_hash_table_insert(ag->cand_prio_hash, GUINT_TO_POINTER(cand->priority), cand);
t_hash_table_insert(ag->foundation_hash, cand, cand);
pair:
pair = __pair_candidate(sfd, ag, cand);
PAIR_SET(pair, LEARNED);
__all_pairs_list(ag);
out:
return pair;
}
/* agent must NOT be locked */
static void __trigger_check(struct ice_candidate_pair *pair) {
struct ice_agent *ag = pair->agent;
ilogs(ice, LOG_DEBUG, "Triggering check for "PAIR_FORMAT, PAIR_FMT(pair));
mutex_lock(&ag->lock);
pair->retransmits = 0;
if (PAIR_CLEAR(pair, FAILED))
PAIR_CLEAR(pair, IN_PROGRESS);
if (ag->triggered.length < 4 * MAX_ICE_CANDIDATES && !PAIR_SET(pair, TRIGGERED))
t_queue_push_tail(&ag->triggered, pair);
mutex_unlock(&ag->lock);
__agent_schedule(ag, 0);
}
/* agent must be locked */
/* also regenerates all_pairs_list */
static void __recalc_pair_prios(struct ice_agent *ag) {
struct ice_candidate_pair *pair;
GQueue nominated, valid, succ, all;
ilogs(ice, LOG_DEBUG, "Recalculating all ICE pair priorities");
rtpe_g_tree_find_remove_all(&nominated, ag->nominated_pairs);
rtpe_g_tree_find_remove_all(&succ, ag->succeeded_pairs);
rtpe_g_tree_find_remove_all(&valid, ag->valid_pairs);
rtpe_g_tree_find_remove_all(&all, ag->all_pairs);
for (__auto_type l = ag->candidate_pairs.head; l; l = l->next) {
pair = l->data;
__do_ice_pair_priority(pair);
/* this changes the packets, so we must keep these from being seen as retransmits */
__new_stun_transaction(pair);
}
rtpe_g_tree_add_all(ag->nominated_pairs, &nominated, __tree_coll_callback);
rtpe_g_tree_add_all(ag->succeeded_pairs, &succ, __tree_coll_callback);
rtpe_g_tree_add_all(ag->valid_pairs, &valid, __tree_coll_callback);
rtpe_g_tree_add_all(ag->all_pairs, &all, __tree_coll_callback);
__all_pairs_list(ag);
}
/* agent must NOT be locked */
static void __role_change(struct ice_agent *ag, int new_controlling) {
if (new_controlling && !AGENT_SET(ag, CONTROLLING))
;
else if (!new_controlling && AGENT_CLEAR(ag, CONTROLLING))
;
else
return;
ilogs(ice, LOG_DEBUG, "ICE role change, now %s", new_controlling ? "controlling" : "controlled");
/* recalc priorities and resort list */
mutex_lock(&ag->lock);
__recalc_pair_prios(ag);
mutex_unlock(&ag->lock);
}
/* initializes "out" */
static void __get_complete_components(candidate_pair_q *out, struct ice_agent *ag, GTree *t, unsigned int flag) {
candidate_pair_q compo1 = TYPED_GQUEUE_INIT;
struct ice_candidate_pair *pair1, *pairX;
struct ice_candidate *cand;
unsigned int i;
__get_pairs_by_component(&compo1, t, 1);
t_queue_init(out);
for (__auto_type l = compo1.head; l; l = l->next) {
pair1 = l->data;
t_queue_clear(out);
t_queue_push_tail(out, pair1);
for (i = 2; i <= ag->active_components; i++) {
cand = __foundation_lookup(ag, &pair1->remote_candidate->foundation, i);
if (!cand)
goto next_foundation;
pairX = __pair_lookup(ag, cand, pair1->local_intf);
if (!pairX)
goto next_foundation;
if (!bf_isset(&pairX->pair_flags, flag))
goto next_foundation;
t_queue_push_tail(out, pairX);
}
goto found;
next_foundation:
;
}
/* nothing found */
t_queue_clear(out);
found:
t_queue_clear(&compo1);
}
/* call(W) or call(R)+agent must be locked - no in_lock or out_lock must be held */
static int __check_valid(struct ice_agent *ag) {
struct call_media *media;
struct packet_stream *ps;
packet_stream_list *l;
candidate_pair_list *k;
candidate_pair_q all_compos;
struct ice_candidate_pair *pair;
// const struct local_intf *ifa;
stream_fd *sfd;
int is_complete = 1;
if (!ag) {
ilogs(ice, LOG_ERR, "ice ag is NULL");
return 0;
}
media = ag->media;
__get_complete_valid_pairs(&all_compos, ag);
if (!all_compos.length) {
is_complete = 0;
__get_complete_succeeded_pairs(&all_compos, ag);
if (!all_compos.length) {
ilogs(ice, LOG_DEBUG, "ICE not completed yet and no usable candidates");
return 0;
}
}
pair = all_compos.head->data;
if (is_complete) {
ilogs(ice, LOG_DEBUG, "ICE completed, using pair " PAIR_FORMAT, PAIR_FMT(pair));
AGENT_SET(ag, COMPLETED);
}
else {
ilogs(ice, LOG_DEBUG, "ICE not completed yet, but can use pair " PAIR_FORMAT, PAIR_FMT(pair));
AGENT_SET(ag, USABLE);
}
for (l = media->streams.head, k = all_compos.head; l && k; l = l->next, k = k->next) {
ps = l->data;
pair = k->data;
mutex_lock(&ps->out_lock);
if (memcmp(&ps->endpoint, &pair->remote_candidate->endpoint, sizeof(ps->endpoint))) {
ilogs(ice, LOG_INFO, "ICE negotiated: new peer for component %u is %s%s%s", ps->component,
FMT_M(endpoint_print_buf(&pair->remote_candidate->endpoint)));
ps->endpoint = pair->remote_candidate->endpoint;
PS_SET(ps, FILLED);
}
else
ilogs(ice, LOG_INFO, "ICE negotiated: peer for component %u is %s%s%s", ps->component,
FMT_M(endpoint_print_buf(&pair->remote_candidate->endpoint)));
mutex_unlock(&ps->out_lock);
for (__auto_type m = ps->sfds.head; m; m = m->next) {
sfd = m->data;
if (sfd->local_intf != pair->local_intf)
continue;
ps->selected_sfd = sfd;
if (ps->component == 1)
ilogs(ice, LOG_INFO, "ICE negotiated: local interface %s",
sockaddr_print_buf(&pair->local_intf->spec->local_address.addr));
break;
}
}
call_media_unkernelize(media, "ICE negotiation event");
t_queue_clear(&all_compos);
return 1;
}
/* call is locked in R */
/* return values:
* 1 = ICE completed, interfaces selected
* 0 = packet processed
* -1 = generic error, process packet as normal
* -2 = role conflict
*/
int ice_request(stream_fd *sfd, const endpoint_t *src,
struct stun_attrs *attrs)
{
struct packet_stream *ps = sfd->stream;
struct call_media *media = ps->media;
struct ice_agent *ag;
const char *err;
struct ice_candidate *cand;
struct ice_candidate_pair *pair;
int ret;
__DBG("received ICE request from %s on %s", endpoint_print_buf(src),
endpoint_print_buf(&sfd->socket.local));
ag = media->ice_agent;
if (!ag)
return -1;
atomic64_set_na(&ag->last_activity, rtpe_now);
/* determine candidate pair */
{
LOCK(&ag->lock);
cand = __cand_lookup(ag, src, ps->component);
if (!cand)
pair = __learned_candidate(ag, sfd, src, attrs->priority);
else
pair = __pair_lookup(ag, cand, sfd->local_intf);
err = "Failed to determine ICE candidate from STUN request";
if (!pair)
goto err;
}
if (!AGENT_ISSET(ag, LITE_SELF)) {
/* determine role conflict */
if (attrs->controlling && AGENT_ISSET(ag, CONTROLLING)) {
if (tie_breaker >= attrs->tiebreaker)
return -2;
else
__role_change(ag, 0);
}
else if (attrs->controlled && !AGENT_ISSET(ag, CONTROLLING)) {
if (tie_breaker >= attrs->tiebreaker)
__role_change(ag, 1);
else
return -2;
}
}
else
PAIR_SET(pair, SUCCEEDED);
if (PAIR_ISSET(pair, SUCCEEDED))
;
else
__trigger_check(pair);
ret = 0;
if (attrs->use && !PAIR_SET(pair, NOMINATED)) {
ilogs(ice, LOG_DEBUG, "ICE pair "PAIR_FORMAT" has been nominated by peer", PAIR_FMT(pair));
LOCK(&ag->lock);
// coverity[use : FALSE]
rtpe_g_tree_insert_coll(ag->nominated_pairs, pair, pair, __tree_coll_callback);
if (PAIR_ISSET(pair, SUCCEEDED)) {
PAIR_SET(pair, VALID);
rtpe_g_tree_insert_coll(ag->valid_pairs, pair, pair, __tree_coll_callback);
}
if (!AGENT_ISSET(ag, CONTROLLING))
ret = __check_valid(ag);
}
return ret;
err:
ilogs(ice, LOG_NOTICE | LOG_FLAG_LIMIT, "%s (from %s%s%s on interface %s)", err, FMT_M(endpoint_print_buf(src)),
endpoint_print_buf(&sfd->socket.local));
return 0;
}
static int __check_succeeded_complete(struct ice_agent *ag) {
candidate_pair_q complete;
int ret;
__get_complete_succeeded_pairs(&complete, ag);
if (complete.length) {
struct ice_candidate_pair *pair = complete.head->data;
ilogs(ice, LOG_DEBUG, "Best succeeded ICE pair with all components is "PAIR_FORMAT, PAIR_FMT(pair));
ret = 1;
}
else {
ilogs(ice, LOG_DEBUG, "No succeeded ICE pairs with all components yet");
ret = 0;
}
t_queue_clear(&complete);
return ret;
}
/* call is locked in R */
int ice_response(stream_fd *sfd, const endpoint_t *src,
struct stun_attrs *attrs, void *transaction)
{
struct ice_candidate_pair *pair, *opair;
struct ice_agent *ag;
struct packet_stream *ps = sfd->stream;
struct call_media *media = ps->media;
const char *err;
unsigned int component;
struct ice_candidate *cand;
const struct local_intf *ifa;
int ret, was_ctl;
__DBG("received ICE response from %s on %s", endpoint_print_buf(src),
endpoint_print_buf(&sfd->socket.local));
ag = media->ice_agent;
if (!ag)
return -1;
atomic64_set_na(&ag->last_activity, rtpe_now);
{
LOCK(&ag->lock);
pair = t_hash_table_lookup(ag->transaction_hash, transaction);
err = "ICE/STUN response with unknown transaction received";
if (!pair)
goto err;
was_ctl = pair->was_controlling;
}
ifa = pair->local_intf;
ilogs(ice, LOG_DEBUG, "Received ICE/STUN response code %u for candidate pair "PAIR_FORMAT" from %s%s%s to %s",
attrs->error_code, PAIR_FMT(pair),
FMT_M(endpoint_print_buf(&pair->remote_candidate->endpoint)),
sockaddr_print_buf(&ifa->spec->local_address.addr));
/* verify endpoints */
err = "ICE/STUN response received, but source address didn't match remote candidate address";
if (!endpoint_eq(src, &pair->remote_candidate->endpoint))
goto err;
err = "ICE/STUN response received, but destination address didn't match local interface address";
if (pair->sfd != sfd)
goto err;
PAIR_CLEAR(pair, IN_PROGRESS);
ret = 0;
/* handle all errors */
if (attrs->error_code) {
err = "ICE/STUN error received";
if (attrs->error_code != 487)
goto err;
__role_change(ag, !was_ctl);
__trigger_check(pair);
goto out;
}
/* we don't discover peer reflexive here (RFC 5245 7.1.3.2.1) as we don't expect to be behind NAT */
/* we also skip parts of 7.1.3.2.2 as we don't do server reflexive */
{
LOCK(&ag->lock);
/* check if we're in the final (controlling) phase */
if (pair->was_nominated && PAIR_CLEAR(pair, TO_USE)) {
ilogs(ice, LOG_DEBUG, "Setting nominated ICE candidate pair "PAIR_FORMAT" as valid", PAIR_FMT(pair));
PAIR_SET(pair, VALID);
rtpe_g_tree_insert_coll(ag->valid_pairs, pair, pair, __tree_coll_callback);
ret = __check_valid(ag);
goto out;
}
if (PAIR_SET(pair, SUCCEEDED))
goto out;
ilogs(ice, LOG_DEBUG, "Setting ICE candidate pair "PAIR_FORMAT" as succeeded", PAIR_FMT(pair));
rtpe_g_tree_insert_coll(ag->succeeded_pairs, pair, pair, __tree_coll_callback);
if (!ag->start_nominating) {
if (__check_succeeded_complete(ag)) {
ag->start_nominating = rtpe_now;
ag->start_nominating += 100000;
__agent_schedule_abs(ag, ag->start_nominating);
}
}
/* now unfreeze all other pairs from the same foundation */
for (component = 1; component <= MAX_COMPONENTS; component++) {
if (component == ps->component)
continue;
cand = __foundation_lookup(ag, &pair->remote_candidate->foundation, component);
if (!cand)
continue;
opair = __pair_lookup(ag, cand, ifa);
if (!opair)
continue;
if (PAIR_ISSET(opair, FAILED))
continue;
if (!PAIR_CLEAR(opair, FROZEN))
continue;
ilogs(ice, LOG_DEBUG, "Unfreezing related ICE pair "PAIR_FORMAT, PAIR_FMT(opair));
}
/* if this was previously nominated by the peer, it's now valid */
if (PAIR_ISSET(pair, NOMINATED)) {
PAIR_SET(pair, VALID);
rtpe_g_tree_insert_coll(ag->valid_pairs, pair, pair, __tree_coll_callback);
}
ret = __check_valid(ag);
}
out:
return ret;
err:
if (err)
ilogs(ice, LOG_NOTICE | LOG_FLAG_LIMIT, "%s (from %s%s%s on interface %s)",
err, FMT_M(endpoint_print_buf(src)), endpoint_print_buf(&sfd->socket.local));
if (pair && attrs->error_code)
__fail_pair(pair);
return 0;
}
void ice_thread_launch(void) {
timerthread_launch(&ice_agents_timer_thread, NULL, 0, "ICE");
}
static void ice_agents_timer_run(void *ptr) {
struct ice_agent *ag = ptr;
call_t *call;
call = ag->call;
log_info_ice_agent(ag);
rwlock_lock_r(&call->master_lock);
/* and run our checks */
__do_ice_checks(ag);
/* finally, release our reference and start over */
log_info_pop();
rwlock_unlock_r(&call->master_lock);
}
static void random_ice_string(char *buf, int len) {
while (len--)
*buf++ = ice_chars[ssl_random() % strlen(ice_chars)];
}
static void create_random_ice_string(call_t *call, str *s, int len) {
char buf[30];
assert(len < sizeof(buf));
if (s->s)
return;
random_ice_string(buf, len);
*s = call_str_cpy_len(buf, len);
}
void ice_foundation(str *s) {
*s = STR_LEN(malloc(ICE_FOUNDATION_LENGTH), ICE_FOUNDATION_LENGTH);
random_ice_string(s->s, ICE_FOUNDATION_LENGTH);
}
void ice_remote_candidates(candidate_q *out, struct ice_agent *ag) {
candidate_pair_q all_compos;
struct ice_candidate_pair *pair;
t_queue_init(out);
mutex_lock(&ag->lock);
__get_complete_valid_pairs(&all_compos, ag);
mutex_unlock(&ag->lock);
for (__auto_type l = all_compos.head; l; l = l->next) {
pair = l->data;
t_queue_push_tail(out, pair->remote_candidate);
}
t_queue_clear(&all_compos);
}
bool ice_peer_address_known(struct ice_agent *ag, const endpoint_t *sin, struct packet_stream *ps,
const struct local_intf *ifa)
{
LOCK(&ag->lock);
struct ice_candidate *cand = __cand_lookup(ag, sin, ps->component);
if (!cand)
return false;
struct ice_candidate_pair *pair = __pair_lookup(ag, cand, ifa);
if (!pair)
return false;
if (!PAIR_ISSET(pair, VALID))
return false;
return true;
}