Implement trickle ICE (#2588)
Squash & merge trickle-ice dev branch to master.
This commit is contained in:
parent
dad6a34680
commit
d65cacddd3
|
@ -54,6 +54,7 @@ static void print_stack(int sig)
|
|||
static void init_signals()
|
||||
{
|
||||
signal(SIGSEGV, &print_stack);
|
||||
signal(SIGABRT, &print_stack);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
|
@ -75,6 +75,7 @@ static void print_stack(int sig)
|
|||
static void init_signals()
|
||||
{
|
||||
signal(SIGSEGV, &print_stack);
|
||||
signal(SIGABRT, &print_stack);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -53,6 +53,7 @@ static void print_stack(int sig)
|
|||
static void init_signals()
|
||||
{
|
||||
signal(SIGSEGV, &print_stack);
|
||||
signal(SIGABRT, &print_stack);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ static void print_stack(int sig)
|
|||
static void init_signals()
|
||||
{
|
||||
signal(SIGSEGV, &print_stack);
|
||||
signal(SIGABRT, &print_stack);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -106,6 +106,7 @@ static void setup_socket_signal()
|
|||
static void setup_signal_handler(void)
|
||||
{
|
||||
signal(SIGSEGV, &print_stack);
|
||||
signal(SIGABRT, &print_stack);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -54,6 +54,7 @@ static void print_stack(int sig)
|
|||
static void init_signals()
|
||||
{
|
||||
signal(SIGSEGV, &print_stack);
|
||||
signal(SIGABRT, &print_stack);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
]
|
||||
)
|
|
@ -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")
|
||||
]
|
||||
)
|
|
@ -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")
|
||||
]
|
||||
)
|
|
@ -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")
|
||||
]
|
||||
)
|
|
@ -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")
|
||||
]
|
||||
)
|
|
@ -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")
|
||||
]
|
||||
)
|
|
@ -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")
|
||||
]
|
||||
)
|
Loading…
Reference in New Issue