static void handle_success(struct icem *icem, struct ice_candpair *cp, const struct sa *laddr) { if (!icem_cand_find(&icem->lcandl, cp->lcand->compid, laddr)) { int err; icecomp_printf(cp->comp, "adding local PRFLX Candidate: %J\n", laddr); err = icem_lcand_add(icem, cp->lcand, ICE_CAND_TYPE_PRFLX, laddr); if (err) { DEBUG_WARNING("failed to add PRFLX: %m\n", err); } } cp = construct_valid_pair(icem, cp, laddr, &cp->rcand->addr); if (!cp) { DEBUG_WARNING("{%s} no valid candidate pair for %J\n", icem->name, laddr); return; } icem_candpair_make_valid(cp); icem_comp_set_selected(cp->comp, cp); cp->nominated = true; #if 0 /* stop conncheck now -- conclude */ icem_conncheck_stop(icem, 0); #endif }
static void stunc_resp_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct ice_candpair *cp = arg; struct icem *icem = cp->icem; struct stun_attr *attr; (void)reason; #if ICE_TRACE icecomp_printf(cp->comp, "Rx %H <--- %H '%u %s'%H\n", icem_cand_print, cp->lcand, icem_cand_print, cp->rcand, scode, reason, print_err, &err); #endif if (err) { icem_candpair_failed(cp, err, scode); goto out; } switch (scode) { case 0: /* Success case */ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR); if (!attr) { DEBUG_WARNING("no XOR-MAPPED-ADDR in response\n"); icem_candpair_failed(cp, EBADMSG, 0); break; } handle_success(icem, cp, &attr->v.sa); break; case 487: /* Role Conflict */ ice_switch_local_role(icem); (void)icem_conncheck_send(cp, false, true); break; default: DEBUG_WARNING("{%s.%u} STUN Response: %u %s\n", icem->name, cp->comp->id, scode, reason); icem_candpair_failed(cp, err, scode); break; } out: pace_next(icem); }
void icem_candpair_set_state(struct candpair *cp, enum candpair_state state) { if (!cp) return; if (cp->state != state) { icecomp_printf(cp->comp, "%5s <---> %5s FSM: %10s ===> %-10s\n", ice_cand_type2name(cp->lcand->type), ice_cand_type2name(cp->rcand->type), ice_candpair_state2name(cp->state), ice_candpair_state2name(state)); cp->state = state; } }
void icem_comp_set_default_rcand(struct icem_comp *comp, struct cand *rcand) { if (!comp) return; icecomp_printf(comp, "Set default remote candidate: %s:%J\n", ice_cand_type2name(rcand->type), &rcand->addr); mem_deref(comp->def_rcand); comp->def_rcand = mem_ref(rcand); if (comp->turnc) { DEBUG_NOTICE("{%s.%u} Default: Add TURN Channel to peer %J\n", comp->icem->name, comp->id, &rcand->addr); (void)turnc_add_chan(comp->turnc, &rcand->addr, NULL, NULL); } }
int icem_rcand_add_prflx(struct cand **rcp, struct icem *icem, uint8_t compid, uint32_t prio, const struct sa *addr) { struct cand *rcand; int err; if (!icem || !addr) return EINVAL; rcand = mem_zalloc(sizeof(*rcand), cand_destructor); if (!rcand) return ENOMEM; list_append(&icem->rcandl, &rcand->le, rcand); rcand->type = CAND_TYPE_PRFLX; rcand->compid = compid; rcand->prio = prio; rcand->addr = *addr; err = re_sdprintf(&rcand->foundation, "%08x", rand_u32()); if (err) goto out; icecomp_printf(icem_comp_find(icem, compid), "added PeerReflexive remote candidate" " with priority %u (%J)\n", prio, addr); out: if (err) mem_deref(rcand); else if (rcp) *rcp = rcand; return err; }
static int handle_stun(struct ice *ice, struct icem *icem, struct icem_comp *comp, const struct sa *src, uint32_t prio, bool use_cand, bool tunnel) { struct cand *lcand = NULL, *rcand; struct candpair *cp = NULL; int err; rcand = icem_cand_find(&icem->rcandl, comp->id, src); if (!rcand) { err = icem_rcand_add_prflx(&rcand, icem, comp->id, prio, src); if (err) return err; } cp = icem_candpair_find_rcand(icem, rcand); if (cp) lcand = cp->lcand; else lcand = icem_lcand_find_checklist(icem, comp->id); if (!lcand) { DEBUG_WARNING("{%s.%u} no local candidate" " (checklist=%u) (src=%J)\n", icem->name, comp->id, list_count(&icem->checkl), src); return 0; } if (ICE_MODE_FULL == ice->lmode) triggered_check(icem, lcand, rcand); if (!cp) { cp = icem_candpair_find_rcand(icem, rcand); if (!cp) { DEBUG_WARNING("{%s.%u} candidate pair not found:" " source=%J\n", icem->name, comp->id, src); return 0; } } #if ICE_TRACE icecomp_printf(comp, "Rx Binding Request from %J via %s" " (candpair=%s) %s\n", src, tunnel ? "Tunnel" : "Socket", cp ? ice_candpair_state2name(cp->state) : "n/a", use_cand ? "[USE]" : ""); #else (void)tunnel; #endif /* 7.2.1.5. Updating the Nominated Flag */ if (use_cand) { if (ice->lrole == ROLE_CONTROLLED) { if (cp->state == CANDPAIR_SUCCEEDED) { cp->nominated = true; icecomp_printf(comp, "setting NOMINATED" " flag on candpair [%H]\n", icem_candpair_debug, cp); } } /* Cancel conncheck. Choose Selected Pair */ icem_candpair_make_valid(cp); if (ice->conf.nom == ICE_NOMINATION_REGULAR) { icem_candpair_cancel(cp); icem_comp_set_selected(comp, cp); } } return 0; }
static void triggered_check(struct icem *icem, struct cand *lcand, struct cand *rcand) { struct candpair *cp = NULL; int err; if (lcand && rcand) cp = icem_candpair_find(&icem->checkl, lcand, rcand); if (cp) { icecomp_printf(cp->comp, "triggered_check: found CandidatePair on" " checklist in state: %H\n", icem_candpair_debug, cp); switch (cp->state) { #if 0 /* TODO: I am not sure why we should cancel the * pending Connectivity check here. this * can lead to a deadlock situation where * both agents are stuck on sending * triggered checks on the same candidate pair */ case CANDPAIR_INPROGRESS: icem_candpair_cancel(cp); /*@fallthrough@*/ #endif case CANDPAIR_FAILED: icem_candpair_set_state(cp, CANDPAIR_WAITING); /*@fallthrough@*/ case CANDPAIR_FROZEN: case CANDPAIR_WAITING: err = icem_conncheck_send(cp, false, true); if (err) { DEBUG_WARNING("triggered check failed\n"); } break; case CANDPAIR_SUCCEEDED: default: break; } } else { #if 0 err = icem_candpair_alloc(&cp, icem, lcand, rcand); if (err) { DEBUG_WARNING("failed to allocate candpair:" " lcand=%p rcand=%p (%m)\n", lcand, rcand, err); return; } icem_candpair_prio_order(&icem->checkl); icem_candpair_set_state(cp, CANDPAIR_WAITING); (void)icem_conncheck_send(cp, false, true); #endif } }
/** * Constructing a Valid Pair * * @return The valid pair */ static struct ice_candpair *construct_valid_pair(struct icem *icem, struct ice_candpair *cp, const struct sa *mapped, const struct sa *dest) { struct ice_cand *lcand, *rcand; struct ice_candpair *cp2; int err; lcand = icem_cand_find(&icem->lcandl, cp->lcand->compid, mapped); rcand = icem_cand_find(&icem->rcandl, cp->rcand->compid, dest); if (!lcand) { DEBUG_WARNING("no such local candidate: %J\n", mapped); return NULL; } if (!rcand) { DEBUG_WARNING("no such remote candidate: %J\n", dest); return NULL; } /* New candidate? -- implicit success */ if (lcand != cp->lcand || rcand != cp->rcand) { if (lcand != cp->lcand) { icecomp_printf(cp->comp, "New local candidate for mapped %J\n", mapped); } if (rcand != cp->rcand) { icecomp_printf(cp->comp, "New remote candidate for dest %J\n", dest); } /* The original candidate pair is set to 'Failed' because * the implicitly discovered pair is 'better'. * This happens for UAs behind NAT where the original * pair is of type 'host' and the implicit pair is 'srflx' */ icem_candpair_make_valid(cp); cp2 = icem_candpair_find(&icem->validl, lcand, rcand); if (cp2) return cp2; err = icem_candpair_clone(&cp2, cp, lcand, rcand); if (err) return NULL; icem_candpair_make_valid(cp2); /*icem_candpair_failed(cp, EINTR, 0);*/ return cp2; } else { /* Add to VALID LIST, the pair that generated the check */ icem_candpair_make_valid(cp); return cp; } }
int icem_conncheck_send(struct ice_candpair *cp, bool use_cand, bool trigged) { struct ice_cand *lcand = cp->lcand; struct icem *icem = cp->icem; char username_buf[64]; size_t presz = 0; uint32_t prio_prflx; uint16_t ctrl_attr; int err = 0; icem_candpair_set_state(cp, ICE_CANDPAIR_INPROGRESS); (void)re_snprintf(username_buf, sizeof(username_buf), "%s:%s", icem->rufrag, icem->lufrag); /* PRIORITY and USE-CANDIDATE */ prio_prflx = ice_cand_calc_prio(ICE_CAND_TYPE_PRFLX, 0, lcand->compid); switch (icem->lrole) { case ICE_ROLE_CONTROLLING: ctrl_attr = STUN_ATTR_CONTROLLING; if (icem->conf.nom == ICE_NOMINATION_AGGRESSIVE) use_cand = true; break; case ICE_ROLE_CONTROLLED: ctrl_attr = STUN_ATTR_CONTROLLED; break; default: return EINVAL; } #if ICE_TRACE icecomp_printf(cp->comp, "Tx %H ---> %H (%s) %s %s\n", icem_cand_print, cp->lcand, icem_cand_print, cp->rcand, ice_candpair_state2name(cp->state), use_cand ? "[USE]" : "", trigged ? "[Trigged]" : ""); #else (void)trigged; #endif /* A connectivity check MUST utilize the STUN short term credential mechanism. */ /* The password is equal to the password provided by the peer */ if (!icem->rpwd) { DEBUG_WARNING("no remote password!\n"); } if (cp->ct_conn) { DEBUG_WARNING("send_req: CONNCHECK already Pending!\n"); return EBUSY; } switch (lcand->type) { case ICE_CAND_TYPE_RELAY: /* Creating Permissions for Relayed Candidates */ err = turnc_add_chan(cp->comp->turnc, &cp->rcand->addr, NULL, NULL); if (err) { DEBUG_WARNING("add channel: %m\n", err); break; } presz = 4; /*@fallthrough@*/ case ICE_CAND_TYPE_HOST: case ICE_CAND_TYPE_SRFLX: case ICE_CAND_TYPE_PRFLX: cp->ct_conn = mem_deref(cp->ct_conn); err = stun_request(&cp->ct_conn, icem->stun, icem->proto, cp->comp->sock, &cp->rcand->addr, presz, STUN_METHOD_BINDING, (uint8_t *)icem->rpwd, str_len(icem->rpwd), true, stunc_resp_handler, cp, 4, STUN_ATTR_USERNAME, username_buf, STUN_ATTR_PRIORITY, &prio_prflx, ctrl_attr, &icem->tiebrk, STUN_ATTR_USE_CAND, use_cand ? &use_cand : 0); break; default: DEBUG_WARNING("unknown candidate type %d\n", lcand->type); err = EINVAL; break; } return err; }