Ticket #851: initial code to support regular nomination in ICE:

- Added option to change nomination strategy in ICE strans and session. Default is still aggressive.
 - Added option to control nomination timer
 - Renamed no_host_cand to max_host_cands in ICE config
 - Updated icedemo
 - Also added timer for controlled agent to wait for nomination from controlling agent



git-svn-id: https://svn.pjsip.org/repos/pjproject/branches/projects/sipit24@2705 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
Benny Prijono 2009-05-17 17:57:19 +00:00
parent 2efb80bba6
commit f4b8e93569
11 changed files with 498 additions and 78 deletions

View File

@ -323,6 +323,39 @@
#endif
/**
* For a controlled agent, specify how long it wants to wait (in milliseconds)
* for the controlling agent to complete sending connectivity check with
* nominated flag set to true for all components after the controlled agent
* has found that all connectivity checks in its checklist have been completed
* and there is at least one successful (but not nominated) check for every
* component.
*
* When selecting the value, bear in mind that the connectivity check from
* controlling agent may be delayed because of delay in receiving SDP answer
* from the controlled agent.
*
* Application may set this value to -1 to disable this timer.
*
* Default: 10000 (milliseconds)
*/
#ifndef ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT
# define ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT 10000
#endif
/**
* For controlling agent if it uses regular nomination, specify the delay to
* perform nominated check (connectivity check with USE-CANDIDATE attribute)
* after all components have a valid pair.
*
* Default: 4*PJ_STUN_RTO_VALUE (milliseconds)
*/
#ifndef PJ_ICE_NOMINATED_CHECK_DELAY
# define PJ_ICE_NOMINATED_CHECK_DELAY (4*PJ_STUN_RTO_VALUE)
#endif
/**
* Minimum interval value to be used for sending STUN keep-alive on the ICE
* stream transport, in seconds. This minimum interval, plus a random value

View File

@ -196,7 +196,12 @@
* host candidate.
*/
#define PJNATH_EICENOHOSTCAND (PJNATH_ERRNO_START+92) /* 370092 */
/**
* @hideinitializer
* Controlled agent timed-out in waiting for the controlling agent to
* send nominated check after all connectivity checks have completed.
*/
#define PJNATH_EICENOMTIMEOUT (PJNATH_ERRNO_START+93) /* 370093 */
/************************************************************
* TURN ERROR CODES

View File

@ -176,12 +176,19 @@ typedef struct pj_ice_sess_check pj_ice_sess_check;
typedef struct pj_ice_sess_comp
{
/**
* The pointer to ICE check which was nominated for this component.
* The value will be NULL if a nominated check has not been found
* for this component.
* Pointer to ICE check with highest priority which connectivity check
* has been successful. The value will be NULL if a no successful check
* has not been found for this component.
*/
pj_ice_sess_check *valid_check;
/**
* Pointer to ICE check with highest priority which connectivity check
* has been successful and it has been nominated. The value may be NULL
* if there is no such check yet.
*/
pj_ice_sess_check *nominated_check;
/**
* The STUN session to be used to send and receive STUN messages for this
* component.
@ -552,6 +559,44 @@ typedef struct pj_ice_rx_check
} pj_ice_rx_check;
/**
* This structure describes various ICE session options. Application
* configure the ICE session with these options by calling
* #pj_ice_sess_set_options().
*/
typedef struct pj_ice_sess_options
{
/**
* Specify whether to use aggressive nomination.
*/
pj_bool_t aggressive;
/**
* For controlling agent if it uses regular nomination, specify the delay
* to perform nominated check (connectivity check with USE-CANDIDATE
* attribute) after all components have a valid pair.
*
* Default value is PJ_ICE_NOMINATED_CHECK_DELAY.
*/
unsigned nominated_check_delay;
/**
* For a controlled agent, specify how long it wants to wait (in
* milliseconds) for the controlling agent to complete sending
* connectivity check with nominated flag set to true for all components
* after the controlled agent has found that all connectivity checks in
* its checklist have been completed and there is at least one successful
* (but not nominated) check for every component.
*
* Default value for this option is
* ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT. Specify -1 to disable
* this timer.
*/
int controlled_agent_want_nom_timeout;
} pj_ice_sess_options;
/**
* This structure describes the ICE session. For this version of PJNATH,
* an ICE session corresponds to a single media stream (unlike the ICE
@ -569,11 +614,13 @@ struct pj_ice_sess
void *user_data; /**< App. data. */
pj_mutex_t *mutex; /**< Mutex. */
pj_ice_sess_role role; /**< ICE role. */
pj_ice_sess_options opt; /**< Options */
pj_timestamp tie_breaker; /**< Tie breaker value */
pj_uint8_t *prefs; /**< Type preference. */
pj_bool_t is_nominating; /**< Nominating stage */
pj_bool_t is_complete; /**< Complete? */
pj_status_t ice_status; /**< Error status. */
pj_timer_entry completion_timer; /**< To call callback. */
pj_timer_entry timer; /**< ICE timer. */
pj_ice_sess_cb cb; /**< Callback. */
pj_stun_config stun_cfg; /**< STUN settings. */
@ -654,6 +701,12 @@ PJ_DECL(void) pj_ice_calc_foundation(pj_pool_t *pool,
pj_ice_cand_type type,
const pj_sockaddr *base_addr);
/**
* Initialize ICE session options with library default values.
*
* @param opt ICE session options.
*/
PJ_DECL(void) pj_ice_sess_options_default(pj_ice_sess_options *opt);
/**
* Create ICE session with the specified role and number of components.
@ -688,6 +741,34 @@ PJ_DECL(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg,
const pj_str_t *local_passwd,
pj_ice_sess **p_ice);
/**
* Get the value of various options of the ICE session.
*
* @param ice The ICE session.
* @param opt The options to be initialized with the values
* from the ICE session.
*
* @return PJ_SUCCESS on success, or the appropriate error.
*/
PJ_DECL(pj_status_t) pj_ice_sess_get_options(pj_ice_sess *ice,
pj_ice_sess_options *opt);
/**
* Specify various options for this ICE session. Application MUST only
* call this function after the ICE session has been created but before
* any connectivity check is started.
*
* Application should call #pj_ice_sess_get_options() to initialize the
* options with their default values.
*
* @param ice The ICE session.
* @param opt Options to be applied to the ICE session.
*
* @return PJ_SUCCESS on success, or the appropriate error.
*/
PJ_DECL(pj_status_t) pj_ice_sess_set_options(pj_ice_sess *ice,
const pj_ice_sess_options *opt);
/**
* Destroy ICE session. This will cancel any connectivity checks currently
* running, if any, and any other events scheduled by this session, as well

View File

@ -209,12 +209,12 @@ typedef struct pj_ice_strans_cfg
pj_stun_sock_cfg cfg;
/**
* Disable host candidates. When this option is set, no
* host candidates will be added.
* Maximum number of host candidates to be added. If the
* value is zero, no host candidates will be added.
*
* Default: PJ_FALSE
* Default: 64
*/
pj_bool_t no_host_cands;
unsigned max_host_cands;
/**
* Include loopback addresses in the host candidates.
@ -385,6 +385,32 @@ PJ_DECL(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st);
PJ_DECL(void*) pj_ice_strans_get_user_data(pj_ice_strans *ice_st);
/**
* Get the value of various options of the ICE stream transport.
*
* @param ice_st The ICE stream transport.
* @param opt The options to be initialized with the values
* from the ICE stream transport.
*
* @return PJ_SUCCESS on success, or the appropriate error.
*/
PJ_DECL(pj_status_t) pj_ice_strans_get_options(pj_ice_strans *ice_st,
pj_ice_sess_options *opt);
/**
* Specify various options for this ICE stream transport. Application
* should call #pj_ice_strans_get_options() to initialize the options
* with their default values.
*
* @param ice_st The ICE stream transport.
* @param opt Options to be applied to this ICE stream transport.
*
* @return PJ_SUCCESS on success, or the appropriate error.
*/
PJ_DECL(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st,
const pj_ice_sess_options *opt);
/**
* Initialize the ICE session in the ICE stream transport.
* When application is about to send an offer containing ICE capability,

View File

@ -141,9 +141,9 @@ static int create_ice_strans(struct test_sess *test_sess,
}
if (ept->cfg.enable_host == 0) {
ice_cfg.stun.no_host_cands = PJ_TRUE;
ice_cfg.stun.max_host_cands = 0;
} else {
ice_cfg.stun.no_host_cands = PJ_FALSE;
//ice_cfg.stun.no_host_cands = PJ_FALSE;
ice_cfg.stun.loop_addr = PJ_TRUE;
}

View File

@ -67,6 +67,7 @@ static const struct
PJ_BUILD_ERR( PJNATH_EICEMISSINGSDP, "Missing ICE SDP attribute"),
PJ_BUILD_ERR( PJNATH_EICEINCANDSDP, "Invalid SDP \"candidate\" attribute"),
PJ_BUILD_ERR( PJNATH_EICENOHOSTCAND, "No host candidate associated with srflx"),
PJ_BUILD_ERR( PJNATH_EICENOMTIMEOUT, "Controlled agent timed out waiting for nomination"),
/* TURN related errors */
PJ_BUILD_ERR( PJNATH_ETURNINTP, "Invalid/unsupported transport"),

View File

@ -66,6 +66,20 @@ static const char *role_names[] =
"Controlling"
};
typedef enum timer_type
{
TIMER_NONE, /**< Timer not active */
TIMER_COMPLETION_CALLBACK, /**< Call on_ice_complete() callback */
TIMER_CONTROLLED_WAIT_NOM, /**< Controlled agent is waiting for
controlling agent to send connectivity
check with nominated flag after it has
valid check for every components. */
TIMER_START_NOMINATED_CHECK,/**< Controlling agent start connectivity
checks with USE-CANDIDATE flag. */
};
/* Candidate type preference */
static pj_uint8_t cand_type_prefs[4] =
{
@ -118,10 +132,13 @@ typedef struct timer_data
/* Forward declarations */
static void on_timer(pj_timer_heap_t *th, pj_timer_entry *te);
static void on_ice_complete(pj_ice_sess *ice, pj_status_t status);
static void destroy_ice(pj_ice_sess *ice,
pj_status_t reason);
static pj_status_t start_periodic_check(pj_timer_heap_t *th,
pj_timer_entry *te);
static void start_nominated_check(pj_ice_sess *ice);
static void periodic_timer(pj_timer_heap_t *th,
pj_timer_entry *te);
static void handle_incoming_check(pj_ice_sess *ice,
@ -296,6 +313,15 @@ static pj_status_t init_comp(pj_ice_sess *ice,
}
/* Init options with default values */
PJ_DEF(void) pj_ice_sess_options_default(pj_ice_sess_options *opt)
{
opt->aggressive = PJ_TRUE;
opt->nominated_check_delay = PJ_ICE_NOMINATED_CHECK_DELAY;
opt->controlled_agent_want_nom_timeout =
ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT;
}
/*
* Create ICE session.
*/
@ -326,6 +352,9 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg,
ice->tie_breaker.u32.hi = pj_rand();
ice->tie_breaker.u32.lo = pj_rand();
ice->prefs = cand_type_prefs;
pj_ice_sess_options_default(&ice->opt);
pj_timer_entry_init(&ice->timer, TIMER_NONE, (void*)ice, &on_timer);
pj_ansi_snprintf(ice->obj_name, sizeof(ice->obj_name),
name, ice);
@ -345,6 +374,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg,
pj_ice_sess_comp *comp;
comp = &ice->comp[i];
comp->valid_check = NULL;
comp->nominated_check = NULL;
status = init_comp(ice, i+1, comp);
if (status != PJ_SUCCESS) {
@ -388,6 +418,29 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg,
}
/*
* Get the value of various options of the ICE session.
*/
PJ_DEF(pj_status_t) pj_ice_sess_get_options(pj_ice_sess *ice,
pj_ice_sess_options *opt)
{
PJ_ASSERT_RETURN(ice, PJ_EINVAL);
pj_memcpy(opt, &ice->opt, sizeof(*opt));
return PJ_SUCCESS;
}
/*
* Specify various options for this ICE session.
*/
PJ_DEF(pj_status_t) pj_ice_sess_set_options(pj_ice_sess *ice,
const pj_ice_sess_options *opt)
{
PJ_ASSERT_RETURN(ice && opt, PJ_EINVAL);
pj_memcpy(&ice->opt, opt, sizeof(*opt));
return PJ_SUCCESS;
}
/*
* Destroy
*/
@ -406,10 +459,10 @@ static void destroy_ice(pj_ice_sess *ice,
pj_mutex_unlock(ice->mutex);
}
if (ice->completion_timer.id) {
if (ice->timer.id) {
pj_timer_heap_cancel(ice->stun_cfg.timer_heap,
&ice->completion_timer);
ice->completion_timer.id = PJ_FALSE;
&ice->timer);
ice->timer.id = PJ_FALSE;
}
for (i=0; i<ice->comp_cnt; ++i) {
@ -1039,18 +1092,31 @@ static pj_status_t prune_checklist(pj_ice_sess *ice,
return PJ_SUCCESS;
}
/* Timer callback to call on_ice_complete() callback */
static void on_completion_timer(pj_timer_heap_t *th,
pj_timer_entry *te)
/* Timer callback */
static void on_timer(pj_timer_heap_t *th, pj_timer_entry *te)
{
pj_ice_sess *ice = (pj_ice_sess*) te->user_data;
enum timer_time type = (enum timer_type)te->id;
PJ_UNUSED_ARG(th);
te->id = PJ_FALSE;
te->id = TIMER_NONE;
if (ice->cb.on_ice_complete)
(*ice->cb.on_ice_complete)(ice, ice->ice_status);
switch (type) {
case TIMER_CONTROLLED_WAIT_NOM:
LOG4((ice->obj_name,
"Controlled agent timed-out in waiting for the controlling "
"agent to send nominated check. Setting state to fail now.."));
on_ice_complete(ice, PJNATH_EICENOMTIMEOUT);
break;
case TIMER_COMPLETION_CALLBACK:
if (ice->cb.on_ice_complete)
(*ice->cb.on_ice_complete)(ice, ice->ice_status);
break;
case TIMER_START_NOMINATED_CHECK:
start_nominated_check(ice);
break;
}
}
/* This function is called when ICE processing completes */
@ -1060,6 +1126,11 @@ static void on_ice_complete(pj_ice_sess *ice, pj_status_t status)
ice->is_complete = PJ_TRUE;
ice->ice_status = status;
if (ice->timer.id != TIMER_NONE) {
pj_timer_heap_cancel(ice->stun_cfg.timer_heap, &ice->timer);
ice->timer.id = TIMER_NONE;
}
/* Log message */
LOG4((ice->obj_name, "ICE process complete, status=%s",
pj_strerror(status, ice->tmp.errmsg,
@ -1071,13 +1142,9 @@ static void on_ice_complete(pj_ice_sess *ice, pj_status_t status)
if (ice->cb.on_ice_complete) {
pj_time_val delay = {0, 0};
ice->completion_timer.cb = &on_completion_timer;
ice->completion_timer.user_data = (void*) ice;
ice->completion_timer.id = PJ_TRUE;
ice->timer.id = TIMER_COMPLETION_CALLBACK;
pj_timer_heap_schedule(ice->stun_cfg.timer_heap,
&ice->completion_timer,
&delay);
&ice->timer, &delay);
}
}
}
@ -1087,10 +1154,13 @@ static void on_ice_complete(pj_ice_sess *ice, pj_status_t status)
static pj_bool_t on_check_complete(pj_ice_sess *ice,
pj_ice_sess_check *check)
{
pj_ice_sess_comp *comp;
unsigned i;
pj_assert(check->state >= PJ_ICE_SESS_CHECK_STATE_SUCCEEDED);
comp = find_comp(ice, check->lcand->comp_id);
/* 7.1.2.2.2. Updating Pair States
*
* The agent sets the state of the pair that generated the check to
@ -1104,6 +1174,7 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice,
* always.
*/
if (check->err_code==PJ_SUCCESS) {
for (i=0; i<ice->clist.count; ++i) {
pj_ice_sess_check *c = &ice->clist.checks[i];
if (pj_strcmp(&c->lcand->foundation, &check->lcand->foundation)==0
@ -1112,6 +1183,19 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice,
check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, 0);
}
}
/* Update valid check */
if (comp->valid_check == NULL) {
comp->valid_check = check;
} else {
if (CMP_CHECK_PRIO(comp->valid_check, check) < 0)
comp->valid_check = check;
}
LOG5((ice->obj_name, "Check %d is successful%s",
GET_CHECK_ID(&ice->clist, check),
(check->nominated ? " and nominated" : "")));
}
/* 8.2. Updating States
@ -1136,12 +1220,6 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice,
* than the lowest priority nominated pair for that component
*/
if (check->err_code==PJ_SUCCESS && check->nominated) {
pj_ice_sess_comp *comp;
LOG5((ice->obj_name, "Check %d is successful and nominated",
GET_CHECK_ID(&ice->clist, check)));
comp = find_comp(ice, check->lcand->comp_id);
for (i=0; i<ice->clist.count; ++i) {
@ -1181,11 +1259,11 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice,
}
/* Update the nominated check for the component */
if (comp->valid_check == NULL) {
comp->valid_check = check;
if (comp->nominated_check == NULL) {
comp->nominated_check = check;
} else {
if (CMP_CHECK_PRIO(comp->valid_check, check) < 0)
comp->valid_check = check;
if (CMP_CHECK_PRIO(comp->nominated_check, check) < 0)
comp->nominated_check = check;
}
}
@ -1211,7 +1289,7 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice,
* ICE processing as success, otherwise wait.
*/
for (i=0; i<ice->comp_cnt; ++i) {
if (ice->comp[i].valid_check == NULL)
if (ice->comp[i].nominated_check == NULL)
break;
}
if (i == ice->comp_cnt) {
@ -1258,23 +1336,16 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice,
/* All checks have completed, but we don't have nominated pair.
* If agent's role is controlled, check if all components have
* valid pair. If it does, this means the controlled agent has
* finished the check list early and it's waiting for controlling
* agent to send a check with USE-CANDIDATE flag set.
* finished the check list and it's waiting for controlling
* agent to send checks with USE-CANDIDATE flag set.
*/
if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLED) {
unsigned comp_id;
for (comp_id=1; comp_id <= ice->comp_cnt; ++comp_id) {
unsigned j;
for (j=0; j<ice->valid_list.count; ++j) {
pj_ice_sess_check *vc = &ice->valid_list.checks[j];
if (vc->lcand->comp_id == comp_id)
break;
}
if (j == ice->valid_list.count)
for (i=0; i < ice->comp_cnt; ++i) {
if (ice->comp[i].valid_check == NULL)
break;
}
if (comp_id <= ice->comp_cnt) {
if (i < ice->comp_cnt) {
/* This component ID doesn't have valid pair.
* Mark ICE as failed.
*/
@ -1284,12 +1355,104 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice,
/* All components have a valid pair.
* We should wait until we receive nominated checks.
*/
if (ice->timer.id == TIMER_NONE &&
ice->opt.controlled_agent_want_nom_timeout >= 0)
{
pj_time_val delay;
delay.sec = 0;
delay.msec = ice->opt.controlled_agent_want_nom_timeout;
pj_time_val_normalize(&delay);
ice->timer.id = TIMER_CONTROLLED_WAIT_NOM;
pj_timer_heap_schedule(ice->stun_cfg.timer_heap,
&ice->timer,
&delay);
LOG5((ice->obj_name,
"All checks have completed. Controlled agent now "
"waits for nomination from controlling agent "
"(timeout=%d msec)",
ice->opt.controlled_agent_want_nom_timeout));
}
return PJ_FALSE;
}
/* Unreached */
} else if (ice->is_nominating) {
/* We are controlling agent and all checks have completed but
* there's at least one component without nominated pair (or
* more likely we don't have any nominated pairs at all).
*/
on_ice_complete(ice, PJNATH_EICEFAILED);
return PJ_TRUE;
} else {
/* We are controlling agent and all checks have completed. If
* we have valid list for every component, then move on to
* sending nominated check, otherwise we have failed.
*/
for (i=0; i<ice->comp_cnt; ++i) {
if (ice->comp[i].valid_check == NULL)
break;
}
if (i < ice->comp_cnt) {
/* At least one component doesn't have a valid check. Mark
* ICE as failed.
*/
on_ice_complete(ice, PJNATH_EICEFAILED);
return PJ_TRUE;
}
/* Now it's time to send connectivity check with nomination
* flag set.
*/
LOG5((ice->obj_name,
"All checks have completed, starting nominated checks now"));
start_nominated_check(ice);
return PJ_FALSE;
}
}
/* If this connectivity check has been successful, scan all components
* and see if they have a valid pair, if we are controlling and we haven't
* started our nominated check yet.
*/
if (check->err_code == PJ_SUCCESS &&
ice->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
!ice->is_nominating &&
ice->timer.id == TIMER_NONE)
{
pj_time_val delay;
for (i=0; i<ice->comp_cnt; ++i) {
if (ice->comp[i].valid_check == NULL)
break;
}
on_ice_complete(ice, PJNATH_EICEFAILED);
return PJ_TRUE;
if (i < ice->comp_cnt) {
/* Some components still don't have valid pair, continue
* processing.
*/
return PJ_FALSE;
}
LOG5((ice->obj_name,
"Scheduling nominated check in %d ms",
ice->opt.nominated_check_delay));
/* All components have valid pair. Let connectivity checks run for
* a little bit more time, then start our nominated check.
*/
delay.sec = 0;
delay.msec = ice->opt.nominated_check_delay;
pj_time_val_normalize(&delay);
ice->timer.id = TIMER_START_NOMINATED_CHECK;
pj_timer_heap_schedule(ice->stun_cfg.timer_heap, &ice->timer, &delay);
return PJ_FALSE;
}
/* We still have checks to perform */
@ -1297,7 +1460,6 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice,
}
/* Create checklist by pairing local candidates with remote candidates */
PJ_DEF(pj_status_t) pj_ice_sess_create_check_list(
pj_ice_sess *ice,
@ -1430,10 +1592,11 @@ PJ_DEF(pj_status_t) pj_ice_sess_create_check_list(
return PJ_SUCCESS;
}
/* Perform check on the specified candidate pair */
/* 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)
unsigned check_id,
pj_bool_t nominate)
{
pj_ice_sess_comp *comp;
pj_ice_msg_data *msg_data;
@ -1481,9 +1644,11 @@ static pj_status_t perform_check(pj_ice_sess *ice,
* 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;
if (nominate) {
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,
@ -1549,7 +1714,7 @@ static pj_status_t start_periodic_check(pj_timer_heap_t *th,
pj_ice_sess_check *check = &clist->checks[i];
if (check->state == PJ_ICE_SESS_CHECK_STATE_WAITING) {
status = perform_check(ice, clist, i);
status = perform_check(ice, clist, i, ice->is_nominating);
if (status != PJ_SUCCESS) {
pj_mutex_unlock(ice->mutex);
return status;
@ -1568,7 +1733,7 @@ static pj_status_t start_periodic_check(pj_timer_heap_t *th,
pj_ice_sess_check *check = &clist->checks[i];
if (check->state == PJ_ICE_SESS_CHECK_STATE_FROZEN) {
status = perform_check(ice, clist, i);
status = perform_check(ice, clist, i, ice->is_nominating);
if (status != PJ_SUCCESS) {
pj_mutex_unlock(ice->mutex);
return status;
@ -1596,6 +1761,49 @@ static pj_status_t start_periodic_check(pj_timer_heap_t *th,
}
/* Start sending connectivity check with USE-CANDIDATE */
static void start_nominated_check(pj_ice_sess *ice)
{
pj_time_val delay;
unsigned i;
pj_status_t status;
LOG4((ice->obj_name, "Starting nominated check.."));
pj_assert(ice->is_nominating == PJ_FALSE);
/* Stop our timer if it's active */
if (ice->timer.id == TIMER_START_NOMINATED_CHECK) {
pj_timer_heap_cancel(ice->stun_cfg.timer_heap, &ice->timer);
ice->timer.id = TIMER_NONE;
}
/* For each component, set the check state of valid check with
* highest priority to Waiting (it should have Success state now).
*/
for (i=0; i<ice->comp_cnt; ++i) {
pj_assert(ice->comp[i].nominated_check == NULL);
pj_assert(ice->comp[i].valid_check->err_code == PJ_SUCCESS);
ice->comp[i].valid_check->state = PJ_ICE_SESS_CHECK_STATE_FROZEN;
check_set_state(ice, ice->comp[i].valid_check,
PJ_ICE_SESS_CHECK_STATE_WAITING, PJ_SUCCESS);
}
/* And (re)start the periodic check */
if (!ice->clist.timer.id) {
ice->clist.timer.id = PJ_TRUE;
delay.sec = delay.msec = 0;
status = pj_timer_heap_schedule(ice->stun_cfg.timer_heap,
&ice->clist.timer, &delay);
if (status != PJ_SUCCESS) {
ice->clist.timer.id = PJ_FALSE;
}
}
ice->is_nominating = PJ_TRUE;
}
/* Timer callback to perform periodic check */
static void periodic_timer(pj_timer_heap_t *th,
pj_timer_entry *te)
@ -1642,6 +1850,10 @@ PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice)
LOG4((ice->obj_name, "Starting ICE check.."));
/* If we are using aggressive nomination, set the is_nominating state */
if (ice->opt.aggressive)
ice->is_nominating = PJ_TRUE;
/* 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
@ -1826,7 +2038,8 @@ static void on_stun_request_complete(pj_stun_session *stun_sess,
/* 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, msg_data->data.req.ckid);
perform_check(ice, clist, msg_data->data.req.ckid,
check->nominated);
pj_mutex_unlock(ice->mutex);
return;
}
@ -2312,8 +2525,11 @@ static void handle_incoming_check(pj_ice_sess *ice,
if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN ||
c->state == PJ_ICE_SESS_CHECK_STATE_WAITING)
{
/* See if we shall nominate this check */
pj_bool_t nominate = (c->nominated || ice->is_nominating);
LOG5((ice->obj_name, "Performing triggered check for check %d",i));
perform_check(ice, &ice->clist, i);
perform_check(ice, &ice->clist, i, nominate);
} else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) {
/* Should retransmit immediately
@ -2361,6 +2577,7 @@ static void handle_incoming_check(pj_ice_sess *ice,
else if (ice->clist.count < PJ_ICE_MAX_CHECKS) {
pj_ice_sess_check *c = &ice->clist.checks[ice->clist.count];
pj_bool_t nominate;
c->lcand = lcand;
c->rcand = rcand;
@ -2369,9 +2586,11 @@ static void handle_incoming_check(pj_ice_sess *ice,
c->nominated = rcheck->use_candidate;
c->err_code = PJ_SUCCESS;
nominate = (c->nominated || ice->is_nominating);
LOG4((ice->obj_name, "New triggered check added: %d",
ice->clist.count));
perform_check(ice, &ice->clist, ice->clist.count++);
perform_check(ice, &ice->clist, ice->clist.count++, nominate);
} else {
LOG4((ice->obj_name, "Error: unable to perform triggered check: "

View File

@ -158,6 +158,7 @@ struct pj_ice_strans
pj_ice_strans_cfg cfg; /**< Configuration. */
pj_ice_strans_cb cb; /**< Application callback. */
pj_lock_t *init_lock; /**< Initialization mutex. */
pj_ice_sess_options opt; /**< ICE session options */
pj_ice_sess *ice; /**< ICE session. */
pj_time_val start_time;/**< Time when ICE was started */
@ -200,6 +201,8 @@ PJ_DEF(void) pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg)
cfg->af = pj_AF_INET();
cfg->stun.port = PJ_STUN_PORT;
cfg->turn.conn_type = PJ_TURN_TP_UDP;
cfg->stun.max_host_cands = 64;
}
@ -245,7 +248,7 @@ static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id)
comp->default_cand = 0;
/* Create STUN transport if configured */
if (ice_st->cfg.stun.server.slen || !ice_st->cfg.stun.no_host_cands) {
if (ice_st->cfg.stun.server.slen || ice_st->cfg.stun.max_host_cands) {
pj_stun_sock_cb stun_sock_cb;
pj_ice_sess_cand *cand;
@ -309,10 +312,10 @@ static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id)
}
/* Add local addresses to host candidates, unless no_host_cands
* flag is set.
/* Add local addresses to host candidates, unless max_host_cands
* is set to zero.
*/
if (ice_st->cfg.stun.no_host_cands == PJ_FALSE) {
if (ice_st->cfg.stun.max_host_cands) {
pj_stun_sock_info stun_sock_info;
unsigned i;
@ -321,7 +324,9 @@ static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id)
if (status != PJ_SUCCESS)
return status;
for (i=0; i<stun_sock_info.alias_cnt; ++i) {
for (i=0; i<stun_sock_info.alias_cnt &&
i<ice_st->cfg.stun.max_host_cands; ++i)
{
char addrinfo[PJ_INET6_ADDRSTRLEN+10];
const pj_sockaddr *addr = &stun_sock_info.aliases[i];
@ -444,6 +449,8 @@ PJ_DEF(pj_status_t) pj_ice_strans_create( const char *name,
ice_st->obj_name = pool->obj_name;
ice_st->user_data = user_data;
pj_ice_sess_options_default(&ice_st->opt);
PJ_LOG(4,(ice_st->obj_name,
"Creating ICE stream transport with %d component(s)",
comp_cnt));
@ -646,6 +653,30 @@ PJ_DEF(void*) pj_ice_strans_get_user_data(pj_ice_strans *ice_st)
}
/*
* Get the value of various options of the ICE stream transport.
*/
PJ_DEF(pj_status_t) pj_ice_strans_get_options( pj_ice_strans *ice_st,
pj_ice_sess_options *opt)
{
PJ_ASSERT_RETURN(ice_st && opt, PJ_EINVAL);
pj_memcpy(opt, &ice_st->opt, sizeof(*opt));
return PJ_SUCCESS;
}
/*
* Specify various options for this ICE stream transport.
*/
PJ_DEF(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st,
const pj_ice_sess_options *opt)
{
PJ_ASSERT_RETURN(ice_st && opt, PJ_EINVAL);
pj_memcpy(&ice_st->opt, opt, sizeof(*opt));
if (ice_st->ice)
pj_ice_sess_set_options(ice_st->ice, &ice_st->opt);
return PJ_SUCCESS;
}
/*
* Create ICE!
*/
@ -682,6 +713,9 @@ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st,
/* Associate user data */
ice_st->ice->user_data = (void*)ice_st;
/* Set options */
pj_ice_sess_set_options(ice_st->ice, &ice_st->opt);
/* If default candidate for components are SRFLX one, upload a custom
* type priority to ICE session so that SRFLX candidates will get
* checked first.

View File

@ -4728,7 +4728,7 @@ static pj_status_t create_ipv6_media_transports(void)
for (j=0; j<MAX_RETRY; ++j) {
status = pjmedia_transport_udp_create3(pjsua_get_pjmedia_endpt(),
pj_AF_INET6(),
NULL,
app_config.rtp_cfg.public_addr.ptr,
&app_config.rtp_cfg.bound_addr,
port,
0, &tp[i].transport);

View File

@ -39,7 +39,8 @@ static struct app_t
{
unsigned comp_cnt;
pj_str_t ns;
pj_bool_t no_host;
int max_host;
pj_bool_t regular;
pj_str_t stun_srv;
pj_str_t turn_srv;
pj_bool_t turn_tcp;
@ -306,8 +307,9 @@ static pj_status_t icedemo_init(void)
/* -= Start initializing ICE stream transport config =- */
/* Disable host candidates? */
icedemo.ice_cfg.stun.no_host_cands = icedemo.opt.no_host;
/* Maximum number of host candidates */
if (icedemo.opt.max_host != -1)
icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host;
/* Configure STUN/srflx candidate resolution */
if (icedemo.opt.stun_srv.slen) {
@ -374,6 +376,7 @@ static pj_status_t icedemo_init(void)
static void icedemo_create_instance(void)
{
pj_ice_strans_cb icecb;
pj_ice_sess_options opt;
pj_status_t status;
if (icedemo.icest != NULL) {
@ -398,6 +401,17 @@ static void icedemo_create_instance(void)
icedemo_perror("error creating ice", status);
else
PJ_LOG(3,(THIS_FILE, "ICE instance successfully created"));
status = pj_ice_strans_get_options(icedemo.icest, &opt);
pj_assert(status == PJ_SUCCESS);
if (icedemo.opt.regular)
opt.aggressive = PJ_FALSE;
else
opt.aggressive = PJ_TRUE;
status = pj_ice_strans_set_options(icedemo.icest, &opt);
pj_assert(status == PJ_SUCCESS);
}
/* Utility to nullify parsed remote info */
@ -960,7 +974,7 @@ static void icedemo_send_data(unsigned comp_id, const char *data)
}
*/
if (comp_id > pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
return;
}
@ -1137,7 +1151,8 @@ static void icedemo_usage()
puts(" --comp-cnt, -c N Component count (default=1)");
puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV");
puts(" resolution");
puts(" --no-host, -H Disable host candidates");
puts(" --max-host, -H N Set max number of host candidates to N");
puts(" --regular, -R Use regular nomination (default aggressive)");
puts(" --help, -h Display this screen.");
puts("");
puts("STUN related options:");
@ -1165,21 +1180,23 @@ int main(int argc, char *argv[])
struct pj_getopt_option long_options[] = {
{ "comp-cnt", 1, 0, 'c'},
{ "nameserver", 1, 0, 'n'},
{ "no-host", 0, 0, 'H'},
{ "max-host", 1, 0, 'H'},
{ "help", 0, 0, 'h'},
{ "stun-srv", 1, 0, 's'},
{ "turn-srv", 1, 0, 't'},
{ "turn-tcp", 0, 0, 'T'},
{ "turn-username", 1, 0, 'u'},
{ "turn-password", 1, 0, 'p'},
{ "turn-fingerprint", 0, 0, 'F'}
{ "turn-fingerprint", 0, 0, 'F'},
{ "regular", 0, 0, 'R'}
};
int c, opt_id;
pj_status_t status;
icedemo.opt.comp_cnt = 1;
icedemo.opt.max_host = -1;
while((c=pj_getopt_long(argc,argv, "n:s:t:u:p:HhTF", long_options, &opt_id))!=-1) {
while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:hTFR", long_options, &opt_id))!=-1) {
switch (c) {
case 'c':
icedemo.opt.comp_cnt = atoi(pj_optarg);
@ -1192,7 +1209,7 @@ int main(int argc, char *argv[])
icedemo.opt.ns = pj_str(pj_optarg);
break;
case 'H':
icedemo.opt.no_host = PJ_TRUE;
icedemo.opt.max_host = atoi(pj_optarg);
break;
case 'h':
icedemo_usage();
@ -1215,6 +1232,9 @@ int main(int argc, char *argv[])
case 'F':
icedemo.opt.turn_fingerprint = PJ_TRUE;
break;
case 'R':
icedemo.opt.regular = PJ_TRUE;
break;
default:
printf("Argument \"%s\" is not valid. Use -h to see help",
argv[pj_optind]);

View File

@ -810,7 +810,8 @@ static pj_status_t create_ice_media_transports(void)
ice_cfg.stun.server = pj_str(stunip);
ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
}
ice_cfg.stun.no_host_cands = pjsua_var.media_cfg.ice_no_host_cands;
if (pjsua_var.media_cfg.ice_no_host_cands)
ice_cfg.stun.max_host_cands = 0;
/* Configure TURN settings */
if (pjsua_var.media_cfg.enable_turn) {