/* agent must be locked */ static struct ice_candidate_pair *__pair_candidate(struct interface_address *addr, struct ice_agent *ag, struct ice_candidate *cand, struct packet_stream *ps) { struct ice_candidate_pair *pair; if (addr->family != family_from_address(&cand->endpoint.ip46)) return NULL; pair = g_slice_alloc0(sizeof(*pair)); pair->agent = ag; pair->remote_candidate = cand; pair->local_address = addr; pair->packet_stream = ps; if (cand->component_id != 1) PAIR_SET(pair, FROZEN); __do_ice_pair_priority(pair); __new_stun_transaction(pair); g_queue_push_tail(&ag->candidate_pairs, pair); g_hash_table_insert(ag->pair_hash, pair, pair); g_tree_insert(ag->all_pairs, pair, pair); ilog(LOG_DEBUG, "Created candidate pair "PAIR_FORMAT" between %s and %s, type %s", PAIR_FMT(pair), smart_ntop_buf(&addr->addr), smart_ntop_ep_buf(&cand->endpoint), ice_candidate_type_str(cand->type)); return pair; }
/* agent must NOT be locked, but call must be locked in R */ static void __do_ice_check(struct ice_candidate_pair *pair) { struct sockaddr_in6 dst; struct packet_stream *ps = pair->packet_stream; struct ice_agent *ag = pair->agent; u_int32_t prio, transact[3]; if (PAIR_ISSET(pair, SUCCEEDED) && !PAIR_ISSET(pair, TO_USE)) return; if (!ag->pwd[0].s) return; ZERO(dst); dst.sin6_port = htons(pair->remote_candidate->endpoint.port); dst.sin6_addr = pair->remote_candidate->endpoint.ip46; dst.sin6_family = AF_INET6; prio = ice_priority(ICT_PRFLX, pair->local_address->preference, pair->remote_candidate->component_id); mutex_lock(&ag->lock); pair->retransmit = g_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++; } timeval_add_usec(&pair->retransmit, pair->retransmit_ms * 1000); __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); ilog(LOG_DEBUG, "Sending %sICE/STUN request for candidate pair "PAIR_FORMAT" from %s to %s", PAIR_ISSET(pair, TO_USE) ? "nominating " : "", PAIR_FMT(pair), smart_ntop_buf(&pair->local_address->addr), smart_ntop_ep_buf(&pair->remote_candidate->endpoint)); stun_binding_request(&dst, transact, &ag->pwd[0], ag->ufrag, AGENT_ISSET(ag, CONTROLLING), tie_breaker, prio, &pair->local_address->addr, ps->sfd->fd.fd, PAIR_ISSET(pair, TO_USE)); }
/* 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 = ag->media; struct packet_stream *ps; GList *l, *k; GQueue all_compos; struct ice_candidate_pair *pair; struct interface_address *ifa; if (!ag) { ilog(LOG_ERR, "ice ag is NULL"); return 0; } __get_complete_valid_pairs(&all_compos, ag); if (!all_compos.length) { ilog(LOG_DEBUG, "ICE not completed yet"); return 0; } pair = all_compos.head->data; ilog(LOG_DEBUG, "ICE completed, using pair "PAIR_FORMAT, PAIR_FMT(pair)); AGENT_SET(ag, COMPLETED); ifa = g_atomic_pointer_get(&media->local_address); if (ifa != pair->local_address && g_atomic_pointer_compare_and_exchange(&media->local_address, ifa, pair->local_address)) ilog(LOG_INFO, "ICE negotiated: local interface %s", smart_ntop_buf(&pair->local_address->addr)); 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))) { ilog(LOG_INFO, "ICE negotiated: peer for component %u is %s", ps->component, smart_ntop_ep_buf(&pair->remote_candidate->endpoint)); ps->endpoint = pair->remote_candidate->endpoint; } mutex_unlock(&ps->out_lock); } call_media_unkernelize(media); g_queue_clear(&all_compos); return 1; }
/* call is locked in R */ int ice_response(struct packet_stream *ps, struct sockaddr_in6 *src, struct in6_addr *dst, struct stun_attrs *attrs, u_int32_t transaction[3]) { struct ice_candidate_pair *pair, *opair; struct ice_agent *ag; struct call_media *media = ps->media; const char *err; unsigned int component; struct ice_candidate *cand; struct interface_address *ifa; int ret, was_ctl; __DBG("received ICE response 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); 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_address; ilog(LOG_DEBUG, "Received ICE/STUN response code %u for candidate pair "PAIR_FORMAT" from %s to %s", attrs->error_code, PAIR_FMT(pair), smart_ntop_ep_buf(&pair->remote_candidate->endpoint), smart_ntop_buf(&ifa->addr)); /* verify endpoints */ err = "ICE/STUN response received, but source address didn't match remote candidate address"; if (memcmp(&src->sin6_addr, &pair->remote_candidate->endpoint.ip46, sizeof(src->sin6_addr))) goto err; if (ntohs(src->sin6_port) != pair->remote_candidate->endpoint.port) goto err; err = "ICE/STUN response received, but destination address didn't match local interface address"; if (memcmp(dst, &ifa->addr, sizeof(*dst))) goto err; if (pair->packet_stream != ps) 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, smart_ntop_port_buf(src), smart_ntop_buf(dst)); if (pair && attrs->error_code) __fail_pair(pair); return 0; }
/* 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; }
static void insert_candidates(struct sdp_chopper *chop, struct packet_stream *rtp, struct packet_stream *rtcp, struct sdp_ng_flags *flags, struct sdp_media *sdp_media) { GList *l; struct interface_address *ifa; unsigned int pref; struct call_media *media; struct local_interface *lif; struct ice_agent *ag; unsigned int type_pref, local_pref; enum ice_candidate_type cand_type; struct ice_candidate *cand; media = rtp->media; cand_type = ICT_HOST; if (flags->ice_force_relay) cand_type = ICT_RELAY; if (MEDIA_ISSET(media, PASSTHRU)) new_priority(sdp_media, cand_type, &type_pref, &local_pref); else { type_pref = ice_type_preference(cand_type); local_pref = -1; } ag = media->ice_agent; lif = ag ? ag->local_interface : media->interface; if (ag && AGENT_ISSET(ag, COMPLETED)) { ifa = g_atomic_pointer_get(&media->local_address); insert_candidate(chop, rtp, 1, type_pref, ifa->preference, cand_type, ifa); if (rtcp) /* rtcp-mux only possible in answer */ insert_candidate(chop, rtcp, 2, type_pref, ifa->preference, cand_type, ifa); if (flags->opmode == OP_OFFER && AGENT_ISSET(ag, CONTROLLING)) { GQueue rc; GList *l; chopper_append_c(chop, "a=remote-candidates:"); ice_remote_candidates(&rc, ag); for (l = rc.head; l; l = l->next) { if (l != rc.head) chopper_append_c(chop, " "); cand = l->data; chopper_append_printf(chop, "%lu %s %u", cand->component_id, smart_ntop_buf(&cand->endpoint.ip46), cand->endpoint.port); } chopper_append_c(chop, "\r\n"); g_queue_clear(&rc); } return; } for (l = lif->list.head; l; l = l->next) { ifa = l->data; pref = (local_pref == -1) ? ifa->preference : local_pref; insert_candidate(chop, rtp, 1, type_pref, pref, cand_type, ifa); if (rtcp) /* rtcp-mux only possible in answer */ insert_candidate(chop, rtcp, 2, type_pref, pref, cand_type, ifa); if (local_pref != -1) local_pref++; } }