📄 ice_session.c
字号:
pj_mutex_unlock(ice->mutex);
return;
}
/* Find local candidate that matches the XOR-MAPPED-ADDRESS */
pj_assert(lcand == NULL);
for (i=0; i<ice->lcand_cnt; ++i) {
if (sockaddr_cmp(&xaddr->sockaddr, &ice->lcand[i].addr) == 0) {
/* Match */
lcand = &ice->lcand[i];
break;
}
}
/* 7.1.2.2.1. Discovering Peer Reflexive Candidates
* If the transport address returned in XOR-MAPPED-ADDRESS does not match
* any of the local candidates that the agent knows about, the mapped
* address represents a new candidate - a peer reflexive candidate.
*/
if (lcand == NULL) {
unsigned cand_id;
pj_str_t foundation;
pj_ice_calc_foundation(ice->pool, &foundation, PJ_ICE_CAND_TYPE_PRFLX,
&check->lcand->base_addr);
/* Still in 7.1.2.2.1. Discovering Peer Reflexive Candidates
* Its priority is set equal to the value of the PRIORITY attribute
* in the Binding Request.
*
* I think the priority calculated by add_cand() should be the same
* as the one calculated in perform_check(), so there's no need to
* get the priority from the PRIORITY attribute.
*/
/* Add new peer reflexive candidate */
status = pj_ice_sess_add_cand(ice, lcand->comp_id,
PJ_ICE_CAND_TYPE_PRFLX,
65535, &foundation,
&xaddr->sockaddr,
&check->lcand->base_addr, NULL,
sizeof(pj_sockaddr_in), &cand_id);
if (status != PJ_SUCCESS) {
check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED,
status);
on_check_complete(ice, check);
pj_mutex_unlock(ice->mutex);
return;
}
/* Update local candidate */
lcand = &ice->lcand[cand_id];
}
/* 7.1.2.2.3. Constructing a Valid Pair
* Next, the agent constructs a candidate pair whose local candidate
* equals the mapped address of the response, and whose remote candidate
* equals the destination address to which the request was sent.
*/
/* Add pair to valid list */
pj_assert(ice->valid_list.count < PJ_ICE_MAX_CHECKS);
new_check = &ice->valid_list.checks[ice->valid_list.count++];
new_check->lcand = lcand;
new_check->rcand = check->rcand;
new_check->prio = CALC_CHECK_PRIO(ice, lcand, check->rcand);
new_check->state = PJ_ICE_SESS_CHECK_STATE_SUCCEEDED;
new_check->nominated = check->nominated;
new_check->err_code = PJ_SUCCESS;
/* Sort valid_list */
sort_checklist(&ice->valid_list);
/* 7.1.2.2.2. Updating Pair States
*
* The agent sets the state of the pair that generated the check to
* Succeeded. The success of this check might also cause the state of
* other checks to change as well.
*/
check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_SUCCEEDED,
PJ_SUCCESS);
/* Perform 7.1.2.2.2. Updating Pair States.
* This may terminate ICE processing.
*/
if (on_check_complete(ice, check)) {
/* ICE complete! */
pj_mutex_unlock(ice->mutex);
return;
}
pj_mutex_unlock(ice->mutex);
}
/* This callback is called by the STUN session associated with a candidate
* when it receives incoming request.
*/
static pj_status_t on_stun_rx_request(pj_stun_session *sess,
const pj_uint8_t *pkt,
unsigned pkt_len,
const pj_stun_msg *msg,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
stun_data *sd;
pj_ice_sess *ice;
pj_stun_priority_attr *prio_attr;
pj_stun_use_candidate_attr *uc_attr;
pj_stun_uint64_attr *role_attr;
pj_stun_tx_data *tdata;
pj_ice_rx_check *rcheck, tmp_rcheck;
pj_status_t status;
PJ_UNUSED_ARG(pkt);
PJ_UNUSED_ARG(pkt_len);
/* Reject any requests except Binding request */
if (msg->hdr.type != PJ_STUN_BINDING_REQUEST) {
status = pj_stun_session_create_res(sess, msg,
PJ_STUN_SC_BAD_REQUEST,
NULL, &tdata);
if (status != PJ_SUCCESS)
return status;
return pj_stun_session_send_msg(sess, PJ_TRUE,
src_addr, src_addr_len, tdata);
}
sd = (stun_data*) pj_stun_session_get_user_data(sess);
ice = sd->ice;
pj_mutex_lock(ice->mutex);
/*
* Note:
* Be aware that when STUN request is received, we might not get
* SDP answer yet, so we might not have remote candidates and
* checklist yet. This case will be handled after we send
* a response.
*/
/* Get PRIORITY attribute */
prio_attr = (pj_stun_priority_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PRIORITY, 0);
if (prio_attr == NULL) {
LOG5((ice->obj_name, "Received Binding request with no PRIORITY"));
pj_mutex_unlock(ice->mutex);
return PJ_SUCCESS;
}
/* Get USE-CANDIDATE attribute */
uc_attr = (pj_stun_use_candidate_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USE_CANDIDATE, 0);
/* Get ICE-CONTROLLING or ICE-CONTROLLED */
role_attr = (pj_stun_uint64_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLING, 0);
if (role_attr == NULL) {
role_attr = (pj_stun_uint64_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLED, 0);
}
/* Handle the case when request comes before answer is received.
* We need to put credential in the response, and since we haven't
* got the response, copy the username from the request.
*/
if (ice->rcand_cnt == 0) {
pj_stun_string_attr *uname_attr;
uname_attr = (pj_stun_string_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0);
pj_assert(uname_attr != NULL);
pj_strdup(ice->pool, &ice->rx_uname, &uname_attr->value);
}
/* 7.2.1.1. Detecting and Repairing Role Conflicts
*/
if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING &&
role_attr && role_attr->hdr.type == PJ_STUN_ATTR_ICE_CONTROLLING)
{
if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) {
/* Switch role to controlled */
LOG4((ice->obj_name,
"Changing role because of ICE-CONTROLLING attribute"));
pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLED);
} else {
/* Generate 487 response */
status = pj_stun_session_create_res(sess, msg,
PJ_STUN_SC_ROLE_CONFLICT,
NULL, &tdata);
if (status == PJ_SUCCESS) {
pj_stun_session_send_msg(sess, PJ_TRUE,
src_addr, src_addr_len, tdata);
}
pj_mutex_unlock(ice->mutex);
return PJ_SUCCESS;
}
} else if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLED &&
role_attr && role_attr->hdr.type == PJ_STUN_ATTR_ICE_CONTROLLED)
{
if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) {
/* Generate 487 response */
status = pj_stun_session_create_res(sess, msg,
PJ_STUN_SC_ROLE_CONFLICT,
NULL, &tdata);
if (status == PJ_SUCCESS) {
pj_stun_session_send_msg(sess, PJ_TRUE,
src_addr, src_addr_len, tdata);
}
pj_mutex_unlock(ice->mutex);
return PJ_SUCCESS;
} else {
/* Switch role to controlled */
LOG4((ice->obj_name,
"Changing role because of ICE-CONTROLLED attribute"));
pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLING);
}
}
/*
* First send response to this request
*/
status = pj_stun_session_create_res(sess, msg, 0, NULL, &tdata);
if (status != PJ_SUCCESS) {
pj_mutex_unlock(ice->mutex);
return status;
}
status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
PJ_STUN_ATTR_XOR_MAPPED_ADDR,
PJ_TRUE, src_addr, src_addr_len);
status = pj_stun_session_send_msg(sess, PJ_TRUE,
src_addr, src_addr_len, tdata);
/*
* Handling early check.
*
* It's possible that we receive this request before we receive SDP
* answer. In this case, we can't perform trigger check since we
* don't have checklist yet, so just save this check in a pending
* triggered check array to be acted upon later.
*/
if (ice->rcand_cnt == 0) {
rcheck = PJ_POOL_ZALLOC_T(ice->pool, pj_ice_rx_check);
} else {
rcheck = &tmp_rcheck;
}
/* Init rcheck */
rcheck->comp_id = sd->comp_id;
rcheck->src_addr_len = src_addr_len;
pj_memcpy(&rcheck->src_addr, src_addr, src_addr_len);
rcheck->use_candidate = (uc_attr != NULL);
rcheck->priority = prio_attr->value;
rcheck->role_attr = role_attr;
if (ice->rcand_cnt == 0) {
/* We don't have answer yet, so keep this request for later */
LOG4((ice->obj_name, "Received an early check for comp %d",
rcheck->comp_id));
pj_list_push_back(&ice->early_check, rcheck);
} else {
/* Handle this check */
handle_incoming_check(ice, rcheck);
}
pj_mutex_unlock(ice->mutex);
return PJ_SUCCESS;
}
/* Handle incoming Binding request and perform triggered check.
* This function may be called by on_stun_rx_request(), or when
* SDP answer is received and we have received early checks.
*/
static void handle_incoming_check(pj_ice_sess *ice,
const pj_ice_rx_check *rcheck)
{
pj_ice_sess_comp *comp;
pj_ice_sess_cand *lcand = NULL;
pj_ice_sess_cand *rcand;
unsigned i;
pj_bool_t is_relayed;
comp = find_comp(ice, rcheck->comp_id);
/* Find remote candidate based on the source transport address of
* the request.
*/
for (i=0; i<ice->rcand_cnt; ++i) {
if (sockaddr_cmp(&rcheck->src_addr, &ice->rcand[i].addr)==0)
break;
}
/* 7.2.1.3. Learning Peer Reflexive Candidates
* If the source transport address of the request does not match any
* existing remote candidates, it represents a new peer reflexive remote
* candidate.
*/
if (i == ice->rcand_cnt) {
rcand = &ice->rcand[ice->rcand_cnt++];
rcand->comp_id = rcheck->comp_id;
rcand->type = PJ_ICE_CAND_TYPE_PRFLX;
rcand->prio = rcheck->priority;
pj_memcpy(&rcand->addr, &rcheck->src_addr, rcheck->src_addr_len);
/* Foundation is random, unique from other foundation */
rcand->foundation.ptr = (char*) pj_pool_alloc(ice->pool, 36);
rcand->foundation.slen = pj_ansi_snprintf(rcand->foundation.ptr, 36,
"f%p",
rcand->foundation.ptr);
LOG4((ice->obj_name,
"Added new remote candidate from the request: %s:%d",
pj_inet_ntoa(rcand->addr.ipv4.sin_addr),
(int)pj_ntohs(rcand->addr.ipv4.sin_port)));
} else {
/* Remote candidate found */
rcand = &ice->rcand[i];
}
#if 0
/* Find again the local candidate by matching the base address
* with the local candidates in the checklist. Checks may have
* been pruned before, so it's possible that if we use the lcand
* as it is, we wouldn't be able to find the check in the checklist
* and we will end up creating a new check unnecessarily.
*/
for (i=0; i<ice->clist.count; ++i) {
pj_ice_sess_check *c = &ice->clist.checks[i];
if (/*c->lcand == lcand ||*/
sockaddr_cmp(&c->lcand->base_addr, &lcand->base_addr)==0)
{
lcand = c->lcand;
break;
}
}
#else
/* Just get candidate with the highest priority for the specified
* component ID in the checklist.
*/
for (i=0; i<ice->clist.count; ++i) {
pj_ice_sess_check *c = &ice->clist.checks[i];
if (c->lcand->comp_id == rcheck->comp_id) {
lcand = c->lcand;
break;
}
}
if (lcand == NULL) {
/* Should not happen, but just in case remote is sending a
* Binding request for a component which it doesn't have.
*/
LOG4((ice->obj_name,
"Received Binding request but no local candidate is found!"));
return;
}
#endif
/*
* Create candidate pair for this request.
*/
/* First check if the source address is the source address of the
* STUN relay, to determine if local candidate is relayed candidate.
*/
PJ_TODO(DETERMINE_IF_REQUEST_COMES_FROM_RELAYED_CANDIDATE);
is_relayed = PJ_FALSE;
/*
* 7.2.1.4. Triggered Checks
*
* Now that we have local and remote candidate, check if we already
* have this pair in our checklist.
*/
for (i=0; i<ice->clist.count; ++i) {
pj_ice_sess_check *c = &ice->clist.checks[i];
if (c->lcand == lcand && c->rcand == rcand)
break;
}
/* If the pair is already on the check list:
* - If the state of that pair is Waiting or Frozen, its state is
* changed to In-Progress and a check for that pair is performed
* immediately. This is called a triggered check.
*
* - If the state of that pair is In-Progress, the agent SHOULD
* generate an immediate retransmit of the Binding Request for the
* check in progress. This is to facilitate rapid completion of
* ICE when both agents are behind NAT.
*
* - If the state of that pair is Failed or Succeeded, no triggered
* check is sent.
*/
if (i != ice->clist.count) {
pj_ice_sess_check *c = &ice->clist.checks[i];
/* If USE-CANDIDATE is present, set nominated flag
* Note: DO NOT overwrite nominated flag if one is already set.
*/
c->nominated = ((rcheck->use_candidate) || c->nominated);
if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN ||
c->state == PJ_ICE_SESS_CHECK_STATE_WAITING)
{
LOG5((ice->obj_name, "Performing triggered check for check %d",i));
perform_check(ice, &ice->clist, i);
} else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) {
/* Should retransmit immediately
*/
LOG5((ice->obj_name, "Triggered check for check %d not performed "
"because it's in progress. Retransmitting", i));
pj_stun_session_retransmit_req(comp->stun_sess, c->tdata);
} else if (c->state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED) {
/* Check complete for this component.
* Note this may end ICE process.
*/
pj_bool_t complete;
unsigned j;
/* If this check is nominated, scan the valid_list for the
* same check and update the nominated flag. A controlled
* agent might have finished the check earlier.
*/
if (rcheck->use_candidate) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -