From d3fc99a7187535ad1b9c015e9e9a4c0d7f4fa0c0 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Thu, 5 Apr 2007 11:32:47 +0000 Subject: ICE (work in progress): handle early check that is received before answer git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1152 74dad513-b988-da41-8d7b-12977e46ad98 --- pjnath/include/pjnath/config.h | 20 +- pjnath/include/pjnath/ice_session.h | 28 ++ pjnath/include/pjnath/stun_session.h | 14 + pjnath/include/pjnath/stun_transaction.h | 10 + pjnath/src/pjnath/ice_session.c | 453 +++++++++++++++++++++---------- pjnath/src/pjnath/stun_msg_dump.c | 2 +- pjnath/src/pjnath/stun_session.c | 20 ++ pjnath/src/pjnath/stun_transaction.c | 16 ++ pjnath/src/pjstun-client/client_main.c | 4 +- 9 files changed, 427 insertions(+), 140 deletions(-) (limited to 'pjnath') diff --git a/pjnath/include/pjnath/config.h b/pjnath/include/pjnath/config.h index 50db9420..27c0f6b1 100644 --- a/pjnath/include/pjnath/config.h +++ b/pjnath/include/pjnath/config.h @@ -164,6 +164,25 @@ #endif +/** + * According to ICE Section 8.2. Updating States, if an In-Progress pair in + * the check list is for the same component as a nominated pair, the agent + * SHOULD cease retransmissions for its check if its pair priority is lower + * than the lowest priority nominated pair for that component. + * + * If a higher priority check is In Progress, this rule would cause that + * check to be performed even when it most likely will fail. + * + * The macro here controls if ICE session should cancel all In Progress + * checks for the same component regardless of its priority. + * + * Default: 1 (yes, cancel all) + */ +#ifndef PJ_ICE_CANCEL_ALL +# define PJ_ICE_CANCEL_ALL 1 +#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 @@ -195,7 +214,6 @@ - /** * @} */ diff --git a/pjnath/include/pjnath/ice_session.h b/pjnath/include/pjnath/ice_session.h index 42bcff4a..aaa07ae6 100644 --- a/pjnath/include/pjnath/ice_session.h +++ b/pjnath/include/pjnath/ice_session.h @@ -418,6 +418,31 @@ typedef enum pj_ice_sess_role } pj_ice_sess_role; +/** + * This structure represents an incoming check (an incoming Binding + * request message), and is mainly used to keep early checks in the + * list in the ICE session. An early check is a request received + * from remote when we haven't received SDP answer yet, therefore we + * can't perform triggered check. For such cases, keep the incoming + * request in a list, and we'll do triggered checks (simultaneously) + * as soon as we receive answer. + */ +typedef struct pj_ice_rx_check +{ + PJ_DECL_LIST_MEMBER(struct pj_ice_rx_check); + + unsigned comp_id; /**< Component ID. */ + + pj_sockaddr src_addr; /**< Source address of request */ + unsigned src_addr_len; /**< Length of src address. */ + + pj_bool_t use_candidate; /**< USE-CANDIDATE is present? */ + pj_uint32_t priority; /**< PRIORITY value in the req. */ + pj_stun_uint64_attr *role_attr; /**< ICE-CONTROLLING/CONTROLLED */ + +} pj_ice_rx_check; + + /** * This structure describes the ICE session. For this version of PJNATH, * an ICE session corresponds to a single media stream (unlike the ICE @@ -463,6 +488,9 @@ struct pj_ice_sess unsigned rcand_cnt; /**< # of remote cand. */ pj_ice_sess_cand rcand[PJ_ICE_MAX_CAND]; /**< Array of cand. */ + /* List of eearly checks */ + pj_ice_rx_check early_check; /**< Early checks. */ + /* Checklist */ pj_ice_sess_checklist clist; /**< Active checklist */ diff --git a/pjnath/include/pjnath/stun_session.h b/pjnath/include/pjnath/stun_session.h index 0cd81f98..dfbf61c0 100644 --- a/pjnath/include/pjnath/stun_session.h +++ b/pjnath/include/pjnath/stun_session.h @@ -357,6 +357,20 @@ PJ_DECL(pj_status_t) pj_stun_session_cancel_req(pj_stun_session *sess, pj_bool_t notify, pj_status_t status); +/** + * Explicitly request retransmission of the request. Normally application + * doesn't need to do this, but this functionality is needed by ICE to + * speed up connectivity check completion. + * + * @param sess The STUN session instance. + * @param tdata The request message previously sent. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess, + pj_stun_tx_data *tdata); + + /** * Application must call this function to notify the STUN session about * the arrival of STUN packet. The STUN packet MUST have been checked diff --git a/pjnath/include/pjnath/stun_transaction.h b/pjnath/include/pjnath/stun_transaction.h index 32dd0c41..05e8b444 100644 --- a/pjnath/include/pjnath/stun_transaction.h +++ b/pjnath/include/pjnath/stun_transaction.h @@ -221,6 +221,16 @@ PJ_DECL(pj_status_t) pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx, void *pkt, unsigned pkt_len); +/** + * Request to retransmit the request. Normally application should not need + * to call this function since retransmission would be handled internally, + * but this functionality is needed by ICE. + * + * @param tsx The STUN client transaction instance. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx); /** diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c index 21696292..502eb360 100644 --- a/pjnath/src/pjnath/ice_session.c +++ b/pjnath/src/pjnath/ice_session.c @@ -80,6 +80,9 @@ static pj_uint8_t cand_type_prefs[4] = #define GET_CHECK_ID(cl, chk) (chk - (cl)->checks) +/* The data that will be attached to the STUN session on each + * component. + */ typedef struct stun_data { pj_ice_sess *ice; @@ -87,6 +90,10 @@ typedef struct stun_data pj_ice_sess_comp *comp; } stun_data; + +/* The data that will be attached to the timer to perform + * periodic check. + */ typedef struct timer_data { pj_ice_sess *ice; @@ -94,13 +101,17 @@ typedef struct timer_data } timer_data; - +/* Forward declarations */ 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 periodic_timer(pj_timer_heap_t *th, - pj_timer_entry *te); + pj_timer_entry *te); +static void handle_incoming_check(pj_ice_sess *ice, + const pj_ice_rx_check *rcheck); + +/* These are the callbacks registered to the STUN sessions */ static pj_status_t on_stun_send_msg(pj_stun_session *sess, const void *pkt, pj_size_t pkt_size, @@ -125,6 +136,7 @@ static pj_status_t on_stun_rx_indication(pj_stun_session *sess, const pj_sockaddr_t *src_addr, unsigned src_addr_len); +/* These are the callbacks for performing STUN authentication */ static pj_status_t stun_auth_get_auth(void *user_data, pj_pool_t *pool, pj_str_t *realm, @@ -298,6 +310,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg, pj_strdup(ice->pool, &ice->rx_pass, local_passwd); } + pj_list_init(&ice->early_check); /* Done */ *p_ice = ice; @@ -482,9 +495,21 @@ static pj_status_t stun_auth_get_password(const pj_stun_msg *msg, * equal to the username fragment generated by the agent in an offer * or answer for a session in-progress, and the MESSAGE-INTEGRITY * is the output of a hash of the password and the STUN packet's - * contents. + * contents. */ - PJ_TODO(CHECK_USERNAME_FOR_INCOMING_STUN_REQUEST); + const char *pos; + pj_str_t ufrag; + + pos = (const char*)pj_memchr(username->ptr, ':', username->slen); + if (pos == NULL) + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_USERNAME); + + ufrag.ptr = (char*)username->ptr; + ufrag.slen = (pos - username->ptr); + + if (pj_strcmp(&ufrag, &ice->rx_ufrag) != 0) + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_USERNAME); + *data_type = 0; *data = ice->rx_pass; @@ -920,20 +945,41 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, * two steps: * * 1. The agent changes the states for all other Frozen pairs for the - * same media stream and same foundation to Waiting. Typically - * these other pairs will have different component IDs but not - * always. + * same media stream and same foundation to Waiting. Typically + * these other pairs will have different component IDs but not + * 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 + && c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN) + { + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); + } + } + } - - /* If there is at least one nominated pair in the valid list: - * - The agent MUST remove all Waiting and Frozen pairs in the check - * list for the same component as the nominated pairs for that - * media stream - * - If an In-Progress pair in the check list is for the same - * component as a nominated pair, the agent SHOULD cease - * retransmissions for its check if its pair priority is lower - * than the lowest priority nominated pair for that component + /* 8.2. Updating States + * + * For both controlling and controlled agents, the state of ICE + * processing depends on the presence of nominated candidate pairs in + * the valid list and on the state of the check list: + * + * o If there are no nominated pairs in the valid list for a media + * stream and the state of the check list is Running, ICE processing + * continues. + * + * o If there is at least one nominated pair in the valid list: + * + * - The agent MUST remove all Waiting and Frozen pairs in the check + * list for the same component as the nominated pairs for that + * media stream + * + * - If an In-Progress pair in the check list is for the same + * component as a nominated pair, the agent SHOULD cease + * retransmissions for its check if its pair priority is lower + * than the lowest priority nominated pair for that component */ if (check->err_code==PJ_SUCCESS && check->nominated) { pj_ice_sess_comp *comp; @@ -945,9 +991,13 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, comp = find_comp(ice, check->lcand->comp_id); for (i=0; iclist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->lcand->comp_id == check->lcand->comp_id) { + if (c->state < PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) { + /* Just fail Frozen/Waiting check */ LOG5((ice->obj_name, "Check %s to be failed because state is %s", @@ -956,7 +1006,10 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_FAILED, PJ_ECANCELLED); - } else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) { + } else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS + && (PJ_ICE_CANCEL_ALL || + CMP_CHECK_PRIO(c, check) < 0)) { + /* State is IN_PROGRESS, cancel transaction */ if (c->tdata) { LOG5((ice->obj_name, @@ -981,36 +1034,23 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, } } - /* Once there is at least one nominated pair in the valid list for - * every component of at least one media stream: - * - The agent MUST change the state of processing for its check - * list for that media stream to Completed. - * - The agent MUST continue to respond to any checks it may still - * receive for that media stream, and MUST perform triggered - * checks if required by the processing of Section 7.2. - * - The agent MAY begin transmitting media for this media stream as - * described in Section 11.1 - */ - /* TODO */ - - /* Once there is at least one nominated pair in the valid list for - * each component of each media stream: - * - The agent sets the state of ICE processing overall to - * Completed. - * - If an agent is controlling, it examines the highest priority - * nominated candidate pair for each component of each media - * stream. If any of those candidate pairs differ from the - * default candidate pairs in the most recent offer/answer - * exchange, the controlling agent MUST generate an updated offer - * as described in Section 9. If the controlling agent is using - * an aggressive nomination algorithm, this may result in several - * updated offers as the pairs selected for media change. An - * agent MAY delay sending the offer for a brief interval (one - * second is RECOMMENDED) in order to allow the selected pairs to - * stabilize. - */ - /* TODO */ + /* Still in 8.2. Updating States + * + * o Once there is at least one nominated pair in the valid list for + * every component of at least one media stream and the state of the + * check list is Running: + * + * * The agent MUST change the state of processing for its check + * list for that media stream to Completed. + * + * * The agent MUST continue to respond to any checks it may still + * receive for that media stream, and MUST perform triggered + * checks if required by the processing of Section 7.2. + * + * * The agent MAY begin transmitting media for this media stream as + * described in Section 11.1 + */ /* See if all components have nominated pair. If they do, then mark * ICE processing as success, otherwise wait. @@ -1025,6 +1065,29 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, return PJ_TRUE; } + /* Note: this is the stuffs that we don't do in 7.1.2.2.2, since our + * ICE session only supports one media stream for now: + * + * 7.1.2.2.2. Updating Pair States + * + * 2. If there is a pair in the valid list for every component of this + * media stream (where this is the actual number of components being + * used, in cases where the number of components signaled in the SDP + * differs from offerer to answerer), the success of this check may + * unfreeze checks for other media streams. + */ + + /* 7.1.2.3. Check List and Timer State Updates + * Regardless of whether the check was successful or failed, the + * completion of the transaction may require updating of check list and + * timer states. + * + * If all of the pairs in the check list are now either in the Failed or + * Succeeded state, and there is not a pair in the valid list for each + * component of the media stream, the state of the check list is set to + * Failed. + */ + /* * See if all checks in the checklist have completed. If we do, * then mark ICE processing as failed. @@ -1365,12 +1428,18 @@ const pj_str_t *find_str(const pj_str_t *strlist[], unsigned count, return NULL; } -/* Start ICE check */ + +/* + * Start ICE periodic check. This function will return immediately, and + * application will be notified about the connectivity check status in + * #pj_ice_sess_cb callback. + */ PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice) { pj_ice_sess_checklist *clist; const pj_ice_sess_cand *cand0; const pj_str_t *flist[PJ_ICE_MAX_CAND]; + pj_ice_rx_check *rcheck; unsigned i, flist_cnt = 0; PJ_ASSERT_RETURN(ice, PJ_EINVAL); @@ -1428,6 +1497,17 @@ PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice) } } + /* First, perform all pending triggered checks, simultaneously. */ + rcheck = ice->early_check.next; + while (rcheck != &ice->early_check) { + LOG4((ice->obj_name, + "Performing delayed triggerred check for component %d", + rcheck->comp_id)); + handle_incoming_check(ice, rcheck); + rcheck = rcheck->next; + } + pj_list_init(&ice->early_check); + /* Start periodic check */ return start_periodic_check(ice->stun_cfg.timer_heap, &clist->timer); } @@ -1491,8 +1571,11 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, char errmsg[PJ_ERR_MSG_SIZE]; if (status==PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_ROLE_CONFLICT)) { + /* Role conclict response. + * * 7.1.2.1. Failure Cases: + * * If the request had contained the ICE-CONTROLLED attribute, * the agent MUST switch to the controlling role if it has not * already done so. If the request had contained the @@ -1517,7 +1600,7 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, if (new_role != ice->role) { LOG4((ice->obj_name, - "Changing role because of role conflict")); + "Changing role because of role conflict response")); pj_ice_sess_change_role(ice, new_role); } @@ -1543,7 +1626,9 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, } - /* The agent MUST check that the source IP address and port of the + /* 7.1.2.1. Failure Cases + * + * The agent MUST check that the source IP address and port of the * response equals the destination IP address and port that the Binding * Request was sent to, and that the destination IP address and port of * the response match the source IP address and port that the Binding @@ -1561,6 +1646,22 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, return; } + /* 7.1.2.2. Success Cases + * + * A check is considered to be a success if all of the following are + * true: + * + * o the STUN transaction generated a success response + * + * o the source IP address and port of the response equals the + * destination IP address and port that the Binding Request was sent + * to + * + * o the destination IP address and port of the response match the + * source IP address and port that the Binding Request was sent from + */ + + LOG4((ice->obj_name, "Check %s%s: connectivity check SUCCESS", dump_check(buffer, sizeof(buffer), &ice->clist, check), @@ -1587,7 +1688,8 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, } } - /* If the transport address returned in XOR-MAPPED-ADDRESS does not match + /* 7.1.2.2.1. Discovering Peer Reflexive Candidates + * If the transport address returned in XOR-MAPPED-ADDRESS does not match * any of the local candidates that the agent knows about, the mapped * address represents a new candidate - a peer reflexive candidate. */ @@ -1598,7 +1700,7 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, pj_ice_calc_foundation(ice->pool, &foundation, PJ_ICE_CAND_TYPE_PRFLX, &check->lcand->base_addr); - /* According to: 7.1.2.2.1. Discovering Peer Reflexive Candidates: + /* Still in 7.1.2.2.1. Discovering Peer Reflexive Candidates * Its priority is set equal to the value of the PRIORITY attribute * in the Binding Request. * @@ -1627,7 +1729,14 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, } + /* 7.1.2.2.3. Constructing a Valid Pair + * Next, the agent constructs a candidate pair whose local candidate + * equals the mapped address of the response, and whose remote candidate + * 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; @@ -1640,13 +1749,16 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, sort_checklist(&ice->valid_list); - /* Sets the state of the original pair that generated the check to - * succeeded. + /* 7.1.2.2.2. Updating Pair States + * + * The agent sets the state of the pair that generated the check to + * Succeeded. The success of this check might also cause the state of + * other checks to change as well. */ check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_SUCCEEDED, PJ_SUCCESS); - /* Inform about check completion. + /* Perform 7.1.2.2.2. Updating Pair States. * This may terminate ICE processing. */ if (on_check_complete(ice, check)) { @@ -1655,53 +1767,10 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, return; } - /* If the pair had a component ID of 1, the agent MUST change the - * states for all other Frozen pairs for the same media stream and - * same foundation, but different component IDs, to Waiting. - */ - if (lcand->comp_id == 1) { - unsigned i; - pj_bool_t unfrozen = PJ_FALSE; - - for (i=0; icount; ++i) { - pj_ice_sess_check *c = &clist->checks[i]; - - if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN && - c->lcand->comp_id != lcand->comp_id && - pj_strcmp(&c->lcand->foundation, &lcand->foundation)==0) - { - /* Unfreeze and start check */ - check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, - PJ_SUCCESS); - unfrozen = PJ_TRUE; - } - } - - if (unfrozen && clist->timer.id == PJ_FALSE) - start_periodic_check(ice->stun_cfg.timer_heap, &clist->timer); - } - - /* If the pair had a component ID equal to the number of components - * for the media stream (where this is the actual number of - * components being used, in cases where the number of components - * signaled in the SDP differs from offerer to answerer), the agent - * MUST change the state for all other Frozen pairs for the first - * component of different media streams (and thus in different check - * lists) but the same foundation, to Waiting. - */ - else if (0) { - PJ_TODO(UNFREEZE_OTHER_COMPONENT_ID); - } - /* If the pair has any other component ID, no other pairs can be - * unfrozen. - */ - else { - PJ_TODO(UNFREEZE_OTHER_COMPONENT_ID1); - } - pj_mutex_unlock(ice->mutex); } + /* This callback is called by the STUN session associated with a candidate * when it receives incoming request. */ @@ -1714,14 +1783,11 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, { stun_data *sd; pj_ice_sess *ice; - pj_stun_priority_attr *ap; - pj_stun_use_candidate_attr *uc; - pj_ice_sess_comp *comp; - pj_ice_sess_cand *lcand = NULL; - pj_ice_sess_cand *rcand; - unsigned i; + pj_stun_priority_attr *prio_attr; + pj_stun_use_candidate_attr *uc_attr; + pj_stun_uint64_attr *role_attr; pj_stun_tx_data *tdata; - pj_bool_t is_relayed; + pj_ice_rx_check *rcheck, tmp_rcheck; pj_status_t status; PJ_UNUSED_ARG(pkt); @@ -1742,32 +1808,85 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, sd = (stun_data*) pj_stun_session_get_user_data(sess); ice = sd->ice; - comp = sd->comp; pj_mutex_lock(ice->mutex); + /* + * Note: + * Be aware that when STUN request is received, we might not get + * SDP answer yet, so we might not have remote candidates and + * checklist yet. This case will be handled after we send + * a response. + */ + /* Get PRIORITY attribute */ - ap = (pj_stun_priority_attr*) - pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PRIORITY, 0); - if (ap == 0) { + prio_attr = (pj_stun_priority_attr*) + pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PRIORITY, 0); + if (prio_attr == NULL) { LOG5((ice->obj_name, "Received Binding request with no PRIORITY")); pj_mutex_unlock(ice->mutex); return PJ_SUCCESS; } /* Get USE-CANDIDATE attribute */ - uc = (pj_stun_use_candidate_attr*) - pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USE_CANDIDATE, 0); + uc_attr = (pj_stun_use_candidate_attr*) + pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USE_CANDIDATE, 0); - /* For simplicity, ignore incoming requests when we don't have remote - * candidates yet. The peer agent should retransmit the STUN request - * and we'll receive it again later. + + /* Get ICE-CONTROLLING or ICE-CONTROLLED */ + role_attr = (pj_stun_uint64_attr*) + pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLING, 0); + if (role_attr == NULL) { + role_attr = (pj_stun_uint64_attr*) + pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLED, 0); + } + + /* 7.2.1.1. Detecting and Repairing Role Conflicts */ - if (ice->rcand_cnt == 0) { - pj_mutex_unlock(ice->mutex); - return PJ_SUCCESS; + if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING && + role_attr && role_attr->hdr.type == PJ_STUN_ATTR_ICE_CONTROLLING) + { + if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) { + /* Switch role to controlled */ + LOG4((ice->obj_name, + "Changing role because of ICE-CONTROLLING attribute")); + pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLED); + } else { + /* Generate 487 response */ + status = pj_stun_session_create_response(sess, msg, + PJ_STUN_SC_ROLE_CONFLICT, + NULL, &tdata); + if (status == PJ_SUCCESS) { + pj_stun_session_send_msg(sess, PJ_TRUE, + src_addr, src_addr_len, tdata); + } + pj_mutex_unlock(ice->mutex); + return PJ_SUCCESS; + } + + } else if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLED && + role_attr && role_attr->hdr.type == PJ_STUN_ATTR_ICE_CONTROLLED) + { + if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) { + /* Generate 487 response */ + status = pj_stun_session_create_response(sess, msg, + PJ_STUN_SC_ROLE_CONFLICT, + NULL, &tdata); + if (status == PJ_SUCCESS) { + pj_stun_session_send_msg(sess, PJ_TRUE, + src_addr, src_addr_len, tdata); + } + pj_mutex_unlock(ice->mutex); + return PJ_SUCCESS; + } else { + /* Switch role to controlled */ + LOG4((ice->obj_name, + "Changing role because of ICE-CONTROLLED attribute")); + pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLING); + } } + /* * First send response to this request */ @@ -1785,28 +1904,81 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, src_addr, src_addr_len, tdata); + /* + * Handling early check. + * + * It's possible that we receive this request before we receive SDP + * answer. In this case, we can't perform trigger check since we + * don't have checklist yet, so just save this check in a pending + * triggered check array to be acted upon later. + */ + if (ice->rcand_cnt == 0) { + rcheck = PJ_POOL_ZALLOC_T(ice->pool, pj_ice_rx_check); + } else { + rcheck = &tmp_rcheck; + } + + /* Init rcheck */ + rcheck->comp_id = sd->comp_id; + rcheck->src_addr_len = src_addr_len; + pj_memcpy(&rcheck->src_addr, src_addr, src_addr_len); + rcheck->use_candidate = (uc_attr != NULL); + rcheck->priority = prio_attr->value; + rcheck->role_attr = role_attr; + + if (ice->rcand_cnt == 0) { + /* We don't have answer yet, so keep this request for later */ + LOG4((ice->obj_name, "Received an early check for comp %d", + rcheck->comp_id)); + pj_list_push_back(&ice->early_check, rcheck); + } else { + /* Handle this check */ + handle_incoming_check(ice, rcheck); + } + + pj_mutex_unlock(ice->mutex); + return PJ_SUCCESS; +} + + +/* 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. + */ +static void handle_incoming_check(pj_ice_sess *ice, + const pj_ice_rx_check *rcheck) +{ + pj_ice_sess_comp *comp; + pj_ice_sess_cand *lcand = NULL; + pj_ice_sess_cand *rcand; + unsigned i; + pj_bool_t is_relayed; + + comp = find_comp(ice, rcheck->comp_id); + /* Find remote candidate based on the source transport address of * the request. */ for (i=0; ircand_cnt; ++i) { - if (sockaddr_cmp((const pj_sockaddr*)src_addr, &ice->rcand[i].addr)==0) + if (sockaddr_cmp(&rcheck->src_addr, &ice->rcand[i].addr)==0) break; } - /* If the source transport address of the request does not match any + /* 7.2.1.3. Learning Peer Reflexive Candidates + * If the source transport address of the request does not match any * existing remote candidates, it represents a new peer reflexive remote * candidate. */ if (i == ice->rcand_cnt) { rcand = &ice->rcand[ice->rcand_cnt++]; - rcand->comp_id = sd->comp_id; + rcand->comp_id = rcheck->comp_id; rcand->type = PJ_ICE_CAND_TYPE_PRFLX; - rcand->prio = ap->value; - pj_memcpy(&rcand->addr, src_addr, src_addr_len); + rcand->prio = rcheck->priority; + pj_memcpy(&rcand->addr, &rcheck->src_addr, rcheck->src_addr_len); /* Foundation is random, unique from other foundation */ - rcand->foundation.ptr = pj_pool_alloc(ice->pool, 32); - rcand->foundation.slen = pj_ansi_snprintf(rcand->foundation.ptr, 32, + rcand->foundation.ptr = pj_pool_alloc(ice->pool, 36); + rcand->foundation.slen = pj_ansi_snprintf(rcand->foundation.ptr, 36, "f%p", rcand->foundation.ptr); @@ -1842,12 +2014,19 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, */ for (i=0; iclist.count; ++i) { pj_ice_sess_check *c = &ice->clist.checks[i]; - if (c->lcand->comp_id == sd->comp_id) { + if (c->lcand->comp_id == rcheck->comp_id) { lcand = c->lcand; break; } } - pj_assert(lcand != NULL); + if (lcand == NULL) { + /* Should not happen, but just in case remote is sending a + * Binding request for a component which it doesn't have. + */ + LOG4((ice->obj_name, + "Received Binding request but no local candidate is found!")); + return; + } #endif /* @@ -1859,7 +2038,10 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, PJ_TODO(DETERMINE_IF_REQUEST_COMES_FROM_RELAYED_CANDIDATE); is_relayed = PJ_FALSE; - /* Now that we have local and remote candidate, check if we already + /* + * 7.2.1.4. Triggered Checks + * + * Now that we have local and remote candidate, check if we already * have this pair in our checklist. */ for (i=0; iclist.count; ++i) { @@ -1887,7 +2069,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, /* If USE-CANDIDATE is present, set nominated flag * Note: DO NOT overwrite nominated flag if one is already set. */ - c->nominated = ((uc != NULL) || c->nominated); + c->nominated = ((rcheck->use_candidate) || c->nominated); if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN || c->state == PJ_ICE_SESS_CHECK_STATE_WAITING) @@ -1896,11 +2078,12 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, perform_check(ice, &ice->clist, i); } else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) { - /* Should retransmit here, but how?? - * TODO + /* Should retransmit immediately */ LOG5((ice->obj_name, "Triggered check for check %d not performed " - "because it's in progress", i)); + "because it's in progress. Retransmitting", i)); + pj_stun_session_retransmit_req(comp->stun_sess, c->tdata); + } else if (c->state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED) { /* Check complete for this component. * Note this may end ICE process. @@ -1912,8 +2095,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, complete = on_check_complete(ice, c); if (complete) { - pj_mutex_unlock(ice->mutex); - return PJ_SUCCESS; + return; } } @@ -1932,7 +2114,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, c->rcand = rcand; c->prio = CALC_CHECK_PRIO(ice, lcand, rcand); c->state = PJ_ICE_SESS_CHECK_STATE_WAITING; - c->nominated = (uc != NULL); + c->nominated = rcheck->use_candidate; c->err_code = PJ_SUCCESS; LOG4((ice->obj_name, "New triggered check added: %d", @@ -1943,9 +2125,6 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, LOG4((ice->obj_name, "Error: unable to perform triggered check: " "TOO MANY CHECKS IN CHECKLIST!")); } - - pj_mutex_unlock(ice->mutex); - return status; } diff --git a/pjnath/src/pjnath/stun_msg_dump.c b/pjnath/src/pjnath/stun_msg_dump.c index 0da2cc21..cbf29a00 100644 --- a/pjnath/src/pjnath/stun_msg_dump.c +++ b/pjnath/src/pjnath/stun_msg_dump.c @@ -227,7 +227,7 @@ PJ_DEF(char*) pj_stun_msg_dump(const pj_stun_msg *msg, APPLY(); len = pj_ansi_snprintf(p, end-p, - " Hdr: length=%d, magic=%08x, tsx_id=%08x %08x %08x\n" + " Hdr: length=%d, magic=%08x, tsx_id=%08x%08x%08x\n" " Attributes:\n", msg->hdr.length, msg->hdr.magic, diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c index cb14da3c..f2dab459 100644 --- a/pjnath/src/pjnath/stun_session.c +++ b/pjnath/src/pjnath/stun_session.c @@ -710,9 +710,29 @@ PJ_DEF(pj_status_t) pj_stun_session_cancel_req( pj_stun_session *sess, pj_mutex_unlock(sess->mutex); return PJ_SUCCESS; +} + +/* + * Explicitly request retransmission of the request. + */ +PJ_DEF(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess, + pj_stun_tx_data *tdata) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL); + + pj_mutex_lock(sess->mutex); + + status = pj_stun_client_tsx_retransmit(tdata->client_tsx); + pj_mutex_unlock(sess->mutex); + + return status; } + /* Send response */ static pj_status_t send_response(pj_stun_session *sess, pj_pool_t *pool, pj_stun_msg *response, diff --git a/pjnath/src/pjnath/stun_transaction.c b/pjnath/src/pjnath/stun_transaction.c index 32cdec4d..30330718 100644 --- a/pjnath/src/pjnath/stun_transaction.c +++ b/pjnath/src/pjnath/stun_transaction.c @@ -294,6 +294,22 @@ static void retransmit_timer_callback(pj_timer_heap_t *timer_heap, } } +/* + * Request to retransmit the request. + */ +PJ_DEF(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx) +{ + if (tsx->destroy_timer.id != 0) { + return PJ_SUCCESS; + } + + if (tsx->retransmit_timer.id != 0) { + pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + return tsx_transmit_msg(tsx); +} /* Timer callback to destroy transaction */ static void destroy_timer_callback(pj_timer_heap_t *timer_heap, diff --git a/pjnath/src/pjstun-client/client_main.c b/pjnath/src/pjstun-client/client_main.c index bca87a32..11094e2b 100644 --- a/pjnath/src/pjstun-client/client_main.c +++ b/pjnath/src/pjstun-client/client_main.c @@ -29,7 +29,9 @@ #define REQ_PORT_PROPS -1 /* -1 to disable */ #define REQ_IP NULL /* IP address string */ -#define OPTIONS PJ_STUN_NO_AUTHENTICATE +//#define OPTIONS PJ_STUN_NO_AUTHENTICATE +#define OPTIONS 0 + static struct global { -- cgit v1.2.3