📄 ice_session.c
字号:
pj_ice_sess_checklist *clist;
unsigned ckid;
};
/* Perform check on the specified candidate pair */
static pj_status_t perform_check(pj_ice_sess *ice,
pj_ice_sess_checklist *clist,
unsigned check_id)
{
pj_ice_sess_comp *comp;
struct req_data *rd;
pj_ice_sess_check *check;
const pj_ice_sess_cand *lcand;
const pj_ice_sess_cand *rcand;
pj_uint32_t prio;
char buffer[128];
pj_status_t status;
check = &clist->checks[check_id];
lcand = check->lcand;
rcand = check->rcand;
comp = find_comp(ice, lcand->comp_id);
LOG5((ice->obj_name,
"Sending connectivity check for check %s",
dump_check(buffer, sizeof(buffer), clist, check)));
/* Create request */
status = pj_stun_session_create_req(comp->stun_sess,
PJ_STUN_BINDING_REQUEST,
NULL, &check->tdata);
if (status != PJ_SUCCESS) {
pjnath_perror(ice->obj_name, "Error creating STUN request", status);
return status;
}
/* Attach data to be retrieved later when STUN request transaction
* completes and on_stun_request_complete() callback is called.
*/
rd = PJ_POOL_ZALLOC_T(check->tdata->pool, struct req_data);
rd->ice = ice;
rd->clist = clist;
rd->ckid = check_id;
check->tdata->user_data = (void*) rd;
/* Add PRIORITY */
prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, 65535,
lcand->comp_id);
pj_stun_msg_add_uint_attr(check->tdata->pool, check->tdata->msg,
PJ_STUN_ATTR_PRIORITY, prio);
/* Add USE-CANDIDATE and set this check to nominated.
* Also add ICE-CONTROLLING or ICE-CONTROLLED
*/
if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING) {
pj_stun_msg_add_empty_attr(check->tdata->pool, check->tdata->msg,
PJ_STUN_ATTR_USE_CANDIDATE);
check->nominated = PJ_TRUE;
pj_stun_msg_add_uint64_attr(check->tdata->pool, check->tdata->msg,
PJ_STUN_ATTR_ICE_CONTROLLING,
&ice->tie_breaker);
} else {
pj_stun_msg_add_uint64_attr(check->tdata->pool, check->tdata->msg,
PJ_STUN_ATTR_ICE_CONTROLLED,
&ice->tie_breaker);
}
/* Note that USERNAME and MESSAGE-INTEGRITY will be added by the
* STUN session.
*/
/* Initiate STUN transaction to send the request */
status = pj_stun_session_send_msg(comp->stun_sess, PJ_FALSE,
&rcand->addr,
sizeof(pj_sockaddr_in), check->tdata);
if (status != PJ_SUCCESS) {
check->tdata = NULL;
pjnath_perror(ice->obj_name, "Error sending STUN request", status);
return status;
}
check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS,
PJ_SUCCESS);
return PJ_SUCCESS;
}
/* Start periodic check for the specified checklist.
* This callback is called by timer on every Ta (20msec by default)
*/
static pj_status_t start_periodic_check(pj_timer_heap_t *th,
pj_timer_entry *te)
{
timer_data *td;
pj_ice_sess *ice;
pj_ice_sess_checklist *clist;
unsigned i, start_count=0;
pj_status_t status;
td = (struct timer_data*) te->user_data;
ice = td->ice;
clist = td->clist;
pj_mutex_lock(ice->mutex);
/* Set timer ID to FALSE first */
te->id = PJ_FALSE;
/* Set checklist state to Running */
clist_set_state(ice, clist, PJ_ICE_SESS_CHECKLIST_ST_RUNNING);
LOG5((ice->obj_name, "Starting checklist periodic check"));
/* Send STUN Binding request for check with highest priority on
* Waiting state.
*/
for (i=0; i<clist->count; ++i) {
pj_ice_sess_check *check = &clist->checks[i];
if (check->state == PJ_ICE_SESS_CHECK_STATE_WAITING) {
status = perform_check(ice, clist, i);
if (status != PJ_SUCCESS) {
pj_mutex_unlock(ice->mutex);
return status;
}
++start_count;
break;
}
}
/* If we don't have anything in Waiting state, perform check to
* highest priority pair that is in Frozen state.
*/
if (start_count==0) {
for (i=0; i<clist->count; ++i) {
pj_ice_sess_check *check = &clist->checks[i];
if (check->state == PJ_ICE_SESS_CHECK_STATE_FROZEN) {
status = perform_check(ice, clist, i);
if (status != PJ_SUCCESS) {
pj_mutex_unlock(ice->mutex);
return status;
}
++start_count;
break;
}
}
}
/* Cannot start check because there's no suitable candidate pair.
*/
if (start_count!=0) {
/* Schedule for next timer */
pj_time_val timeout = {0, PJ_ICE_TA_VAL};
te->id = PJ_TRUE;
pj_time_val_normalize(&timeout);
pj_timer_heap_schedule(th, te, &timeout);
}
pj_mutex_unlock(ice->mutex);
return PJ_SUCCESS;
}
/* Timer callback to perform periodic check */
static void periodic_timer(pj_timer_heap_t *th,
pj_timer_entry *te)
{
start_periodic_check(th, te);
}
/* Utility: find string in string array */
const pj_str_t *find_str(const pj_str_t *strlist[], unsigned count,
const pj_str_t *str)
{
unsigned i;
for (i=0; i<count; ++i) {
if (pj_strcmp(strlist[i], str)==0)
return strlist[i];
}
return NULL;
}
/*
* Start ICE periodic check. This function will return immediately, and
* application will be notified about the connectivity check status in
* #pj_ice_sess_cb callback.
*/
PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice)
{
pj_ice_sess_checklist *clist;
const pj_ice_sess_cand *cand0;
const pj_str_t *flist[PJ_ICE_MAX_CAND];
pj_ice_rx_check *rcheck;
unsigned i, flist_cnt = 0;
PJ_ASSERT_RETURN(ice, PJ_EINVAL);
/* Checklist must have been created */
PJ_ASSERT_RETURN(ice->clist.count > 0, PJ_EINVALIDOP);
LOG4((ice->obj_name, "Starting ICE check.."));
/* The agent examines the check list for the first media stream (a
* media stream is the first media stream when it is described by
* the first m-line in the SDP offer and answer). For that media
* stream, it:
*
* - Groups together all of the pairs with the same foundation,
*
* - For each group, sets the state of the pair with the lowest
* component ID to Waiting. If there is more than one such pair,
* the one with the highest priority is used.
*/
clist = &ice->clist;
/* Pickup the first pair for component 1. */
for (i=0; i<clist->count; ++i) {
if (clist->checks[i].lcand->comp_id == 1)
break;
}
if (i == clist->count) {
pj_assert(!"Unable to find checklist for component 1");
return PJNATH_EICEINCOMPID;
}
/* Set this check to WAITING */
check_set_state(ice, &clist->checks[i],
PJ_ICE_SESS_CHECK_STATE_WAITING, PJ_SUCCESS);
cand0 = clist->checks[i].lcand;
flist[flist_cnt++] = &clist->checks[i].lcand->foundation;
/* Find all of the other pairs in that check list with the same
* component ID, but different foundations, and sets all of their
* states to Waiting as well.
*/
for (++i; i<clist->count; ++i) {
const pj_ice_sess_cand *cand1;
cand1 = clist->checks[i].lcand;
if (cand1->comp_id==cand0->comp_id &&
find_str(flist, flist_cnt, &cand1->foundation)==NULL)
{
check_set_state(ice, &clist->checks[i],
PJ_ICE_SESS_CHECK_STATE_WAITING, PJ_SUCCESS);
flist[flist_cnt++] = &cand1->foundation;
}
}
/* First, perform all pending triggered checks, simultaneously. */
rcheck = ice->early_check.next;
while (rcheck != &ice->early_check) {
LOG4((ice->obj_name,
"Performing delayed triggerred check for component %d",
rcheck->comp_id));
handle_incoming_check(ice, rcheck);
rcheck = rcheck->next;
}
pj_list_init(&ice->early_check);
/* Start periodic check */
return start_periodic_check(ice->stun_cfg.timer_heap, &clist->timer);
}
//////////////////////////////////////////////////////////////////////////////
/* Callback called by STUN session to send the STUN message.
* STUN session also doesn't have a transport, remember?!
*/
static pj_status_t on_stun_send_msg(pj_stun_session *sess,
const void *pkt,
pj_size_t pkt_size,
const pj_sockaddr_t *dst_addr,
unsigned addr_len)
{
stun_data *sd = (stun_data*) pj_stun_session_get_user_data(sess);
pj_ice_sess *ice = sd->ice;
return (*ice->cb.on_tx_pkt)(ice, sd->comp_id,
pkt, pkt_size,
dst_addr, addr_len);
}
/* This callback is called when outgoing STUN request completed */
static void on_stun_request_complete(pj_stun_session *stun_sess,
pj_status_t status,
pj_stun_tx_data *tdata,
const pj_stun_msg *response,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
struct req_data *rd = (struct req_data*) tdata->user_data;
pj_ice_sess *ice;
pj_ice_sess_check *check, *new_check;
pj_ice_sess_cand *lcand;
pj_ice_sess_checklist *clist;
pj_stun_xor_mapped_addr_attr *xaddr;
char buffer[CHECK_NAME_LEN];
unsigned i;
PJ_UNUSED_ARG(stun_sess);
PJ_UNUSED_ARG(src_addr_len);
ice = rd->ice;
check = &rd->clist->checks[rd->ckid];
clist = rd->clist;
/* Mark STUN transaction as complete */
pj_assert(tdata == check->tdata);
check->tdata = NULL;
pj_mutex_lock(ice->mutex);
/* Init lcand to NULL. lcand will be found from the mapped address
* found in the response.
*/
lcand = NULL;
if (status != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
if (status==PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_ROLE_CONFLICT)) {
/* Role conclict response.
*
* 7.1.2.1. Failure Cases:
*
* If the request had contained the ICE-CONTROLLED attribute,
* the agent MUST switch to the controlling role if it has not
* already done so. If the request had contained the
* ICE-CONTROLLING attribute, the agent MUST switch to the
* controlled role if it has not already done so. Once it has
* switched, the agent MUST immediately retry the request with
* the ICE-CONTROLLING or ICE-CONTROLLED attribute reflecting
* its new role.
*/
pj_ice_sess_role new_role = PJ_ICE_SESS_ROLE_UNKNOWN;
pj_stun_msg *req = tdata->msg;
if (pj_stun_msg_find_attr(req, PJ_STUN_ATTR_ICE_CONTROLLING, 0)) {
new_role = PJ_ICE_SESS_ROLE_CONTROLLED;
} else if (pj_stun_msg_find_attr(req, PJ_STUN_ATTR_ICE_CONTROLLED,
0)) {
new_role = PJ_ICE_SESS_ROLE_CONTROLLING;
} else {
pj_assert(!"We should have put CONTROLLING/CONTROLLED attr!");
new_role = PJ_ICE_SESS_ROLE_CONTROLLED;
}
if (new_role != ice->role) {
LOG4((ice->obj_name,
"Changing role because of role conflict response"));
pj_ice_sess_change_role(ice, new_role);
}
/* Resend request */
LOG4((ice->obj_name, "Resending check because of role conflict"));
check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_WAITING, 0);
perform_check(ice, clist, rd->ckid);
pj_mutex_unlock(ice->mutex);
return;
}
pj_strerror(status, errmsg, sizeof(errmsg));
LOG4((ice->obj_name,
"Check %s%s: connectivity check FAILED: %s",
dump_check(buffer, sizeof(buffer), &ice->clist, check),
(check->nominated ? " (nominated)" : " (not nominated)"),
errmsg));
check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status);
on_check_complete(ice, check);
pj_mutex_unlock(ice->mutex);
return;
}
/* 7.1.2.1. Failure Cases
*
* The agent MUST check that the source IP address and port of the
* response equals the destination IP address and port that the Binding
* Request was sent to, and that the destination IP address and port of
* the response match the source IP address and port that the Binding
* Request was sent from.
*/
if (sockaddr_cmp(&check->rcand->addr, (const pj_sockaddr*)src_addr) != 0) {
status = PJNATH_EICEINSRCADDR;
LOG4((ice->obj_name,
"Check %s%s: connectivity check FAILED: source address mismatch",
dump_check(buffer, sizeof(buffer), &ice->clist, check),
(check->nominated ? " (nominated)" : " (not nominated)")));
check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status);
on_check_complete(ice, check);
pj_mutex_unlock(ice->mutex);
return;
}
/* 7.1.2.2. Success Cases
*
* A check is considered to be a success if all of the following are
* true:
*
* o the STUN transaction generated a success response
*
* o the source IP address and port of the response equals the
* destination IP address and port that the Binding Request was sent
* to
*
* o the destination IP address and port of the response match the
* source IP address and port that the Binding Request was sent from
*/
LOG4((ice->obj_name,
"Check %s%s: connectivity check SUCCESS",
dump_check(buffer, sizeof(buffer), &ice->clist, check),
(check->nominated ? " (nominated)" : " (not nominated)")));
/* Get the STUN XOR-MAPPED-ADDRESS attribute. */
xaddr = (pj_stun_xor_mapped_addr_attr*)
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR,0);
if (!xaddr) {
check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED,
PJNATH_ESTUNNOMAPPEDADDR);
on_check_complete(ice, check);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -