Implement trickle ICE (#2588)

Squash & merge trickle-ice dev branch to master.
This commit is contained in:
Nanang Izzuddin 2020-12-11 09:40:57 +07:00 committed by GitHub
parent dad6a34680
commit d65cacddd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 3176 additions and 435 deletions

View File

@ -54,6 +54,7 @@ static void print_stack(int sig)
static void init_signals()
{
signal(SIGSEGV, &print_stack);
signal(SIGABRT, &print_stack);
}
#else

View File

@ -75,6 +75,7 @@ static void print_stack(int sig)
static void init_signals()
{
signal(SIGSEGV, &print_stack);
signal(SIGABRT, &print_stack);
}
#else

View File

@ -74,6 +74,25 @@ typedef struct pjmedia_ice_cb
pj_status_t status,
void *user_data);
/**
* Callback to report a new ICE local candidate, e.g: after successful
* STUN Binding, after a successful TURN allocation. Only new candidates
* whose type is server reflexive or relayed will be notified via this
* callback. This callback also indicates end-of-candidate via parameter
* 'last'.
*
* Trickle ICE can use this callback to convey the new candidate
* to remote agent and monitor end-of-candidate indication.
*
* @param tp PJMEDIA ICE transport.
* @param cand The new local candidate, can be NULL when the last
* local candidate initialization failed/timeout.
* @param last PJ_TRUE if this is the last of local candidate.
*/
void (*on_new_candidate)(pjmedia_transport *tp,
const pj_ice_sess_cand *cand,
pj_bool_t last);
} pjmedia_ice_cb;
@ -289,6 +308,142 @@ PJ_DECL(pj_status_t) pjmedia_ice_remove_ice_cb(pjmedia_transport *tp,
void *user_data);
/**
* Check if trickle support is signalled in the specified SDP. This function
* will check trickle indication in the media level first, if not found, it
* will check in the session level.
*
* @param sdp The SDP.
* @param med_idx The media index to be checked.
*
* @return PJ_TRUE if trickle ICE indication is found.
*/
PJ_DECL(pj_bool_t) pjmedia_ice_sdp_has_trickle(const pjmedia_sdp_session *sdp,
unsigned med_idx);
/**
* Update check list after new local or remote ICE candidates are added,
* or signal ICE session that trickling is done. Application typically would
* call this function after finding (and conveying) new local ICE candidates
* to remote, after receiving remote ICE candidates, or after receiving
* end-of-candidates indication.
*
* This function is only applicable when trickle ICE is not disabled and
* after ICE connectivity checks are started, i.e: after
* pjmedia_transport_media_start() has been invoked.
*
* @param tp The ICE media transport.
* @param rem_ufrag Remote ufrag, as seen in the SDP received from
* the remote agent.
* @param rem_passwd Remote password, as seen in the SDP received from
* the remote agent.
* @param rcand_cnt Number of new remote candidates in the array.
* @param rcand New remote candidates array.
* @param rcand_end Set to PJ_TRUE if remote has signalled
* end-of-candidate.
*
* @return PJ_SUCCESS, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pjmedia_ice_trickle_update(
pjmedia_transport *tp,
const pj_str_t *rem_ufrag,
const pj_str_t *rem_passwd,
unsigned rcand_cnt,
const pj_ice_sess_cand rcand[],
pj_bool_t rcand_end);
/**
* Decode trickle ICE info from the specified SDP.
*
* @param sdp The SDP containing trickle ICE info.
* @param media_index The media index.
* @param mid Output, media ID.
* @param ufrag Output, ufrag.
* @param passwd Output, password.
* @param cand_cnt On input, maximum number of candidate array.
* On output, the number of candidates.
* @param cand Output, the candidates.
* @param end_of_cand Output, end of candidate indication.
*
* @return PJ_SUCCESS, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pjmedia_ice_trickle_decode_sdp(
const pjmedia_sdp_session *sdp,
unsigned media_index,
pj_str_t *mid,
pj_str_t *ufrag,
pj_str_t *passwd,
unsigned *cand_cnt,
pj_ice_sess_cand cand[],
pj_bool_t *end_of_cand);
/**
* Encode trickle ICE info into the specified SDP. This function may generate
* the following SDP attributes:
* - Media ID, "a=mid".
* - ICE ufrag & password, "a=ice-ufrag" & "a=ice-pwd".
* - Trickle ICE support indication, "a=ice-options:trickle".
* - ICE candidates, "a=candidate".
* - End of candidate indication, "a=end-of-candidates".
*
* @param sdp_pool The memory pool for generating SDP attributes.
* @param sdp The SDP to be updated.
* @param mid The media ID.
* @param ufrag The ufrag, optional.
* @param passwd The password, optional.
* @param cand_cnt The number of local candidates, can be zero.
* @param cand The local candidates.
* @param end_of_cand End of candidate indication.
*
* @return PJ_SUCCESS, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pjmedia_ice_trickle_encode_sdp(
pj_pool_t *sdp_pool,
pjmedia_sdp_session *sdp,
const pj_str_t *mid,
const pj_str_t *ufrag,
const pj_str_t *passwd,
unsigned cand_cnt,
const pj_ice_sess_cand cand[],
pj_bool_t end_of_cand);
/**
* Check if trickling ICE has found any new local candidates.
*
* @param tp The ICE media transport.
*
* @return PJ_TRUE if new local canditates are available.
*/
PJ_DECL(pj_bool_t) pjmedia_ice_trickle_has_new_cand(pjmedia_transport *tp);
/**
* Check for new local candidates, and if any new or forced, update the
* specified SDP with all current local candidates to be conveyed to remote
* (e.g: via SIP INFO).
*
* @param tp The ICE media transport.
* @param sdp_pool The memory pool for generating SDP attributes.
* @param sdp The SDP.
* @param p_end_of_cand Optional, pointer to receive the indication that
* candidate gathering has been completed.
*
* @return PJ_SUCCESS if any new local candidates is found and
* SDP is updated with the candidates,
* PJ_ENOTFOUND if no new local candidate is found,
* or the appropriate error code.
*/
PJ_DECL(pj_status_t) pjmedia_ice_trickle_send_local_cand(
pjmedia_transport *tp,
pj_pool_t *sdp_pool,
pjmedia_sdp_session *sdp,
pj_bool_t *p_end_of_cand);
PJ_END_DECL

View File

@ -1939,24 +1939,6 @@ static void on_rx_rtp( pjmedia_tp_cb_param *param)
goto on_return;
}
/* Handle incoming DTMF. */
if (hdr->pt == stream->rx_event_pt) {
pj_timestamp ts;
/* Ignore out-of-order packet as it will be detected as new
* digit. Also ignore duplicate packet as it serves no use.
*/
if (seq_st.status.flag.outorder || seq_st.status.flag.dup) {
goto on_return;
}
/* Get the timestamp of the event */
ts.u64 = pj_ntohl(hdr->ts);
handle_incoming_dtmf(stream, &ts, payload, payloadlen);
goto on_return;
}
/* See if source address of RTP packet is different than the
* configured address, and check if we need to tell the
* media transport to switch RTP remote address.
@ -2013,6 +1995,24 @@ static void on_rx_rtp( pjmedia_tp_cb_param *param)
}
}
/* Handle incoming DTMF. */
if (hdr->pt == stream->rx_event_pt) {
pj_timestamp ts;
/* Ignore out-of-order packet as it will be detected as new
* digit. Also ignore duplicate packet as it serves no use.
*/
if (seq_st.status.flag.outorder || seq_st.status.flag.dup) {
goto on_return;
}
/* Get the timestamp of the event */
ts.u64 = pj_ntohl(hdr->ts);
handle_incoming_dtmf(stream, &ts, payload, payloadlen);
goto on_return;
}
/* Put "good" packet to jitter buffer, or reset the jitter buffer
* when RTP session is restarted.
*/

View File

