diff --git a/pjnath/include/pjnath/ice_session.h b/pjnath/include/pjnath/ice_session.h index 2bc4508224..4905fe4a25 100644 --- a/pjnath/include/pjnath/ice_session.h +++ b/pjnath/include/pjnath/ice_session.h @@ -721,6 +721,16 @@ typedef struct pj_ice_sess_options */ pj_bool_t check_src_addr; + /** + * Specify whether the session implements ICE-Lite. An ICE-Lite session + * always uses the controlled role, responds to connectivity checks, and + * never originates ordinary or triggered connectivity checks. The + * application is responsible for supplying host candidates only. + * + * Default value is PJ_FALSE. + */ + pj_bool_t lite; + } pj_ice_sess_options; @@ -1170,4 +1180,3 @@ PJ_END_DECL #endif /* __PJNATH_ICE_SESSION_H__ */ - diff --git a/pjnath/src/pjnath-test/ice_test.c b/pjnath/src/pjnath-test/ice_test.c index 1b21b1486b..06343661ce 100644 --- a/pjnath/src/pjnath-test/ice_test.c +++ b/pjnath/src/pjnath-test/ice_test.c @@ -1648,6 +1648,7 @@ struct wvp_sess pj_str_t pass; pj_bool_t completed; pj_status_t status; + unsigned binding_requests_sent; }; static struct wvp_sess *wvp_find_dst(struct wvp_test *t, @@ -1670,9 +1671,16 @@ static pj_status_t wvp_on_tx_pkt(pj_ice_sess *ice, unsigned comp_id, struct wvp_test *t = src->test; struct wvp_sess *dst; struct wvp_pkt *p; + pj_uint16_t msg_type; PJ_UNUSED_ARG(dst_addr_len); + if (size >= sizeof(msg_type)) { + pj_memcpy(&msg_type, pkt, sizeof(msg_type)); + if (pj_ntohs(msg_type) == PJ_STUN_BINDING_REQUEST) + ++src->binding_requests_sent; + } + /* Sends to the bogon address fail synchronously, as if the local route * to it is prohibited (EPERM). */ @@ -2037,3 +2045,247 @@ int ice_wait_valid_pair_test(void) return rc; } + +int ice_lite_test(void) +{ + app_sess_t app_sess; + pj_str_t bogon_ip; + pj_status_t status; + int rc = 0; + + PJ_LOG(3,(THIS_FILE, "ICE-Lite")); + pj_log_push_indent(); + + status = create_stun_config(&app_sess); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + return -10; + } + + bogon_ip = pj_str((char*)WVP_BOGON_IP); + + /* A full agent nominates a peer-reflexive pair on the ICE-Lite agent. */ + { + struct wvp_test t; + struct wvp_sess caller, callee; + pj_ice_sess_cand rcand; + pj_ice_sess_options opt; + unsigned i; + + pj_bzero(&t, sizeof(t)); + pj_bzero(&caller, sizeof(caller)); + pj_bzero(&callee, sizeof(callee)); + t.stun_cfg = &app_sess.stun_cfg; + t.pool = app_sess.pool; + pj_list_init(&t.txq); + pj_sockaddr_init(pj_AF_INET(), &t.bogon, &bogon_ip, WVP_BOGON_PORT); + t.caller = &caller; + t.callee = &callee; + + if ((rc=wvp_create_sess(&t, &caller, "liteUA1", + PJ_ICE_SESS_ROLE_CONTROLLING, 34201, -2))) { + rc = -20; goto s1_done; + } + if ((rc=wvp_create_sess(&t, &callee, "liteUA2", + PJ_ICE_SESS_ROLE_CONTROLLED, 34202, -2))) { + rc = -21; goto s1_done; + } + + pj_ice_sess_get_options(callee.ice, &opt); + opt.lite = PJ_TRUE; + opt.trickle = PJ_ICE_SESS_TRICKLE_FULL; + if (pj_ice_sess_set_options(callee.ice, &opt) != PJ_SUCCESS) { + rc = -22; goto s1_done; + } + if (pj_ice_sess_change_role(callee.ice, + PJ_ICE_SESS_ROLE_CONTROLLING) != + PJ_EINVALIDOP) + { + rc = -23; goto s1_done; + } + + wvp_init_rcand(&rcand, &callee.addr); + if (pj_ice_sess_create_check_list(caller.ice, &callee.ufrag, + &callee.pass, 1, &rcand)) { + rc = -24; goto s1_done; + } + wvp_init_rcand(&rcand, &t.bogon); + if (pj_ice_sess_create_check_list(callee.ice, &caller.ufrag, + &caller.pass, 1, &rcand)) { + rc = -25; goto s1_done; + } + + if (pj_ice_sess_start_check(callee.ice) != PJ_SUCCESS) { + rc = -26; goto s1_done; + } + if (pj_ice_sess_update_check_list(callee.ice, &caller.ufrag, + &caller.pass, 1, &rcand, + PJ_FALSE) != PJ_SUCCESS) { + rc = -27; goto s1_done; + } + if (pj_ice_sess_start_check(caller.ice) != PJ_SUCCESS) { + rc = -28; goto s1_done; + } + + for (i=0; i<80 && !(caller.completed && callee.completed); ++i) + wvp_poll(&t, 100); + + if (!caller.completed || caller.status != PJ_SUCCESS) { + rc = -29; goto s1_done; + } + if (!callee.completed || callee.status != PJ_SUCCESS) { + rc = -30; goto s1_done; + } + if (callee.ice->comp[0].nominated_check->rcand->type != + PJ_ICE_CAND_TYPE_PRFLX) + { + rc = -31; goto s1_done; + } + if (caller.binding_requests_sent == 0 || + callee.binding_requests_sent != 0) + { + rc = -32; goto s1_done; + } + if (t.shim_err) { + rc = -33; goto s1_done; + } + + PJ_LOG(3,(THIS_FILE, INDENT "ok: peer-reflexive pair nominated " + "without outbound checks")); + + s1_done: + if (caller.ice) pj_ice_sess_destroy(caller.ice); + if (callee.ice) pj_ice_sess_destroy(callee.ice); + pj_list_init(&t.txq); + if (rc) goto on_return; + } + + /* A silent peer causes bounded failure without an outbound check. */ + { + struct wvp_test t; + struct wvp_sess callee; + pj_ice_sess_cand rcand; + pj_ice_sess_options opt; + + pj_bzero(&t, sizeof(t)); + pj_bzero(&callee, sizeof(callee)); + t.stun_cfg = &app_sess.stun_cfg; + t.pool = app_sess.pool; + pj_list_init(&t.txq); + pj_sockaddr_init(pj_AF_INET(), &t.bogon, &bogon_ip, WVP_BOGON_PORT); + t.callee = &callee; + + if ((rc=wvp_create_sess(&t, &callee, "liteUA3", + PJ_ICE_SESS_ROLE_CONTROLLED, 34203, -2))) { + rc = -40; goto s2_done; + } + + pj_ice_sess_get_options(callee.ice, &opt); + opt.lite = PJ_TRUE; + opt.controlled_agent_want_nom_timeout = 100; + if (pj_ice_sess_set_options(callee.ice, &opt) != PJ_SUCCESS) { + rc = -41; goto s2_done; + } + wvp_init_rcand(&rcand, &t.bogon); + if (pj_ice_sess_create_check_list(callee.ice, &callee.ufrag, + &callee.pass, 1, &rcand)) { + rc = -42; goto s2_done; + } + if (pj_ice_sess_start_check(callee.ice) != PJ_SUCCESS) { + rc = -43; goto s2_done; + } + + wvp_poll(&t, 300); + if (!callee.completed || + callee.status != PJNATH_EICENOMTIMEOUT || + callee.binding_requests_sent != 0) + { + rc = -44; goto s2_done; + } + + PJ_LOG(3,(THIS_FILE, INDENT "ok: silent peer timed out without " + "outbound checks")); + + s2_done: + if (callee.ice) pj_ice_sess_destroy(callee.ice); + pj_list_init(&t.txq); + if (rc) goto on_return; + } + + /* An early nominated check completes after remote data is installed. */ + { + struct wvp_test t; + struct wvp_sess caller, callee; + pj_ice_sess_cand rcand; + pj_ice_sess_options opt; + unsigned i; + + pj_bzero(&t, sizeof(t)); + pj_bzero(&caller, sizeof(caller)); + pj_bzero(&callee, sizeof(callee)); + t.stun_cfg = &app_sess.stun_cfg; + t.pool = app_sess.pool; + pj_list_init(&t.txq); + pj_sockaddr_init(pj_AF_INET(), &t.bogon, &bogon_ip, WVP_BOGON_PORT); + t.caller = &caller; + t.callee = &callee; + + if ((rc=wvp_create_sess(&t, &caller, "liteUA4", + PJ_ICE_SESS_ROLE_CONTROLLING, 34204, -2))) { + rc = -50; goto s3_done; + } + if ((rc=wvp_create_sess(&t, &callee, "liteUA5", + PJ_ICE_SESS_ROLE_CONTROLLED, 34205, -2))) { + rc = -51; goto s3_done; + } + + pj_ice_sess_get_options(callee.ice, &opt); + opt.lite = PJ_TRUE; + if (pj_ice_sess_set_options(callee.ice, &opt) != PJ_SUCCESS) { + rc = -52; goto s3_done; + } + + wvp_init_rcand(&rcand, &callee.addr); + if (pj_ice_sess_create_check_list(caller.ice, &callee.ufrag, + &callee.pass, 1, &rcand)) { + rc = -53; goto s3_done; + } + if (pj_ice_sess_start_check(caller.ice) != PJ_SUCCESS) { + rc = -54; goto s3_done; + } + wvp_poll(&t, 100); + + wvp_init_rcand(&rcand, &caller.addr); + if (pj_ice_sess_create_check_list(callee.ice, &caller.ufrag, + &caller.pass, 1, &rcand)) { + rc = -55; goto s3_done; + } + if (pj_ice_sess_start_check(callee.ice) != PJ_SUCCESS) { + rc = -56; goto s3_done; + } + + for (i=0; i<20 && !callee.completed; ++i) + wvp_poll(&t, 100); + + if (!callee.completed || callee.status != PJ_SUCCESS || + callee.binding_requests_sent != 0 || t.shim_err) + { + rc = -57; goto s3_done; + } + + PJ_LOG(3,(THIS_FILE, INDENT "ok: early check completed without " + "outbound checks")); + + s3_done: + if (caller.ice) pj_ice_sess_destroy(caller.ice); + if (callee.ice) pj_ice_sess_destroy(callee.ice); + pj_list_init(&t.txq); + if (rc) goto on_return; + } + +on_return: + destroy_stun_config(&app_sess); + pj_log_pop_indent(); + + return rc; +} diff --git a/pjnath/src/pjnath-test/test.c b/pjnath/src/pjnath-test/test.c index a46be0c72c..03698cfad3 100644 --- a/pjnath/src/pjnath-test/test.c +++ b/pjnath/src/pjnath-test/test.c @@ -249,6 +249,7 @@ static int test_inner(int argc, char *argv[]) #if INCLUDE_ICE_TEST UT_ADD_TEST(&test_app.ut_app, ice_wait_valid_pair_test, 0); + UT_ADD_TEST(&test_app.ut_app, ice_lite_test, 0); #endif #if INCLUDE_TURN_SOCK_TEST @@ -301,4 +302,3 @@ int test_main(int argc, char *argv[]) return -1; } - diff --git a/pjnath/src/pjnath-test/test.h b/pjnath/src/pjnath-test/test.h index e96ce6b054..0c1ffe00ba 100644 --- a/pjnath/src/pjnath-test/test.h +++ b/pjnath/src/pjnath-test/test.h @@ -70,6 +70,7 @@ int ice_test(void*); int ice_conc_test(void); int trickle_ice_test(void); int ice_wait_valid_pair_test(void); +int ice_lite_test(void); int concur_test(void); int test_main(int argc, char *argv[]); @@ -123,4 +124,3 @@ pj_turn_tp_type get_turn_tp_type(pj_uint32_t flag); #define ERR_MEMORY_LEAK 1 #define ERR_TIMER_LEAK 2 - diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c index de4876657c..85eb723f80 100644 --- a/pjnath/src/pjnath/ice_session.c +++ b/pjnath/src/pjnath/ice_session.c @@ -332,6 +332,7 @@ PJ_DEF(void) pj_ice_sess_options_default(pj_ice_sess_options *opt) opt->wait_valid_pair_timeout = PJ_ICE_WAIT_VALID_PAIR_TIMEOUT; opt->trickle = PJ_ICE_SESS_TRICKLE_DISABLED; opt->check_src_addr = PJ_ICE_SESS_CHECK_SRC_ADDR; + opt->lite = PJ_FALSE; } /* @@ -458,7 +459,11 @@ 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_grp_lock_acquire(ice->grp_lock); pj_memcpy(&ice->opt, opt, sizeof(*opt)); + if (ice->opt.lite) + ice->role = PJ_ICE_SESS_ROLE_CONTROLLED; ice->is_trickling = (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED); if (ice->is_trickling) { LOG5((ice->obj_name, "Trickle ICE is active (%s mode)", @@ -474,6 +479,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_set_options(pj_ice_sess *ice, LOG5((ice->obj_name, "ICE nomination type set to %s", (ice->opt.aggressive ? "aggressive" : "regular"))); + pj_grp_lock_release(ice->grp_lock); return PJ_SUCCESS; } @@ -565,6 +571,9 @@ PJ_DEF(pj_status_t) pj_ice_sess_change_role(pj_ice_sess *ice, { PJ_ASSERT_RETURN(ice, PJ_EINVAL); + if (ice->opt.lite && new_role != PJ_ICE_SESS_ROLE_CONTROLLED) + return PJ_EINVALIDOP; + if (new_role != ice->role) { ice->role = new_role; LOG4((ice->obj_name, "Role changed to %s", role_names[new_role])); @@ -2172,7 +2181,8 @@ static pj_status_t add_rcand_and_update_checklist( /* For trickle ICE: resume the periodic check, it may be halted when * there is no available check pair. */ - if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED && + if (!ice->opt.lite && + ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED && clist->count > 0 && !ice->is_complete && clist->state == PJ_ICE_SESS_CHECKLIST_ST_RUNNING) { @@ -2675,7 +2685,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice) pj_log_push_indent(); /* If we are using aggressive nomination, set the is_nominating state */ - if (ice->opt.aggressive) + if (ice->opt.aggressive && !ice->opt.lite) ice->is_nominating = PJ_TRUE; /* The agent examines the check list for the first media stream (a @@ -2691,7 +2701,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice) */ clist = &ice->clist; - for (i=0; i < clist->foundation_cnt; ++i) { + for (i=0; !ice->opt.lite && i < clist->foundation_cnt; ++i) { unsigned k; pj_ice_sess_check *chk = NULL; @@ -2742,12 +2752,33 @@ PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice) } pj_list_init(&ice->early_check); + if (ice->opt.lite && !ice->is_complete && + 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); + status = pj_timer_heap_schedule_w_grp_lock( + ice->stun_cfg.timer_heap, + &ice->timer, &delay, + TIMER_CONTROLLED_WAIT_NOM, + ice->grp_lock); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(ice->grp_lock); + pj_log_pop_indent(); + return status; + } + } + /* Start periodic check */ /* We could start it immediately like below, but lets schedule timer * instead to reduce stack usage: * return start_periodic_check(ice->stun_cfg.timer_heap, &clist->timer); */ - if (!pj_timer_entry_running(&clist->timer)) { + if (!ice->opt.lite && !pj_timer_entry_running(&clist->timer)) { pj_time_val delay = {0, 0}; status = pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &clist->timer, &delay, @@ -3325,6 +3356,13 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, src_addr, src_addr_len); pj_grp_lock_release(ice->grp_lock); return PJ_SUCCESS; + } else if (ice->opt.lite) { + /* An ICE-Lite agent never changes to the controlling role. */ + pj_stun_session_respond(sess, rdata, PJ_STUN_SC_ROLE_CONFLICT, + NULL, token, PJ_TRUE, + src_addr, src_addr_len); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; } else { /* Switch role to controlled */ LOG4((ice->obj_name, @@ -3438,6 +3476,47 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, } +/* Complete a candidate pair from an incoming ICE-Lite check. */ +static void complete_incoming_lite_check(pj_ice_sess *ice, + pj_ice_sess_check *check) +{ + pj_ice_sess_check *valid; + unsigned i; + + for (i=0; ivalid_list.count; ++i) { + valid = &ice->valid_list.checks[i]; + if (valid->lcand == check->lcand && valid->rcand == check->rcand) + break; + } + + if (i == ice->valid_list.count) { + if (ice->valid_list.count >= PJ_ICE_MAX_CHECKS) { + LOG4((ice->obj_name, "Unable to add ICE-Lite valid pair: " + "valid list is full")); + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, + PJ_ETOOMANY); + on_check_complete(ice, check); + return; + } + + valid = &ice->valid_list.checks[ice->valid_list.count++]; + pj_bzero(valid, sizeof(*valid)); + valid->lcand = check->lcand; + valid->rcand = check->rcand; + valid->prio = check->prio; + valid->foundation_idx = check->foundation_idx; + valid->state = PJ_ICE_SESS_CHECK_STATE_SUCCEEDED; + valid->err_code = PJ_SUCCESS; + } + + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_SUCCEEDED, + PJ_SUCCESS); + valid->nominated = (valid->nominated || check->nominated); + update_comp_check(ice, valid->lcand->comp_id, valid); + sort_checklist(ice, &ice->valid_list); + on_check_complete(ice, check); +} + /* Handle incoming Binding request and perform triggered check. * This function may be called by on_stun_rx_request(), or when * SDP answer is received and we have received early checks. @@ -3569,6 +3648,11 @@ static void handle_incoming_check(pj_ice_sess *ice, rcand->checked = PJ_TRUE; + if (ice->opt.lite) { + complete_incoming_lite_check(ice, c); + return; + } + if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN || c->state == PJ_ICE_SESS_CHECK_STATE_WAITING) { @@ -3659,6 +3743,14 @@ static void handle_incoming_check(pj_ice_sess *ice, LOG4((ice->obj_name, "New triggered check added: %d", check_id)); + if (ice->opt.lite) { + c->foundation_idx = get_check_foundation_idx(ice, lcand, rcand, + PJ_TRUE); + complete_incoming_lite_check(ice, c); + sort_checklist(ice, &ice->clist); + return; + } + /* If we are nominating in regular nomination, don't nominate this * newly found pair. */ @@ -3931,5 +4023,3 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice, return status; } - -