/* agent must be locked */ static struct ice_candidate_pair *__pair_candidate(struct 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_slice_alloc0(sizeof(*pair)); 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); 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), sockaddr_print_buf(&sfd->socket.local.address), endpoint_print_buf(&cand->endpoint), ice_candidate_type_str(cand->type)); return pair; }
static int replace_network_address(struct sdp_chopper *chop, struct network_address *address, struct packet_stream *ps, struct sdp_ng_flags *flags) { char buf[64]; int len; struct packet_stream *sink = packet_stream_sink(ps); if (is_addr_unspecified(&address->parsed) && !(sink && is_trickle_ice_address(&sink->advertised_endpoint))) return 0; if (copy_up_to(chop, &address->address_type)) return -1; if (flags->media_address.s && is_addr_unspecified(&flags->parsed_media_address)) __parse_address(&flags->parsed_media_address, NULL, NULL, &flags->media_address); if (!is_addr_unspecified(&flags->parsed_media_address)) len = sprintf(buf, "%s %s", flags->parsed_media_address.family->rfc_name, sockaddr_print_buf(&flags->parsed_media_address)); else call_stream_address46(buf, ps, SAF_NG, &len, NULL); chopper_append_dup(chop, buf, len); if (skip_over(chop, &address->address)) return -1; return 0; }
/* 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, *m; GQueue all_compos; struct ice_candidate_pair *pair; // const struct local_intf *ifa; struct stream_fd *sfd; 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); 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, endpoint_print_buf(&pair->remote_candidate->endpoint)); ps->endpoint = pair->remote_candidate->endpoint; } mutex_unlock(&ps->out_lock); for (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) ilog(LOG_INFO, "ICE negotiated: local interface %s", sockaddr_print_buf(&pair->local_intf->spec->local_address.addr)); } } call_media_unkernelize(media); g_queue_clear(&all_compos); return 1; }
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) { const struct local_intf *ifa; struct call_media *media; 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; if (ag && AGENT_ISSET(ag, COMPLETED)) { ifa = rtp->selected_sfd->local_intf; insert_candidate(chop, rtp->selected_sfd, type_pref, ifa->unique_id, cand_type); if (rtcp) /* rtcp-mux only possible in answer */ insert_candidate(chop, rtcp->selected_sfd, type_pref, ifa->unique_id, cand_type); 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, sockaddr_print_buf(&cand->endpoint.address), cand->endpoint.port); } chopper_append_c(chop, "\r\n"); g_queue_clear(&rc); } return; } insert_sfd_candidates(chop, rtp, type_pref, local_pref, cand_type); if (rtcp) /* rtcp-mux only possible in answer */ insert_sfd_candidates(chop, rtcp, type_pref, local_pref, cand_type); }
/* agent must NOT be locked, but call must be locked in R */ static void __do_ice_check(struct ice_candidate_pair *pair) { struct stream_fd *sfd = pair->sfd; 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; prio = ice_priority(ICT_PRFLX, pair->local_intf->unique_id, 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), sockaddr_print_buf(&pair->local_intf->spec->local_address.addr), 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)); }
struct control_ng_stats* get_control_ng_stats(struct control_ng* c, const sockaddr_t *addr) { struct callmaster *m = c->callmaster; struct control_ng_stats* cur; mutex_lock(&m->cngs_lock); cur = g_hash_table_lookup(m->cngs_hash, addr); if (!cur) { cur = g_slice_alloc0(sizeof(struct control_ng_stats)); cur->proxy = *addr; ilog(LOG_DEBUG,"Adding a proxy for control ng stats:%s", sockaddr_print_buf(addr)); g_hash_table_insert(m->cngs_hash, &cur->proxy, cur); } mutex_unlock(&m->cngs_lock); return cur; }
// st->stream->out_lock (or call->master_lock/W) must be held already static int send_timer_send(struct send_timer *st, struct codec_packet *cp) { if (cp->to_send.tv_sec && timeval_cmp(&cp->to_send, &rtpe_now) > 0) return -1; // not yet if (!st->sink->selected_sfd) goto out; struct rtp_header *rh = (void *) cp->s.s; ilog(LOG_DEBUG, "Forward to sink endpoint: %s%s:%d%s (RTP seq %u TS %u)", FMT_M(sockaddr_print_buf(&st->sink->endpoint.address), st->sink->endpoint.port), ntohs(rh->seq_num), ntohl(rh->timestamp)); socket_sendto(&st->sink->selected_sfd->socket, cp->s.s, cp->s.len, &st->sink->endpoint); out: codec_packet_free(cp); 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; }
static int if_addr_parse(GQueue *q, char *s, struct ifaddrs *ifas) { str name; char *c; sockaddr_t *addr, adv; GQueue addrs = G_QUEUE_INIT; struct intf_config *ifa; /* name */ c = strchr(s, '/'); if (c) { *c++ = 0; str_init(&name, s); s = c; } else str_init(&name, "default"); /* advertised address */ c = strchr(s, '!'); if (c) *c++ = 0; /* address */ addr = g_slice_alloc(sizeof(*addr)); if (!sockaddr_parse_any(addr, s)) { if (is_addr_unspecified(addr)) return -1; g_queue_push_tail(&addrs, addr); } else { // could be an interface name? ilog(LOG_DEBUG, "Could not parse '%s' as network address, checking to see if " "it's an interface", s); for (struct ifaddrs *ifa = ifas; ifa; ifa = ifa->ifa_next) { if (strcmp(ifa->ifa_name, s)) continue; if (!(ifa->ifa_flags & IFF_UP)) continue; if (!ifa->ifa_addr) continue; if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *sin = (void *) ifa->ifa_addr; addr->family = __get_socket_family_enum(SF_IP4); addr->u.ipv4 = sin->sin_addr; } else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin = (void *) ifa->ifa_addr; if (sin->sin6_scope_id) continue; // link-local addr->family = __get_socket_family_enum(SF_IP6); addr->u.ipv6 = sin->sin6_addr; } else continue; // got one ilog(LOG_DEBUG, "Determined address %s for interface '%s'", sockaddr_print_buf(addr), s); g_queue_push_tail(&addrs, addr); addr = g_slice_alloc(sizeof(*addr)); } // free last unused entry g_slice_free1(sizeof(*addr), addr); } if (!addrs.length) // nothing found return -1; ZERO(adv); if (c) { if (sockaddr_parse_any(&adv, c)) return -1; if (is_addr_unspecified(&adv)) return -1; } while ((addr = g_queue_pop_head(&addrs))) { ifa = g_slice_alloc0(sizeof(*ifa)); ifa->name = name; ifa->local_address.addr = *addr; ifa->local_address.type = socktype_udp; ifa->advertised_address.addr = adv; if (is_addr_unspecified(&ifa->advertised_address.addr)) ifa->advertised_address.addr = *addr; ifa->advertised_address.type = ifa->local_address.type; ifa->port_min = rtpe_config.port_min; ifa->port_max = rtpe_config.port_max; // handle "base:suffix" separation for round-robin selection ifa->name_rr_spec = ifa->name; str_token(&ifa->name_base, &ifa->name_rr_spec, ':'); // sets name_rr_spec to null string if no ':' found g_queue_push_tail(q, ifa); g_slice_free1(sizeof(*addr), addr); } return 0; }