@ -44,6 +44,7 @@ struct sdp_state
pj_bool_t ice_mismatch; /* Address doesn't match candidates */
pj_bool_t ice_restart; /* Offer to restart ICE */
pj_ice_sess_role local_role; /* Our role */
pj_bool_t has_trickle; /* Has trickle ICE attribute */
};
/* ICE listener */
@ -88,6 +89,10 @@ struct transport_ice
unsigned tx_drop_pct; /**< Percent of tx pkts to drop. */
unsigned rx_drop_pct; /**< Percent of rx pkts to drop. */
pj_ice_sess_trickle trickle_ice; /**< Trickle ICE mode. */
unsigned last_cand_cnt; /**< Last local candidate count. */
pj_str_t sdp_mid; /**< SDP "a=mid" attribute. */
void (*rtp_cb)(void*,
void*,
pj_ssize_t);
@ -162,6 +167,9 @@ static void ice_on_rx_data(pj_ice_strans *ice_st,
static void ice_on_ice_complete(pj_ice_strans *ice_st,
pj_ice_strans_op op,
pj_status_t status);
static void ice_on_new_candidate(pj_ice_strans *ice_st,
const pj_ice_sess_cand *cand,
pj_bool_t last);
/*
* Clean up ICE resources.
@ -198,12 +206,30 @@ static const pj_str_t STR_RTCP = { "rtcp", 4 };
static const pj_str_t STR_RTCP_MUX = { "rtcp-mux", 8 };
static const pj_str_t STR_BANDW_RR = { "RR", 2 };
static const pj_str_t STR_BANDW_RS = { "RS", 2 };
static const pj_str_t STR_ICE_OPTIONS = { "ice-options", 11 };
static const pj_str_t STR_TRICKLE = { "trickle", 7 };
static const pj_str_t STR_END_OF_CAND = { "end-of-candidates", 17 };
enum {
COMP_RTP = 1,
COMP_RTCP = 2
};
/* Forward declaration of internal functions */
static int print_sdp_cand_attr(char *buffer, int max_len,
const pj_ice_sess_cand *cand);
static void get_ice_attr(const pjmedia_sdp_session *rem_sdp,
const pjmedia_sdp_media *rem_m,
const pjmedia_sdp_attr **p_ice_ufrag,
const pjmedia_sdp_attr **p_ice_pwd);
static pj_status_t parse_cand(const char *obj_name,
pj_pool_t *pool,
const pj_str_t *orig_input,
pj_ice_sess_cand *cand);
/*
* Create ICE media transport.
*/
@ -265,6 +291,7 @@ PJ_DEF(pj_status_t) pjmedia_ice_create3(pjmedia_endpt *endpt,
tp_ice->initial_sdp = PJ_TRUE;
tp_ice->oa_role = ROLE_NONE;
tp_ice->use_ice = PJ_FALSE;
tp_ice->trickle_ice = cfg->opt.trickle;
pj_list_init(&tp_ice->listener);
pj_list_init(&tp_ice->listener_empty);
@ -281,6 +308,7 @@ PJ_DEF(pj_status_t) pjmedia_ice_create3(pjmedia_endpt *endpt,
pj_bzero(&ice_st_cb, sizeof(ice_st_cb));
ice_st_cb.on_ice_complete = &ice_on_ice_complete;
ice_st_cb.on_rx_data = &ice_on_rx_data;
ice_st_cb.on_new_candidate = &ice_on_new_candidate;
/* Configure RTP socket buffer settings, if not set */
if (ice_st_cfg.comp[COMP_RTP-1].so_rcvbuf_size == 0) {
@ -391,6 +419,336 @@ PJ_DEF(pj_status_t) pjmedia_ice_remove_ice_cb( pjmedia_transport *tp,
return (il != &tp_ice->listener? PJ_SUCCESS : PJ_ENOTFOUND);
}
/* Check if trickle support is signalled in the specified SDP. */
PJ_DEF(pj_bool_t) pjmedia_ice_sdp_has_trickle( const pjmedia_sdp_session *sdp,
unsigned med_idx)
{
const pjmedia_sdp_media *m;
const pjmedia_sdp_attr *a;
PJ_ASSERT_RETURN(sdp && med_idx < sdp->media_count, PJ_EINVAL);
/* Find in media level */
m = sdp->media[med_idx];
a = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_ICE_OPTIONS, NULL);
if (a && pj_strstr(&a->value, &STR_TRICKLE))
return PJ_TRUE;
/* Find in session level */
a = pjmedia_sdp_attr_find(sdp->attr_count, sdp->attr, &STR_ICE_OPTIONS,
NULL);
if (a && pj_strstr(&a->value, &STR_TRICKLE))
return PJ_TRUE;
return PJ_FALSE;
}
/* Update check list after discovering and conveying new local ICE candidate,
* or receiving update of remote ICE candidates in trickle ICE.
*/
PJ_DEF(pj_status_t) pjmedia_ice_trickle_update(
pjmedia_transport *tp,
const pj_str_t *rem_ufrag,
const pj_str_t *rem_passwd,
unsigned rcand_cnt,
const pj_ice_sess_cand rcand[],
pj_bool_t rcand_end)
{
struct transport_ice *tp_ice = (struct transport_ice*)tp;
PJ_ASSERT_RETURN(tp_ice && tp_ice->ice_st, PJ_EINVAL);
return pj_ice_strans_update_check_list(tp_ice->ice_st,
rem_ufrag, rem_passwd,
rcand_cnt, rcand, rcand_end);
}
/* Fetch trickle ICE info from the specified SDP. */
PJ_DEF(pj_status_t) pjmedia_ice_trickle_decode_sdp(
const pjmedia_sdp_session *sdp,
unsigned media_index,
pj_str_t *mid,
pj_str_t *ufrag,
pj_str_t *passwd,
unsigned *cand_cnt,
pj_ice_sess_cand cand[],
pj_bool_t *end_of_cand)
{
const pjmedia_sdp_media *m;
const pjmedia_sdp_attr *a;
PJ_ASSERT_RETURN(sdp && media_index < sdp->media_count, PJ_EINVAL);
m = sdp->media[media_index];
if (mid) {
a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "mid", NULL);
if (a) {
*mid = a->value;
} else {
pj_bzero(mid, sizeof(*mid));
}
}
if (ufrag && passwd) {
const pjmedia_sdp_attr *a_ufrag, *a_pwd;
get_ice_attr(sdp, m, &a_ufrag, &a_pwd);
if (a_ufrag && a_pwd) {
*ufrag = a_ufrag->value;
*passwd = a_pwd->value;
} else {
pj_bzero(ufrag, sizeof(*ufrag));
pj_bzero(passwd, sizeof(*passwd));
}
}
if (cand_cnt && cand && *cand_cnt > 0) {
pj_status_t status;
unsigned i, cnt = 0;
for (i=0; i<m->attr_count && cnt<*cand_cnt; ++i) {
a = m->attr[i];
if (pj_strcmp(&a->name, &STR_CANDIDATE)!=0)
continue;
/* Parse candidate */
status = parse_cand("trickle-ice", NULL, &a->value, &cand[cnt]);
if (status != PJ_SUCCESS) {
PJ_PERROR(4,("trickle-ice", status,
"Error in parsing SDP candidate attribute '%.*s', "
"candidate is ignored",
(int)a->value.slen, a->value.ptr));
continue;
}
++cnt;
}
*cand_cnt = cnt;
}
if (end_of_cand) {
a = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_END_OF_CAND,
NULL);
if (!a) {
a = pjmedia_sdp_attr_find(sdp->attr_count, sdp->attr,
&STR_END_OF_CAND, NULL);
}
*end_of_cand = (a != NULL);
}
return PJ_SUCCESS;
}
/* Generate SDP attributes for trickle ICE in the specified SDP. */
PJ_DEF(pj_status_t) pjmedia_ice_trickle_encode_sdp(
pj_pool_t *sdp_pool,
pjmedia_sdp_session *sdp,
const pj_str_t *mid,
const pj_str_t *ufrag,
const pj_str_t *passwd,
unsigned cand_cnt,
const pj_ice_sess_cand cand[],
pj_bool_t end_of_cand)
{
pjmedia_sdp_media *m = NULL;
pjmedia_sdp_attr *a;
unsigned i;
PJ_ASSERT_RETURN(sdp_pool && sdp, PJ_EINVAL);
/* Find media by checking "a=mid"*/
for (i = 0; i < sdp->media_count; ++i) {
m = sdp->media[i];
a = pjmedia_sdp_media_find_attr2(m, "mid", NULL);
if (a && pj_strcmp(&a->value, mid)==0)
break;
}
/* Media not exist, try to add it */
if (i == sdp->media_count) {
if (sdp->media_count >= PJMEDIA_MAX_SDP_MEDIA) {
PJ_LOG(3,(THIS_FILE,"Trickle ICE failed to encode candidates, "
"the specified SDP has too many media"));
return PJ_ETOOMANY;
}
/* Add a new media to the SDP */
m = PJ_POOL_ZALLOC_T(sdp_pool, pjmedia_sdp_media);
m->desc.media = pj_str("audio");
m->desc.fmt_count = 1;
m->desc.fmt[0] = pj_str("0");
m->desc.transport = pj_str("RTP/AVP");
sdp->media[sdp->media_count++] = m;
/* Add media ID attribute "a=mid" */
a = pjmedia_sdp_attr_create(sdp_pool, "mid", mid);
pjmedia_sdp_attr_add(&m->attr_count, m->attr, a);
}
/* Add "a=ice-options:trickle" in session level */
a = pjmedia_sdp_attr_find(sdp->attr_count, sdp->attr, &STR_ICE_OPTIONS,
NULL);
if (!a || !pj_strstr(&a->value, &STR_TRICKLE)) {
a = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_OPTIONS.ptr,
&STR_TRICKLE);
pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
}
/* Add "a=ice-options:trickle" in media level */
/*
a = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_ICE_OPTIONS,
NULL);
if (!a || !pj_strstr(&a->value, &STR_TRICKLE)) {
a = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_OPTIONS.ptr,
&STR_TRICKLE);
pjmedia_sdp_attr_add(&m->attr_count, m->attr, a);
}
*/
/* Add ice-ufrag & ice-pwd attributes */
if (ufrag && passwd &&
!pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_ICE_UFRAG, NULL))
{
a = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, ufrag);
pjmedia_sdp_attr_add(&m->attr_count, m->attr, a);
a = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, passwd);
pjmedia_sdp_attr_add(&m->attr_count, m->attr, a);
}
/* Add candidates */
for (i=0; i<cand_cnt; ++i) {
enum {
ATTR_BUF_LEN = 160, /* Max len of a=candidate attr */
RATTR_BUF_LEN= 160 /* Max len of a=remote-candidates attr */
};
char attr_buf[ATTR_BUF_LEN];
pj_str_t value;
value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN, &cand[i]);
if (value.slen < 0) {
pj_assert(!"Not enough attr_buf to print candidate");
return PJ_EBUG;
}
value.ptr = attr_buf;
a = pjmedia_sdp_attr_create(sdp_pool, STR_CANDIDATE.ptr, &value);
pjmedia_sdp_attr_add(&m->attr_count, m->attr, a);
}
/* Add "a=end-of-candidates" */
if (end_of_cand) {
a = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_END_OF_CAND,
NULL);
if (!a) {
a = pjmedia_sdp_attr_create(sdp_pool, STR_END_OF_CAND.ptr, NULL);
pjmedia_sdp_attr_add(&m->attr_count, m->attr, a);
}
}
return PJ_SUCCESS;
}
PJ_DEF(pj_bool_t) pjmedia_ice_trickle_has_new_cand(pjmedia_transport *tp)
{
struct transport_ice *tp_ice = (struct transport_ice*)tp;
unsigned i, cand_cnt = 0;
/* Make sure ICE transport has session already */
if (!tp_ice->ice_st || !pj_ice_strans_has_sess(tp_ice->ice_st))
return PJ_FALSE;
/* Count all local candidates */
for (i = 0; i < tp_ice->comp_cnt; ++i) {
cand_cnt += pj_ice_strans_get_cands_count(tp_ice->ice_st, i+1);
}
return (cand_cnt > tp_ice->last_cand_cnt);
}
/* Add any new local candidates to the specified SDP to be conveyed to
* remote (e.g: via SIP INFO).
*/
PJ_DEF(pj_status_t) pjmedia_ice_trickle_send_local_cand(
pjmedia_transport *tp,
pj_pool_t *sdp_pool,
pjmedia_sdp_session *sdp,
pj_bool_t *p_end_of_cand)
{
struct transport_ice *tp_ice = (struct transport_ice*)tp;
pj_str_t ufrag, pwd;
pj_ice_strans_state ice_state;
pj_ice_sess_cand cand[PJ_ICE_MAX_CAND];
unsigned cand_cnt, i;
pj_bool_t end_of_cand;
pj_status_t status;
PJ_ASSERT_RETURN(tp && sdp_pool && sdp, PJ_EINVAL);
/* Make sure ICE transport has session already */
if (!tp_ice->ice_st || !pj_ice_strans_has_sess(tp_ice->ice_st))
return PJ_EINVALIDOP;
ice_state = pj_ice_strans_get_state(tp_ice->ice_st);
end_of_cand = (ice_state == PJ_ICE_STRANS_STATE_READY ||
ice_state == PJ_ICE_STRANS_STATE_SESS_READY);
/* Get ufrag and pwd from current session */
pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, &ufrag, &pwd, NULL, NULL);
cand_cnt = 0;
for (i = 0; i < tp_ice->comp_cnt; ++i) {
unsigned cnt = PJ_ICE_MAX_CAND - cand_cnt;
/* Get all local candidates for this comp */
status = pj_ice_strans_enum_cands(tp_ice->ice_st, i+1,
&cnt, &cand[cand_cnt]);
if (status != PJ_SUCCESS) {
PJ_PERROR(3,(tp_ice->base.name, status,
"Failed enumerating local candidates for comp-id=%d",
i+1));
continue;
}
cand_cnt += cnt;
}
/* Update the SDP with all local candidates (not just the new ones).
* https://tools.ietf.org/html/draft-ietf-mmusic-trickle-ice-sip-18:
* 4.4. Delivering Candidates in INFO Requests: the agent MUST
* repeat in the INFO request body all candidates that were previously
* sent under the same combination of "a=ice-pwd:" and "a=ice-ufrag:"
* in the same order as they were sent before.
*/
status = pjmedia_ice_trickle_encode_sdp(sdp_pool, sdp, &tp_ice->sdp_mid,
&ufrag, &pwd, cand_cnt, cand,
end_of_cand);
if (status != PJ_SUCCESS) {
PJ_PERROR(3,(tp_ice->base.name, status,
"Failed adding new local candidates to SDP"));
}
/* Update ICE checklist if there is any new local candidate and
* checklist has been created (e.g: ICE nego is running).
*/
if (tp_ice->last_cand_cnt < cand_cnt &&
pj_ice_strans_sess_is_running(tp_ice->ice_st))
{
pjmedia_ice_trickle_update(tp, NULL, NULL, 0, NULL, PJ_FALSE);
}
pj_assert(tp_ice->last_cand_cnt <= cand_cnt);
tp_ice->last_cand_cnt = cand_cnt;
if (p_end_of_cand)
*p_end_of_cand = end_of_cand;
return PJ_SUCCESS;
}
/* Disable ICE when SDP from remote doesn't contain a=candidate line */
static void set_no_ice(struct transport_ice *tp_ice, const char *reason,
pj_status_t err)
@ -508,7 +866,8 @@ static pj_status_t encode_session_in_sdp(struct transport_ice *tp_ice,
unsigned media_index,
unsigned comp_cnt,
pj_bool_t restart_session,
pj_bool_t rtcp_mux)
pj_bool_t rtcp_mux,
pj_bool_t trickle)
{
enum {
ATTR_BUF_LEN = 160, /* Max len of a=candidate attr */
@ -773,6 +1132,32 @@ static pj_status_t encode_session_in_sdp(struct transport_ice *tp_ice,
m->attr[m->attr_count++] = add_attr;
}
/* Add trickle ICE attributes */
if (trickle) {
pj_ice_strans_state ice_state;
pj_bool_t end_of_cand;
/* Add media ID attribute "a=mid" */
attr = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "mid", NULL);
if (!attr) {
attr = pjmedia_sdp_attr_create(sdp_pool, "mid", &tp_ice->sdp_mid);
pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
}
ice_state = pj_ice_strans_get_state(tp_ice->ice_st);
end_of_cand = (ice_state == PJ_ICE_STRANS_STATE_READY ||
ice_state == PJ_ICE_STRANS_STATE_SESS_READY);
status = pjmedia_ice_trickle_encode_sdp(sdp_pool, sdp_local,
&tp_ice->sdp_mid, NULL, NULL,
0, NULL, end_of_cand);
if (status != PJ_SUCCESS) {
PJ_PERROR(3,(tp_ice->base.name, status,
"Failed in adding trickle ICE attributes"));
return status;
}
}
return PJ_SUCCESS;
}
@ -799,7 +1184,11 @@ static pj_status_t parse_cand(const char *obj_name,
TRACE__((obj_name, "Expecting ICE foundation in candidate"));
goto on_return;
}
pj_strdup(pool, &cand->foundation, &token);
if (pool) {
pj_strdup(pool, &cand->foundation, &token);
} else {
cand->foundation = token;
}
/* Component ID */
found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen);
@ -908,7 +1297,9 @@ static pj_status_t create_initial_offer(struct transport_ice *tp_ice,
/* Encode ICE in SDP */
status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
tp_ice->comp_cnt, PJ_FALSE,
tp_ice->enable_rtcp_mux);
tp_ice->enable_rtcp_mux,
tp_ice->trickle_ice !=
PJ_ICE_SESS_TRICKLE_DISABLED);
if (status != PJ_SUCCESS) {
set_no_ice(tp_ice, "Error encoding SDP answer", status);
return status;
@ -1142,14 +1533,23 @@ static pj_status_t verify_ice_sdp(struct transport_ice *tp_ice,
}
}
/* Check trickle ICE indication */
if (tp_ice->trickle_ice != PJ_ICE_SESS_TRICKLE_DISABLED) {
sdp_state->has_trickle = pjmedia_ice_sdp_has_trickle(rem_sdp,
media_index);
} else {
sdp_state->has_trickle = PJ_FALSE;
}
PJ_LOG(4,(tp_ice->base.name,
"Processing SDP: support ICE=%u, common comp_cnt=%u, "
"ice_mismatch=%u, ice_restart=%u, local_role=%s",
"ice_mismatch=%u, ice_restart=%u, local_role=%s, trickle=%u",
(sdp_state->match_comp_cnt != 0),
sdp_state->match_comp_cnt,
sdp_state->ice_mismatch,
sdp_state->ice_restart,
pj_ice_sess_role_name(sdp_state->local_role)));
pj_ice_sess_role_name(sdp_state->local_role),
sdp_state->has_trickle));
return PJ_SUCCESS;
@ -1218,6 +1618,7 @@ static pj_status_t create_initial_answer(struct transport_ice *tp_ice,
unsigned media_index)
{
const pjmedia_sdp_media *rem_m = rem_sdp->media[media_index];
pj_bool_t with_trickle;
pj_status_t status;
/* Check if media is removed (just in case) */
@ -1250,9 +1651,12 @@ static pj_status_t create_initial_answer(struct transport_ice *tp_ice,
}
/* Encode ICE in SDP */
with_trickle = tp_ice->rem_offer_state.has_trickle &&
tp_ice->trickle_ice != PJ_ICE_SESS_TRICKLE_DISABLED;
status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
tp_ice->rem_offer_state.match_comp_cnt,
PJ_FALSE, tp_ice->use_rtcp_mux);
PJ_FALSE, tp_ice->use_rtcp_mux,
with_trickle);
if (status != PJ_SUCCESS) {
set_no_ice(tp_ice, "Error encoding SDP answer", status);
return status;
@ -1278,7 +1682,8 @@ static pj_status_t create_subsequent_offer(struct transport_ice *tp_ice,
comp_cnt = pj_ice_strans_get_running_comp_cnt(tp_ice->ice_st);
return encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
comp_cnt, PJ_FALSE, tp_ice->enable_rtcp_mux);
comp_cnt, PJ_FALSE, tp_ice->enable_rtcp_mux,
PJ_FALSE);
}
@ -1320,13 +1725,15 @@ static pj_status_t create_subsequent_answer(struct transport_ice *tp_ice,
status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
tp_ice->rem_offer_state.match_comp_cnt,
tp_ice->rem_offer_state.ice_restart,
tp_ice->use_rtcp_mux);
tp_ice->use_rtcp_mux, PJ_FALSE);
if (status != PJ_SUCCESS)
return status;
/* Done */
} else {
pj_bool_t with_trickle;
/*
* Received subsequent offer while we DON'T have ICE active.
*/
@ -1354,10 +1761,13 @@ static pj_status_t create_subsequent_answer(struct transport_ice *tp_ice,
return status;
}
with_trickle = tp_ice->rem_offer_state.has_trickle &&
tp_ice->trickle_ice != PJ_ICE_SESS_TRICKLE_DISABLED;
status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
tp_ice->rem_offer_state.match_comp_cnt,
tp_ice->rem_offer_state.ice_restart,
tp_ice->use_rtcp_mux);
tp_ice->use_rtcp_mux,
with_trickle);
if (status != PJ_SUCCESS)
return status;
@ -1390,6 +1800,38 @@ static pj_status_t transport_media_create(pjmedia_transport *tp,
tp_ice->oa_role = ROLE_NONE;
tp_ice->initial_sdp = PJ_TRUE;
/* Init SDP "a=mid" attribute. Get from remote SDP for answerer or
* generate one for offerer (or if remote doesn't specify).
*/
tp_ice->sdp_mid.slen = 0;
if (rem_sdp) {
pjmedia_sdp_media *m = rem_sdp->media[media_index];
pjmedia_sdp_attr *a;
a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "mid", NULL);
if (a) {
pj_strdup(tp_ice->pool, &tp_ice->sdp_mid, &a->value);
}
}
if (tp_ice->sdp_mid.slen == 0) {
char tmp_buf[8];
pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf), "%d", media_index+1);
tp_ice->sdp_mid = pj_strdup3(tp_ice->pool, tmp_buf);
}
/* If RTCP mux is being used, set component count to 1 */
if (rem_sdp && tp_ice->enable_rtcp_mux) {
pjmedia_sdp_media *rem_m = rem_sdp->media[media_index];
pjmedia_sdp_attr *attr;
attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr,
&STR_RTCP_MUX, NULL);
tp_ice->use_rtcp_mux = (attr? PJ_TRUE: PJ_FALSE);
}
if (tp_ice->use_rtcp_mux &&
pj_ice_strans_get_running_comp_cnt(tp_ice->ice_st)>1)
{
pj_ice_strans_update_comp_cnt(tp_ice->ice_st, 1);
}
/* Init ICE, the initial role is set now based on availability of
* rem_sdp, but it will be checked again later.
*/
@ -1459,6 +1901,18 @@ static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
tp_ice->oa_role = ROLE_ANSWERER;
else
tp_ice->oa_role = ROLE_OFFERER;
if (tp_ice->use_ice) {
/* Update last local candidate count, so trickle ICE can identify
* if there is any new local candidate.
*/
unsigned i;
tp_ice->last_cand_cnt = 0;
for (i = 0; i < tp_ice->comp_cnt; ++i) {
tp_ice->last_cand_cnt +=
pj_ice_strans_get_cands_count(tp_ice->ice_st, i+1);
}
}
}
return status;
@ -1743,6 +2197,7 @@ static pj_status_t transport_get_info(pjmedia_transport *tp,
{
struct transport_ice *tp_ice = (struct transport_ice*)tp;
pj_ice_sess_cand cand;
pj_sockaddr_t *addr;
pj_status_t status;
pj_bzero(&info->sock_info, sizeof(info->sock_info));
@ -1753,17 +2208,57 @@ static pj_status_t transport_get_info(pjmedia_transport *tp,
if (status != PJ_SUCCESS)
return status;
pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.addr);
/* Address of the default candidate may not be available (e.g:
* STUN/TURN address is still being resolved), so let's find address
* of any other candidate, if still not available, the draft RFC
* seems to allow us using "0.0.0.0:9" in SDP.
*/
addr = NULL;
if (pj_sockaddr_has_addr(&cand.addr)) {
addr = &cand.addr;
} else if (pj_ice_strans_has_sess(tp_ice->ice_st)) {
unsigned i, cnt = PJ_ICE_ST_MAX_CAND;
pj_ice_sess_cand cands[PJ_ICE_ST_MAX_CAND];
pj_ice_strans_enum_cands(tp_ice->ice_st, 1, &cnt, cands);
for (i = 0; i < cnt && !addr; ++i) {
if (pj_sockaddr_has_addr(&cands[i].addr))
addr = &cands[i].addr;
}
}
if (addr) {
pj_sockaddr_cp(&info->sock_info.rtp_addr_name, addr);
} else {
pj_sockaddr_init(PJ_AF_INET, &info->sock_info.rtp_addr_name, NULL, 9);
}
/* Get RTCP default address */
if (tp_ice->use_rtcp_mux) {
pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr);
pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, addr);
} else if (tp_ice->comp_cnt > 1) {
status = pj_ice_strans_get_def_cand(tp_ice->ice_st, 2, &cand);
if (status != PJ_SUCCESS)
return status;
pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr);
/* Address of the default candidate may not be available (e.g:
* STUN/TURN address is still being resolved), so let's find address
* of any other candidate. If none is available, SDP must not include
* "a=rtcp" attribute.
*/
addr = NULL;
if (pj_sockaddr_has_addr(&cand.addr)) {
addr = &cand.addr;
} else if (pj_ice_strans_has_sess(tp_ice->ice_st)) {
unsigned i, cnt = PJ_ICE_ST_MAX_CAND;
pj_ice_sess_cand cands[PJ_ICE_ST_MAX_CAND];
pj_ice_strans_enum_cands(tp_ice->ice_st, 2, &cnt, cands);
for (i = 0; i < cnt && !addr; ++i) {
if (pj_sockaddr_has_addr(&cands[i].addr))
addr = &cands[i].addr;
}
}
if (addr) {
pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, addr);
}
}
/* Set remote address originating RTP & RTCP if this transport has
@ -2124,6 +2619,31 @@ static void ice_on_ice_complete(pj_ice_strans *ice_st,
}
static void ice_on_new_candidate(pj_ice_strans *ice_st,
const pj_ice_sess_cand *cand,
pj_bool_t last)
{
struct transport_ice *tp_ice;
ice_listener *il;
tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st);
if (!tp_ice) {
/* Destroy on progress */
return;
}
/* Notify application */
if (tp_ice->cb.on_new_candidate)
(*tp_ice->cb.on_new_candidate)(&tp_ice->base, cand, last);
for (il=tp_ice->listener.next; il!=&tp_ice->listener; il=il->next) {
if (il->cb.on_new_candidate) {
(*il->cb.on_new_candidate)(&tp_ice->base, cand, last);
}
}
}
/* Simulate lost */
static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
pjmedia_dir dir,

View File

@ -53,6 +53,7 @@ static void print_stack(int sig)
static void init_signals()
{
signal(SIGSEGV, &print_stack);
signal(SIGABRT, &print_stack);
}
#else

View File

