From 08640cc9411ca092e6456304bcce41f81b3bd3ce Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Fri, 29 May 2009 13:04:03 +0000 Subject: Integration of Sipit24 branch, many tickets involved: - #793: AMR encoder should regard 'mode-set' param specified by remote decoder. - #831: Automatically switch to TCP transport when sending large request - #832: Support for outbound proxy setting without using Route header - #849: Modify conference audio switch behavior in connecting ports. - #850: Remove 'Require=replaces' param in 'Refer-To' header (in call transfer with replaces). - #851: Support for regular nomination in ICE - #852: --ip-addr support for IPv6 for media transport in pjsua - #854: Adding SOFTWARE attribute in all outgoing requests may cause compatibility problem with older STUN server (thanks Alexei Kuznetsov for the report) - #855: Bug in digit map frequencies for DTMF digits (thanks FCCH for the report) - #856: Put back the ICE candidate priority values according to the default values in the draft-mmusic-ice - #857: Support for ICE keep-alive with Binding indication - #858: Do not authenticate STUN 438 response - #859: AMR-WB format param in the SDP is not negotiated correctly. - #867: Return error instead of asserting when PJSUA-LIB fails to open log file git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2724 74dad513-b988-da41-8d7b-12977e46ad98 --- pjnath/include/pjnath/config.h | 75 ++++- pjnath/include/pjnath/errno.h | 7 +- pjnath/include/pjnath/ice_session.h | 90 +++++- pjnath/include/pjnath/ice_strans.h | 41 ++- pjnath/include/pjnath/stun_msg.h | 4 + pjnath/include/pjnath/stun_session.h | 12 + pjnath/src/pjnath-test/ice_test.c | 6 +- pjnath/src/pjnath/errno.c | 1 + pjnath/src/pjnath/ice_session.c | 512 ++++++++++++++++++++++++++++++----- pjnath/src/pjnath/ice_strans.c | 65 ++++- pjnath/src/pjnath/stun_auth.c | 1 + pjnath/src/pjnath/stun_msg_dump.c | 10 +- pjnath/src/pjnath/stun_session.c | 23 +- 13 files changed, 742 insertions(+), 105 deletions(-) (limited to 'pjnath') diff --git a/pjnath/include/pjnath/config.h b/pjnath/include/pjnath/config.h index f9552721..f265e2c7 100644 --- a/pjnath/include/pjnath/config.h +++ b/pjnath/include/pjnath/config.h @@ -259,12 +259,23 @@ */ #define PJ_ICE_MAX_COMP (2<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 d102a09d..1656ce33 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 f5ba8e3c..ad08639c 100644 --- a/pjnath/src/pjnath/ice_session.c +++ b/pjnath/src/pjnath/ice_session.c @@ -29,7 +29,6 @@ #include #include - /* String names for candidate types */ static const char *cand_type_names[] = { @@ -66,6 +65,20 @@ static const char *role_names[] = "Controlling" }; +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. */ + TIMER_KEEP_ALIVE /**< ICE keep-alive timer. */ + +}; + /* Candidate type preference */ static pj_uint8_t cand_type_prefs[4] = { @@ -118,10 +131,14 @@ 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 ice_keep_alive(pj_ice_sess *ice, pj_bool_t send_now); 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, @@ -225,7 +242,7 @@ PJ_DEF(void) pj_ice_calc_foundation(pj_pool_t *pool, pj_ice_cand_type type, const pj_sockaddr *base_addr) { -#if 0 +#if PJNATH_ICE_PRIO_STD char buf[64]; pj_uint32_t val; @@ -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,31 @@ 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)); + LOG5((ice->obj_name, "ICE nomination type set to %s", + (ice->opt.aggressive ? "aggressive" : "regular"))); + return PJ_SUCCESS; +} + + /* * Destroy */ @@ -406,10 +461,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) { @@ -603,7 +658,7 @@ static pj_uint32_t CALC_CAND_PRIO(pj_ice_sess *ice, pj_uint32_t local_pref, pj_uint32_t comp_id) { -#if 0 +#if PJNATH_ICE_PRIO_STD return ((ice->prefs[type] & 0xFF) << 24) + ((local_pref & 0xFFFF) << 8) + (((256 - comp_id) & 0xFF) << 0); @@ -1039,18 +1094,112 @@ 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_type type = (enum timer_type)te->id; PJ_UNUSED_ARG(th); - te->id = PJ_FALSE; + pj_mutex_lock(ice->mutex); + + te->id = TIMER_NONE; + + 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: + /* Start keep-alive timer but don't send any packets yet. + * Need to do it here just in case app destroy the session + * in the callback. + */ + if (ice->ice_status == PJ_SUCCESS) + ice_keep_alive(ice, PJ_FALSE); + + /* Notify app about ICE completion*/ + 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; + case TIMER_KEEP_ALIVE: + ice_keep_alive(ice, PJ_TRUE); + break; + case TIMER_NONE: + /* Nothing to do, just to get rid of gcc warning */ + break; + } - if (ice->cb.on_ice_complete) - (*ice->cb.on_ice_complete)(ice, ice->ice_status); + pj_mutex_unlock(ice->mutex); +} + +/* Send keep-alive */ +static void ice_keep_alive(pj_ice_sess *ice, pj_bool_t send_now) +{ + if (send_now) { + /* Send Binding Indication for the component */ + pj_ice_sess_comp *comp = &ice->comp[ice->comp_ka]; + pj_stun_tx_data *tdata; + pj_ice_sess_check *the_check; + pj_ice_msg_data *msg_data; + int addr_len; + pj_bool_t saved; + pj_status_t status; + + /* Must have nominated check by now */ + pj_assert(comp->nominated_check != NULL); + the_check = comp->nominated_check; + + /* Create the Binding Indication */ + status = pj_stun_session_create_ind(comp->stun_sess, + PJ_STUN_BINDING_INDICATION, + &tdata); + if (status != PJ_SUCCESS) + goto done; + + /* Need the transport_id */ + msg_data = PJ_POOL_ZALLOC_T(tdata->pool, pj_ice_msg_data); + msg_data->transport_id = the_check->lcand->transport_id; + + /* Temporarily disable FINGERPRINT. The Binding Indication + * SHOULD NOT contain any attributes. + */ + saved = pj_stun_session_use_fingerprint(comp->stun_sess, PJ_FALSE); + + /* Send to session */ + addr_len = pj_sockaddr_get_len(&the_check->rcand->addr); + status = pj_stun_session_send_msg(comp->stun_sess, msg_data, + PJ_FALSE, PJ_FALSE, + &the_check->rcand->addr, + addr_len, tdata); + + /* Restore FINGERPRINT usage */ + pj_stun_session_use_fingerprint(comp->stun_sess, saved); + +done: + ice->comp_ka = (ice->comp_ka + 1) % ice->comp_cnt; + } + + if (ice->timer.id == TIMER_NONE) { + pj_time_val delay = { 0, 0 }; + + delay.msec = (PJ_ICE_SESS_KEEP_ALIVE_MIN + + (pj_rand() % PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND)) * 1000 / + ice->comp_cnt; + pj_time_val_normalize(&delay); + + ice->timer.id = TIMER_KEEP_ALIVE; + pj_timer_heap_schedule(ice->stun_cfg.timer_heap, &ice->timer, &delay); + + } else { + pj_assert(!"Not expected any timer active"); + } } /* This function is called when ICE processing completes */ @@ -1060,6 +1209,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,26 +1225,49 @@ 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); } } } +/* Update valid check and nominated check for the candidate */ +static void update_comp_check(pj_ice_sess *ice, unsigned comp_id, + pj_ice_sess_check *check) +{ + pj_ice_sess_comp *comp; + + comp = find_comp(ice, comp_id); + if (comp->valid_check == NULL) { + comp->valid_check = check; + } else { + if (CMP_CHECK_PRIO(comp->valid_check, check) < 0) + comp->valid_check = check; + } + + if (check->nominated) { + /* Update the nominated check for the component */ + if (comp->nominated_check == NULL) { + comp->nominated_check = check; + } else { + if (CMP_CHECK_PRIO(comp->nominated_check, check) < 0) + comp->nominated_check = check; + } + } +} /* This function is called when one check completes */ 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 +1281,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 +1290,11 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); } } + + 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 +1319,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) { @@ -1179,14 +1356,6 @@ 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; - } else { - if (CMP_CHECK_PRIO(comp->valid_check, check) < 0) - comp->valid_check = check; - } } @@ -1211,7 +1380,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 +1427,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 +1446,109 @@ 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. + */ + LOG4((ice->obj_name, + "All checks have completed, starting nominated checks now")); + start_nominated_check(ice); + return PJ_FALSE; } + } - on_ice_complete(ice, PJNATH_EICEFAILED); - return PJ_TRUE; + /* 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; + } + + if (i < ice->comp_cnt) { + /* Some components still don't have valid pair, continue + * processing. + */ + return PJ_FALSE; + } + + LOG4((ice->obj_name, + "Scheduling nominated check in %d ms", + ice->opt.nominated_check_delay)); + + if (ice->timer.id != TIMER_NONE) { + pj_timer_heap_cancel(ice->stun_cfg.timer_heap, &ice->timer); + ice->timer.id = TIMER_NONE; + } + + /* 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 +1556,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 +1688,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; @@ -1472,8 +1731,13 @@ static pj_status_t perform_check(pj_ice_sess *ice, msg_data->data.req.ckid = check_id; /* Add PRIORITY */ +#if PJNATH_ICE_PRIO_STD + prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, 65535, + lcand->comp_id); +#else prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, 0, lcand->comp_id); +#endif pj_stun_msg_add_uint_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_PRIORITY, prio); @@ -1481,9 +1745,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 +1815,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 +1834,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 +1862,66 @@ 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) { + unsigned j; + const pj_ice_sess_check *vc = ice->comp[i].valid_check; + + pj_assert(ice->comp[i].nominated_check == NULL); + pj_assert(vc->err_code == PJ_SUCCESS); + + for (j=0; jclist.count; ++j) { + pj_ice_sess_check *c = &ice->clist.checks[j]; + if (c->lcand->transport_id == vc->lcand->transport_id && + c->rcand == vc->rcand) + { + pj_assert(c->err_code == PJ_SUCCESS); + c->state = PJ_ICE_SESS_CHECK_STATE_FROZEN; + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, + PJ_SUCCESS); + break; + } + } + } + + /* And (re)start the periodic check */ + if (!ice->clist.timer.id) { + pj_timer_heap_cancel(ice->stun_cfg.timer_heap, &ice->clist.timer); + ice->clist.timer.id = PJ_FALSE; + } + + 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; + } else { + LOG5((ice->obj_name, "Periodic timer rescheduled..")); + } + + 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 +1968,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 +2156,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 || ice->is_nominating); pj_mutex_unlock(ice->mutex); return; } @@ -1959,19 +2290,34 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, * 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; + /* Add pair to valid list, if it's not there, otherwise just update + * nominated flag + */ + for (i=0; ivalid_list.count; ++i) { + if (ice->valid_list.checks[i].lcand == lcand && + ice->valid_list.checks[i].rcand == check->rcand) + break; + } + + if (i==ice->valid_list.count) { + 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; + } else { + new_check = &ice->valid_list.checks[i]; + ice->valid_list.checks[i].nominated = check->nominated; + } /* Sort valid_list */ sort_checklist(&ice->valid_list); + /* Update valid check and nominated check for the component */ + update_comp_check(ice, new_check->lcand->comp_id, new_check); /* 7.1.2.2.2. Updating Pair States * @@ -2312,8 +2658,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 @@ -2336,8 +2685,17 @@ static void handle_incoming_check(pj_ice_sess *ice, if (rcheck->use_candidate) { for (j=0; jvalid_list.count; ++j) { pj_ice_sess_check *vc = &ice->valid_list.checks[j]; - if (vc->lcand == c->lcand && vc->rcand == c->rcand) { + if (vc->lcand->transport_id == c->lcand->transport_id && + vc->rcand == c->rcand) + { + /* Set nominated flag */ vc->nominated = PJ_TRUE; + + /* Update valid check and nominated check for the component */ + update_comp_check(ice, vc->lcand->comp_id, vc); + + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->valid_list, vc); + LOG5((ice->obj_name, "Valid check %s is nominated", ice->tmp.txt)); } } } @@ -2361,6 +2719,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 +2728,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: " @@ -2388,6 +2749,8 @@ static pj_status_t on_stun_rx_indication(pj_stun_session *sess, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { + struct stun_data *sd; + PJ_UNUSED_ARG(sess); PJ_UNUSED_ARG(pkt); PJ_UNUSED_ARG(pkt_len); @@ -2396,9 +2759,18 @@ static pj_status_t on_stun_rx_indication(pj_stun_session *sess, PJ_UNUSED_ARG(src_addr); PJ_UNUSED_ARG(src_addr_len); - PJ_TODO(SUPPORT_RX_BIND_REQUEST_AS_INDICATION); + sd = (struct stun_data*) pj_stun_session_get_user_data(sess); - return PJ_ENOTSUP; + if (msg->hdr.type == PJ_STUN_BINDING_INDICATION) { + LOG5((sd->ice->obj_name, "Received Binding Indication keep-alive " + "for component %d", sd->comp_id)); + } else { + LOG4((sd->ice->obj_name, "Received unexpected %s indication " + "for component %d", pj_stun_get_method_name(msg->hdr.type), + sd->comp_id)); + } + + return PJ_SUCCESS; } diff --git a/pjnath/src/pjnath/ice_strans.c b/pjnath/src/pjnath/ice_strans.c index b244f0fc..92f550d0 100644 --- a/pjnath/src/pjnath/ice_strans.c +++ b/pjnath/src/pjnath/ice_strans.c @@ -50,20 +50,34 @@ enum tp_type /* Candidate's local preference values. This is mostly used to * specify preference among candidates with the same type. Since * we don't have the facility to specify that, we'll just set it - * all to zero. + * all to the same value. */ -#define SRFLX_PREF 0 -#define HOST_PREF 0 -#define RELAY_PREF 0 +#if PJNATH_ICE_PRIO_STD +# define SRFLX_PREF 65535 +# define HOST_PREF 65535 +# define RELAY_PREF 65535 +#else +# define SRFLX_PREF 0 +# define HOST_PREF 0 +# define RELAY_PREF 0 +#endif + /* The candidate type preference when STUN candidate is used */ static pj_uint8_t srflx_pref_table[4] = { +#if PJNATH_ICE_PRIO_STD + 100, /**< PJ_ICE_HOST_PREF */ + 126, /**< PJ_ICE_SRFLX_PREF */ + 110, /**< PJ_ICE_PRFLX_PREF */ + 0 /**< PJ_ICE_RELAYED_PREF */ +#else /* Keep it to 2 bits */ 1, /**< PJ_ICE_HOST_PREF */ 2, /**< PJ_ICE_SRFLX_PREF */ 3, /**< PJ_ICE_PRFLX_PREF */ 0 /**< PJ_ICE_RELAYED_PREF */ +#endif }; @@ -197,9 +211,13 @@ PJ_DEF(void) pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg) pj_stun_sock_cfg_default(&cfg->stun.cfg); pj_turn_alloc_param_default(&cfg->turn.alloc_param); + pj_ice_sess_options_default(&cfg->opt); + 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 +263,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 +327,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 +339,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]; @@ -646,6 +666,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->cfg.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->cfg.opt, opt, sizeof(*opt)); + if (ice_st->ice) + pj_ice_sess_set_options(ice_st->ice, &ice_st->cfg.opt); + return PJ_SUCCESS; +} + /* * Create ICE! */ @@ -682,6 +726,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->cfg.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/pjnath/src/pjnath/stun_auth.c b/pjnath/src/pjnath/stun_auth.c index cfca3382..7cb0ab12 100644 --- a/pjnath/src/pjnath/stun_auth.c +++ b/pjnath/src/pjnath/stun_auth.c @@ -528,6 +528,7 @@ PJ_DEF(pj_bool_t) pj_stun_auth_valid_for_msg(const pj_stun_msg *msg) switch (err_attr->err_code) { case PJ_STUN_SC_BAD_REQUEST: /* 400 (Bad Request) */ case PJ_STUN_SC_UNAUTHORIZED: /* 401 (Unauthorized) */ + case PJ_STUN_SC_STALE_NONCE: /* 438 (Stale Nonce) */ /* Due to the way this response is generated here, we can't really * authenticate 420 (Unknown Attribute) response */ diff --git a/pjnath/src/pjnath/stun_msg_dump.c b/pjnath/src/pjnath/stun_msg_dump.c index 2ff4a74b..afe9530c 100644 --- a/pjnath/src/pjnath/stun_msg_dump.c +++ b/pjnath/src/pjnath/stun_msg_dump.c @@ -57,11 +57,19 @@ static int print_attr(char *buffer, unsigned length, const pj_stun_attr_hdr *ahdr) { char *p = buffer, *end = buffer + length; + const char *attr_name = pj_stun_get_attr_name(ahdr->type); + char attr_buf[32]; int len; + if (*attr_name == '?') { + pj_ansi_snprintf(attr_buf, sizeof(attr_buf), "Attr 0x%x", + ahdr->type); + attr_name = attr_buf; + } + len = pj_ansi_snprintf(p, end-p, " %s: length=%d", - pj_stun_get_attr_name(ahdr->type), + attr_name, (int)ahdr->length); APPLY(); diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c index fd71beb8..fb5c88c6 100644 --- a/pjnath/src/pjnath/stun_session.c +++ b/pjnath/src/pjnath/stun_session.c @@ -219,10 +219,15 @@ static pj_status_t apply_msg_options(pj_stun_session *sess, /* If the agent is sending a request, it SHOULD add a SOFTWARE attribute * to the request. The server SHOULD include a SOFTWARE attribute in all - * responses + * responses. + * + * If magic value is not PJ_STUN_MAGIC, only apply the attribute for + * responses. */ - if (sess->srv_name.slen && !PJ_STUN_IS_INDICATION(msg->hdr.type) && - pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_SOFTWARE, 0)==NULL) + if (sess->srv_name.slen && + pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_SOFTWARE, 0)==NULL && + (PJ_STUN_IS_RESPONSE(msg->hdr.type) || + PJ_STUN_IS_REQUEST(msg->hdr.type) && msg->hdr.magic==PJ_STUN_MAGIC)) { pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SOFTWARE, &sess->srv_name); @@ -630,6 +635,18 @@ PJ_DEF(void) pj_stun_session_set_log( pj_stun_session *sess, sess->log_flag = flags; } +PJ_DEF(pj_bool_t) pj_stun_session_use_fingerprint(pj_stun_session *sess, + pj_bool_t use) +{ + pj_bool_t old_use; + + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + old_use = sess->use_fingerprint; + sess->use_fingerprint = use; + return old_use; +} + static pj_status_t get_auth(pj_stun_session *sess, pj_stun_tx_data *tdata) { -- cgit v1.2.3