summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pjmedia/src/pjmedia/transport_ice.c16
-rw-r--r--pjnath/include/pjnath/config.h20
-rw-r--r--pjnath/include/pjnath/ice_session.h28
-rw-r--r--pjnath/include/pjnath/stun_session.h14
-rw-r--r--pjnath/include/pjnath/stun_transaction.h10
-rw-r--r--pjnath/src/pjnath/ice_session.c453
-rw-r--r--pjnath/src/pjnath/stun_msg_dump.c2
-rw-r--r--pjnath/src/pjnath/stun_session.c20
-rw-r--r--pjnath/src/pjnath/stun_transaction.c16
-rw-r--r--pjnath/src/pjstun-client/client_main.c4
10 files changed, 435 insertions, 148 deletions
diff --git a/pjmedia/src/pjmedia/transport_ice.c b/pjmedia/src/pjmedia/transport_ice.c
index 6952b53f..9eb3719e 100644
--- a/pjmedia/src/pjmedia/transport_ice.c
+++ b/pjmedia/src/pjmedia/transport_ice.c
@@ -464,12 +464,12 @@ PJ_DEF(pj_status_t) pjmedia_ice_start_ice(pjmedia_transport *tp,
pj_sockaddr_in_init(&conn_addr.ipv4, &conn->addr,
(pj_uint16_t)sdp_med->desc.port);
- /* Find ice-ufrag attribute in session descriptor */
- attr = pjmedia_sdp_attr_find2(rem_sdp->attr_count, rem_sdp->attr,
+ /* Find ice-ufrag attribute in media descriptor */
+ attr = pjmedia_sdp_attr_find2(sdp_med->attr_count, sdp_med->attr,
"ice-ufrag", NULL);
if (attr == NULL) {
- /* Find in media descriptor */
- attr = pjmedia_sdp_attr_find2(sdp_med->attr_count, sdp_med->attr,
+ /* Find ice-ufrag attribute in session descriptor */
+ attr = pjmedia_sdp_attr_find2(rem_sdp->attr_count, rem_sdp->attr,
"ice-ufrag", NULL);
if (attr == NULL) {
set_no_ice(tp_ice, "ice-ufrag attribute not found");
@@ -478,12 +478,12 @@ PJ_DEF(pj_status_t) pjmedia_ice_start_ice(pjmedia_transport *tp,
}
uname = attr->value;
- /* Find ice-pwd attribute in session descriptor */
- attr = pjmedia_sdp_attr_find2(rem_sdp->attr_count, rem_sdp->attr,
+ /* Find ice-pwd attribute in media descriptor */
+ attr = pjmedia_sdp_attr_find2(sdp_med->attr_count, sdp_med->attr,
"ice-pwd", NULL);
if (attr == NULL) {
- /* Not found, find in media descriptor */
- attr = pjmedia_sdp_attr_find2(sdp_med->attr_count, sdp_med->attr,
+ /* Find ice-pwd attribute in session descriptor */
+ attr = pjmedia_sdp_attr_find2(rem_sdp->attr_count, rem_sdp->attr,
"ice-pwd", NULL);
if (attr == NULL) {
set_no_ice(tp_ice, "ice-pwd attribute not found");
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
@@ -165,6 +165,25 @@
/**
+ * 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
* which maximum is PJ_ICE_ST_KEEP_ALIVE_MAX_RAND, specify the actual interval
@@ -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
@@ -419,6 +419,31 @@ typedef enum 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
* session described in the ICE standard where an ICE session covers the
@@ -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
@@ -358,6 +358,20 @@ PJ_DECL(pj_status_t) pj_stun_session_cancel_req(pj_stun_session *sess,
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
* first with #pj_stun_msg_check() to verify that this is indeed a valid
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; i<ice->clist.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; i<ice->clist.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; i<clist->count; ++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; i<ice->rcand_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; i<ice->clist.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; i<ice->clist.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
{