@ -286,7 +286,7 @@
/**
* Maximum number of ICE components.
*/
#define PJ_ICE_MAX_COMP (2<<PJ_ICE_COMP_BITS)
#define PJ_ICE_MAX_COMP (1<<PJ_ICE_COMP_BITS)
/**
* Use the priority value according to the ice-draft.

View File

@ -170,6 +170,8 @@ typedef struct pj_ice_sess pj_ice_sess;
/** Forward declaration for pj_ice_sess_check */
typedef struct pj_ice_sess_check pj_ice_sess_check;
/** Forward declaration for pj_ice_sess_cand */
typedef struct pj_ice_sess_cand pj_ice_sess_cand;
/**
* This structure describes ICE component.
@ -221,6 +223,8 @@ typedef struct pj_ice_msg_data
pj_ice_sess *ice; /**< ICE session */
pj_ice_sess_checklist *clist; /**< Checklist */
unsigned ckid; /**< Check ID */
pj_ice_sess_cand *lcand; /**< Local cand */
pj_ice_sess_cand *rcand; /**< Remote cand */
} req;
} data;
@ -235,7 +239,7 @@ typedef struct pj_ice_msg_data
* (server reflexive, relayed or host), priority, foundation, and
* base.
*/
typedef struct pj_ice_sess_cand
struct pj_ice_sess_cand
{
/**
* The candidate ID.
@ -314,7 +318,7 @@ typedef struct pj_ice_sess_cand
*/
pj_sockaddr rel_addr;
} pj_ice_sess_cand;
};
/**
@ -379,6 +383,11 @@ struct pj_ice_sess_check
*/
pj_ice_sess_cand *rcand;
/**
* Foundation index, referring to foundation array defined in checklist.
*/
int foundation_idx;
/**
* Check priority.
*/
@ -457,6 +466,16 @@ struct pj_ice_sess_checklist
*/
pj_ice_sess_check checks[PJ_ICE_MAX_CHECKS];
/**
* Number of foundations.
*/
unsigned foundation_cnt;
/**
* Array of foundations, check foundation index refers to this array.
*/
pj_str_t foundation[PJ_ICE_MAX_CHECKS];
/**
* A timer used to perform periodic check for this checklist.
*/
@ -577,6 +596,39 @@ typedef struct pj_ice_rx_check
} pj_ice_rx_check;
/**
* This enumeration describes the modes of trickle ICE.
*/
typedef enum pj_ice_sess_trickle
{
/**
* Trickle ICE is disabled.
*/
PJ_ICE_SESS_TRICKLE_DISABLED,
/**
* Half trickle ICE. This mode has better interoperability when remote
* capability of ICE trickle is unknown at ICE initialization.
*
* As ICE initiator, it will convey all local ICE candidates to remote
* (just like regular ICE) and be ready to receive either response,
* trickle or regular ICE. As ICE answerer, it will do trickle ICE if
* it receives an offer with trickle ICE indication, otherwise it will do
* regular ICE.
*/
PJ_ICE_SESS_TRICKLE_HALF,
/**
* Full trickle ICE. Only use this mode if it is known that that remote
* supports trickle ICE. The discovery whether remote supports trickle
* ICE should be done prior to ICE initialization and done by application
* (ICE does not provide the discovery mechanism).
*/
PJ_ICE_SESS_TRICKLE_FULL
} pj_ice_sess_trickle;
/**
* This structure describes various ICE session options. Application
* configure the ICE session with these options by calling
@ -585,7 +637,8 @@ typedef struct pj_ice_rx_check
typedef struct pj_ice_sess_options
{
/**
* Specify whether to use aggressive nomination.
* Specify whether to use aggressive nomination. This setting can only
* be enabled when trickle ICE is disabled.
*/
pj_bool_t aggressive;
@ -612,6 +665,14 @@ typedef struct pj_ice_sess_options
*/
int controlled_agent_want_nom_timeout;
/**
* Trickle ICE mode. Note that, when enabled, aggressive nomination will
* be automatically disabled.
*
* Default value is PJ_ICE_SESS_TRICKLE_DISABLED.
*/
pj_ice_sess_trickle trickle;
} pj_ice_sess_options;
@ -639,6 +700,8 @@ struct pj_ice_sess
pj_bool_t is_complete; /**< Complete? */
pj_bool_t is_destroying; /**< Destroy is called */
pj_bool_t valid_pair_found; /**< First pair found */
pj_bool_t is_trickling; /**< End-of-candidates ind
sent/received? */
pj_status_t ice_status; /**< Error status. */
pj_timer_entry timer; /**< ICE timer. */
pj_ice_sess_cb cb; /**< Callback. */
@ -661,10 +724,14 @@ struct pj_ice_sess
/* Local candidates */
unsigned lcand_cnt; /**< # of local cand. */
pj_ice_sess_cand lcand[PJ_ICE_MAX_CAND]; /**< Array of cand. */
unsigned lcand_paired; /**< # of local cand
paired (trickling) */
/* Remote candidates */
unsigned rcand_cnt; /**< # of remote cand. */
pj_ice_sess_cand rcand[PJ_ICE_MAX_CAND]; /**< Array of cand. */
unsigned rcand_paired; /**< # of remote cand
paired (trickling) */
/** Array of transport datas */
pj_ice_msg_data tp_data[PJ_ICE_MAX_STUN + PJ_ICE_MAX_TURN];
@ -921,6 +988,44 @@ pj_ice_sess_create_check_list(pj_ice_sess *ice,
unsigned rem_cand_cnt,
const pj_ice_sess_cand rem_cand[]);
/**
* Update check list after new local or remote ICE candidates are added,
* or signal ICE session that trickling is done. Application typically would
* call this function after finding (and conveying) new local ICE candidates
* to remote, after receiving remote ICE candidates, or after receiving
* end-of-candidates indication.
*
* After check list is updated, ICE connectivity check will automatically
* start if check list has any candidate pair.
*
* This function is only applicable when trickle ICE is not disabled.
*
* @param ice ICE session instance.
* @param rem_ufrag Remote ufrag, as seen in the SDP received from
* the remote agent.
* @param rem_passwd Remote password, as seen in the SDP received from
* the remote agent.
* @param rem_cand_cnt Number of remote candidates.
* @param rem_cand Remote candidate array. Remote candidates are
* gathered from the SDP received from the remote
* agent.
* @param trickle_done Flag to indicate end of trickling, set to PJ_TRUE
* after all local candidates have been gathered AND
* after receiving end-of-candidate indication from
* remote.
*
* @return PJ_SUCCESS or the appropriate error code.
*/
PJ_DECL(pj_status_t)
pj_ice_sess_update_check_list(pj_ice_sess *ice,
const pj_str_t *rem_ufrag,
const pj_str_t *rem_passwd,
unsigned rem_cand_cnt,
const pj_ice_sess_cand rem_cand[],
pj_bool_t trickle_done);
/**
* Start ICE periodic check. This function will return immediately, and
* application will be notified about the connectivity check status in

View File

@ -200,6 +200,25 @@ typedef struct pj_ice_strans_cb
pj_ice_strans_op op,
pj_status_t status);
/**
* Callback to report a new ICE local candidate, e.g: after successful
* STUN Binding, after a successful TURN allocation. Only new candidates
* whose type is server reflexive or relayed will be notified via this
* callback. This callback also indicates end-of-candidate via parameter
* 'last'.
*
* Trickle ICE can use this callback to convey the new candidate
* to remote agent and monitor end-of-candidate indication.
*
* @param ice_st The ICE stream transport.
* @param cand The new local candidate, can be NULL when the last
* local candidate initialization failed/timeout.
* @param end_of_cand PJ_TRUE if this is the last of local candidate.
*/
void (*on_new_candidate)(pj_ice_strans *ice_st,
const pj_ice_sess_cand *cand,
pj_bool_t end_of_cand);
} pj_ice_strans_cb;
@ -701,6 +720,19 @@ PJ_DECL(pj_status_t) pj_ice_strans_get_options(pj_ice_strans *ice_st,
PJ_DECL(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st,
const pj_ice_sess_options *opt);
/**
* Update number of components of the ICE stream transport. This can only
* reduce the number of components from the initial value specified in
* pj_ice_strans_create() and before ICE session is initialized.
*
* @param ice_st The ICE stream transport.
* @param comp_cnt Number of components.
*
* @return PJ_SUCCESS on success, or the appropriate error.
*/
PJ_DECL(pj_status_t) pj_ice_strans_update_comp_cnt(pj_ice_strans *ice_st,
unsigned comp_cnt);
/**
* Get the group lock for this ICE stream transport.
*
@ -913,6 +945,36 @@ PJ_DECL(pj_status_t) pj_ice_strans_start_ice(pj_ice_strans *ice_st,
unsigned rcand_cnt,
const pj_ice_sess_cand rcand[]);
/**
* Update check list after new local or remote ICE candidates are added,
* or signal ICE session that trickling is done. Application typically would
* call this function after finding (and conveying) new local ICE candidates
* to remote, after receiving remote ICE candidates, or after receiving
* end-of-candidates indication.
*
* This function is only applicable when trickle ICE is not disabled and
* after ICE connectivity checks are started using pj_ice_strans_start_ice().
*
* @param ice_st The ICE stream transport.
* @param rem_ufrag Remote ufrag, as seen in the SDP received from
* the remote agent.
* @param rem_passwd Remote password, as seen in the SDP received from
* the remote agent.
* @param rcand_cnt Number of new remote candidates in the array.
* @param rcand New remote candidates array.
* @param rcand_end Set to PJ_TRUE if remote has signalled
* end-of-candidate.
*
* @return PJ_SUCCESS, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pj_ice_strans_update_check_list(
pj_ice_strans *ice_st,
const pj_str_t *rem_ufrag,
const pj_str_t *rem_passwd,
unsigned rcand_cnt,
const pj_ice_sess_cand rcand[],
pj_bool_t rcand_end);
/**
* Retrieve the candidate pair that has been nominated and successfully
* checked for the specified component. If ICE negotiation is still in
@ -999,16 +1061,19 @@ PJ_DECL(pj_status_t) pj_ice_strans_sendto(pj_ice_strans *ice_st,
/**
* Send outgoing packet using this transport.
* Send outgoing packet using this transport.
* Application can send data (normally RTP or RTCP packets) at any time
* by calling this function. This function takes a destination
* address as one of the arguments, and this destination address should
* be taken from the default transport address of the component (that is
* the address in SDP c= and m= lines, or in a=rtcp attribute).
* If ICE negotiation is in progress, this function will send the data
* to the destination address. Otherwise if ICE negotiation has completed
* successfully, this function will send the data to the nominated remote
* address, as negotiated by ICE.
* the address in SDP c= and m= lines, or in a=rtcp attribute).
* If ICE negotiation is in progress, this function will try to send the data
* via any valid candidate pair (which has passed ICE connectivity test).
* If ICE negotiation has completed successfully, this function will send
* the data to the nominated remote address, as negotiated by ICE.
* If the ICE negotiation fails or valid candidate pair is not yet available,
* this function will send the data using default candidate to the specified
* destination address.
*
* Note that application shouldn't mix using pj_ice_strans_sendto() and
* pj_ice_strans_sendto2() to avoid inconsistent calling of

View File

@ -391,7 +391,7 @@ PJ_DECL(pj_status_t) pj_stun_sock_create(pj_stun_config *stun_cfg,
* queued, or the appropriate error code on failure.
* When this function returns PJ_SUCCESS, the final
* result of the allocation process will be notified
* to application in \a on_state() callback.
* to application in \a on_status() callback.
*/
PJ_DECL(pj_status_t) pj_stun_sock_start(pj_stun_sock *stun_sock,
const pj_str_t *domain,

View File

@ -70,6 +70,7 @@ struct test_cfg
struct test_result expected;/* Expected result */
pj_bool_t nom_regular; /* Use regular nomination? */
pj_ice_sess_trickle trickle; /* Trickle ICE mode */
};
/* ICE endpoint state */
@ -81,6 +82,9 @@ struct ice_ept
pj_str_t ufrag; /* username fragment. */
pj_str_t pass; /* password */
/* Trickle ICE */
pj_bool_t last_cand; /* Got last candidate? */
};
/* Session param */
@ -122,6 +126,10 @@ static void ice_on_rx_data(pj_ice_strans *ice_st,
static void ice_on_ice_complete(pj_ice_strans *ice_st,
pj_ice_strans_op op,
pj_status_t status);
static void ice_on_new_candidate(pj_ice_strans *ice_st,
const pj_ice_sess_cand *cand,
pj_bool_t last);
static void destroy_sess(struct test_sess *sess, unsigned wait_msec);
#if USE_IPV6
@ -235,9 +243,11 @@ static int create_ice_strans(struct test_sess *test_sess,
pj_bzero(&ice_cb, sizeof(ice_cb));
ice_cb.on_rx_data = &ice_on_rx_data;
ice_cb.on_ice_complete = &ice_on_ice_complete;
ice_cb.on_new_candidate = &ice_on_new_candidate;
/* Init ICE stream transport configuration structure */
pj_ice_strans_cfg_default(&ice_cfg);
ice_cfg.opt.trickle = ept->cfg.trickle;
pj_memcpy(&ice_cfg.stun_cfg, test_sess->stun_cfg, sizeof(pj_stun_config));
if ((ept->cfg.enable_stun & SRV)==SRV || (ept->cfg.enable_turn & SRV)==SRV)
ice_cfg.resolver = test_sess->resolver;
@ -452,6 +462,32 @@ static void ice_on_ice_complete(pj_ice_strans *ice_st,
}
}
static void ice_on_new_candidate(pj_ice_strans *ice_st,
const pj_ice_sess_cand *cand,
pj_bool_t last)
{
struct ice_ept *ept;
char buf1[PJ_INET6_ADDRSTRLEN+10];
char buf2[PJ_INET6_ADDRSTRLEN+10];
ept = (struct ice_ept*) pj_ice_strans_get_user_data(ice_st);
if (!ept)
return;
ept->last_cand = last;
if (cand) {
PJ_LOG(4,(THIS_FILE, INDENT "%p: discovered a new candidate "
"comp=%d, type=%s, addr=%s, baseaddr=%s, end=%d",
ept->ice, cand->comp_id,
pj_ice_get_cand_type_name(cand->type),
pj_sockaddr_print(&cand->addr, buf1, sizeof(buf1), 3),
pj_sockaddr_print(&cand->base_addr, buf2, sizeof(buf2), 3),
last));
} else if (ept->ice && last) {
PJ_LOG(4,(THIS_FILE, INDENT "%p: end of candidate", ept->ice));
}
}
/* Start ICE negotiation on the endpoint, based on parameter from
* the other endpoint.
@ -459,18 +495,21 @@ static void ice_on_ice_complete(pj_ice_strans *ice_st,
static pj_status_t start_ice(struct ice_ept *ept, const struct ice_ept *remote)
{
pj_ice_sess_cand rcand[32];
unsigned i, rcand_cnt = 0;
unsigned rcand_cnt = 0;
pj_status_t status;
/* Enum remote candidates */
for (i=0; i<remote->cfg.comp_cnt; ++i) {
unsigned cnt = PJ_ARRAY_SIZE(rcand) - rcand_cnt;
status = pj_ice_strans_enum_cands(remote->ice, i+1, &cnt, rcand+rcand_cnt);
if (status != PJ_SUCCESS) {
app_perror(INDENT "err: pj_ice_strans_enum_cands()", status);
return status;
if (ept->cfg.trickle == PJ_ICE_SESS_TRICKLE_DISABLED) {
unsigned i;
for (i=0; i<remote->cfg.comp_cnt; ++i) {
unsigned cnt = PJ_ARRAY_SIZE(rcand) - rcand_cnt;
status = pj_ice_strans_enum_cands(remote->ice, i+1, &cnt, rcand+rcand_cnt);
if (status != PJ_SUCCESS) {
app_perror(INDENT "err: pj_ice_strans_enum_cands()", status);
return status;
}
rcand_cnt += cnt;
}
rcand_cnt += cnt;
}
status = pj_ice_strans_start_ice(ept->ice, &remote->ufrag, &remote->pass,
@ -1228,3 +1267,338 @@ on_return:
return rc;
}
struct timer_data
{
struct test_sess *sess;
unsigned caller_last_cand_cnt[PJ_ICE_MAX_COMP];
unsigned callee_last_cand_cnt[PJ_ICE_MAX_COMP];
};
/* Timer callback to check & signal new candidates */
static void timer_new_cand(pj_timer_heap_t *th, pj_timer_entry *te)
{
struct timer_data *data = (struct timer_data*)te->user_data;
struct test_sess *sess = data->sess;
struct ice_ept *caller = &sess->caller;
struct ice_ept *callee = &sess->callee;
pj_bool_t caller_last_cand, callee_last_cand;
unsigned i, ncomp;
pj_status_t rc;
/* ICE transport may have been destroyed */
if (!caller->ice || !callee->ice)
return;
caller_last_cand = caller->last_cand;
callee_last_cand = callee->last_cand;
//PJ_LOG(3,(THIS_FILE, INDENT "End-of-cand status: caller=%d callee=%d",
// caller_last_cand, callee_last_cand));
ncomp = PJ_MIN(caller->cfg.comp_cnt, callee->cfg.comp_cnt);
for (i = 0; i < ncomp; ++i) {
pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
unsigned j, cnt;
/* Check caller candidates */
cnt = PJ_ICE_ST_MAX_CAND;
rc = pj_ice_strans_enum_cands(caller->ice, i+1, &cnt, cand);
if (rc != PJ_SUCCESS) {
app_perror(INDENT "err: caller pj_ice_strans_enum_cands()", rc);
continue;
}
if (cnt > data->caller_last_cand_cnt[i]) {
unsigned new_cnt = cnt - data->caller_last_cand_cnt[i];
/* Update remote with new candidates */
rc = pj_ice_strans_update_check_list(callee->ice,
&caller->ufrag,
&caller->pass,
new_cnt, &cand[cnt - new_cnt],
caller_last_cand && (i==ncomp-1));
if (rc != PJ_SUCCESS) {
app_perror(INDENT "err: callee pj_ice_strans_update_check_list()", rc);
continue;
}
data->caller_last_cand_cnt[i] = cnt;
PJ_LOG(4,(THIS_FILE, INDENT "Updated callee with %d new candidates %s",
new_cnt, (caller_last_cand?"(last)":"")));
for (j = 0; j < new_cnt; ++j) {
pj_ice_sess_cand *c = &cand[cnt - new_cnt + j];
char buf1[PJ_INET6_ADDRSTRLEN+10];
char buf2[PJ_INET6_ADDRSTRLEN+10];
PJ_LOG(4,(THIS_FILE, INDENT
"%d: comp=%d, type=%s, addr=%s, baseaddr=%s",
j+1, c->comp_id,
pj_ice_get_cand_type_name(c->type),
pj_sockaddr_print(&c->addr, buf1, sizeof(buf1), 3),
pj_sockaddr_print(&c->base_addr, buf2, sizeof(buf2), 3)
));
}
}
/* Check callee candidates */
cnt = PJ_ICE_ST_MAX_CAND;
rc = pj_ice_strans_enum_cands(callee->ice, i+1, &cnt, cand);
if (rc != PJ_SUCCESS) {
app_perror(INDENT "err: caller pj_ice_strans_enum_cands()", rc);
continue;
}
if (cnt > data->callee_last_cand_cnt[i]) {
unsigned new_cnt = cnt - data->callee_last_cand_cnt[i];
/* Update remote with new candidates */
rc = pj_ice_strans_update_check_list(caller->ice,
&callee->ufrag,
&callee->pass,
new_cnt, &cand[cnt - new_cnt],
callee_last_cand && (i==ncomp-1));
if (rc != PJ_SUCCESS) {
app_perror(INDENT "err: caller pj_ice_strans_update_check_list()", rc);
continue;
}
data->callee_last_cand_cnt[i] = cnt;
PJ_LOG(4,(THIS_FILE, INDENT "Updated caller with %d new candidates %s",
new_cnt, (callee_last_cand?"(last)":"")));
for (j = 0; j < new_cnt; ++j) {
pj_ice_sess_cand *c = &cand[cnt - new_cnt + j];
char buf1[PJ_INET6_ADDRSTRLEN+10];
char buf2[PJ_INET6_ADDRSTRLEN+10];
PJ_LOG(4,(THIS_FILE, INDENT
"%d: comp=%d, type=%s, addr=%s, baseaddr=%s",
j+1, c->comp_id,
pj_ice_get_cand_type_name(c->type),
pj_sockaddr_print(&c->addr, buf1, sizeof(buf1), 3),
pj_sockaddr_print(&c->base_addr, buf2, sizeof(buf2), 3)
));
}
}
}
if (!caller_last_cand || !callee_last_cand) {
/* Reschedule until all candidates are gathered */
pj_time_val timeout = {0, 10};
pj_time_val_normalize(&timeout);
pj_timer_heap_schedule(th, te, &timeout);
//PJ_LOG(3,(THIS_FILE, INDENT "Rescheduled new candidate check"));
}
}
static int perform_trickle_test(const char *title,
pj_stun_config *stun_cfg,
unsigned server_flag,
struct test_cfg *caller_cfg,
struct test_cfg *callee_cfg,
struct sess_param *test_param)
{
pjlib_state pjlib_state;
struct test_sess *sess;
struct timer_data timer_data;
pj_timer_entry te_new_cand;
int rc;
PJ_LOG(3,(THIS_FILE, "%s, %d vs %d components",
title, caller_cfg->comp_cnt, callee_cfg->comp_cnt));
capture_pjlib_state(stun_cfg, &pjlib_state);
rc = create_sess(stun_cfg, server_flag, caller_cfg, callee_cfg,
test_param, &sess);
if (rc != 0)
return rc;
/* Init ICE on caller */
rc = pj_ice_strans_init_ice(sess->caller.ice, sess->caller.cfg.role,
&sess->caller.ufrag, &sess->caller.pass);
if (rc != PJ_SUCCESS) {
app_perror(INDENT "err: caller pj_ice_strans_init_ice()", rc);
rc = -100;
goto on_return;
}
/* Init ICE on callee */
rc = pj_ice_strans_init_ice(sess->callee.ice, sess->callee.cfg.role,
&sess->callee.ufrag, &sess->callee.pass);
if (rc != PJ_SUCCESS) {
app_perror(INDENT "err: callee pj_ice_strans_init_ice()", rc);
rc = -110;
goto on_return;
}
/* Start ICE on callee */
rc = start_ice(&sess->callee, &sess->caller);
if (rc != PJ_SUCCESS) {
int retval = (rc == sess->callee.cfg.expected.start_status)?0:-120;
rc = retval;
goto on_return;
}
/* Start ICE on caller */
rc = start_ice(&sess->caller, &sess->callee);
if (rc != PJ_SUCCESS) {
int retval = (rc == sess->caller.cfg.expected.start_status)?0:-130;
rc = retval;
goto on_return;
}
/* Start polling new candidate */
//if (!sess->caller.last_cand || !sess->callee.last_cand)
{
pj_time_val timeout = {0, 10};
pj_bzero(&timer_data, sizeof(timer_data));
timer_data.sess = sess;
pj_time_val_normalize(&timeout);
pj_timer_entry_init(&te_new_cand, 0, &timer_data, &timer_new_cand);
pj_timer_heap_schedule(stun_cfg->timer_heap, &te_new_cand, &timeout);
}
WAIT_UNTIL(30000, ALL_DONE, rc);
if (!ALL_DONE) {
PJ_LOG(3,(THIS_FILE, INDENT "err: negotiation timed-out"));
rc = -140;
goto on_return;
}
if (rc != 0)
goto on_return;
if (sess->caller.result.nego_status != sess->caller.cfg.expected.nego_status) {
app_perror(INDENT "err: caller negotiation failed", sess->caller.result.nego_status);
rc = -150;
goto on_return;
}
if (sess->callee.result.nego_status != sess->callee.cfg.expected.nego_status) {
app_perror(INDENT "err: callee negotiation failed", sess->callee.result.nego_status);
rc = -160;
goto on_return;
}
/* Verify that both agents have agreed on the same pair */
rc = check_pair(&sess->caller, &sess->callee, -170);
if (rc != 0) {
goto on_return;
}
rc = check_pair(&sess->callee, &sess->caller, -180);
if (rc != 0) {
goto on_return;
}
/* Looks like everything is okay */
/* Destroy ICE stream transports first to let it de-allocate
* TURN relay (otherwise there'll be timer/memory leak, unless
* we wait for long time in the last poll_events() below).
*/
if (sess->caller.ice) {
pj_ice_strans_destroy(sess->caller.ice);
sess->caller.ice = NULL;
}
if (sess->callee.ice) {
pj_ice_strans_destroy(sess->callee.ice);
sess->callee.ice = NULL;
}
on_return:
/* Wait.. */
poll_events(stun_cfg, 200, PJ_FALSE);
/* Now destroy everything */
destroy_sess(sess, 500);
/* Flush events */
poll_events(stun_cfg, 100, PJ_FALSE);
if (rc == 0)
rc = check_pjlib_state(stun_cfg, &pjlib_state);
return rc;
}
/* Simple trickle ICE test */
int trickle_ice_test(void)
{
pj_pool_t *pool;
pj_stun_config stun_cfg;
struct sess_param test_param;
unsigned i;
int rc;
struct sess_cfg_t {
const char *title;
unsigned server_flag;
struct test_cfg ua1;
struct test_cfg ua2;
} cfg[] = {
{
"With host-only",
0x1FFF,
/*Role comp# host? stun? turn? flag? ans_del snd_del des_del */
{ROLE1, 1, YES, NO, NO, CLIENT_IPV4, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS, PJ_SUCCESS}},
{ROLE2, 1, YES, NO, NO, CLIENT_IPV4, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS, PJ_SUCCESS}}
},
{
"With turn-only",
0x1FFF,
/*Role comp# host? stun? turn? flag? ans_del snd_del des_del */
{ROLE1, 1, NO, NO, YES, CLIENT_IPV4, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS, PJ_SUCCESS}},
{ROLE2, 1, NO, NO, YES, CLIENT_IPV4, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS, PJ_SUCCESS}}
},
{
/* STUN candidates will be pruned */
"With host+turn",
0x1FFF,
/*Role comp# host? stun? turn? flag? ans_del snd_del des_del */
{ROLE1, 1, YES, YES, YES, CLIENT_IPV4, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS, PJ_SUCCESS}},
{ROLE2, 1, YES, YES, YES, CLIENT_IPV4, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS, PJ_SUCCESS}}
}};
PJ_LOG(3,(THIS_FILE, "Trickle ICE"));
pj_log_push_indent();
pool = pj_pool_create(mem, NULL, 512, 512, NULL);
rc = create_stun_config(pool, &stun_cfg);
if (rc != PJ_SUCCESS) {
pj_pool_release(pool);
pj_log_pop_indent();
return -10;
}
for (i = 0; i < PJ_ARRAY_SIZE(cfg) && !rc; ++i) {
unsigned c1, c2;
cfg[i].ua1.trickle = PJ_ICE_SESS_TRICKLE_FULL;
cfg[i].ua2.trickle = PJ_ICE_SESS_TRICKLE_FULL;
for (c1 = 1; c1 <= 2 && !rc; ++c1) {
for (c2 = 1; c2 <= 2 && !rc; ++c2) {
pj_bzero(&test_param, sizeof(test_param));
cfg[i].ua1.comp_cnt = c1;
cfg[i].ua2.comp_cnt = c2;
rc = perform_trickle_test(cfg[i].title,
&stun_cfg,
cfg[i].server_flag,
&cfg[i].ua1,
&cfg[i].ua2,
&test_param);
}
}
}
destroy_stun_config(&stun_cfg);
pj_pool_release(pool);
pj_log_pop_indent();
return rc;
}

View File

@ -53,6 +53,7 @@ static void print_stack(int sig)
static void init_signals()
{
signal(SIGSEGV, &print_stack);
signal(SIGABRT, &print_stack);
}
#else

View File

@ -29,6 +29,8 @@
#define CERT_PRIVKEY_FILE CERT_DIR "privkey.pem"
#define CERT_PRIVKEY_PASS ""
#define RETURN_ERROR(rc) {app_perror("", rc);return rc;}
static pj_bool_t stun_on_data_recvfrom(pj_activesock_t *asock,
void *data,
pj_size_t size,
@ -101,7 +103,7 @@ pj_status_t create_test_server(pj_stun_config *stun_cfg,
} else {
status = pj_gethostip(GET_AF(use_ipv6), &hostip);
if (status != PJ_SUCCESS)
return status;
RETURN_ERROR(status);
}
pool = pj_pool_create(mem, THIS_FILE, 512, 512, NULL);
@ -122,7 +124,7 @@ pj_status_t create_test_server(pj_stun_config *stun_cfg,
0, &test_srv->dns_server);
if (status != PJ_SUCCESS) {
destroy_test_server(test_srv);
return status;
RETURN_ERROR(status);
}
/* Add DNS A record for the domain, for fallback */
@ -161,14 +163,14 @@ pj_status_t create_test_server(pj_stun_config *stun_cfg,
&test_srv->stun_sock, NULL);
if (status != PJ_SUCCESS) {
destroy_test_server(test_srv);
return status;
RETURN_ERROR(status);
}
status = pj_activesock_start_recvfrom(test_srv->stun_sock, pool,
MAX_STUN_PKT, 0);
if (status != PJ_SUCCESS) {
destroy_test_server(test_srv);
return status;
RETURN_ERROR(status);
}
if (test_srv->dns_server && (flags & CREATE_STUN_SERVER_DNS_SRV)) {
@ -222,7 +224,7 @@ pj_status_t create_test_server(pj_stun_config *stun_cfg,
if (status != PJ_SUCCESS) {
destroy_test_server(test_srv);
return status;
RETURN_ERROR(status);
}
status = pj_activesock_start_recvfrom(test_srv->turn_sock, pool,
@ -236,20 +238,26 @@ pj_status_t create_test_server(pj_stun_config *stun_cfg,
status = pj_sock_socket(GET_AF(use_ipv6), pj_SOCK_STREAM(), 0,
&sock_fd);
if (status != PJ_SUCCESS) {
return status;
RETURN_ERROR(status);
}
{
int val = 1;
pj_sock_setsockopt(sock_fd, pj_SOL_SOCKET(), pj_SO_REUSEADDR(),
&val, sizeof(val));
}
status = pj_sock_bind(sock_fd, &bound_addr,
pj_sockaddr_get_len(&bound_addr));
if (status != PJ_SUCCESS) {
pj_sock_close(sock_fd);
return status;
RETURN_ERROR(status);
}
status = pj_sock_listen(sock_fd, 4);
if (status != PJ_SUCCESS) {
pj_sock_close(sock_fd);
return status;
RETURN_ERROR(status);
}
status = pj_activesock_create(pool, sock_fd, pj_SOCK_STREAM(),
@ -259,7 +267,7 @@ pj_status_t create_test_server(pj_stun_config *stun_cfg,
&test_srv->turn_sock);
if (status != PJ_SUCCESS) {
pj_sock_close(sock_fd);
return status;
RETURN_ERROR(status);
}
status = pj_activesock_start_accept(test_srv->turn_sock,
@ -309,7 +317,7 @@ pj_status_t create_test_server(pj_stun_config *stun_cfg,
#endif
if (status != PJ_SUCCESS) {
destroy_test_server(test_srv);
return status;
RETURN_ERROR(status);
}
if (test_srv->dns_server && (flags & CREATE_TURN_SERVER_DNS_SRV)) {

View File

@ -21,14 +21,16 @@
#include <pjlib.h>
#include <pj/compat/socket.h>
void app_perror(const char *msg, pj_status_t rc)
void app_perror_dbg(const char *msg, pj_status_t rc,
const char *file, int line)
{
char errbuf[256];
PJ_CHECK_STACK();
pj_strerror(rc, errbuf, sizeof(errbuf));
PJ_LOG(1,("test", "%s: [pj_status_t=%d] %s", msg, rc, errbuf));
PJ_LOG(1,("test", "%s:%d: %s: [pj_status_t=%d] %s", file, line, msg,
rc, errbuf));
}
/* Set socket to nonblocking. */
@ -222,6 +224,10 @@ static int test_inner(void)
DO_TEST(ice_test());
#endif
#if INCLUDE_TRICKLE_ICE_TEST
DO_TEST(trickle_ice_test());
#endif
#if INCLUDE_STUN_SOCK_TEST
DO_TEST(stun_sock_test());
#endif

View File

@ -29,6 +29,7 @@
#define INCLUDE_STUN_TEST 1
#define INCLUDE_ICE_TEST 1
#define INCLUDE_TRICKLE_ICE_TEST 1
#define INCLUDE_STUN_SOCK_TEST 1
#define INCLUDE_TURN_SOCK_TEST 1
#define INCLUDE_CONCUR_TEST 1
@ -52,10 +53,13 @@ int sess_auth_test(void);
int stun_sock_test(void);
int turn_sock_test(void);
int ice_test(void);
int trickle_ice_test(void);
int concur_test(void);
int test_main(void);
extern void app_perror(const char *title, pj_status_t rc);
#define app_perror(msg, rc) app_perror_dbg(msg, rc, __FILE__, __LINE__)
extern void app_perror_dbg(const char *msg, pj_status_t rc,
const char *file, int line);
extern void app_set_sock_nb(pj_sock_t sock);
extern pj_pool_factory *mem;

File diff suppressed because it is too large Load Diff

View File

@ -226,6 +226,11 @@ struct pj_ice_strans
pj_bool_t destroy_req;/**< Destroy has been called? */
pj_bool_t cb_called; /**< Init error callback called?*/
pj_bool_t call_send_cb;/**< Need to call send cb? */
pj_bool_t rem_cand_end;/**< Trickle ICE: remote has
signalled end of candidate? */
pj_bool_t loc_cand_end;/**< Trickle ICE: local has
signalled end of candidate? */
};
@ -961,6 +966,15 @@ PJ_DEF(pj_status_t) pj_ice_strans_create( const char *name,
/* Check if all candidates are ready (this may call callback) */
sess_init_update(ice_st);
/* If ICE init done, notify app about end of candidate gathering via
* on_new_candidate() callback.
*/
if (ice_st->state==PJ_ICE_STRANS_STATE_READY &&
ice_st->cb.on_new_candidate)
{
(*ice_st->cb.on_new_candidate)(ice_st, NULL, PJ_TRUE);
}
pj_log_pop_indent();
return PJ_SUCCESS;
@ -1133,6 +1147,13 @@ static void sess_init_update(pj_ice_strans *ice_st)
if (ice_st->cb.on_ice_complete)
(*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_INIT,
status);
/* Tell ICE session that trickling is done */
ice_st->loc_cand_end = PJ_TRUE;
if (ice_st->ice && ice_st->ice->is_trickling && ice_st->rem_cand_end) {
pj_ice_sess_update_check_list(ice_st->ice, NULL, NULL, 0, NULL,
PJ_TRUE);
}
}
/*
@ -1179,6 +1200,49 @@ PJ_DEF(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st,
return PJ_SUCCESS;
}
/*
* Update number of components of the ICE stream transport.
*/
PJ_DEF(pj_status_t) pj_ice_strans_update_comp_cnt( pj_ice_strans *ice_st,
unsigned comp_cnt)
{
unsigned i;
PJ_ASSERT_RETURN(ice_st && comp_cnt < ice_st->comp_cnt, PJ_EINVAL);
PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EINVALIDOP);
pj_grp_lock_acquire(ice_st->grp_lock);
for (i=comp_cnt; i<ice_st->comp_cnt; ++i) {
pj_ice_strans_comp *comp = ice_st->comp[i];
unsigned j;
/* Destroy the component */
for (j = 0; j < ice_st->cfg.stun_tp_cnt; ++j) {
if (comp->stun[j].sock) {
pj_stun_sock_destroy(comp->stun[j].sock);
comp->stun[j].sock = NULL;
}
}
for (j = 0; j < ice_st->cfg.turn_tp_cnt; ++j) {
if (comp->turn[j].sock) {
pj_turn_sock_destroy(comp->turn[j].sock);
comp->turn[j].sock = NULL;
}
}
comp->cand_cnt = 0;
ice_st->comp[i] = NULL;
}
ice_st->comp_cnt = comp_cnt;
pj_grp_lock_release(ice_st->grp_lock);
PJ_LOG(4,(ice_st->obj_name,
"Updated ICE stream transport components number to %d",
comp_cnt));
return PJ_SUCCESS;
}
/**
* Get the group lock for this ICE stream transport.
*/
@ -1461,42 +1525,29 @@ PJ_DEF(pj_status_t) pj_ice_strans_change_role( pj_ice_strans *ice_st,
return pj_ice_sess_change_role(ice_st->ice, new_role);
}
/*
* Start ICE processing !
*/
PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st,
const pj_str_t *rem_ufrag,
const pj_str_t *rem_passwd,
unsigned rem_cand_cnt,
const pj_ice_sess_cand rem_cand[])
static pj_status_t setup_turn_perm( pj_ice_strans *ice_st,
unsigned rem_cand_cnt,
const pj_ice_sess_cand rem_cand[])
{
unsigned n;
pj_status_t status;
PJ_ASSERT_RETURN(ice_st && rem_ufrag && rem_passwd &&
rem_cand_cnt && rem_cand, PJ_EINVAL);
/* Mark start time */
pj_gettimeofday(&ice_st->start_time);
/* Build check list */
status = pj_ice_sess_create_check_list(ice_st->ice, rem_ufrag, rem_passwd,
rem_cand_cnt, rem_cand);
if (status != PJ_SUCCESS)
return status;
/* If we have TURN candidate, now is the time to create the permissions */
for (n = 0; n < ice_st->cfg.turn_tp_cnt; ++n) {
for (n = 0; n < ice_st->cfg.turn_tp_cnt && rem_cand_cnt; ++n) {
unsigned i;
for (i=0; i<ice_st->comp_cnt; ++i) {
pj_ice_strans_comp *comp = ice_st->comp[i];
pj_turn_session_info info;
pj_sockaddr addrs[PJ_ICE_ST_MAX_CAND];
unsigned j, count=0;
if (!comp->turn[n].sock)
continue;
status = pj_turn_sock_get_info(comp->turn[n].sock, &info);
if (status != PJ_SUCCESS || info.state != PJ_TURN_STATE_READY)
continue;
/* Gather remote addresses for this component */
for (j=0; j<rem_cand_cnt && count<PJ_ARRAY_SIZE(addrs); ++j) {
if (rem_cand[j].comp_id==i+1 &&
@ -1519,6 +1570,40 @@ PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st,
}
}
return PJ_SUCCESS;
}
/*
* Start ICE processing !
*/
PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st,
const pj_str_t *rem_ufrag,
const pj_str_t *rem_passwd,
unsigned rem_cand_cnt,
const pj_ice_sess_cand rem_cand[])
{
pj_status_t status;
PJ_ASSERT_RETURN(ice_st && rem_ufrag && rem_passwd &&
((ice_st->ice && ice_st->ice->is_trickling) ||
(rem_cand_cnt && rem_cand)), PJ_EINVAL);
/* Mark start time */
pj_gettimeofday(&ice_st->start_time);
/* Build check list */
status = pj_ice_sess_create_check_list(ice_st->ice, rem_ufrag, rem_passwd,
rem_cand_cnt, rem_cand);
if (status != PJ_SUCCESS)
return status;
/* If we have TURN candidate, now is the time to create the permissions */
status = setup_turn_perm(ice_st, rem_cand_cnt, rem_cand);
if (status != PJ_SUCCESS) {
pj_ice_strans_stop_ice(ice_st);
return status;
}
/* Start ICE negotiation! */
status = pj_ice_sess_start_check(ice_st->ice);
if (status != PJ_SUCCESS) {
@ -1530,6 +1615,52 @@ PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st,
return status;
}
/*
* Update check list after discovering and conveying new local ICE candidate,
* or receiving update of remote ICE candidates in trickle ICE.
*/
PJ_DEF(pj_status_t) pj_ice_strans_update_check_list(
pj_ice_strans *ice_st,
const pj_str_t *rem_ufrag,
const pj_str_t *rem_passwd,
unsigned rem_cand_cnt,
const pj_ice_sess_cand rem_cand[],
pj_bool_t rcand_end)
{
pj_status_t status;
PJ_ASSERT_RETURN(ice_st && ((rem_cand_cnt==0) ||
(rem_ufrag && rem_passwd && rem_cand)),
PJ_EINVAL);
pj_grp_lock_acquire(ice_st->grp_lock);
/* If we have TURN candidate, update the permissions */
status = setup_turn_perm(ice_st, rem_cand_cnt, rem_cand);
if (status != PJ_SUCCESS) {
pj_ice_strans_stop_ice(ice_st);
return status;
}
/* Update checklist */
if (rcand_end && !ice_st->rem_cand_end)
ice_st->rem_cand_end = PJ_TRUE;
status = pj_ice_sess_update_check_list(ice_st->ice, rem_ufrag, rem_passwd,
rem_cand_cnt, rem_cand,
(ice_st->rem_cand_end &&
ice_st->loc_cand_end));
if (status != PJ_SUCCESS) {
pj_ice_strans_stop_ice(ice_st);
pj_grp_lock_release(ice_st->grp_lock);
return status;
}
pj_grp_lock_release(ice_st->grp_lock);
return PJ_SUCCESS;
}
/*
* Get valid pair.
*/
@ -1659,18 +1790,20 @@ static pj_status_t send_data(pj_ice_strans *ice_st,
}
}
/* If ICE is available, send data with ICE, otherwise send with the
* default candidate selected during initialization.
/* If ICE is available, send data with ICE. If ICE nego is not completed
* yet, ICE will try to send using any valid candidate pair. For any
* failure, it will fallback to sending with the default candidate
* selected during initialization.
*
* https://trac.pjsip.org/repos/ticket/1416:
* Once ICE has failed, also send data with the default candidate.
*/
if (ice_st->ice && ice_st->state == PJ_ICE_STRANS_STATE_RUNNING) {
if (ice_st->ice && ice_st->state <= PJ_ICE_STRANS_STATE_RUNNING) {
status = pj_ice_sess_send_data(ice_st->ice, comp_id, buf, data_len);
pj_grp_lock_release(ice_st->grp_lock);
goto on_return;
if (status == PJ_SUCCESS || status == PJ_EPENDING) {
pj_grp_lock_release(ice_st->grp_lock);
goto on_return;
}
}
pj_grp_lock_release(ice_st->grp_lock);
@ -2278,6 +2411,7 @@ static pj_bool_t stun_on_status(pj_stun_sock *stun_sock,
"Binding discovery complete" :
"srflx address changed";
pj_bool_t dup = PJ_FALSE;
pj_bool_t init_done;
if (info.mapped_addr.addr.sa_family == pj_AF_INET() &&
cand->base_addr.addr.sa_family == pj_AF_INET6())
@ -2346,6 +2480,22 @@ static pj_bool_t stun_on_status(pj_stun_sock *stun_sock,
/* Otherwise update the address */
pj_sockaddr_cp(&cand->addr, &info.mapped_addr);
cand->status = PJ_SUCCESS;
/* Add the candidate (for trickle ICE) */
if (pj_ice_strans_has_sess(ice_st)) {
status = pj_ice_sess_add_cand(
ice_st->ice,
comp->comp_id,
cand->transport_id,
cand->type,
cand->local_pref,
&cand->foundation,
&cand->addr,
&cand->base_addr,
&cand->rel_addr,
pj_sockaddr_get_len(&cand->addr),
NULL);
}
}
PJ_LOG(4,(comp->ice_st->obj_name,
@ -2356,7 +2506,16 @@ static pj_bool_t stun_on_status(pj_stun_sock *stun_sock,
sizeof(ipaddr), 3)));
sess_init_update(ice_st);
/* Invoke on_new_candidate() callback */
init_done = (ice_st->state==PJ_ICE_STRANS_STATE_READY);
if (op == PJ_STUN_SOCK_BINDING_OP && status == PJ_SUCCESS &&
ice_st->cb.on_new_candidate && (!dup || init_done))
{
(*ice_st->cb.on_new_candidate)
(ice_st, (dup? NULL:cand), init_done);
}
if (op == PJ_STUN_SOCK_MAPPED_ADDR_CHANGE &&
ice_st->cb.on_ice_complete)
{
@ -2377,6 +2536,8 @@ static pj_bool_t stun_on_status(pj_stun_sock *stun_sock,
sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT,
"STUN binding request failed", status);
} else {
pj_bool_t init_done;
PJ_LOG(4,(ice_st->obj_name,
"STUN error is ignored for comp %d",
comp->comp_id));
@ -2391,6 +2552,14 @@ static pj_bool_t stun_on_status(pj_stun_sock *stun_sock,
}
sess_init_update(ice_st);
/* Invoke on_new_candidate() callback */
init_done = (ice_st->state==PJ_ICE_STRANS_STATE_READY);
if (op == PJ_STUN_SOCK_BINDING_OP &&
ice_st->cb.on_new_candidate && init_done)
{
(*ice_st->cb.on_new_candidate) (ice_st, NULL, PJ_TRUE);
}
}
}
break;
@ -2523,10 +2692,12 @@ static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state,
break;
}
}
pj_assert(cand != NULL);
pj_grp_lock_release(comp->ice_st->grp_lock);
if (cand == NULL)
goto on_return;
/* Update candidate */
pj_sockaddr_cp(&cand->addr, &rel_info.relay_addr);
pj_sockaddr_cp(&cand->base_addr, &rel_info.relay_addr);
@ -2569,8 +2740,67 @@ static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state,
pj_sockaddr_print(&rel_info.relay_addr, ipaddr,
sizeof(ipaddr), 3)));
/* For trickle ICE, add the candidate to ICE session and setup TURN
* permission for remote candidates.
*/
if (comp->ice_st->cfg.opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED &&
pj_ice_strans_has_sess(comp->ice_st))
{
pj_sockaddr addrs[PJ_ICE_ST_MAX_CAND];
pj_ice_sess *sess = comp->ice_st->ice;
unsigned j, count=0;
pj_status_t status;
/* Add the candidate */
status = pj_ice_sess_add_cand(comp->ice_st->ice,
comp->comp_id,
cand->transport_id,
cand->type,
cand->local_pref,
&cand->foundation,
&cand->addr,
&cand->base_addr,
&cand->rel_addr,
pj_sockaddr_get_len(&cand->addr),
NULL);
if (status != PJ_SUCCESS) {
PJ_PERROR(4,(comp->ice_st->obj_name, status,
"Comp %d/%d: failed to add TURN (tpid=%d) to ICE",
comp->comp_id, cand_idx, cand->transport_id));
sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT,
"adding TURN candidate failed", status);
}
/* Gather remote addresses for this component */
for (j=0; j<sess->rcand_cnt && count<PJ_ARRAY_SIZE(addrs); ++j) {
if (sess->rcand[j].addr.addr.sa_family==
rel_info.relay_addr.addr.sa_family)
{
pj_sockaddr_cp(&addrs[count++], &sess->rcand[j].addr);
}
}
if (count) {
status = pj_turn_sock_set_perm(turn_sock, count, addrs, 0);
if (status != PJ_SUCCESS) {
PJ_PERROR(4,(comp->ice_st->obj_name, status,
"Comp %d/%d: TURN set perm (tpid=%d) failed",
comp->comp_id, cand_idx, cand->transport_id));
sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT,
"TURN set permission failed", status);
}
}
}
sess_init_update(comp->ice_st);
/* Invoke on_new_candidate() callback */
if (comp->ice_st->cb.on_new_candidate) {
(*comp->ice_st->cb.on_new_candidate)
(comp->ice_st, cand,
(comp->ice_st->state==PJ_ICE_STRANS_STATE_READY));
}
} else if ((old_state == PJ_TURN_STATE_RESOLVING ||
old_state == PJ_TURN_STATE_RESOLVED ||
old_state == PJ_TURN_STATE_ALLOCATING) &&
@ -2622,6 +2852,13 @@ static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state,
sess_init_update(comp->ice_st);
/* Invoke on_new_candidate() callback */
if (comp->ice_st->cb.on_new_candidate &&
comp->ice_st->state==PJ_ICE_STRANS_STATE_READY)
{
(*comp->ice_st->cb.on_new_candidate)(comp->ice_st, NULL, PJ_TRUE);
}
} else if (new_state >= PJ_TURN_STATE_DEALLOCATING) {
pj_turn_session_info info;
@ -2654,6 +2891,7 @@ static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state,
}
}
on_return:
pj_grp_lock_dec_ref(comp->ice_st->grp_lock);
pj_log_pop_indent();

View File

@ -886,7 +886,12 @@ PJ_DEF(pj_status_t) pj_turn_session_set_perm( pj_turn_session *sess,
}
}
pj_assert(attr_added != 0);
/* No address to set */
if (attr_added == 0) {
pj_stun_msg_destroy_tdata(sess->stun, tdata);
pj_grp_lock_release(sess->grp_lock);
return PJ_SUCCESS;
}
/* Send the request */
status = pj_stun_session_send_msg(sess->stun, req_token, PJ_FALSE,

View File

@ -106,6 +106,7 @@ static void setup_socket_signal()
static void setup_signal_handler(void)
{
signal(SIGSEGV, &print_stack);
signal(SIGABRT, &print_stack);
}
#else

View File

@ -182,6 +182,7 @@ static void usage(void)
puts ("Media Transport Options:");
puts (" --use-ice Enable ICE (default:no)");
puts (" --ice-regular Use ICE regular nomination (default: aggressive)");
puts (" --ice-trickle=N Use trickle ICE? 0:disabled, 1:half, 2:full (default=0)");
puts (" --ice-max-hosts=N Set maximum number of ICE host candidates");
puts (" --ice-no-rtcp Disable RTCP component in ICE (default: no)");
puts (" --rtp-port=N Base port to try for RTP (default=4000)");
@ -370,7 +371,8 @@ static pj_status_t parse_args(int argc, char *argv[],
OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
OPT_AUTO_ANSWER, OPT_AUTO_PLAY, OPT_AUTO_PLAY_HANGUP, OPT_AUTO_LOOP,
OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_SND_CLOCK_RATE, OPT_STEREO,
OPT_USE_ICE, OPT_ICE_REGULAR, OPT_USE_SRTP, OPT_SRTP_SECURE,
OPT_USE_ICE, OPT_ICE_REGULAR, OPT_ICE_TRICKLE,
OPT_USE_SRTP, OPT_SRTP_SECURE,
OPT_USE_TURN, OPT_ICE_MAX_HOSTS, OPT_ICE_NO_RTCP, OPT_TURN_SRV,
OPT_TURN_TCP, OPT_TURN_USER, OPT_TURN_PASSWD, OPT_TURN_TLS,
OPT_TURN_TLS_CA_FILE, OPT_TURN_TLS_CERT_FILE,
@ -462,9 +464,10 @@ static pj_status_t parse_args(int argc, char *argv[],
{ "use-ice", 0, 0, OPT_USE_ICE},
{ "ice-regular",0, 0, OPT_ICE_REGULAR},
{ "use-turn", 0, 0, OPT_USE_TURN},
{ "ice-trickle",1, 0, OPT_ICE_TRICKLE},
{ "ice-max-hosts",1, 0, OPT_ICE_MAX_HOSTS},
{ "ice-no-rtcp",0, 0, OPT_ICE_NO_RTCP},
{ "use-turn", 0, 0, OPT_USE_TURN},
{ "turn-srv", 1, 0, OPT_TURN_SRV},
{ "turn-tcp", 0, 0, OPT_TURN_TCP},
#if PJ_HAS_SSL_SOCK
@ -1009,6 +1012,22 @@ static pj_status_t parse_args(int argc, char *argv[],
cur_acc->ice_cfg.ice_opt.aggressive = PJ_FALSE;
break;
case OPT_ICE_TRICKLE:
cfg->media_cfg.ice_opt.trickle =
cur_acc->ice_cfg.ice_opt.trickle = my_atoi(pj_optarg);
if (!pj_isdigit(*pj_optarg) || cfg->media_cfg.ice_opt.trickle>2) {
PJ_LOG(1,(THIS_FILE, "Invalid value for --ice-trickle option"));
return -1;
}
/* Automatically disable ICE aggressive mode */
if (cfg->media_cfg.ice_opt.trickle > 0) {
cfg->media_cfg.ice_opt.aggressive =
cur_acc->ice_cfg.ice_opt.aggressive = PJ_FALSE;
}
break;
case OPT_USE_TURN:
cfg->media_cfg.enable_turn =
cur_acc->turn_cfg.enable_turn = PJ_TRUE;
@ -1832,6 +1851,12 @@ static void write_account_settings(int acc_index, pj_str_t *result)
if (acc_cfg->ice_cfg.ice_opt.aggressive == PJ_FALSE)
pj_strcat2(result, "--ice-regular\n");
if (acc_cfg->ice_cfg.ice_opt.trickle > 0) {
pj_ansi_sprintf(line, "--ice-trickle %d\n",
acc_cfg->ice_cfg.ice_opt.trickle);
pj_strcat2(result, line);
}
if (acc_cfg->turn_cfg.enable_turn)
pj_strcat2(result, "--use-turn\n");

View File

@ -54,6 +54,8 @@ if not CPP_PATH:
sys.exit(1)
# Hardcoded!
# Note for win32:
# - temporarily comment "#include <pj/compat/socket.h>" in pj/sock.h (line ~29)
if sys.platform == 'win32':
PYCPARSER_DIR="D:/work/tool/pycparser-master"
elif sys.platform == "linux2":

View File

@ -153,6 +153,7 @@ typedef enum pj_ssl_sock_proto
PJ_SSL_SOCK_PROTO_TLS1 = 1 << 2,
PJ_SSL_SOCK_PROTO_TLS1_1 = 1 << 3,
PJ_SSL_SOCK_PROTO_TLS1_2 = 1 << 4,
PJ_SSL_SOCK_PROTO_TLS1_3 = 1 << 5,
PJ_SSL_SOCK_PROTO_SSL23 = (1 << 16) - 1,
PJ_SSL_SOCK_PROTO_ALL = PJ_SSL_SOCK_PROTO_SSL23,
PJ_SSL_SOCK_PROTO_DTLS1 = 1 << 16
@ -183,6 +184,13 @@ typedef enum pj_ssl_cert_verify_flag_t
PJ_SSL_CERT_EUNKNOWN = 1 << 31
} pj_ssl_cert_verify_flag_t;
typedef enum pj_ice_sess_trickle
{
PJ_ICE_SESS_TRICKLE_DISABLED,
PJ_ICE_SESS_TRICKLE_HALF,
PJ_ICE_SESS_TRICKLE_FULL
} pj_ice_sess_trickle;
typedef enum pj_stun_nat_type
{
PJ_STUN_NAT_TYPE_UNKNOWN,
@ -235,12 +243,14 @@ typedef enum pjmedia_event_type
PJMEDIA_EVENT_RX_RTCP_FB = ((('B' << 24) | ('F' << 16)) | ('T' << 8)) | 'R',
PJMEDIA_EVENT_AUD_DEV_ERROR = ((('R' << 24) | ('R' << 16)) | ('E' << 8)) | 'A',
PJMEDIA_EVENT_VID_DEV_ERROR = ((('R' << 24) | ('R' << 16)) | ('E' << 8)) | 'V',
PJMEDIA_EVENT_MEDIA_TP_ERR = ((('R' << 24) | ('R' << 16)) | ('E' << 8)) | 'T'
PJMEDIA_EVENT_MEDIA_TP_ERR = ((('R' << 24) | ('R' << 16)) | ('E' << 8)) | 'T',
PJMEDIA_EVENT_CALLBACK = (((' ' << 24) | (' ' << 16)) | ('B' << 8)) | 'C'
} pjmedia_event_type;
typedef enum pjmedia_srtp_use
{
PJMEDIA_SRTP_DISABLED,
PJMEDIA_SRTP_UNKNOWN = PJMEDIA_SRTP_DISABLED,
PJMEDIA_SRTP_OPTIONAL,
PJMEDIA_SRTP_MANDATORY
} pjmedia_srtp_use;
@ -285,6 +295,7 @@ typedef enum pjmedia_vid_dev_cap
PJMEDIA_VID_DEV_CAP_ORIENTATION = 128,
PJMEDIA_VID_DEV_CAP_SWITCH = 256,
PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS = 512,
PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN = 1024,
PJMEDIA_VID_DEV_CAP_MAX = 16384
} pjmedia_vid_dev_cap;
@ -431,6 +442,7 @@ typedef enum pjmedia_format_id
PJMEDIA_FORMAT_I420 = ((('0' << 24) | ('2' << 16)) | ('4' << 8)) | 'I',
PJMEDIA_FORMAT_IYUV = PJMEDIA_FORMAT_I420,
PJMEDIA_FORMAT_YV12 = ((('2' << 24) | ('1' << 16)) | ('V' << 8)) | 'Y',
PJMEDIA_FORMAT_NV12 = ((('2' << 24) | ('1' << 16)) | ('V' << 8)) | 'N',
PJMEDIA_FORMAT_NV21 = ((('1' << 24) | ('2' << 16)) | ('V' << 8)) | 'N',
PJMEDIA_FORMAT_I422 = ((('2' << 24) | ('2' << 16)) | ('4' << 8)) | 'I',
PJMEDIA_FORMAT_I420JPEG = ((('0' << 24) | ('2' << 16)) | ('4' << 8)) | 'J',
@ -439,6 +451,8 @@ typedef enum pjmedia_format_id
PJMEDIA_FORMAT_H263 = ((('3' << 24) | ('6' << 16)) | ('2' << 8)) | 'H',
PJMEDIA_FORMAT_H263P = ((('3' << 24) | ('6' << 16)) | ('2' << 8)) | 'P',
PJMEDIA_FORMAT_H264 = ((('4' << 24) | ('6' << 16)) | ('2' << 8)) | 'H',
PJMEDIA_FORMAT_VP8 = ((('0' << 24) | ('8' << 16)) | ('P' << 8)) | 'V',
PJMEDIA_FORMAT_VP9 = ((('0' << 24) | ('9' << 16)) | ('P' << 8)) | 'V',
PJMEDIA_FORMAT_MJPEG = ((('G' << 24) | ('P' << 16)) | ('J' << 8)) | 'M',
PJMEDIA_FORMAT_MPEG1VIDEO = ((('V' << 24) | ('1' << 16)) | ('P' << 8)) | 'M',
PJMEDIA_FORMAT_MPEG2VIDEO = ((('V' << 24) | ('2' << 16)) | ('P' << 8)) | 'M',
@ -568,7 +582,7 @@ typedef enum pjsip_status_code
PJSIP_SC_REJECTED = 608,
PJSIP_SC_TSX_TIMEOUT = PJSIP_SC_REQUEST_TIMEOUT,
PJSIP_SC_TSX_TRANSPORT_ERROR = PJSIP_SC_SERVICE_UNAVAILABLE,
PJSIP_SC__force_32bit = 0x7FFFFFFF
PJSIP_SC__force_32bit = 0x7FFFFFFF
} pjsip_status_code;
typedef enum pjsip_hdr_e
@ -626,6 +640,7 @@ typedef enum pjsip_transport_type_e
PJSIP_TRANSPORT_UDP,
PJSIP_TRANSPORT_TCP,
PJSIP_TRANSPORT_TLS,
PJSIP_TRANSPORT_DTLS,
PJSIP_TRANSPORT_SCTP,
PJSIP_TRANSPORT_LOOP,
PJSIP_TRANSPORT_LOOP_DGRAM,
@ -633,7 +648,8 @@ typedef enum pjsip_transport_type_e
PJSIP_TRANSPORT_IPV6 = 128,
PJSIP_TRANSPORT_UDP6 = PJSIP_TRANSPORT_UDP + PJSIP_TRANSPORT_IPV6,
PJSIP_TRANSPORT_TCP6 = PJSIP_TRANSPORT_TCP + PJSIP_TRANSPORT_IPV6,
PJSIP_TRANSPORT_TLS6 = PJSIP_TRANSPORT_TLS + PJSIP_TRANSPORT_IPV6
PJSIP_TRANSPORT_TLS6 = PJSIP_TRANSPORT_TLS + PJSIP_TRANSPORT_IPV6,
PJSIP_TRANSPORT_DTLS6 = PJSIP_TRANSPORT_DTLS + PJSIP_TRANSPORT_IPV6
} pjsip_transport_type_e;
enum pjsip_transport_flags_e
@ -659,6 +675,7 @@ typedef enum pjsip_ssl_method
PJSIP_TLSV1_METHOD = 31,
PJSIP_TLSV1_1_METHOD = 32,
PJSIP_TLSV1_2_METHOD = 33,
PJSIP_TLSV1_3_METHOD = 34,
PJSIP_SSLV23_METHOD = 23
} pjsip_ssl_method;

