From f4b8e935698dd153a4e0965a65ddce16310ce01f Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Sun, 17 May 2009 17:57:19 +0000 Subject: [PATCH] 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 --- pjnath/include/pjnath/config.h | 33 +++ pjnath/include/pjnath/errno.h | 7 +- pjnath/include/pjnath/ice_session.h | 89 +++++++- pjnath/include/pjnath/ice_strans.h | 34 ++- pjnath/src/pjnath-test/ice_test.c | 4 +- pjnath/src/pjnath/errno.c | 1 + pjnath/src/pjnath/ice_session.c | 321 +++++++++++++++++++++++----- pjnath/src/pjnath/ice_strans.c | 44 +++- pjsip-apps/src/pjsua/pjsua_app.c | 2 +- pjsip-apps/src/samples/icedemo.c | 38 +++- pjsip/src/pjsua-lib/pjsua_media.c | 3 +- 11 files changed, 498 insertions(+), 78 deletions(-) diff --git a/pjnath/include/pjnath/config.h b/pjnath/include/pjnath/config.h index f95527218..263fea64c 100644 --- a/pjnath/include/pjnath/config.h +++ b/pjnath/include/pjnath/config.h @@ -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 diff --git a/pjnath/include/pjnath/errno.h b/pjnath/include/pjnath/errno.h index e17f94603..2bc47dc12 100644 --- a/pjnath/include/pjnath/errno.h +++ b/pjnath/include/pjnath/errno.h @@ -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 diff --git a/pjnath/include/pjnath/ice_session.h b/pjnath/include/pjnath/ice_session.h index 18f7ffd6d..fd0cc1115 100644 --- a/pjnath/include/pjnath/ice_session.h +++ b/pjnath/include/pjnath/ice_session.h @@ -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 diff --git a/pjnath/include/pjnath/ice_strans.h b/pjnath/include/pjnath/ice_strans.h index 02397073f..b96b5bb7c 100644 --- a/pjnath/include/pjnath/ice_strans.h +++ b/pjnath/include/pjnath/ice_strans.h @@ -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, diff --git a/pjnath/src/pjnath-test/ice_test.c b/pjnath/src/pjnath-test/ice_test.c index ffa1077d9..60a261ad2 100644 --- a/pjnath/src/pjnath-test/ice_test.c +++ b/pjnath/src/pjnath-test/ice_test.c @@ -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; } diff --git a/pjnath/src/pjnath/errno.c b/pjnath/src/pjnath/errno.c index d102a09d3..1656ce33e 100644 --- a/pjnath/src/pjnath/errno.c +++ b/pjnath/src/pjnath/errno.c @@ -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"), diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c index f5ba8e3cf..654d1e357 100644 --- a/pjnath/src/pjnath/ice_session.c +++ b/pjnath/src/pjnath/ice_session.c @@ -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; icomp_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; iclist.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; iclist.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; icomp_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; jvalid_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; icomp_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; icomp_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; icomp_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: " diff --git a/pjnath/src/pjnath/ice_strans.c b/pjnath/src/pjnath/ice_strans.c index b244f0fc0..8d653086f 100644 --- a/pjnath/src/pjnath/ice_strans.c +++ b/pjnath/src/pjnath/ice_strans.c @@ -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; icfg.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. diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index c6b9a2067..d17019d53 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -4728,7 +4728,7 @@ static pj_status_t create_ipv6_media_transports(void) for (j=0; j 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]); diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index 8d9643b3b..2fd58f0c8 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -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) {