mirror of https://github.com/sipwise/rtpengine.git
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.
1541 lines
42 KiB
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;
|
|
}
|