View File

@ -4,6 +4,7 @@ pj/log.h pj_log_decoration
pj/sock_qos.h pj_qos_type pj_qos_flag pj_qos_wmm_prio pj_qos_params
pj/ssl_sock.h pj_ssl_cipher pj_ssl_sock_proto pj_ssl_cert_name_type pj_ssl_cert_verify_flag_t
pjnath/ice_session.h pj_ice_sess_trickle
pjnath/nat_detect.h pj_stun_nat_type
pjnath/turn_session.h pj_turn_tp_type

View File

@ -376,7 +376,17 @@ enum pjsip_inv_option
* Session timer extension will always be used even when peer doesn't
* support/want session timer.
*/
PJSIP_INV_ALWAYS_USE_TIMER = 128
PJSIP_INV_ALWAYS_USE_TIMER = 128,
/**
* Indicate support for trickle ICE
*/
PJSIP_INV_SUPPORT_TRICKLE_ICE = 256,
/**
* Require trickle ICE support.
*/
PJSIP_INV_REQUIRE_TRICKLE_ICE = 512,
};
@ -1062,6 +1072,25 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_body(pj_pool_t *pool,
PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata);
/**
* Retrieve SDP information from an incoming message. Application should
* prefer to use this function rather than parsing the SDP manually since
* this function supports multipart message body.
*
* This function will only parse the SDP once, the first time it is called
* on the same message. Subsequent call on the same message will just pick
* up the already parsed SDP from the message.
*
* @param rdata The incoming message.
* @param med_type The SDP media type.
*
* @return The SDP info.
*/
PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info2(
pjsip_rx_data *rdata,
const pjsip_media_type *med_type);
PJ_END_DECL
/**

View File

@ -393,6 +393,7 @@ typedef struct pj_stun_resolve_result pj_stun_resolve_result;
# define PJSUA_SEPARATE_WORKER_FOR_TIMER 0
#endif
/**
* Default options that will be passed when creating ice transport.
* See #pjmedia_transport_ice_options.
@ -401,6 +402,19 @@ typedef struct pj_stun_resolve_result pj_stun_resolve_result;
# define PJSUA_ICE_TRANSPORT_OPTION 0
#endif
/**
* Interval of checking for any new ICE candidate when trickle ICE is active.
* Trickle ICE gathers local ICE candidates, such as STUN and TURN candidates,
* in the background, while SDP offer/answer negotiation is being performed.
* Later, when any new ICE candidate is found, the endpoint will convey
* the candidate to the remote endpoint via SIP INFO.
*
* Default: 100 ms
*/
#ifndef PJSUA_TRICKLE_ICE_NEW_CAND_CHECK_INTERVAL
# define PJSUA_TRICKLE_ICE_NEW_CAND_CHECK_INTERVAL 100
#endif
/**
* This enumeration represents pjsua state.

View File

@ -42,6 +42,7 @@ struct pjsua_call_media
pjsua_call *call; /**< Parent call. */
pjmedia_type type; /**< Media type. */
unsigned idx; /**< This media index in parent call. */
pj_str_t rem_mid; /**< Remote SDP "a=mid" attribute. */
pjsua_call_media_status state; /**< Media state. */
pjsua_call_media_status prev_state;/**< Previous media state. */
pjmedia_dir dir; /**< Media direction. */
@ -206,6 +207,16 @@ struct pjsua_call
variable is used to handle such
case, see ticket #1916. */
struct {
pj_bool_t enabled;
pj_bool_t remote_sup;
pj_bool_t remote_dlg_est;
pj_bool_t trickling;
int retrans18x_count;
pj_bool_t pending_info;
pj_timer_entry timer;
} trickle_ice;
pj_timer_entry hangup_timer; /**< Hangup retry timer. */
unsigned hangup_retry; /**< Number of hangup retries. */
unsigned hangup_code; /**< Hangup code. */
@ -702,6 +713,8 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
const pjmedia_sdp_session *remote_sdp);
pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id);
void pjsua_ice_check_start_trickling(pjsua_call *call, pjsip_event *e);
/*
* Error message when media operation is requested while another is in progress
*/

View File

@ -490,6 +490,13 @@ struct AccountNatConfig : public PersistentObject
*/
bool iceEnabled;
/**
* Set trickle ICE mode for ICE media transport.
*
* Default: PJ_ICE_SESS_TRICKLE_DISABLED
*/
pj_ice_sess_trickle iceTrickle;
/**
* Set the maximum number of ICE host candidates.
*

View File

@ -898,6 +898,8 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg,
options |= PJSIP_INV_SUPPORT_100REL;
if (options & PJSIP_INV_REQUIRE_TIMER)
options |= PJSIP_INV_SUPPORT_TIMER;
if (options & PJSIP_INV_REQUIRE_TRICKLE_ICE)
options |= PJSIP_INV_SUPPORT_TRICKLE_ICE;
/* Create the session */
inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session);
@ -963,7 +965,16 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg,
return PJ_SUCCESS;
}
PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
{
return pjsip_rdata_get_sdp_info2(rdata, NULL);
}
PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info2(
pjsip_rx_data *rdata,
const pjsip_media_type *med_type)
{
pjsip_rdata_sdp_info *sdp_info;
pjsip_msg_body *body = rdata->msg_info.msg->body;
@ -980,7 +991,11 @@ PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
PJ_ASSERT_RETURN(mod_inv.mod.id >= 0, sdp_info);
rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info;
pjsip_media_type_init2(&app_sdp, "application", "sdp");
if (!med_type) {
pjsip_media_type_init2(&app_sdp, "application", "sdp");
} else {
pj_memcpy(&app_sdp, med_type, sizeof(app_sdp));
}
if (body && ctype_hdr &&
pj_stricmp(&ctype_hdr->media.type, &app_sdp.type)==0 &&
@ -1060,6 +1075,8 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
*options |= PJSIP_INV_SUPPORT_TIMER;
if (*options & PJSIP_INV_REQUIRE_ICE)
*options |= PJSIP_INV_SUPPORT_ICE;
if (*options & PJSIP_INV_REQUIRE_TRICKLE_ICE)
*options |= PJSIP_INV_SUPPORT_TRICKLE_ICE;
if (rdata) {
/* Get the message in rdata */
@ -1286,6 +1303,7 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
const pj_str_t STR_100REL = { "100rel", 6};
const pj_str_t STR_TIMER = { "timer", 5};
const pj_str_t STR_ICE = { "ice", 3 };
const pj_str_t STR_TRICKLE_ICE = { "trickle-ice", 11 };
for (i=0; i<sup_hdr->count; ++i) {
if (pj_stricmp(&sup_hdr->values[i], &STR_100REL)==0)
@ -1294,6 +1312,8 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
rem_option |= PJSIP_INV_SUPPORT_TIMER;
else if (pj_stricmp(&sup_hdr->values[i], &STR_ICE)==0)
rem_option |= PJSIP_INV_SUPPORT_ICE;
else if (pj_stricmp(&sup_hdr->values[i], &STR_TRICKLE_ICE)==0)
rem_option |= PJSIP_INV_SUPPORT_TRICKLE_ICE;
}
}
@ -1308,6 +1328,7 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
const pj_str_t STR_REPLACES = { "replaces", 8 };
const pj_str_t STR_TIMER = { "timer", 5 };
const pj_str_t STR_ICE = { "ice", 3 };
const pj_str_t STR_TRICKLE_ICE = { "trickle-ice", 11 };
unsigned unsupp_cnt = 0;
pj_str_t unsupp_tags[PJSIP_GENERIC_ARRAY_MAX_COUNT];
@ -1334,6 +1355,11 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
{
rem_option |= PJSIP_INV_REQUIRE_ICE;
} else if ((*options & PJSIP_INV_SUPPORT_TRICKLE_ICE) &&
pj_stricmp(&req_hdr->values[i], &STR_TRICKLE_ICE)==0)
{
rem_option |= PJSIP_INV_REQUIRE_TRICKLE_ICE;
} else if (!pjsip_endpt_has_capability(endpt, PJSIP_H_SUPPORTED,
NULL, &req_hdr->values[i]))
{
@ -1381,9 +1407,11 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
* by peer.
*/
if ( msg && (((*options & PJSIP_INV_REQUIRE_100REL)!=0 &&
(rem_option & PJSIP_INV_SUPPORT_100REL)==0) ||
((*options & PJSIP_INV_REQUIRE_TIMER)!=0 &&
(rem_option & PJSIP_INV_SUPPORT_TIMER)==0)))
(rem_option & PJSIP_INV_SUPPORT_100REL)==0) ||
((*options & PJSIP_INV_REQUIRE_TIMER)!=0 &&
(rem_option & PJSIP_INV_SUPPORT_TIMER)==0) ||
((*options & PJSIP_INV_REQUIRE_TRICKLE_ICE)!=0 &&
(rem_option & PJSIP_INV_SUPPORT_TRICKLE_ICE)==0)))
{
code = PJSIP_SC_EXTENSION_REQUIRED;
status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
@ -1399,6 +1427,8 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
req_hdr->values[req_hdr->count++] = pj_str("100rel");
if (*options & PJSIP_INV_REQUIRE_TIMER)
req_hdr->values[req_hdr->count++] = pj_str("timer");
if (*options & PJSIP_INV_REQUIRE_TRICKLE_ICE)
req_hdr->values[req_hdr->count++] = pj_str("trickle-ice");
pj_list_push_back(&res_hdr_list, req_hdr);
@ -1428,6 +1458,10 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
pj_assert(*options & PJSIP_INV_SUPPORT_TIMER);
*options |= PJSIP_INV_REQUIRE_TIMER;
}
if (rem_option & PJSIP_INV_REQUIRE_TRICKLE_ICE) {
pj_assert(*options & PJSIP_INV_SUPPORT_TRICKLE_ICE);
*options |= PJSIP_INV_REQUIRE_TRICKLE_ICE;
}
on_return:
@ -1789,7 +1823,8 @@ static void cleanup_allow_sup_hdr(unsigned inv_option,
{
/* If all extensions are enabled, nothing to do */
if ((inv_option & PJSIP_INV_SUPPORT_100REL) &&
(inv_option & PJSIP_INV_SUPPORT_TIMER))
(inv_option & PJSIP_INV_SUPPORT_TIMER) &&
(inv_option & PJSIP_INV_SUPPORT_TRICKLE_ICE))
{
return;
}
@ -1813,6 +1848,11 @@ static void cleanup_allow_sup_hdr(unsigned inv_option,
remove_val_from_array_hdr(sup_hdr, &STR_TIMER);
}
if ((inv_option & PJSIP_INV_SUPPORT_TRICKLE_ICE) == 0 && sup_hdr) {
const pj_str_t STR_TRICKLE_ICE = { "trickle-ice", 11 };
remove_val_from_array_hdr(sup_hdr, &STR_TRICKLE_ICE);
}
if ((inv_option & PJSIP_INV_SUPPORT_100REL) == 0) {
const pj_str_t STR_PRACK = { "PRACK", 5 };
const pj_str_t STR_100REL = { "100rel", 6 };
@ -1920,7 +1960,8 @@ PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv,
/* Add Require header. */
if ((inv->options & PJSIP_INV_REQUIRE_100REL) ||
(inv->options & PJSIP_INV_REQUIRE_TIMER))
(inv->options & PJSIP_INV_REQUIRE_TIMER) ||
(inv->options & PJSIP_INV_REQUIRE_TRICKLE_ICE))
{
pjsip_require_hdr *hreq;
@ -1930,6 +1971,8 @@ PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv,
hreq->values[hreq->count++] = pj_str("100rel");
if (inv->options & PJSIP_INV_REQUIRE_TIMER)
hreq->values[hreq->count++] = pj_str("timer");
if (inv->options & PJSIP_INV_REQUIRE_TRICKLE_ICE)
hreq->values[hreq->count++] = pj_str("trickle-ice");
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hreq);
}

View File

