/* 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) { GList *l, *k; struct ice_candidate *cand, *dup; struct call_media *media; struct call *call; int recalc = 0; unsigned int comps; struct packet_stream *components[MAX_COMPONENTS], *ps; GQueue *candidates; struct stream_fd *sfd; if (!ag) return; atomic64_set(&ag->last_activity, poller_now); media = ag->media; call = media->call; __role_change(ag, MEDIA_ISSET(media, ICE_CONTROLLING)); if (sp) { /* check for ICE restarts */ if (ag->ufrag[0].s && sp->ice_ufrag.s && str_cmp_str(&ag->ufrag[0], &sp->ice_ufrag)) __ice_restart(ag); else if (ag->pwd[0].s && sp->ice_pwd.s && str_cmp_str(&ag->pwd[0], &sp->ice_pwd)) __ice_restart(ag); else if (ag->logical_intf != media->logical_intf) __ice_restart(ag); /* update remote info */ if (sp->ice_ufrag.s) call_str_cpy(call, &ag->ufrag[0], &sp->ice_ufrag); if (sp->ice_pwd.s) call_str_cpy(call, &ag->pwd[0], &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 (l = media->streams.head; l; l = l->next) components[comps++] = l->data; if (comps == 2 && MEDIA_ISSET(media, RTCP_MUX)) components[1] = NULL; comps = 0; for (l = candidates->head; l; l = l->next) { if (ag->remote_candidates.length >= MAX_ICE_CANDIDATES) { ilog(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 = g_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) { ilog(LOG_DEBUG, "Replacing previously learned prflx ICE candidate with " STR_FORMAT":%lu", STR_FMT(&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) { ilog(LOG_DEBUG, "Dropping new ICE candidate "STR_FORMAT" in favour of " STR_FORMAT":%lu", STR_FMT(&cand->foundation), STR_FMT(&dup->foundation), cand->component_id); continue; } ilog(LOG_DEBUG, "Replacing known ICE candidate "STR_FORMAT" with higher " "priority " STR_FORMAT":%lu", STR_FMT(&dup->foundation), STR_FMT(&cand->foundation), cand->component_id); } /* priority and foundation may change */ g_hash_table_remove(ag->foundation_hash, dup); recalc += __copy_cand(call, dup, cand); } else { ilog(LOG_DEBUG, "Learning new ICE candidate "STR_FORMAT":%lu", STR_FMT(&cand->foundation), cand->component_id); dup = g_slice_alloc(sizeof(*dup)); __copy_cand(call, dup, cand); g_hash_table_insert(ag->candidate_hash, dup, dup); g_queue_push_tail(&ag->remote_candidates, dup); } g_hash_table_insert(ag->foundation_hash, dup, dup); pair: if (!ps) continue; for (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); }
/* return values: * 1 = ICE completed, interfaces selected * 0 = packet processed * -1 = generic error, process packet as normal * -2 = role conflict */ int ice_request(struct packet_stream *ps, struct sockaddr_in6 *src, struct in6_addr *dst, struct stun_attrs *attrs) { struct call_media *media = ps->media; struct ice_agent *ag; struct interface_address *ifa; const char *err; struct ice_candidate *cand; struct ice_candidate_pair *pair; int ret; __DBG("received ICE request from %s on %s", smart_ntop_port_buf(src), smart_ntop_buf(dst)); ag = media->ice_agent; if (!ag) return -1; atomic64_set(&ag->last_activity, poller_now); ifa = get_interface_from_address(ag->local_interface, dst); err = "ICE/STUN binding request received on unknown local interface address"; if (!ifa) goto err; /* determine candidate pair */ mutex_lock(&ag->lock); cand = __cand_lookup(ag, src, ps->component); if (!cand) pair = __learned_candidate(ag, ps, src, ifa, attrs->priority); else pair = __pair_lookup(ag, cand, ifa); err = "Failed to determine ICE candidate from STUN request"; if (!pair) goto err_unlock; mutex_unlock(&ag->lock); /* 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; } if (PAIR_ISSET(pair, SUCCEEDED)) ; else __trigger_check(pair); ret = 0; if (attrs->use && !PAIR_SET(pair, NOMINATED)) { ilog(LOG_DEBUG, "ICE pair "PAIR_FORMAT" has been nominated by peer", PAIR_FMT(pair)); mutex_lock(&ag->lock); g_tree_insert(ag->nominated_pairs, pair, pair); if (PAIR_ISSET(pair, SUCCEEDED)) { PAIR_SET(pair, VALID); g_tree_insert(ag->valid_pairs, pair, pair); } if (!AGENT_ISSET(ag, CONTROLLING)) ret = __check_valid(ag); mutex_unlock(&ag->lock); } return ret; err_unlock: mutex_unlock(&ag->lock); err: ilog(LOG_NOTICE, "%s (from %s on interface %s)", err, smart_ntop_port_buf(src), smart_ntop_buf(dst)); return 0; }
/* call is locked in R */ int ice_response(struct stream_fd *sfd, const endpoint_t *src, struct stun_attrs *attrs, u_int32_t transaction[3]) { 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(&ag->last_activity, poller_now); mutex_lock(&ag->lock); pair = g_hash_table_lookup(ag->transaction_hash, transaction); err = "ICE/STUN response with unknown transaction received"; if (!pair) goto err_unlock; was_ctl = pair->was_controlling; mutex_unlock(&ag->lock); ifa = pair->local_intf; ilog(LOG_DEBUG, "Received ICE/STUN response code %u for candidate pair "PAIR_FORMAT" from %s to %s", attrs->error_code, PAIR_FMT(pair), 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 */ mutex_lock(&ag->lock); /* check if we're in the final (controlling) phase */ if (pair->was_nominated && PAIR_CLEAR(pair, TO_USE)) { ilog(LOG_DEBUG, "Setting nominated ICE candidate pair "PAIR_FORMAT" as valid", PAIR_FMT(pair)); PAIR_SET(pair, VALID); g_tree_insert(ag->valid_pairs, pair, pair); ret = __check_valid(ag); goto out_unlock; } if (PAIR_SET(pair, SUCCEEDED)) goto out_unlock; ilog(LOG_DEBUG, "Setting ICE candidate pair "PAIR_FORMAT" as succeeded", PAIR_FMT(pair)); g_tree_insert(ag->succeeded_pairs, pair, pair); if (!ag->start_nominating.tv_sec) { if (__check_succeeded_complete(ag)) { ag->start_nominating = g_now; timeval_add_usec(&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; ilog(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); g_tree_insert(ag->valid_pairs, pair, pair); if (!AGENT_ISSET(ag, CONTROLLING)) ret = __check_valid(ag); } out_unlock: mutex_unlock(&ag->lock); out: return ret; err_unlock: mutex_unlock(&ag->lock); err: if (err) ilog(LOG_NOTICE, "%s (from %s on interface %s)", err, endpoint_print_buf(src), endpoint_print_buf(&sfd->socket.local)); if (pair && attrs->error_code) __fail_pair(pair); return 0; }