@ -138,6 +138,12 @@ static void hangup_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry);
/* Check and send reinvite for lock codec and ICE update */
static pj_status_t process_pending_reinvite(pjsua_call *call);
/* Timer callbacks for trickle ICE */
static void trickle_ice_send_sip_info(pj_timer_heap_t *th,
struct pj_timer_entry *te);
static void trickle_ice_retrans_18x(pj_timer_heap_t *th,
struct pj_timer_entry *te);
/*
* Reset call descriptor.
*/
@ -170,6 +176,8 @@ static void reset_call(pjsua_call_id id)
pjsua_call_setting_default(&call->opt);
pj_timer_entry_init(&call->reinv_timer, PJ_FALSE,
(void*)(pj_size_t)id, &reinv_timer_cb);
pj_timer_entry_init(&call->trickle_ice.timer, 0, call,
&trickle_ice_send_sip_info);
}
/* Get DTMF method type name */
@ -192,6 +200,7 @@ pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg)
pjsip_inv_callback inv_cb;
unsigned i;
const pj_str_t str_norefersub = { "norefersub", 10 };
const pj_str_t str_trickle_ice = { "trickle-ice", 11 };
pj_status_t status;
/* Init calls array. */
@ -239,6 +248,10 @@ pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg)
pjsip_endpt_add_capability(pjsua_var.endpt, NULL, PJSIP_H_ALLOW,
NULL, 1, &pjsip_info_method.name);
/* Add "trickle-ice" in Supported header */
pjsip_endpt_add_capability(pjsua_var.endpt, NULL, PJSIP_H_SUPPORTED,
NULL, 1, &str_trickle_ice);
return status;
}
@ -480,6 +493,11 @@ on_make_call_med_tp_complete(pjsua_call_id call_id,
else if (acc->cfg.use_timer == PJSUA_SIP_TIMER_ALWAYS)
options |= PJSIP_INV_ALWAYS_USE_TIMER;
}
if (acc->cfg.ice_cfg.enable_ice &&
acc->cfg.ice_cfg.ice_opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED)
{
options |= PJSIP_INV_SUPPORT_TRICKLE_ICE;
}
status = pjsip_inv_create_uac( dlg, offer, options, &inv);
if (status != PJ_SUCCESS) {
@ -1647,8 +1665,14 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
options |= PJSIP_INV_SUPPORT_TIMER;
if (pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_MANDATORY)
options |= PJSIP_INV_REQUIRE_100REL;
if (pjsua_var.acc[acc_id].cfg.ice_cfg.enable_ice)
if (pjsua_var.acc[acc_id].cfg.ice_cfg.enable_ice) {
options |= PJSIP_INV_SUPPORT_ICE;
if (pjsua_var.acc[acc_id].cfg.ice_cfg.ice_opt.trickle !=
PJ_ICE_SESS_TRICKLE_DISABLED)
{
options |= PJSIP_INV_SUPPORT_TRICKLE_ICE;
}
}
if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_REQUIRED)
options |= PJSIP_INV_REQUIRE_TIMER;
else if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_ALWAYS)
@ -4279,6 +4303,454 @@ static pj_status_t process_pending_reinvite(pjsua_call *call)
}
static void trickle_ice_retrans_18x(pj_timer_heap_t *th,
struct pj_timer_entry *te)
{
pjsua_call *call = (pjsua_call*)te->user_data;
pjsip_tx_data *tdata;
pj_time_val delay;
PJ_UNUSED_ARG(th);
/* If trickling has been started or dialog has been established on
* both sides, stop 18x retransmission.
*/
if (call->trickle_ice.trickling || call->trickle_ice.remote_dlg_est)
return;
/* Make sure last tdata is 18x response */
tdata = call->inv->invite_tsx->last_tx;
if (tdata->msg->type != PJSIP_RESPONSE_MSG ||
tdata->msg->line.status.code/10 != 18)
{
return;
}
/* Retransmit 18x */
++call->trickle_ice.retrans18x_count;
PJ_LOG(4,(THIS_FILE,
"Call %d: ICE trickle retransmitting 18x (retrans #%d)",
call->index, call->trickle_ice.retrans18x_count));
pjsip_tx_data_add_ref(tdata);
pjsip_tsx_retransmit_no_state(call->inv->invite_tsx, tdata);
/* Schedule next retransmission */
if (call->trickle_ice.retrans18x_count < 6) {
pj_uint32_t tmp;
tmp = (1 << call->trickle_ice.retrans18x_count) * pjsip_cfg()->tsx.t1;
delay.sec = 0;
delay.msec = tmp;
pj_time_val_normalize(&delay);
} else {
delay.sec = 1;
delay.msec = 500;
}
pjsua_schedule_timer(te, &delay);
}
static void trickle_ice_recv_sip_info(pjsua_call *call, pjsip_rx_data *rdata)
{
pjsip_media_type med_type;
pjsip_rdata_sdp_info *sdp_info;
pj_status_t status;
unsigned i, j, med_cnt;
pj_bool_t use_med_prov;
pjsip_media_type_init2(&med_type, "application", "trickle-ice-sdpfrag");
/* Parse the SDP */
sdp_info = pjsip_rdata_get_sdp_info2(rdata, &med_type);
if (!sdp_info->sdp) {
pj_status_t err = sdp_info->body.ptr? sdp_info->sdp_err:PJ_ENOTFOUND;
pjsua_perror(THIS_FILE, "Failed to parse trickle ICE SDP in "
"incoming INFO", err);
return;
}
PJSUA_LOCK();
/* Retrieve the candidates from the SDP */
use_med_prov = call->med_prov_cnt > call->med_cnt;
med_cnt = use_med_prov? call->med_prov_cnt : call->med_cnt;
for (i = 0; i < sdp_info->sdp->media_count; ++i) {
pjmedia_transport *tp = NULL;
pj_str_t mid, ufrag, pwd;
unsigned cand_cnt = PJ_ICE_ST_MAX_CAND;
pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
pj_bool_t end_of_cand;
status = pjmedia_ice_trickle_decode_sdp(sdp_info->sdp, i, &mid,
&ufrag, &pwd,
&cand_cnt, cand,
&end_of_cand);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Failed to retrive ICE candidates from "
"SDP in incoming INFO", status);
continue;
}
for (j = 0; j < med_cnt; ++j) {
pjsua_call_media *cm = use_med_prov? &call->media_prov[j] :
&call->media[j];
tp = cm->tp_orig;
if (tp && tp->type == PJMEDIA_TRANSPORT_TYPE_ICE &&
pj_strcmp(&cm->rem_mid, &mid) == 0)
{
break;
}
}
if (j == med_cnt)
continue;
/* Update ICE checklist */
status = pjmedia_ice_trickle_update(tp, &ufrag, &pwd, cand_cnt, cand,
end_of_cand);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Failed to update ICE checklist from "
"incoming INFO", status);
}
}
PJSUA_UNLOCK();
}
static void trickle_ice_send_sip_info(pj_timer_heap_t *th,
struct pj_timer_entry *te)
{
pjsua_call *call = (pjsua_call*)te->user_data;
pj_pool_t *tmp_pool = NULL;
pj_bool_t all_end_of_cand, use_med_prov;
pjmedia_sdp_session *sdp;
unsigned i, med_cnt;
pjsua_msg_data msg_data;
pjsip_generic_string_hdr hdr1, hdr2;
pj_status_t status = PJ_SUCCESS;
pj_bool_t forced, need_send = PJ_FALSE;
pj_sockaddr orig_addr;
pj_str_t SIP_INFO = {"INFO", 4};
pj_str_t CONTENT_DISP_STR = {"Content-Disposition", 19};
pj_str_t INFO_PKG_STR = {"Info-Package", 12};
pj_str_t TRICKLE_ICE_STR = {"trickle-ice", 11};
PJ_UNUSED_ARG(th);
PJSUA_LOCK();
/* Check provisional media or active media to use */
use_med_prov = call->med_prov_cnt > call->med_cnt;
med_cnt = use_med_prov? call->med_prov_cnt : call->med_cnt;
/* Check if any pending INFO already */
if (call->trickle_ice.pending_info)
goto on_return;
/* Check if any new candidate, if not forced */
forced = (te->id == 2);
if (!forced) {
for (i = 0; i < med_cnt; ++i) {
pjsua_call_media *cm = use_med_prov? &call->media_prov[i] :
&call->media[i];
pjmedia_transport *tp = cm->tp_orig;
if (!tp || tp->type != PJMEDIA_TRANSPORT_TYPE_ICE)
continue;
if (pjmedia_ice_trickle_has_new_cand(tp))
break;
}
/* No new local candidate */
if (i == med_cnt)
goto on_return;
}
PJ_LOG(4,(THIS_FILE, "Call %d: ICE trickle sending SIP INFO",
call->index));
/* Create temporary pool */
tmp_pool = pjsua_pool_create("tmp_ice", 128, 128);
/* Create empty SDP */
pj_sockaddr_init(pj_AF_INET(), &orig_addr, NULL, 0);
status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, tmp_pool,
NULL, &orig_addr, &sdp);
if (status != PJ_SUCCESS)
goto on_return;
/* Generate SDP for SIP INFO */
all_end_of_cand = PJ_TRUE;
for (i = 0; i < med_cnt; ++i) {
pjsua_call_media *cm = use_med_prov? &call->media_prov[i] :
&call->media[i];
pjmedia_transport *tp = cm->tp_orig;
pj_bool_t end_of_cand = PJ_FALSE;
if (!tp || tp->type != PJMEDIA_TRANSPORT_TYPE_ICE)
continue;
status = pjmedia_ice_trickle_send_local_cand(tp, tmp_pool, sdp,
&end_of_cand);
if (status != PJ_SUCCESS || !end_of_cand)
all_end_of_cand = PJ_FALSE;
need_send |= (status==PJ_SUCCESS);
}
if (!need_send)
goto on_return;
/* Generate and send SIP INFO */
pjsua_msg_data_init(&msg_data);
pjsip_generic_string_hdr_init2(&hdr1, &INFO_PKG_STR, &TRICKLE_ICE_STR);
pj_list_push_back(&msg_data.hdr_list, &hdr1);
pjsip_generic_string_hdr_init2(&hdr2, &CONTENT_DISP_STR, &INFO_PKG_STR);
pj_list_push_back(&msg_data.hdr_list, &hdr2);
msg_data.content_type = pj_str("application/trickle-ice-sdpfrag");
msg_data.msg_body.ptr = pj_pool_alloc(tmp_pool, PJSIP_MAX_PKT_LEN);
msg_data.msg_body.slen = pjmedia_sdp_print(sdp, msg_data.msg_body.ptr,
PJSIP_MAX_PKT_LEN);
if (msg_data.msg_body.slen == -1) {
PJ_LOG(3,(THIS_FILE,
"Warning! Call %d: ICE trickle failed to print SDP for "
"SIP INFO due to insufficient buffer", call->index));
goto on_return;
}
status = pjsua_call_send_request(call->index, &SIP_INFO, &msg_data);
if (status != PJ_SUCCESS)
goto on_return;
/* Set flag for pending SIP INFO */
call->trickle_ice.pending_info = PJ_TRUE;
if (all_end_of_cand) {
PJ_LOG(4,(THIS_FILE, "Call %d: ICE trickle stopped trickling "
"as local candidate gathering completed",
call->index));
call->trickle_ice.trickling = PJ_FALSE;
}
on_return:
if (tmp_pool)
pj_pool_release(tmp_pool);
/* Reschedule if we are trickling */
if (call->trickle_ice.trickling) {
pj_time_val delay = {0, PJSUA_TRICKLE_ICE_NEW_CAND_CHECK_INTERVAL};
/* Reset forced mode after successfully sending forced SIP INFO */
te->id = (status==PJ_SUCCESS? 0 : 2);
pj_time_val_normalize(&delay);
pjsua_schedule_timer(te, &delay);
}
PJSUA_UNLOCK();
}
/* Before sending INFO can be started, UA needs to confirm these:
* 1. dialog is established (perhaps early) at both sides,
* 2. trickle ICE is supported by peer.
*
* This function needs to be called when:
* - UAS sending 18x, to start 18x retrans
* - UAC receiving 18x, to forcefully send SIP INFO & start trickling
* - UAS receiving INFO, to cease 18x retrans & start trickling
* - UAS receiving PRACK, to start trickling
* - UAC/UAS receiving remote SDP (and check for trickle ICE support),
* to start trickling.
*/
void pjsua_ice_check_start_trickling(pjsua_call *call, pjsip_event *e)
{
pjsip_inv_session *inv = call->inv;
pj_bool_t forced_trickling = PJ_FALSE;
/* Make sure trickling/sending-INFO has not been started */
if (call->trickle_ice.trickling)
return;
/* Make sure trickle ICE is enabled */
if (!call->trickle_ice.enabled)
return;
/* Make sure the dialog state is established */
if (!inv || inv->dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED)
return;
/* First, make sure remote dialog is also established. */
if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
/* Set flag indicating remote dialog is established */
call->trickle_ice.remote_dlg_est = PJ_TRUE;
} else if (inv->state > PJSIP_INV_STATE_CONFIRMED) {
/* Call is terminating/terminated (just trying to be safe) */
call->trickle_ice.remote_dlg_est = PJ_FALSE;
} else if (!call->trickle_ice.remote_dlg_est && e) {
/* Call is being initialized */
pjsip_msg *msg = NULL;
pjsip_rx_data *rdata = NULL;
pjsip_tx_data *tdata = NULL;
pj_bool_t has_100rel = (inv->options & PJSIP_INV_REQUIRE_100REL);
pj_timer_entry *te = &call->trickle_ice.timer;
if (e->type == PJSIP_EVENT_TSX_STATE &&
e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
{
rdata = e->body.tsx_state.src.rdata;
} else if (e->type == PJSIP_EVENT_TSX_STATE &&
e->body.tsx_state.type == PJSIP_EVENT_TX_MSG)
{
tdata = e->body.tsx_state.src.tdata;
} else {
return;
}
/* UAC must have received 18x at this point, so dialog must have been
* established at the remote side.
*/
if (inv->role == PJSIP_ROLE_UAC) {
/* UAC needs to send SIP INFO when receiving 18x and 100rel is not
* active.
* Note that 18x may not have SDP (so we don't know if remote
* supports trickle ICE), but we should send INFO anyway, as the
* draft allows start trickling without answer.
*/
if (!has_100rel && rdata &&
rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG &&
rdata->msg_info.msg->line.status.code/10 == 18)
{
pjsip_rdata_sdp_info *sdp_info;
sdp_info = pjsip_rdata_get_sdp_info(rdata);
if (sdp_info->sdp) {
unsigned i;
for (i = 0; i < sdp_info->sdp->media_count; ++i) {
if (pjmedia_ice_sdp_has_trickle(sdp_info->sdp, i)) {
call->trickle_ice.remote_sup = PJ_TRUE;
break;
}
}
} else {
/* Start sending SIP INFO forcefully */
forced_trickling = PJ_TRUE;
}
if (forced_trickling || call->trickle_ice.remote_sup) {
PJ_LOG(4,(THIS_FILE,
"Call %d: ICE trickle started after UAC "
"receiving 18x (with%s SDP)",
call->index, sdp_info->sdp?"":"out"));
}
}
}
/* But if we are the UAS, we need to wait for SIP PRACK or INFO to
* confirm dialog state at remote. And while waiting, 18x needs to be
* retransmitted.
*/
else {
if (tdata && e->body.tsx_state.tsx == inv->invite_tsx &&
call->trickle_ice.retrans18x_count == 0)
{
/* Ignite 18x retransmission */
msg = tdata->msg;
if (msg->type == PJSIP_RESPONSE_MSG &&
msg->line.status.code/10 == 18)
{
pj_time_val delay;
delay.sec = pjsip_cfg()->tsx.t1 / 1000;
delay.msec = pjsip_cfg()->tsx.t1 % 1000;
pj_assert(!pj_timer_entry_running(te));
te->cb = &trickle_ice_retrans_18x;
pjsua_schedule_timer(te, &delay);
PJ_LOG(4,(THIS_FILE,
"Call %d: ICE trickle start retransmitting 18x",
call->index));
}
return;
}
/* Check for incoming PRACK or INFO to stop 18x retransmission */
if (!rdata)
return;
msg = rdata->msg_info.msg;
if (has_100rel) {
/* With 100rel, has received PRACK? */
if (msg->type != PJSIP_REQUEST_MSG ||
pjsip_method_cmp(&msg->line.req.method,
pjsip_get_prack_method()))
{
return;
}
} else {
pj_str_t INFO_PKG_STR = {"Info-Package", 12};
pjsip_generic_string_hdr *hdr;
/* Without 100rel, has received INFO? */
if (msg->type != PJSIP_REQUEST_MSG ||
pjsip_method_cmp(&msg->line.req.method,
&pjsip_info_method))
{
return;
}
/* With Info-Package header containing 'trickle-ice' */
hdr = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name(msg, &INFO_PKG_STR, NULL);
if (!hdr || pj_strcmp2(&hdr->hvalue, "trickle-ice"))
return;
/* Set the flag indicating remote supports trickle ICE */
call->trickle_ice.remote_sup = PJ_TRUE;
}
PJ_LOG(4,(THIS_FILE,
"Call %d: ICE trickle stop retransmitting 18x after "
"receiving %s",
call->index, (has_100rel?"PRACK":"INFO")));
}
/* Set flag indicating remote dialog is established.
* Any 18x retransmission should be ceased automatically.
*/
call->trickle_ice.remote_dlg_est = PJ_TRUE;
}
/* Check if ICE trickling can be started */
if (!forced_trickling &&
(!call->trickle_ice.remote_dlg_est || !call->trickle_ice.remote_sup))
{
return;
}
/* Let's start trickling (or sending SIP INFO) */
if (!call->trickle_ice.trickling) {
pj_timer_entry *te = &call->trickle_ice.timer;
pj_time_val delay = {0,0};
call->trickle_ice.trickling = PJ_TRUE;
pjsua_cancel_timer(te);
te->id = forced_trickling? 2 : 0;
te->cb = &trickle_ice_send_sip_info;
pjsua_schedule_timer(te, &delay);
PJ_LOG(4,(THIS_FILE,
"Call %d: ICE trickle start trickling",
call->index));
}
}
/*
* This callback receives notification from invite session when the
* session state has changed.
@ -5531,6 +6003,12 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
pjsip_transaction *tsx,
pjsip_event *e)
{
/* Incoming INFO request for media control, DTMF, trickle ICE, etc. */
const pj_str_t STR_APPLICATION = { "application", 11};
const pj_str_t STR_MEDIA_CONTROL_XML = { "media_control+xml", 17 };
const pj_str_t STR_DTMF_RELAY = { "dtmf-relay", 10 };
const pj_str_t STR_TRICKLE_ICE_SDP = { "trickle-ice-sdpfrag", 19 };
pjsua_call *call;
pj_log_push_indent();
@ -5721,22 +6199,13 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
pjsua_media_prov_clean_up(call->index);
}
} else if (tsx->role==PJSIP_ROLE_UAS &&
tsx->state==PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp(&tsx->method, &pjsip_info_method)==0)
tsx->state==PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp(&tsx->method, &pjsip_info_method)==0)
{
/*
* Incoming INFO request for media control.
*/
const pj_str_t STR_APPLICATION = { "application", 11};
const pj_str_t STR_MEDIA_CONTROL_XML = { "media_control+xml", 17 };
/*
* Incoming INFO request for DTMF.
*/
const pj_str_t STR_DTMF_RELAY = { "dtmf-relay", 10 };
pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
pjsip_msg_body *body = rdata->msg_info.msg->body;
/* Check Media Control content in the INFO message */
if (body && body->len &&
pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML)==0)
@ -5759,9 +6228,12 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
if (status == PJ_SUCCESS)
status = pjsip_tsx_send_msg(tsx, tdata);
}
} else if (body && body->len &&
pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
pj_stricmp(&body->content_type.subtype, &STR_DTMF_RELAY)==0)
}
/* Check DTMF content in the INFO message */
else if (body && body->len &&
pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
pj_stricmp(&body->content_type.subtype, &STR_DTMF_RELAY)==0)
{
pjsip_tx_data *tdata;
pj_status_t status;
@ -5770,7 +6242,7 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
if (pjsua_var.ua_cfg.cb.on_dtmf_digit2 ||
pjsua_var.ua_cfg.cb.on_dtmf_event)
{
pjsua_dtmf_info info;
pjsua_dtmf_info info = {0};
pj_str_t delim, token, input;
pj_ssize_t found_idx;
@ -5869,16 +6341,44 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
status = pjsip_tsx_send_msg(tsx, tdata);
}
}
/* Check Trickle ICE content in the INFO message */
else if (body && body->len &&
pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
pj_stricmp(&body->content_type.subtype,
&STR_TRICKLE_ICE_SDP)==0)
{
pjsip_tx_data *tdata;
pj_status_t status;
/* Trickle ICE tasks:
* - UAS receiving INFO, cease 18x retrans & start trickling
*/
if (call->trickle_ice.enabled) {
pjsua_ice_check_start_trickling(call, e);
/* Process the SIP INFO content */
trickle_ice_recv_sip_info(call, rdata);
/* Send 200 response, regardless */
status = pjsip_endpt_create_response(tsx->endpt, rdata,
200, NULL, &tdata);
} else {
/* Trickle ICE not enabled, send 400 response */
status = pjsip_endpt_create_response(tsx->endpt, rdata,
400, NULL, &tdata);
}
if (status == PJ_SUCCESS)
status = pjsip_tsx_send_msg(tsx, tdata);
}
} else if (tsx->role == PJSIP_ROLE_UAC &&
pjsip_method_cmp(&tsx->method, &pjsip_info_method)==0 &&
(tsx->state == PJSIP_TSX_STATE_COMPLETED ||
(tsx->state == PJSIP_TSX_STATE_TERMINATED &&
e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED)))
{
const pj_str_t STR_APPLICATION = { "application", 11};
const pj_str_t STR_DTMF_RELAY = { "dtmf-relay", 10 };
pjsip_msg_body *body = NULL;
pj_bool_t dtmf_info = PJ_FALSE;
if (e->body.tsx_state.type == PJSIP_EVENT_TX_MSG)
body = e->body.tsx_state.src.tdata->msg->body;
@ -5889,13 +6389,6 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
if (body && body->len &&
pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
pj_stricmp(&body->content_type.subtype, &STR_DTMF_RELAY)==0)
{
dtmf_info = PJ_TRUE;
}
if (dtmf_info && (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
(tsx->state == PJSIP_TSX_STATE_TERMINATED &&
e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED)))
{
/* Status of outgoing INFO request */
if (tsx->status_code >= 200 && tsx->status_code < 300) {
@ -5911,8 +6404,37 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
tsx->status_text.ptr));
}
}
/* Check Trickle ICE content in the INFO message */
else if (body && body->len &&
pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
pj_stricmp(&body->content_type.subtype,
&STR_TRICKLE_ICE_SDP)==0)
{
/* Reset pending SIP INFO for Trickle ICE */
call->trickle_ice.pending_info = PJ_FALSE;
}
} else if (inv->state < PJSIP_INV_STATE_CONFIRMED &&
pjsip_method_cmp(&tsx->method, pjsip_get_invite_method())==0 &&
tsx->state == PJSIP_TSX_STATE_PROCEEDING &&
tsx->status_code/10 == 18)
{
/* Trickle ICE tasks:
* - UAS sending 18x, start 18x retrans
* - UAC receiving 18x, forcefully send SIP INFO & start trickling
*/
pjsua_ice_check_start_trickling(call, e);
} else if (tsx->role == PJSIP_ROLE_UAS &&
pjsip_method_cmp(&tsx->method, pjsip_get_prack_method())==0 &&
tsx->state==PJSIP_TSX_STATE_TRYING)
{
/* Trickle ICE tasks:
* - UAS receiving PRACK, start trickling
*/
pjsua_ice_check_start_trickling(call, e);
}
on_return:
pj_log_pop_indent();
}

View File

@ -757,7 +757,7 @@ static void ice_init_complete_cb(void *user_data)
{
pjsua_call_media *call_med = (pjsua_call_media*)user_data;
if (call_med->call == NULL)
if (call_med->call == NULL || call_med->tp_ready == PJ_SUCCESS)
return;
/* No need to acquire_call() if we only change the tp_ready flag
@ -852,6 +852,16 @@ static void on_ice_complete(pjmedia_transport *tp,
(void*)(pj_ssize_t)call->index, 1);
}
}
/* Stop trickling */
if (call->trickle_ice.trickling) {
call->trickle_ice.trickling = PJ_FALSE;
pjsua_cancel_timer(&call->trickle_ice.timer);
PJ_LOG(4,(THIS_FILE, "Call %d: ICE trickle stopped trickling as "
"ICE nego completed",
call->index));
}
/* Check if default ICE transport address is changed */
call->reinv_ice_sent = PJ_FALSE;
pjsua_call_schedule_reinvite_check(call, 0);
@ -928,6 +938,8 @@ static pj_status_t create_ice_media_transport(
unsigned comp_cnt;
pj_status_t status;
pj_bool_t use_ipv6, use_nat64;
pj_bool_t trickle = PJ_FALSE;
pjmedia_sdp_session *rem_sdp;
acc_cfg = &pjsua_var.acc[call_med->call->acc_id].cfg;
use_ipv6 = (acc_cfg->ipv6_media_use != PJSUA_IPV6_DISABLED);
@ -957,15 +969,16 @@ static pj_status_t create_ice_media_transport(
ice_cfg.resolver = pjsua_var.resolver;
ice_cfg.opt = acc_cfg->ice_cfg.ice_opt;
rem_sdp = call_med->call->async_call.rem_sdp;
if (call_med->call->async_call.rem_sdp) {
if (rem_sdp) {
/* Match the default address family according to the offer */
const pj_str_t ID_IP6 = { "IP6", 3};
const pjmedia_sdp_media *m;
const pjmedia_sdp_conn *c;
m = call_med->call->async_call.rem_sdp->media[call_med->idx];
c = m->conn? m->conn : call_med->call->async_call.rem_sdp->conn;
m = rem_sdp->media[call_med->idx];
c = m->conn? m->conn : rem_sdp->conn;
if (pj_stricmp(&c->addr_type, &ID_IP6) == 0)
ice_cfg.af = pj_AF_INET6();
@ -973,6 +986,25 @@ static pj_status_t create_ice_media_transport(
ice_cfg.af = pj_AF_INET6();
}
/* Should not wait for ICE STUN/TURN ready when trickle ICE is enabled */
if (ice_cfg.opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED) {
if (rem_sdp) {
/* As answerer: and when remote signals trickle ICE in SDP */
trickle = pjmedia_ice_sdp_has_trickle(rem_sdp, call_med->idx);
if (trickle) {
call_med->call->trickle_ice.remote_sup = PJ_TRUE;
call_med->call->trickle_ice.enabled = PJ_TRUE;
}
} else {
/* As offerer: and when trickle ICE mode is full */
trickle = (ice_cfg.opt.trickle==PJ_ICE_SESS_TRICKLE_FULL);
call_med->call->trickle_ice.enabled = PJ_TRUE;
}
/* Check if trickle ICE can start trickling/sending SIP INFO */
pjsua_ice_check_start_trickling(call_med->call, NULL);
}
/* If STUN transport is configured, initialize STUN transport settings */
if ((pj_sockaddr_has_addr(&pjsua_var.stun_srv) &&
pjsua_media_acc_is_using_stun(call_med->call->acc_id)) ||
@ -1117,7 +1149,7 @@ static pj_status_t create_ice_media_transport(
pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
ice_cb.on_ice_complete = &on_ice_complete;
pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
call_med->tp_ready = PJ_EPENDING;
call_med->tp_ready = trickle? PJ_SUCCESS : PJ_EPENDING;
comp_cnt = 1;
if (PJMEDIA_ADVERTISE_RTCP && !acc_cfg->ice_cfg.ice_no_rtcp)
@ -1133,7 +1165,7 @@ static pj_status_t create_ice_media_transport(
}
/* Wait until transport is initialized, or time out */
if (!async) {
if (!async && !trickle) {
pj_bool_t has_pjsua_lock = PJSUA_LOCK_IS_LOCKED();
pjsip_dialog *dlg = call_med->call->inv ?
call_med->call->inv->dlg : NULL;
@ -3031,6 +3063,12 @@ pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
stop_media_session(call_id);
/* Stop trickle ICE timer */
if (call->trickle_ice.trickling) {
call->trickle_ice.trickling = PJ_FALSE;
pjsua_cancel_timer(&call->trickle_ice.timer);
}
/* Clean up media transports */
pjsua_media_prov_clean_up(call_id);
call->med_prov_cnt = 0;
@ -3393,6 +3431,22 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
#endif
}
/* Find and save "a=mid". Currently this is for trickle ICE. Trickle
* ICE match media in SDP of SIP INFO by comparing this attribute,
* so remote SDP must be received first before remote SDP in SIP INFO
* can be processed.
*/
{
const pjmedia_sdp_media *m = remote_sdp->media[mi];
pjmedia_sdp_attr *a;
a = pjmedia_sdp_media_find_attr2(m, "mid", NULL);
if (a)
call_med->rem_mid = a->value;
else
pj_bzero(&call_med->rem_mid, sizeof(call_med->rem_mid));
}
/* Apply media update action */
if (call_med->type==PJMEDIA_TYPE_AUDIO) {
pjmedia_stream_info the_si, *si = &the_si;

View File

@ -389,6 +389,7 @@ void AccountNatConfig::readObject(const ContainerNode &node)
NODE_READ_NUM_T ( this_node, pjsua_stun_use, mediaStunUse);
NODE_READ_NUM_T ( this_node, pjsua_nat64_opt, nat64Opt);
NODE_READ_BOOL ( this_node, iceEnabled);
NODE_READ_NUM_T ( this_node, pj_ice_sess_trickle, iceTrickle);
NODE_READ_INT ( this_node, iceMaxHostCands);
NODE_READ_BOOL ( this_node, iceAggressiveNomination);
NODE_READ_UNSIGNED( this_node, iceNominatedCheckDelayMsec);
@ -422,6 +423,7 @@ void AccountNatConfig::writeObject(ContainerNode &node) const
NODE_WRITE_NUM_T ( this_node, pjsua_stun_use, mediaStunUse);
NODE_WRITE_NUM_T ( this_node, pjsua_nat64_opt, nat64Opt);
NODE_WRITE_BOOL ( this_node, iceEnabled);
NODE_WRITE_NUM_T ( this_node, pj_ice_sess_trickle, iceTrickle);
NODE_WRITE_INT ( this_node, iceMaxHostCands);
NODE_WRITE_BOOL ( this_node, iceAggressiveNomination);
NODE_WRITE_UNSIGNED( this_node, iceNominatedCheckDelayMsec);
@ -635,6 +637,7 @@ void AccountConfig::toPj(pjsua_acc_config &ret) const
ret.nat64_opt = natConfig.nat64Opt;
ret.ice_cfg_use = PJSUA_ICE_CONFIG_USE_CUSTOM;
ret.ice_cfg.enable_ice = natConfig.iceEnabled;
ret.ice_cfg.ice_opt.trickle = natConfig.iceTrickle;
ret.ice_cfg.ice_max_host_cands = natConfig.iceMaxHostCands;
ret.ice_cfg.ice_opt.aggressive = natConfig.iceAggressiveNomination;
ret.ice_cfg.ice_opt.nominated_check_delay =
@ -792,6 +795,7 @@ void AccountConfig::fromPj(const pjsua_acc_config &prm,
natConfig.nat64Opt = prm.nat64_opt;
if (prm.ice_cfg_use == PJSUA_ICE_CONFIG_USE_CUSTOM) {
natConfig.iceEnabled = PJ2BOOL(prm.ice_cfg.enable_ice);
natConfig.iceTrickle = prm.ice_cfg.ice_opt.trickle;
natConfig.iceMaxHostCands = prm.ice_cfg.ice_max_host_cands;
natConfig.iceAggressiveNomination =
PJ2BOOL(prm.ice_cfg.ice_opt.aggressive);

View File

@ -54,6 +54,7 @@ static void print_stack(int sig)
static void init_signals()
{
signal(SIGSEGV, &print_stack);
signal(SIGABRT, &print_stack);
}
#else

View File

@ -8,6 +8,32 @@ from inc_cfg import *
# Load configuration
cfg_file = imp.load_source("cfg_file", ARGS[1])
# Trigger address switch for media flow between ua1 and ua2.
# When the receiver uses STUN while both sides are actually in the same
# private network, initial media packets may be sent to public IP address
# as specified in the receiver SDP and those packets may not be delivered
# if the NAT does not support hairpinning. This function will make both
# sides to send some initial packets to trigger destination address switch
# in media transport, so future packets will be delivered to the correct
# address (private IP address).
def hole_punch(ua1, ua2):
if ua1.use_telnet:
ua1.send("# 987")
else:
ua1.send("#")
ua1.expect("#")
ua1.send("987")
if ua2.use_telnet:
ua2.send("# 789")
else:
ua2.send("#")
ua2.expect("#")
ua2.send("789")
time.sleep(0.1)
# Check media flow between ua1 and ua2
def check_media(ua1, ua2):
if ua1.use_telnet:
@ -37,6 +63,9 @@ def test_func(t):
# Check if ICE is used
use_ice = ("--use-ice" in caller.inst_param.arg) and ("--use-ice" in callee.inst_param.arg)
# Check if STUN is used (by either side)
use_stun = ("--stun-srv" in caller.inst_param.arg) or ("--stun-srv" in callee.inst_param.arg)
# Check if DTLS-SRTP is used
use_dtls_srtp = "--srtp-keying=1" in caller.inst_param.arg
@ -97,6 +126,10 @@ def test_func(t):
#callee.expect("SRTP started, keying=DTLS-SRTP")
time.sleep(0.5)
# Trigger address switch before checking media
if use_stun and not use_ice:
hole_punch(caller, callee)
# Test that media is okay
check_media(caller, callee)
check_media(callee, caller)
@ -130,6 +163,10 @@ def test_func(t):
caller.sync_stdout()
callee.sync_stdout()
# Trigger address switch before checking media
if use_stun and not use_ice:
hole_punch(caller, callee)
# Test that media is okay
check_media(caller, callee)
check_media(callee, caller)
@ -167,6 +204,10 @@ def test_func(t):
caller.sync_stdout()
callee.sync_stdout()
# Trigger address switch before checking media
if use_stun and not use_ice:
hole_punch(caller, callee)
# Test that media is okay
# Wait for some time for ICE negotiation
##time.sleep(0.6)
@ -190,6 +231,10 @@ def test_func(t):
caller.sync_stdout()
callee.sync_stdout()
# Trigger address switch before checking media
if use_stun and not use_ice:
hole_punch(caller, callee)
# Test that media is okay
##time.sleep(0.1)
check_media(caller, callee)
@ -209,6 +254,10 @@ def test_func(t):
caller.sync_stdout()
callee.sync_stdout()
# Trigger address switch before checking media
if use_stun and not use_ice:
hole_punch(caller, callee)
# Test that media is okay
##time.sleep(0.1)
check_media(caller, callee)

View File

@ -221,7 +221,7 @@ class Expect(threading.Thread):
self.lock.release()
raise inc.TestError(self.name + ": " + line)
self.output = '\n'.join(lines[found_at+1:]) if found_at >= 0 else ""
self.output = '\n'.join(lines[found_at+1:])+"\n" if found_at >= 0 else ""
self.lock.release()
if found_at >= 0:

View File

@ -0,0 +1,12 @@
# $Id$
#
from inc_cfg import *
# No ICE vs trickle ICE full, should be okay (as ICE will be disabled)
test_param = TestParam(
"Callee=Trickle ICE (full), caller=no ICE",
[
InstanceParam("callee", "--null-audio --max-calls=1 --use-ice --ice-trickle=2 --stun-srv stun.pjsip.org"),
InstanceParam("caller", "--null-audio --max-calls=1")
]
)

View File

@ -0,0 +1,12 @@
# $Id$
#
from inc_cfg import *
# Regular ICE vs trickle ICE full, should be okay (as trickle will be disabled)
test_param = TestParam(
"Callee=Trickle ICE (full), caller=Regular ICE",
[
InstanceParam("callee", "--null-audio --max-calls=1 --use-ice --ice-trickle=2 --stun-srv stun.pjsip.org"),
InstanceParam("caller", "--null-audio --max-calls=1 --use-ice --ice-trickle=0 --stun-srv stun.pjsip.org")
]
)

View File

@ -0,0 +1,12 @@
# $Id$
#
from inc_cfg import *
# Trickle ICE full vs full
test_param = TestParam(
"Callee=Trickle ICE (full), caller=Trickle ICE (full)",
[
InstanceParam("callee", "--null-audio --max-calls=1 --use-ice --ice-trickle=2 --stun-srv stun.pjsip.org"),
InstanceParam("caller", "--null-audio --max-calls=1 --use-ice --ice-trickle=2 --stun-srv stun.pjsip.org")
]
)

View File

@ -0,0 +1,12 @@
# $Id$
#
from inc_cfg import *
# Trickle ICE full vs no ICE, should be okay (as there are host candidates)
test_param = TestParam(
"Callee=no ICE, caller=Trickle ICE (full)",
[
InstanceParam("callee", "--null-audio --max-calls=1"),
InstanceParam("caller", "--null-audio --max-calls=1 --use-ice --ice-trickle=2 --stun-srv stun.pjsip.org")
]
)

View File

@ -0,0 +1,12 @@
# $Id$
#
from inc_cfg import *
# Trickle ICE full vs regular, should be okay (as there are host candidates)
test_param = TestParam(
"Callee=Regular ICE, caller=Trickle ICE (full)",
[
InstanceParam("callee", "--null-audio --max-calls=1 --use-ice --ice-trickle=0 --stun-srv stun.pjsip.org"),
InstanceParam("caller", "--null-audio --max-calls=1 --use-ice --ice-trickle=2 --stun-srv stun.pjsip.org")
]
)

View File

@ -0,0 +1,12 @@
# $Id$
#
from inc_cfg import *
# Trickle ICE half vs half
test_param = TestParam(
"Callee=Trickle ICE (half), caller=Trickle ICE (half)",
[
InstanceParam("callee", "--null-audio --max-calls=1 --use-ice --ice-trickle=1 --stun-srv stun.pjsip.org"),
InstanceParam("caller", "--null-audio --max-calls=1 --use-ice --ice-trickle=1 --stun-srv stun.pjsip.org")
]
)

View File

@ -0,0 +1,12 @@
# $Id$
#
from inc_cfg import *
# Trickle ICE half vs regular, should be okay (just like regular vs regular)
test_param = TestParam(
"Callee=Regular ICE, caller=Trickle ICE (half)",
[
InstanceParam("callee", "--null-audio --max-calls=1 --use-ice --ice-trickle=0 --stun-srv stun.pjsip.org"),
InstanceParam("caller", "--null-audio --max-calls=1 --use-ice --ice-trickle=1 --stun-srv stun.pjsip.org")
]
)