diff options
author | Benny Prijono <bennylp@teluu.com> | 2009-05-29 13:04:03 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2009-05-29 13:04:03 +0000 |
commit | 08640cc9411ca092e6456304bcce41f81b3bd3ce (patch) | |
tree | 6d1ad4e6304b1fb8d95b00858648cfbcd829a2ea | |
parent | ad8907b8ea9f5715c05b19b41ea7b85509591153 (diff) |
Integration of Sipit24 branch, many tickets involved:
- #793: AMR encoder should regard 'mode-set' param specified by remote decoder.
- #831: Automatically switch to TCP transport when sending large request
- #832: Support for outbound proxy setting without using Route header
- #849: Modify conference audio switch behavior in connecting ports.
- #850: Remove 'Require=replaces' param in 'Refer-To' header (in call transfer with replaces).
- #851: Support for regular nomination in ICE
- #852: --ip-addr support for IPv6 for media transport in pjsua
- #854: Adding SOFTWARE attribute in all outgoing requests may cause compatibility problem with older STUN server (thanks Alexei Kuznetsov for the report)
- #855: Bug in digit map frequencies for DTMF digits (thanks FCCH for the report)
- #856: Put back the ICE candidate priority values according to the default values in the draft-mmusic-ice
- #857: Support for ICE keep-alive with Binding indication
- #858: Do not authenticate STUN 438 response
- #859: AMR-WB format param in the SDP is not negotiated correctly.
- #867: Return error instead of asserting when PJSUA-LIB fails to open log file
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2724 74dad513-b988-da41-8d7b-12977e46ad98
34 files changed, 1126 insertions, 207 deletions
diff --git a/pjlib-util/src/pjlib-util/resolver.c b/pjlib-util/src/pjlib-util/resolver.c index ebe4b3fa..ee0b9e78 100644 --- a/pjlib-util/src/pjlib-util/resolver.c +++ b/pjlib-util/src/pjlib-util/resolver.c @@ -28,6 +28,7 @@ #include <pj/os.h> #include <pj/pool.h> #include <pj/pool_buf.h> +#include <pj/rand.h> #include <pj/string.h> #include <pj/sock.h> #include <pj/timer.h> @@ -783,6 +784,7 @@ PJ_DEF(pj_status_t) pj_dns_resolver_start_query( pj_dns_resolver *resolver, q = alloc_qnode(resolver, options, user_data, cb); /* Save the ID and key */ + /* TODO: dnsext-forgery-resilient: randomize id for security */ q->id = resolver->last_id++; if (resolver->last_id == 0) resolver->last_id = 1; diff --git a/pjmedia/src/pjmedia-codec/ipp_codecs.c b/pjmedia/src/pjmedia-codec/ipp_codecs.c index b22b4f19..4777b0bb 100644 --- a/pjmedia/src/pjmedia-codec/ipp_codecs.c +++ b/pjmedia/src/pjmedia-codec/ipp_codecs.c @@ -233,17 +233,19 @@ static struct ipp_codec { ipp_codec[] = { # if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR + /* AMR-NB SID seems to produce noise, so let's just disable its VAD. */ {1, "AMR", PJMEDIA_RTP_PT_AMR, &USC_GSMAMR_Fxns, 8000, 1, 160, - 5900, 12200, 4, 1, 1, - &predecode_amr, &parse_amr, &pack_amr - /*, {1, {{{"octet-align", 11}, {"1", 1}}} } */ + 7400, 12200, 2, 0, 1, + &predecode_amr, &parse_amr, &pack_amr, + {1, {{{"octet-align", 11}, {"1", 1}}} } }, # endif # if PJMEDIA_HAS_INTEL_IPP_CODEC_AMRWB {1, "AMR-WB", PJMEDIA_RTP_PT_AMRWB, &USC_AMRWB_Fxns, 16000, 1, 320, 15850, 23850, 1, 1, 1, - &predecode_amr, &parse_amr, &pack_amr + &predecode_amr, &parse_amr, &pack_amr, + {1, {{{"octet-align", 11}, {"1", 1}}} } }, # endif @@ -560,7 +562,14 @@ static pj_status_t parse_amr(ipp_private_t *codec_data, void *pkt, /* Check Change Mode Request. */ if ((setting->amr_nb && cmr <= 7) || (!setting->amr_nb && cmr <= 8)) { + struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx]; + s->enc_mode = cmr; + codec_data->info->params.modes.bitrate = s->enc_setting.amr_nb? + pjmedia_codec_amrnb_bitrates[s->enc_mode] : + pjmedia_codec_amrwb_bitrates[s->enc_mode]; + ippc->fxns->std.Control(&codec_data->info->params.modes, + codec_data->enc); } return PJ_SUCCESS; @@ -1033,7 +1042,7 @@ static pj_status_t ipp_codec_open( pjmedia_codec *codec, codec_data->info->params.direction = USC_DECODE; /* Not sure if VAD affects decoder, just try to be safe */ - codec_data->info->params.modes.vad = ippc->has_native_vad; + //codec_data->info->params.modes.vad = ippc->has_native_vad; /* Get number of memory blocks needed by the decoder */ if (USC_NoError != ippc->fxns->std.NumAlloc(&codec_data->info->params, @@ -1084,34 +1093,79 @@ static pj_status_t ipp_codec_open( pjmedia_codec *codec, if (ippc->pt == PJMEDIA_RTP_PT_AMR || ippc->pt == PJMEDIA_RTP_PT_AMRWB) { amr_settings_t *s; pj_uint8_t octet_align = 0; - const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11}; + pj_int8_t enc_mode = -1; + + /* Check AMR specific attributes */ - /* Check octet-align */ for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) { + /* octet-align, one of the parameters that must have same value + * in offer & answer (RFC 4867 Section 8.3.1). Just check fmtp + * in the decoder side, since it's value is guaranteed to fulfil + * above requirement (by SDP negotiator). + */ + const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11}; + if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, &STR_FMTP_OCTET_ALIGN) == 0) { octet_align=(pj_uint8_t) - (pj_strtoul(&attr->setting.dec_fmtp.param[i].val)); + pj_strtoul(&attr->setting.dec_fmtp.param[i].val); + break; + } + } + for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) { + /* mode-set */ + const pj_str_t STR_FMTP_MODE_SET = {"mode-set", 8}; + + if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, + &STR_FMTP_MODE_SET) == 0) + { + pj_int8_t tmp; + + /* Just get the first value. */ + tmp = (pj_int8_t) + pj_strtoul(&attr->setting.enc_fmtp.param[i].val); + + if ((ippc->pt == PJMEDIA_RTP_PT_AMR && tmp > 0 && tmp < 8) || + (ippc->pt == PJMEDIA_RTP_PT_AMRWB && tmp > 0 && tmp < 9)) + { + enc_mode = tmp; + PJ_LOG(4,(THIS_FILE, "Remote specifies AMR mode-set attr, " + "selected: %d", enc_mode)); + } break; } } + /* Initialize AMR specific settings */ s = PJ_POOL_ZALLOC_T(pool, amr_settings_t); codec_data->codec_setting = s; - s->enc_mode = pjmedia_codec_amr_get_mode(ippc->def_bitrate); - if (s->enc_mode < 0) - goto on_error; - s->enc_setting.amr_nb = (pj_uint8_t)(ippc->pt == PJMEDIA_RTP_PT_AMR); s->enc_setting.octet_aligned = octet_align; s->enc_setting.reorder = PJ_TRUE; s->enc_setting.cmr = 15; - + s->dec_setting.amr_nb = (pj_uint8_t)(ippc->pt == PJMEDIA_RTP_PT_AMR); s->dec_setting.octet_aligned = octet_align; s->dec_setting.reorder = PJ_TRUE; + + s->enc_mode = pjmedia_codec_amr_get_mode( + codec_data->info->params.modes.bitrate); + if (s->enc_mode < 0) + goto on_error; + + if (enc_mode != -1) { + s->enc_mode = enc_mode; + + /* Apply requested encoder bitrate */ + codec_data->info->params.modes.bitrate = s->enc_setting.amr_nb? + pjmedia_codec_amrnb_bitrates[s->enc_mode] : + pjmedia_codec_amrwb_bitrates[s->enc_mode]; + ippc->fxns->std.Control(&codec_data->info->params.modes, + codec_data->enc); + } + } #endif diff --git a/pjmedia/src/pjmedia/conf_switch.c b/pjmedia/src/pjmedia/conf_switch.c index da239df7..616a85b3 100644 --- a/pjmedia/src/pjmedia/conf_switch.c +++ b/pjmedia/src/pjmedia/conf_switch.c @@ -553,10 +553,32 @@ PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf, return PJMEDIA_ENCSAMPLESPFRAME; } - /* Check if sink is listening to other ports */ + /* If sink is currently listening to other ports, it needs to be released + * first before the new connection made. + */ if (dst_port->transmitter_cnt > 0) { - pj_mutex_unlock(conf->mutex); - return PJ_ETOOMANYCONN; + unsigned j; + pj_bool_t transmitter_found = PJ_FALSE; + + pj_assert(dst_port->transmitter_cnt == 1); + for (j=0; j<conf->max_ports && !transmitter_found; ++j) { + if (conf->ports[j]) { + unsigned k; + + for (k=0; k < conf->ports[j]->listener_cnt; ++k) { + if (conf->ports[j]->listener_slots[k] == sink_slot) { + PJ_LOG(4,(THIS_FILE, "Connection [%d->%d] is " + "disconnected forcedly for the new " + "connection [%d->%d]", + j, sink_slot, src_slot, sink_slot)); + pjmedia_conf_disconnect_port(conf, j, sink_slot); + transmitter_found = PJ_TRUE; + break; + } + } + } + } + pj_assert(dst_port->transmitter_cnt == 0); } /* Check if connection has been made */ diff --git a/pjmedia/src/pjmedia/sdp_neg.c b/pjmedia/src/pjmedia/sdp_neg.c index 60ca36ef..9c4e9cfa 100644 --- a/pjmedia/src/pjmedia/sdp_neg.c +++ b/pjmedia/src/pjmedia/sdp_neg.c @@ -865,12 +865,14 @@ static pj_status_t process_m_answer( pj_pool_t *pool, (ar.param.slen==1 && *ar.param.ptr=='1'))) { /* Further check for G7221, negotiate bitrate. */ - if (pj_strcmp2(&or_.enc_name, "G7221") == 0) { + if (pj_stricmp2(&or_.enc_name, "G7221") == 0) { if (match_g7221(offer, i, answer, j)) break; } else /* Further check for AMR, negotiate fmtp. */ - if (pj_strcmp2(&or_.enc_name, "AMR") == 0) { + if (pj_stricmp2(&or_.enc_name, "AMR") == 0 || + pj_stricmp2(&or_.enc_name, "AMR-WB") == 0) + { if (match_amr(offer, i, answer, j, PJ_FALSE, NULL)) break; @@ -1070,7 +1072,7 @@ static pj_status_t match_offer(pj_pool_t *pool, } pjmedia_sdp_attr_get_rtpmap(a, &or_); - if (!pj_strcmp2(&or_.enc_name, "telephone-event")) { + if (!pj_stricmp2(&or_.enc_name, "telephone-event")) { master_has_telephone_event = 1; if (found_matching_telephone_event) continue; @@ -1097,19 +1099,21 @@ static pj_status_t match_offer(pj_pool_t *pool, */ if (!pj_stricmp(&or_.enc_name, &lr.enc_name) && or_.clock_rate == lr.clock_rate && - (pj_strcmp(&or_.param, &lr.param)==0 || + (pj_stricmp(&or_.param, &lr.param)==0 || (or_.param.slen==1 && *or_.param.ptr=='1'))) { /* Match! */ if (is_codec) { /* Further check for G7221, negotiate bitrate */ - if (pj_strcmp2(&or_.enc_name, "G7221") == 0 && + if (pj_stricmp2(&or_.enc_name, "G7221") == 0 && !match_g7221(master, i, slave, j)) { continue; } else /* Further check for AMR, negotiate fmtp */ - if (pj_strcmp2(&or_.enc_name, "AMR")==0) { + if (pj_stricmp2(&or_.enc_name, "AMR")==0 || + pj_stricmp2(&or_.enc_name, "AMR-WB")==0) + { unsigned o_med_idx, a_med_idx; o_med_idx = prefer_remote_codec_order? i:j; diff --git a/pjmedia/src/pjmedia/tonegen.c b/pjmedia/src/pjmedia/tonegen.c index f160fb79..2a545055 100644 --- a/pjmedia/src/pjmedia/tonegen.c +++ b/pjmedia/src/pjmedia/tonegen.c @@ -384,13 +384,13 @@ static pjmedia_tone_digit_map digit_map = { '0', 941, 1336 }, { '1', 697, 1209 }, { '2', 697, 1336 }, - { '3', 697, 1447 }, + { '3', 697, 1477 }, { '4', 770, 1209 }, { '5', 770, 1336 }, - { '6', 770, 1447 }, + { '6', 770, 1477 }, { '7', 852, 1209 }, { '8', 852, 1336 }, - { '9', 852, 1447 }, + { '9', 852, 1477 }, { 'a', 697, 1633 }, { 'b', 770, 1633 }, { 'c', 852, 1633 }, diff --git a/pjnath/include/pjnath/config.h b/pjnath/include/pjnath/config.h index f9552721..f265e2c7 100644 --- a/pjnath/include/pjnath/config.h +++ b/pjnath/include/pjnath/config.h @@ -259,12 +259,23 @@ */ #define PJ_ICE_MAX_COMP (2<<PJ_ICE_COMP_BITS) +/** + * Use the priority value according to the ice-draft. + */ +#ifndef PJNATH_ICE_PRIO_STD +# define PJNATH_ICE_PRIO_STD 1 +#endif + /** * The number of bits to represent candidate type preference. */ #ifndef PJ_ICE_CAND_TYPE_PREF_BITS -# define PJ_ICE_CAND_TYPE_PREF_BITS 2 +# if PJNATH_ICE_PRIO_STD +# define PJ_ICE_CAND_TYPE_PREF_BITS 8 +# else +# define PJ_ICE_CAND_TYPE_PREF_BITS 2 +# endif #endif @@ -324,32 +335,74 @@ /** + * For a controlled agent, specify how long it wants to wait (in milliseconds) + * for the controlling agent to complete sending connectivity check with + * nominated flag set to true for all components after the controlled agent + * has found that all connectivity checks in its checklist have been completed + * and there is at least one successful (but not nominated) check for every + * component. + * + * When selecting the value, bear in mind that the connectivity check from + * controlling agent may be delayed because of delay in receiving SDP answer + * from the controlled agent. + * + * Application may set this value to -1 to disable this timer. + * + * Default: 10000 (milliseconds) + */ +#ifndef ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT +# define ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT 10000 +#endif + + +/** + * For controlling agent if it uses regular nomination, specify the delay to + * perform nominated check (connectivity check with USE-CANDIDATE attribute) + * after all components have a valid pair. + * + * Default: 4*PJ_STUN_RTO_VALUE (milliseconds) + */ +#ifndef PJ_ICE_NOMINATED_CHECK_DELAY +# define PJ_ICE_NOMINATED_CHECK_DELAY (4*PJ_STUN_RTO_VALUE) +#endif + + +/** * Minimum interval value to be used for sending STUN keep-alive on the ICE - * stream transport, in seconds. This minimum interval, plus a random value - * which maximum is PJ_ICE_ST_KEEP_ALIVE_MAX_RAND, specify the actual interval + * session, in seconds. This minimum interval, plus a random value + * which maximum is PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND, specify the actual interval * of the STUN keep-alive. * - * Default: 20 seconds + * Default: 15 seconds * - * @see PJ_ICE_ST_KEEP_ALIVE_MAX_RAND + * @see PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND */ -#ifndef PJ_ICE_ST_KEEP_ALIVE_MIN -# define PJ_ICE_ST_KEEP_ALIVE_MIN 20 +#ifndef PJ_ICE_SESS_KEEP_ALIVE_MIN +# define PJ_ICE_SESS_KEEP_ALIVE_MIN 20 #endif +/* Warn about deprecated macro */ +#ifdef PJ_ICE_ST_KEEP_ALIVE_MIN +# error PJ_ICE_ST_KEEP_ALIVE_MIN is deprecated +#endif /** * To prevent STUN keep-alives to be sent simultaneously, application should - * add random interval to minimum interval (PJ_ICE_ST_KEEP_ALIVE_MIN). This + * add random interval to minimum interval (PJ_ICE_SESS_KEEP_ALIVE_MIN). This * setting specifies the maximum random value to be added to the minimum * interval, in seconds. * * Default: 5 seconds * - * @see PJ_ICE_ST_KEEP_ALIVE_MIN + * @see PJ_ICE_SESS_KEEP_ALIVE_MIN */ -#ifndef PJ_ICE_ST_KEEP_ALIVE_MAX_RAND -# define PJ_ICE_ST_KEEP_ALIVE_MAX_RAND 5 +#ifndef PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND +# define PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND 5 +#endif + +/* Warn about deprecated macro */ +#ifdef PJ_ICE_ST_KEEP_ALIVE_MAX_RAND +# error PJ_ICE_ST_KEEP_ALIVE_MAX_RAND is deprecated #endif diff --git a/pjnath/include/pjnath/errno.h b/pjnath/include/pjnath/errno.h index e17f9460..2bc47dc1 100644 --- a/pjnath/include/pjnath/errno.h +++ b/pjnath/include/pjnath/errno.h @@ -196,7 +196,12 @@ * host candidate. */ #define PJNATH_EICENOHOSTCAND (PJNATH_ERRNO_START+92) /* 370092 */ - +/** + * @hideinitializer + * Controlled agent timed-out in waiting for the controlling agent to + * send nominated check after all connectivity checks have completed. + */ +#define PJNATH_EICENOMTIMEOUT (PJNATH_ERRNO_START+93) /* 370093 */ /************************************************************ * TURN ERROR CODES diff --git a/pjnath/include/pjnath/ice_session.h b/pjnath/include/pjnath/ice_session.h index 18f7ffd6..e606f075 100644 --- a/pjnath/include/pjnath/ice_session.h +++ b/pjnath/include/pjnath/ice_session.h @@ -176,13 +176,20 @@ typedef struct pj_ice_sess_check pj_ice_sess_check; typedef struct pj_ice_sess_comp { /** - * The pointer to ICE check which was nominated for this component. - * The value will be NULL if a nominated check has not been found - * for this component. + * Pointer to ICE check with highest priority which connectivity check + * has been successful. The value will be NULL if a no successful check + * has not been found for this component. */ pj_ice_sess_check *valid_check; /** + * Pointer to ICE check with highest priority which connectivity check + * has been successful and it has been nominated. The value may be NULL + * if there is no such check yet. + */ + pj_ice_sess_check *nominated_check; + + /** * The STUN session to be used to send and receive STUN messages for this * component. */ @@ -553,6 +560,44 @@ typedef struct pj_ice_rx_check /** + * This structure describes various ICE session options. Application + * configure the ICE session with these options by calling + * #pj_ice_sess_set_options(). + */ +typedef struct pj_ice_sess_options +{ + /** + * Specify whether to use aggressive nomination. + */ + pj_bool_t aggressive; + + /** + * For controlling agent if it uses regular nomination, specify the delay + * to perform nominated check (connectivity check with USE-CANDIDATE + * attribute) after all components have a valid pair. + * + * Default value is PJ_ICE_NOMINATED_CHECK_DELAY. + */ + unsigned nominated_check_delay; + + /** + * For a controlled agent, specify how long it wants to wait (in + * milliseconds) for the controlling agent to complete sending + * connectivity check with nominated flag set to true for all components + * after the controlled agent has found that all connectivity checks in + * its checklist have been completed and there is at least one successful + * (but not nominated) check for every component. + * + * Default value for this option is + * ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT. Specify -1 to disable + * this timer. + */ + int controlled_agent_want_nom_timeout; + +} pj_ice_sess_options; + + +/** * This structure describes the ICE session. For this version of PJNATH, * an ICE session corresponds to a single media stream (unlike the ICE * session described in the ICE standard where an ICE session covers the @@ -569,11 +614,13 @@ struct pj_ice_sess void *user_data; /**< App. data. */ pj_mutex_t *mutex; /**< Mutex. */ pj_ice_sess_role role; /**< ICE role. */ + pj_ice_sess_options opt; /**< Options */ pj_timestamp tie_breaker; /**< Tie breaker value */ pj_uint8_t *prefs; /**< Type preference. */ + pj_bool_t is_nominating; /**< Nominating stage */ pj_bool_t is_complete; /**< Complete? */ pj_status_t ice_status; /**< Error status. */ - pj_timer_entry completion_timer; /**< To call callback. */ + pj_timer_entry timer; /**< ICE timer. */ pj_ice_sess_cb cb; /**< Callback. */ pj_stun_config stun_cfg; /**< STUN settings. */ @@ -589,6 +636,7 @@ struct pj_ice_sess /* Components */ unsigned comp_cnt; /**< # of components. */ pj_ice_sess_comp comp[PJ_ICE_MAX_COMP]; /**< Component array */ + unsigned comp_ka; /**< Next comp for KA */ /* Local candidates */ unsigned lcand_cnt; /**< # of local cand. */ @@ -654,6 +702,12 @@ PJ_DECL(void) pj_ice_calc_foundation(pj_pool_t *pool, pj_ice_cand_type type, const pj_sockaddr *base_addr); +/** + * Initialize ICE session options with library default values. + * + * @param opt ICE session options. + */ +PJ_DECL(void) pj_ice_sess_options_default(pj_ice_sess_options *opt); /** * Create ICE session with the specified role and number of components. @@ -689,6 +743,34 @@ PJ_DECL(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg, pj_ice_sess **p_ice); /** + * Get the value of various options of the ICE session. + * + * @param ice The ICE session. + * @param opt The options to be initialized with the values + * from the ICE session. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_sess_get_options(pj_ice_sess *ice, + pj_ice_sess_options *opt); + +/** + * Specify various options for this ICE session. Application MUST only + * call this function after the ICE session has been created but before + * any connectivity check is started. + * + * Application should call #pj_ice_sess_get_options() to initialize the + * options with their default values. + * + * @param ice The ICE session. + * @param opt Options to be applied to the ICE session. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_sess_set_options(pj_ice_sess *ice, + const pj_ice_sess_options *opt); + +/** * Destroy ICE session. This will cancel any connectivity checks currently * running, if any, and any other events scheduled by this session, as well * as all memory resources. diff --git a/pjnath/include/pjnath/ice_strans.h b/pjnath/include/pjnath/ice_strans.h index 02397073..28487870 100644 --- a/pjnath/include/pjnath/ice_strans.h +++ b/pjnath/include/pjnath/ice_strans.h @@ -197,6 +197,13 @@ typedef struct pj_ice_strans_cfg pj_dns_resolver *resolver; /** + * This contains various STUN session options. Once the ICE stream + * transport is created, application may also change the options + * with #pj_ice_strans_set_options(). + */ + pj_ice_sess_options opt; + + /** * STUN and local transport settings. This specifies the * settings for local UDP socket, which will be resolved * to get the STUN mapped address. @@ -209,12 +216,12 @@ typedef struct pj_ice_strans_cfg pj_stun_sock_cfg cfg; /** - * Disable host candidates. When this option is set, no - * host candidates will be added. + * Maximum number of host candidates to be added. If the + * value is zero, no host candidates will be added. * - * Default: PJ_FALSE + * Default: 64 */ - pj_bool_t no_host_cands; + unsigned max_host_cands; /** * Include loopback addresses in the host candidates. @@ -386,6 +393,32 @@ PJ_DECL(void*) pj_ice_strans_get_user_data(pj_ice_strans *ice_st); /** + * Get the value of various options of the ICE stream transport. + * + * @param ice_st The ICE stream transport. + * @param opt The options to be initialized with the values + * from the ICE stream transport. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_strans_get_options(pj_ice_strans *ice_st, + pj_ice_sess_options *opt); + +/** + * Specify various options for this ICE stream transport. Application + * should call #pj_ice_strans_get_options() to initialize the options + * with their default values. + * + * @param ice_st The ICE stream transport. + * @param opt Options to be applied to this ICE stream transport. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st, + const pj_ice_sess_options *opt); + + +/** * Initialize the ICE session in the ICE stream transport. * When application is about to send an offer containing ICE capability, * or when it receives an offer containing ICE capability, it must diff --git a/pjnath/include/pjnath/stun_msg.h b/pjnath/include/pjnath/stun_msg.h index 57fc38c4..5339f1b2 100644 --- a/pjnath/include/pjnath/stun_msg.h +++ b/pjnath/include/pjnath/stun_msg.h @@ -197,6 +197,10 @@ typedef enum pj_stun_msg_type */ PJ_STUN_BINDING_ERROR_RESPONSE = 0x0111, + /** + * Binding Indication (ICE) + */ + PJ_STUN_BINDING_INDICATION = 0x0011, /** * STUN SHARED-SECRET reqeust. diff --git a/pjnath/include/pjnath/stun_session.h b/pjnath/include/pjnath/stun_session.h index 2a6fd022..686a09a1 100644 --- a/pjnath/include/pjnath/stun_session.h +++ b/pjnath/include/pjnath/stun_session.h @@ -485,6 +485,18 @@ PJ_DECL(pj_status_t) pj_stun_session_set_credential(pj_stun_session *sess, */ PJ_DECL(void) pj_stun_session_set_log(pj_stun_session *sess, unsigned flags); +/** + * Configure whether the STUN session should utilize FINGERPRINT in + * outgoing messages. + * + * @param sess The STUN session instance. + * @param use Boolean for the setting. + * + * @return The previous configured value of FINGERPRINT + * utilization of the sessoin. + */ +PJ_DECL(pj_bool_t) pj_stun_session_use_fingerprint(pj_stun_session *sess, + pj_bool_t use); /** * Create a STUN request message. After the message has been successfully diff --git a/pjnath/src/pjnath-test/ice_test.c b/pjnath/src/pjnath-test/ice_test.c index ffa1077d..7603b402 100644 --- a/pjnath/src/pjnath-test/ice_test.c +++ b/pjnath/src/pjnath-test/ice_test.c @@ -64,6 +64,8 @@ struct test_cfg unsigned destroy_delay; /* Delay before destroy() */ struct test_result expected;/* Expected result */ + + pj_bool_t nom_regular; /* Use regular nomination? */ }; /* ICE endpoint state */ @@ -141,9 +143,9 @@ static int create_ice_strans(struct test_sess *test_sess, } if (ept->cfg.enable_host == 0) { - ice_cfg.stun.no_host_cands = PJ_TRUE; + ice_cfg.stun.max_host_cands = 0; } else { - ice_cfg.stun.no_host_cands = PJ_FALSE; + //ice_cfg.stun.no_host_cands = PJ_FALSE; ice_cfg.stun.loop_addr = PJ_TRUE; } diff --git a/pjnath/src/pjnath/errno.c b/pjnath/src/pjnath/errno.c index d102a09d..1656ce33 100644 --- a/pjnath/src/pjnath/errno.c +++ b/pjnath/src/pjnath/errno.c @@ -67,6 +67,7 @@ static const struct PJ_BUILD_ERR( PJNATH_EICEMISSINGSDP, "Missing ICE SDP attribute"), PJ_BUILD_ERR( PJNATH_EICEINCANDSDP, "Invalid SDP \"candidate\" attribute"), PJ_BUILD_ERR( PJNATH_EICENOHOSTCAND, "No host candidate associated with srflx"), + PJ_BUILD_ERR( PJNATH_EICENOMTIMEOUT, "Controlled agent timed out waiting for nomination"), /* TURN related errors */ PJ_BUILD_ERR( PJNATH_ETURNINTP, "Invalid/unsupported transport"), diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c index f5ba8e3c..ad08639c 100644 --- a/pjnath/src/pjnath/ice_session.c +++ b/pjnath/src/pjnath/ice_session.c @@ -29,7 +29,6 @@ #include <pj/rand.h> #include <pj/string.h> - /* String names for candidate types */ static const char *cand_type_names[] = { @@ -66,6 +65,20 @@ static const char *role_names[] = "Controlling" }; +enum timer_type +{ + TIMER_NONE, /**< Timer not active */ + TIMER_COMPLETION_CALLBACK, /**< Call on_ice_complete() callback */ + TIMER_CONTROLLED_WAIT_NOM, /**< Controlled agent is waiting for + controlling agent to send connectivity + check with nominated flag after it has + valid check for every components. */ + TIMER_START_NOMINATED_CHECK,/**< Controlling agent start connectivity + checks with USE-CANDIDATE flag. */ + TIMER_KEEP_ALIVE /**< ICE keep-alive timer. */ + +}; + /* Candidate type preference */ static pj_uint8_t cand_type_prefs[4] = { @@ -118,10 +131,14 @@ typedef struct timer_data /* Forward declarations */ +static void on_timer(pj_timer_heap_t *th, pj_timer_entry *te); +static void on_ice_complete(pj_ice_sess *ice, pj_status_t status); +static void ice_keep_alive(pj_ice_sess *ice, pj_bool_t send_now); static void destroy_ice(pj_ice_sess *ice, pj_status_t reason); static pj_status_t start_periodic_check(pj_timer_heap_t *th, pj_timer_entry *te); +static void start_nominated_check(pj_ice_sess *ice); static void periodic_timer(pj_timer_heap_t *th, pj_timer_entry *te); static void handle_incoming_check(pj_ice_sess *ice, @@ -225,7 +242,7 @@ PJ_DEF(void) pj_ice_calc_foundation(pj_pool_t *pool, pj_ice_cand_type type, const pj_sockaddr *base_addr) { -#if 0 +#if PJNATH_ICE_PRIO_STD char buf[64]; pj_uint32_t val; @@ -296,6 +313,15 @@ static pj_status_t init_comp(pj_ice_sess *ice, } +/* Init options with default values */ +PJ_DEF(void) pj_ice_sess_options_default(pj_ice_sess_options *opt) +{ + opt->aggressive = PJ_TRUE; + opt->nominated_check_delay = PJ_ICE_NOMINATED_CHECK_DELAY; + opt->controlled_agent_want_nom_timeout = + ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT; +} + /* * Create ICE session. */ @@ -326,6 +352,9 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg, ice->tie_breaker.u32.hi = pj_rand(); ice->tie_breaker.u32.lo = pj_rand(); ice->prefs = cand_type_prefs; + pj_ice_sess_options_default(&ice->opt); + + pj_timer_entry_init(&ice->timer, TIMER_NONE, (void*)ice, &on_timer); pj_ansi_snprintf(ice->obj_name, sizeof(ice->obj_name), name, ice); @@ -345,6 +374,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg, pj_ice_sess_comp *comp; comp = &ice->comp[i]; comp->valid_check = NULL; + comp->nominated_check = NULL; status = init_comp(ice, i+1, comp); if (status != PJ_SUCCESS) { @@ -389,6 +419,31 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg, /* + * Get the value of various options of the ICE session. + */ +PJ_DEF(pj_status_t) pj_ice_sess_get_options(pj_ice_sess *ice, + pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + pj_memcpy(opt, &ice->opt, sizeof(*opt)); + return PJ_SUCCESS; +} + +/* + * Specify various options for this ICE session. + */ +PJ_DEF(pj_status_t) pj_ice_sess_set_options(pj_ice_sess *ice, + const pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice && opt, PJ_EINVAL); + pj_memcpy(&ice->opt, opt, sizeof(*opt)); + LOG5((ice->obj_name, "ICE nomination type set to %s", + (ice->opt.aggressive ? "aggressive" : "regular"))); + return PJ_SUCCESS; +} + + +/* * Destroy */ static void destroy_ice(pj_ice_sess *ice, @@ -406,10 +461,10 @@ static void destroy_ice(pj_ice_sess *ice, pj_mutex_unlock(ice->mutex); } - if (ice->completion_timer.id) { + if (ice->timer.id) { pj_timer_heap_cancel(ice->stun_cfg.timer_heap, - &ice->completion_timer); - ice->completion_timer.id = PJ_FALSE; + &ice->timer); + ice->timer.id = PJ_FALSE; } for (i=0; i<ice->comp_cnt; ++i) { @@ -603,7 +658,7 @@ static pj_uint32_t CALC_CAND_PRIO(pj_ice_sess *ice, pj_uint32_t local_pref, pj_uint32_t comp_id) { -#if 0 +#if PJNATH_ICE_PRIO_STD return ((ice->prefs[type] & 0xFF) << 24) + ((local_pref & 0xFFFF) << 8) + (((256 - comp_id) & 0xFF) << 0); @@ -1039,18 +1094,112 @@ static pj_status_t prune_checklist(pj_ice_sess *ice, return PJ_SUCCESS; } -/* Timer callback to call on_ice_complete() callback */ -static void on_completion_timer(pj_timer_heap_t *th, - pj_timer_entry *te) +/* Timer callback */ +static void on_timer(pj_timer_heap_t *th, pj_timer_entry *te) { pj_ice_sess *ice = (pj_ice_sess*) te->user_data; + enum timer_type type = (enum timer_type)te->id; PJ_UNUSED_ARG(th); - te->id = PJ_FALSE; + pj_mutex_lock(ice->mutex); + + te->id = TIMER_NONE; + + switch (type) { + case TIMER_CONTROLLED_WAIT_NOM: + LOG4((ice->obj_name, + "Controlled agent timed-out in waiting for the controlling " + "agent to send nominated check. Setting state to fail now..")); + on_ice_complete(ice, PJNATH_EICENOMTIMEOUT); + break; + case TIMER_COMPLETION_CALLBACK: + /* Start keep-alive timer but don't send any packets yet. + * Need to do it here just in case app destroy the session + * in the callback. + */ + if (ice->ice_status == PJ_SUCCESS) + ice_keep_alive(ice, PJ_FALSE); + + /* Notify app about ICE completion*/ + if (ice->cb.on_ice_complete) + (*ice->cb.on_ice_complete)(ice, ice->ice_status); + break; + case TIMER_START_NOMINATED_CHECK: + start_nominated_check(ice); + break; + case TIMER_KEEP_ALIVE: + ice_keep_alive(ice, PJ_TRUE); + break; + case TIMER_NONE: + /* Nothing to do, just to get rid of gcc warning */ + break; + } - if (ice->cb.on_ice_complete) - (*ice->cb.on_ice_complete)(ice, ice->ice_status); + pj_mutex_unlock(ice->mutex); +} + +/* Send keep-alive */ +static void ice_keep_alive(pj_ice_sess *ice, pj_bool_t send_now) +{ + if (send_now) { + /* Send Binding Indication for the component */ + pj_ice_sess_comp *comp = &ice->comp[ice->comp_ka]; + pj_stun_tx_data *tdata; + pj_ice_sess_check *the_check; + pj_ice_msg_data *msg_data; + int addr_len; + pj_bool_t saved; + pj_status_t status; + + /* Must have nominated check by now */ + pj_assert(comp->nominated_check != NULL); + the_check = comp->nominated_check; + + /* Create the Binding Indication */ + status = pj_stun_session_create_ind(comp->stun_sess, + PJ_STUN_BINDING_INDICATION, + &tdata); + if (status != PJ_SUCCESS) + goto done; + + /* Need the transport_id */ + msg_data = PJ_POOL_ZALLOC_T(tdata->pool, pj_ice_msg_data); + msg_data->transport_id = the_check->lcand->transport_id; + + /* Temporarily disable FINGERPRINT. The Binding Indication + * SHOULD NOT contain any attributes. + */ + saved = pj_stun_session_use_fingerprint(comp->stun_sess, PJ_FALSE); + + /* Send to session */ + addr_len = pj_sockaddr_get_len(&the_check->rcand->addr); + status = pj_stun_session_send_msg(comp->stun_sess, msg_data, + PJ_FALSE, PJ_FALSE, + &the_check->rcand->addr, + addr_len, tdata); + + /* Restore FINGERPRINT usage */ + pj_stun_session_use_fingerprint(comp->stun_sess, saved); + +done: + ice->comp_ka = (ice->comp_ka + 1) % ice->comp_cnt; + } + + if (ice->timer.id == TIMER_NONE) { + pj_time_val delay = { 0, 0 }; + + delay.msec = (PJ_ICE_SESS_KEEP_ALIVE_MIN + + (pj_rand() % PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND)) * 1000 / + ice->comp_cnt; + pj_time_val_normalize(&delay); + + ice->timer.id = TIMER_KEEP_ALIVE; + pj_timer_heap_schedule(ice->stun_cfg.timer_heap, &ice->timer, &delay); + + } else { + pj_assert(!"Not expected any timer active"); + } } /* This function is called when ICE processing completes */ @@ -1060,6 +1209,11 @@ static void on_ice_complete(pj_ice_sess *ice, pj_status_t status) ice->is_complete = PJ_TRUE; ice->ice_status = status; + if (ice->timer.id != TIMER_NONE) { + pj_timer_heap_cancel(ice->stun_cfg.timer_heap, &ice->timer); + ice->timer.id = TIMER_NONE; + } + /* Log message */ LOG4((ice->obj_name, "ICE process complete, status=%s", pj_strerror(status, ice->tmp.errmsg, @@ -1071,26 +1225,49 @@ static void on_ice_complete(pj_ice_sess *ice, pj_status_t status) if (ice->cb.on_ice_complete) { pj_time_val delay = {0, 0}; - ice->completion_timer.cb = &on_completion_timer; - ice->completion_timer.user_data = (void*) ice; - ice->completion_timer.id = PJ_TRUE; - + ice->timer.id = TIMER_COMPLETION_CALLBACK; pj_timer_heap_schedule(ice->stun_cfg.timer_heap, - &ice->completion_timer, - &delay); + &ice->timer, &delay); } } } +/* Update valid check and nominated check for the candidate */ +static void update_comp_check(pj_ice_sess *ice, unsigned comp_id, + pj_ice_sess_check *check) +{ + pj_ice_sess_comp *comp; + + comp = find_comp(ice, comp_id); + if (comp->valid_check == NULL) { + comp->valid_check = check; + } else { + if (CMP_CHECK_PRIO(comp->valid_check, check) < 0) + comp->valid_check = check; + } + + if (check->nominated) { + /* Update the nominated check for the component */ + if (comp->nominated_check == NULL) { + comp->nominated_check = check; + } else { + if (CMP_CHECK_PRIO(comp->nominated_check, check) < 0) + comp->nominated_check = check; + } + } +} /* This function is called when one check completes */ static pj_bool_t on_check_complete(pj_ice_sess *ice, pj_ice_sess_check *check) { + pj_ice_sess_comp *comp; unsigned i; pj_assert(check->state >= PJ_ICE_SESS_CHECK_STATE_SUCCEEDED); + comp = find_comp(ice, check->lcand->comp_id); + /* 7.1.2.2.2. Updating Pair States * * The agent sets the state of the pair that generated the check to @@ -1104,6 +1281,7 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, * always. */ if (check->err_code==PJ_SUCCESS) { + for (i=0; i<ice->clist.count; ++i) { pj_ice_sess_check *c = &ice->clist.checks[i]; if (pj_strcmp(&c->lcand->foundation, &check->lcand->foundation)==0 @@ -1112,6 +1290,11 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); } } + + LOG5((ice->obj_name, "Check %d is successful%s", + GET_CHECK_ID(&ice->clist, check), + (check->nominated ? " and nominated" : ""))); + } /* 8.2. Updating States @@ -1136,12 +1319,6 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, * than the lowest priority nominated pair for that component */ if (check->err_code==PJ_SUCCESS && check->nominated) { - pj_ice_sess_comp *comp; - - LOG5((ice->obj_name, "Check %d is successful and nominated", - GET_CHECK_ID(&ice->clist, check))); - - comp = find_comp(ice, check->lcand->comp_id); for (i=0; i<ice->clist.count; ++i) { @@ -1179,14 +1356,6 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, } } } - - /* Update the nominated check for the component */ - if (comp->valid_check == NULL) { - comp->valid_check = check; - } else { - if (CMP_CHECK_PRIO(comp->valid_check, check) < 0) - comp->valid_check = check; - } } @@ -1211,7 +1380,7 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, * ICE processing as success, otherwise wait. */ for (i=0; i<ice->comp_cnt; ++i) { - if (ice->comp[i].valid_check == NULL) + if (ice->comp[i].nominated_check == NULL) break; } if (i == ice->comp_cnt) { @@ -1258,23 +1427,16 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, /* All checks have completed, but we don't have nominated pair. * If agent's role is controlled, check if all components have * valid pair. If it does, this means the controlled agent has - * finished the check list early and it's waiting for controlling - * agent to send a check with USE-CANDIDATE flag set. + * finished the check list and it's waiting for controlling + * agent to send checks with USE-CANDIDATE flag set. */ if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLED) { - unsigned comp_id; - for (comp_id=1; comp_id <= ice->comp_cnt; ++comp_id) { - unsigned j; - for (j=0; j<ice->valid_list.count; ++j) { - pj_ice_sess_check *vc = &ice->valid_list.checks[j]; - if (vc->lcand->comp_id == comp_id) - break; - } - if (j == ice->valid_list.count) + for (i=0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check == NULL) break; } - if (comp_id <= ice->comp_cnt) { + if (i < ice->comp_cnt) { /* This component ID doesn't have valid pair. * Mark ICE as failed. */ @@ -1284,12 +1446,109 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, /* All components have a valid pair. * We should wait until we receive nominated checks. */ + if (ice->timer.id == TIMER_NONE && + ice->opt.controlled_agent_want_nom_timeout >= 0) + { + pj_time_val delay; + + delay.sec = 0; + delay.msec = ice->opt.controlled_agent_want_nom_timeout; + pj_time_val_normalize(&delay); + + ice->timer.id = TIMER_CONTROLLED_WAIT_NOM; + pj_timer_heap_schedule(ice->stun_cfg.timer_heap, + &ice->timer, + &delay); + + LOG5((ice->obj_name, + "All checks have completed. Controlled agent now " + "waits for nomination from controlling agent " + "(timeout=%d msec)", + ice->opt.controlled_agent_want_nom_timeout)); + } return PJ_FALSE; } + + /* Unreached */ + + } else if (ice->is_nominating) { + /* We are controlling agent and all checks have completed but + * there's at least one component without nominated pair (or + * more likely we don't have any nominated pairs at all). + */ + on_ice_complete(ice, PJNATH_EICEFAILED); + return PJ_TRUE; + + } else { + /* We are controlling agent and all checks have completed. If + * we have valid list for every component, then move on to + * sending nominated check, otherwise we have failed. + */ + for (i=0; i<ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check == NULL) + break; + } + + if (i < ice->comp_cnt) { + /* At least one component doesn't have a valid check. Mark + * ICE as failed. + */ + on_ice_complete(ice, PJNATH_EICEFAILED); + return PJ_TRUE; + } + + /* Now it's time to send connectivity check with nomination + * flag set. + */ + LOG4((ice->obj_name, + "All checks have completed, starting nominated checks now")); + start_nominated_check(ice); + return PJ_FALSE; } + } - on_ice_complete(ice, PJNATH_EICEFAILED); - return PJ_TRUE; + /* If this connectivity check has been successful, scan all components + * and see if they have a valid pair, if we are controlling and we haven't + * started our nominated check yet. + */ + if (check->err_code == PJ_SUCCESS && + ice->role==PJ_ICE_SESS_ROLE_CONTROLLING && + !ice->is_nominating && + ice->timer.id == TIMER_NONE) + { + pj_time_val delay; + + for (i=0; i<ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check == NULL) + break; + } + + if (i < ice->comp_cnt) { + /* Some components still don't have valid pair, continue + * processing. + */ + return PJ_FALSE; + } + + LOG4((ice->obj_name, + "Scheduling nominated check in %d ms", + ice->opt.nominated_check_delay)); + + if (ice->timer.id != TIMER_NONE) { + pj_timer_heap_cancel(ice->stun_cfg.timer_heap, &ice->timer); + ice->timer.id = TIMER_NONE; + } + + /* All components have valid pair. Let connectivity checks run for + * a little bit more time, then start our nominated check. + */ + delay.sec = 0; + delay.msec = ice->opt.nominated_check_delay; + pj_time_val_normalize(&delay); + + ice->timer.id = TIMER_START_NOMINATED_CHECK; + pj_timer_heap_schedule(ice->stun_cfg.timer_heap, &ice->timer, &delay); + return PJ_FALSE; } /* We still have checks to perform */ @@ -1297,7 +1556,6 @@ static pj_bool_t on_check_complete(pj_ice_sess *ice, } - /* Create checklist by pairing local candidates with remote candidates */ PJ_DEF(pj_status_t) pj_ice_sess_create_check_list( pj_ice_sess *ice, @@ -1430,10 +1688,11 @@ PJ_DEF(pj_status_t) pj_ice_sess_create_check_list( return PJ_SUCCESS; } -/* Perform check on the specified candidate pair */ +/* Perform check on the specified candidate pair. */ static pj_status_t perform_check(pj_ice_sess *ice, pj_ice_sess_checklist *clist, - unsigned check_id) + unsigned check_id, + pj_bool_t nominate) { pj_ice_sess_comp *comp; pj_ice_msg_data *msg_data; @@ -1472,8 +1731,13 @@ static pj_status_t perform_check(pj_ice_sess *ice, msg_data->data.req.ckid = check_id; /* Add PRIORITY */ +#if PJNATH_ICE_PRIO_STD + prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, 65535, + lcand->comp_id); +#else prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, 0, lcand->comp_id); +#endif pj_stun_msg_add_uint_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_PRIORITY, prio); @@ -1481,9 +1745,11 @@ static pj_status_t perform_check(pj_ice_sess *ice, * Also add ICE-CONTROLLING or ICE-CONTROLLED */ if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING) { - pj_stun_msg_add_empty_attr(check->tdata->pool, check->tdata->msg, - PJ_STUN_ATTR_USE_CANDIDATE); - check->nominated = PJ_TRUE; + if (nominate) { + pj_stun_msg_add_empty_attr(check->tdata->pool, check->tdata->msg, + PJ_STUN_ATTR_USE_CANDIDATE); + check->nominated = PJ_TRUE; + } pj_stun_msg_add_uint64_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_ICE_CONTROLLING, @@ -1549,7 +1815,7 @@ static pj_status_t start_periodic_check(pj_timer_heap_t *th, pj_ice_sess_check *check = &clist->checks[i]; if (check->state == PJ_ICE_SESS_CHECK_STATE_WAITING) { - status = perform_check(ice, clist, i); + status = perform_check(ice, clist, i, ice->is_nominating); if (status != PJ_SUCCESS) { pj_mutex_unlock(ice->mutex); return status; @@ -1568,7 +1834,7 @@ static pj_status_t start_periodic_check(pj_timer_heap_t *th, pj_ice_sess_check *check = &clist->checks[i]; if (check->state == PJ_ICE_SESS_CHECK_STATE_FROZEN) { - status = perform_check(ice, clist, i); + status = perform_check(ice, clist, i, ice->is_nominating); if (status != PJ_SUCCESS) { pj_mutex_unlock(ice->mutex); return status; @@ -1596,6 +1862,66 @@ static pj_status_t start_periodic_check(pj_timer_heap_t *th, } +/* Start sending connectivity check with USE-CANDIDATE */ +static void start_nominated_check(pj_ice_sess *ice) +{ + pj_time_val delay; + unsigned i; + pj_status_t status; + + LOG4((ice->obj_name, "Starting nominated check..")); + + pj_assert(ice->is_nominating == PJ_FALSE); + + /* Stop our timer if it's active */ + if (ice->timer.id == TIMER_START_NOMINATED_CHECK) { + pj_timer_heap_cancel(ice->stun_cfg.timer_heap, &ice->timer); + ice->timer.id = TIMER_NONE; + } + + /* For each component, set the check state of valid check with + * highest priority to Waiting (it should have Success state now). + */ + for (i=0; i<ice->comp_cnt; ++i) { + unsigned j; + const pj_ice_sess_check *vc = ice->comp[i].valid_check; + + pj_assert(ice->comp[i].nominated_check == NULL); + pj_assert(vc->err_code == PJ_SUCCESS); + + for (j=0; j<ice->clist.count; ++j) { + pj_ice_sess_check *c = &ice->clist.checks[j]; + if (c->lcand->transport_id == vc->lcand->transport_id && + c->rcand == vc->rcand) + { + pj_assert(c->err_code == PJ_SUCCESS); + c->state = PJ_ICE_SESS_CHECK_STATE_FROZEN; + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, + PJ_SUCCESS); + break; + } + } + } + + /* And (re)start the periodic check */ + if (!ice->clist.timer.id) { + pj_timer_heap_cancel(ice->stun_cfg.timer_heap, &ice->clist.timer); + ice->clist.timer.id = PJ_FALSE; + } + + ice->clist.timer.id = PJ_TRUE; + delay.sec = delay.msec = 0; + status = pj_timer_heap_schedule(ice->stun_cfg.timer_heap, + &ice->clist.timer, &delay); + if (status != PJ_SUCCESS) { + ice->clist.timer.id = PJ_FALSE; + } else { + LOG5((ice->obj_name, "Periodic timer rescheduled..")); + } + + ice->is_nominating = PJ_TRUE; +} + /* Timer callback to perform periodic check */ static void periodic_timer(pj_timer_heap_t *th, pj_timer_entry *te) @@ -1642,6 +1968,10 @@ PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice) LOG4((ice->obj_name, "Starting ICE check..")); + /* If we are using aggressive nomination, set the is_nominating state */ + if (ice->opt.aggressive) + ice->is_nominating = PJ_TRUE; + /* The agent examines the check list for the first media stream (a * media stream is the first media stream when it is described by * the first m-line in the SDP offer and answer). For that media @@ -1826,7 +2156,8 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, /* Resend request */ LOG4((ice->obj_name, "Resending check because of role conflict")); check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); - perform_check(ice, clist, msg_data->data.req.ckid); + perform_check(ice, clist, msg_data->data.req.ckid, + check->nominated || ice->is_nominating); pj_mutex_unlock(ice->mutex); return; } @@ -1959,19 +2290,34 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, * equals the destination address to which the request was sent. */ - /* Add pair to valid list */ - pj_assert(ice->valid_list.count < PJ_ICE_MAX_CHECKS); - new_check = &ice->valid_list.checks[ice->valid_list.count++]; - new_check->lcand = lcand; - new_check->rcand = check->rcand; - new_check->prio = CALC_CHECK_PRIO(ice, lcand, check->rcand); - new_check->state = PJ_ICE_SESS_CHECK_STATE_SUCCEEDED; - new_check->nominated = check->nominated; - new_check->err_code = PJ_SUCCESS; + /* Add pair to valid list, if it's not there, otherwise just update + * nominated flag + */ + for (i=0; i<ice->valid_list.count; ++i) { + if (ice->valid_list.checks[i].lcand == lcand && + ice->valid_list.checks[i].rcand == check->rcand) + break; + } + + if (i==ice->valid_list.count) { + pj_assert(ice->valid_list.count < PJ_ICE_MAX_CHECKS); + new_check = &ice->valid_list.checks[ice->valid_list.count++]; + new_check->lcand = lcand; + new_check->rcand = check->rcand; + new_check->prio = CALC_CHECK_PRIO(ice, lcand, check->rcand); + new_check->state = PJ_ICE_SESS_CHECK_STATE_SUCCEEDED; + new_check->nominated = check->nominated; + new_check->err_code = PJ_SUCCESS; + } else { + new_check = &ice->valid_list.checks[i]; + ice->valid_list.checks[i].nominated = check->nominated; + } /* Sort valid_list */ sort_checklist(&ice->valid_list); + /* Update valid check and nominated check for the component */ + update_comp_check(ice, new_check->lcand->comp_id, new_check); /* 7.1.2.2.2. Updating Pair States * @@ -2312,8 +2658,11 @@ static void handle_incoming_check(pj_ice_sess *ice, if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN || c->state == PJ_ICE_SESS_CHECK_STATE_WAITING) { + /* See if we shall nominate this check */ + pj_bool_t nominate = (c->nominated || ice->is_nominating); + LOG5((ice->obj_name, "Performing triggered check for check %d",i)); - perform_check(ice, &ice->clist, i); + perform_check(ice, &ice->clist, i, nominate); } else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) { /* Should retransmit immediately @@ -2336,8 +2685,17 @@ static void handle_incoming_check(pj_ice_sess *ice, if (rcheck->use_candidate) { for (j=0; j<ice->valid_list.count; ++j) { pj_ice_sess_check *vc = &ice->valid_list.checks[j]; - if (vc->lcand == c->lcand && vc->rcand == c->rcand) { + if (vc->lcand->transport_id == c->lcand->transport_id && + vc->rcand == c->rcand) + { + /* Set nominated flag */ vc->nominated = PJ_TRUE; + + /* Update valid check and nominated check for the component */ + update_comp_check(ice, vc->lcand->comp_id, vc); + + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->valid_list, vc); + LOG5((ice->obj_name, "Valid check %s is nominated", ice->tmp.txt)); } } } @@ -2361,6 +2719,7 @@ static void handle_incoming_check(pj_ice_sess *ice, else if (ice->clist.count < PJ_ICE_MAX_CHECKS) { pj_ice_sess_check *c = &ice->clist.checks[ice->clist.count]; + pj_bool_t nominate; c->lcand = lcand; c->rcand = rcand; @@ -2369,9 +2728,11 @@ static void handle_incoming_check(pj_ice_sess *ice, c->nominated = rcheck->use_candidate; c->err_code = PJ_SUCCESS; + nominate = (c->nominated || ice->is_nominating); + LOG4((ice->obj_name, "New triggered check added: %d", ice->clist.count)); - perform_check(ice, &ice->clist, ice->clist.count++); + perform_check(ice, &ice->clist, ice->clist.count++, nominate); } else { LOG4((ice->obj_name, "Error: unable to perform triggered check: " @@ -2388,6 +2749,8 @@ static pj_status_t on_stun_rx_indication(pj_stun_session *sess, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { + struct stun_data *sd; + PJ_UNUSED_ARG(sess); PJ_UNUSED_ARG(pkt); PJ_UNUSED_ARG(pkt_len); @@ -2396,9 +2759,18 @@ static pj_status_t on_stun_rx_indication(pj_stun_session *sess, PJ_UNUSED_ARG(src_addr); PJ_UNUSED_ARG(src_addr_len); - PJ_TODO(SUPPORT_RX_BIND_REQUEST_AS_INDICATION); + sd = (struct stun_data*) pj_stun_session_get_user_data(sess); - return PJ_ENOTSUP; + if (msg->hdr.type == PJ_STUN_BINDING_INDICATION) { + LOG5((sd->ice->obj_name, "Received Binding Indication keep-alive " + "for component %d", sd->comp_id)); + } else { + LOG4((sd->ice->obj_name, "Received unexpected %s indication " + "for component %d", pj_stun_get_method_name(msg->hdr.type), + sd->comp_id)); + } + + return PJ_SUCCESS; } diff --git a/pjnath/src/pjnath/ice_strans.c b/pjnath/src/pjnath/ice_strans.c index b244f0fc..92f550d0 100644 --- a/pjnath/src/pjnath/ice_strans.c +++ b/pjnath/src/pjnath/ice_strans.c @@ -50,20 +50,34 @@ enum tp_type /* Candidate's local preference values. This is mostly used to * specify preference among candidates with the same type. Since * we don't have the facility to specify that, we'll just set it - * all to zero. + * all to the same value. */ -#define SRFLX_PREF 0 -#define HOST_PREF 0 -#define RELAY_PREF 0 +#if PJNATH_ICE_PRIO_STD +# define SRFLX_PREF 65535 +# define HOST_PREF 65535 +# define RELAY_PREF 65535 +#else +# define SRFLX_PREF 0 +# define HOST_PREF 0 +# define RELAY_PREF 0 +#endif + /* The candidate type preference when STUN candidate is used */ static pj_uint8_t srflx_pref_table[4] = { +#if PJNATH_ICE_PRIO_STD + 100, /**< PJ_ICE_HOST_PREF */ + 126, /**< PJ_ICE_SRFLX_PREF */ + 110, /**< PJ_ICE_PRFLX_PREF */ + 0 /**< PJ_ICE_RELAYED_PREF */ +#else /* Keep it to 2 bits */ 1, /**< PJ_ICE_HOST_PREF */ 2, /**< PJ_ICE_SRFLX_PREF */ 3, /**< PJ_ICE_PRFLX_PREF */ 0 /**< PJ_ICE_RELAYED_PREF */ +#endif }; @@ -197,9 +211,13 @@ PJ_DEF(void) pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg) pj_stun_sock_cfg_default(&cfg->stun.cfg); pj_turn_alloc_param_default(&cfg->turn.alloc_param); + pj_ice_sess_options_default(&cfg->opt); + cfg->af = pj_AF_INET(); cfg->stun.port = PJ_STUN_PORT; cfg->turn.conn_type = PJ_TURN_TP_UDP; + + cfg->stun.max_host_cands = 64; } @@ -245,7 +263,7 @@ static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id) comp->default_cand = 0; /* Create STUN transport if configured */ - if (ice_st->cfg.stun.server.slen || !ice_st->cfg.stun.no_host_cands) { + if (ice_st->cfg.stun.server.slen || ice_st->cfg.stun.max_host_cands) { pj_stun_sock_cb stun_sock_cb; pj_ice_sess_cand *cand; @@ -309,10 +327,10 @@ static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id) } - /* Add local addresses to host candidates, unless no_host_cands - * flag is set. + /* Add local addresses to host candidates, unless max_host_cands + * is set to zero. */ - if (ice_st->cfg.stun.no_host_cands == PJ_FALSE) { + if (ice_st->cfg.stun.max_host_cands) { pj_stun_sock_info stun_sock_info; unsigned i; @@ -321,7 +339,9 @@ static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id) if (status != PJ_SUCCESS) return status; - for (i=0; i<stun_sock_info.alias_cnt; ++i) { + for (i=0; i<stun_sock_info.alias_cnt && + i<ice_st->cfg.stun.max_host_cands; ++i) + { char addrinfo[PJ_INET6_ADDRSTRLEN+10]; const pj_sockaddr *addr = &stun_sock_info.aliases[i]; @@ -647,6 +667,30 @@ PJ_DEF(void*) pj_ice_strans_get_user_data(pj_ice_strans *ice_st) /* + * Get the value of various options of the ICE stream transport. + */ +PJ_DEF(pj_status_t) pj_ice_strans_get_options( pj_ice_strans *ice_st, + pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice_st && opt, PJ_EINVAL); + pj_memcpy(opt, &ice_st->cfg.opt, sizeof(*opt)); + return PJ_SUCCESS; +} + +/* + * Specify various options for this ICE stream transport. + */ +PJ_DEF(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st, + const pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice_st && opt, PJ_EINVAL); + pj_memcpy(&ice_st->cfg.opt, opt, sizeof(*opt)); + if (ice_st->ice) + pj_ice_sess_set_options(ice_st->ice, &ice_st->cfg.opt); + return PJ_SUCCESS; +} + +/* * Create ICE! */ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st, @@ -682,6 +726,9 @@ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st, /* Associate user data */ ice_st->ice->user_data = (void*)ice_st; + /* Set options */ + pj_ice_sess_set_options(ice_st->ice, &ice_st->cfg.opt); + /* If default candidate for components are SRFLX one, upload a custom * type priority to ICE session so that SRFLX candidates will get * checked first. diff --git a/pjnath/src/pjnath/stun_auth.c b/pjnath/src/pjnath/stun_auth.c index cfca3382..7cb0ab12 100644 --- a/pjnath/src/pjnath/stun_auth.c +++ b/pjnath/src/pjnath/stun_auth.c @@ -528,6 +528,7 @@ PJ_DEF(pj_bool_t) pj_stun_auth_valid_for_msg(const pj_stun_msg *msg) switch (err_attr->err_code) { case PJ_STUN_SC_BAD_REQUEST: /* 400 (Bad Request) */ case PJ_STUN_SC_UNAUTHORIZED: /* 401 (Unauthorized) */ + case PJ_STUN_SC_STALE_NONCE: /* 438 (Stale Nonce) */ /* Due to the way this response is generated here, we can't really * authenticate 420 (Unknown Attribute) response */ diff --git a/pjnath/src/pjnath/stun_msg_dump.c b/pjnath/src/pjnath/stun_msg_dump.c index 2ff4a74b..afe9530c 100644 --- a/pjnath/src/pjnath/stun_msg_dump.c +++ b/pjnath/src/pjnath/stun_msg_dump.c @@ -57,11 +57,19 @@ static int print_attr(char *buffer, unsigned length, const pj_stun_attr_hdr *ahdr) { char *p = buffer, *end = buffer + length; + const char *attr_name = pj_stun_get_attr_name(ahdr->type); + char attr_buf[32]; int len; + if (*attr_name == '?') { + pj_ansi_snprintf(attr_buf, sizeof(attr_buf), "Attr 0x%x", + ahdr->type); + attr_name = attr_buf; + } + len = pj_ansi_snprintf(p, end-p, " %s: length=%d", - pj_stun_get_attr_name(ahdr->type), + attr_name, (int)ahdr->length); APPLY(); diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c index fd71beb8..fb5c88c6 100644 --- a/pjnath/src/pjnath/stun_session.c +++ b/pjnath/src/pjnath/stun_session.c @@ -219,10 +219,15 @@ static pj_status_t apply_msg_options(pj_stun_session *sess, /* If the agent is sending a request, it SHOULD add a SOFTWARE attribute * to the request. The server SHOULD include a SOFTWARE attribute in all - * responses + * responses. + * + * If magic value is not PJ_STUN_MAGIC, only apply the attribute for + * responses. */ - if (sess->srv_name.slen && !PJ_STUN_IS_INDICATION(msg->hdr.type) && - pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_SOFTWARE, 0)==NULL) + if (sess->srv_name.slen && + pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_SOFTWARE, 0)==NULL && + (PJ_STUN_IS_RESPONSE(msg->hdr.type) || + PJ_STUN_IS_REQUEST(msg->hdr.type) && msg->hdr.magic==PJ_STUN_MAGIC)) { pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SOFTWARE, &sess->srv_name); @@ -630,6 +635,18 @@ PJ_DEF(void) pj_stun_session_set_log( pj_stun_session *sess, sess->log_flag = flags; } +PJ_DEF(pj_bool_t) pj_stun_session_use_fingerprint(pj_stun_session *sess, + pj_bool_t use) +{ + pj_bool_t old_use; + + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + old_use = sess->use_fingerprint; + sess->use_fingerprint = use; + return old_use; +} + static pj_status_t get_auth(pj_stun_session *sess, pj_stun_tx_data *tdata) { diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index dd81f9e9..8be930f7 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -264,7 +264,8 @@ static void usage(void) puts (""); puts ("Media Transport Options:"); puts (" --use-ice Enable ICE (default:no)"); - puts (" --ice-no-host Disable ICE host candidates (default: no)"); + puts (" --ice-regular Use ICE regular nomination (default: aggressive)"); + 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)"); puts (" --rx-drop-pct=PCT Drop PCT percent of RX RTP (for pkt lost sim, default: 0)"); @@ -476,8 +477,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_USE_SRTP, OPT_SRTP_SECURE, - OPT_USE_TURN, OPT_ICE_NO_HOST, OPT_ICE_NO_RTCP, OPT_TURN_SRV, + OPT_USE_ICE, OPT_ICE_REGULAR, 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_PLAY_FILE, OPT_PLAY_TONE, OPT_RTP_PORT, OPT_ADD_CODEC, OPT_ILBC_MODE, OPT_REC_FILE, OPT_AUTO_REC, @@ -553,8 +554,9 @@ static pj_status_t parse_args(int argc, char *argv[], { "rtp-port", 1, 0, OPT_RTP_PORT}, { "use-ice", 0, 0, OPT_USE_ICE}, + { "ice-regular",0, 0, OPT_ICE_REGULAR}, { "use-turn", 0, 0, OPT_USE_TURN}, - { "ice-no-host",0, 0, OPT_ICE_NO_HOST}, + { "ice-max-hosts",1, 0, OPT_ICE_MAX_HOSTS}, { "ice-no-rtcp",0, 0, OPT_ICE_NO_RTCP}, { "turn-srv", 1, 0, OPT_TURN_SRV}, { "turn-tcp", 0, 0, OPT_TURN_TCP}, @@ -992,12 +994,16 @@ static pj_status_t parse_args(int argc, char *argv[], cfg->media_cfg.enable_ice = PJ_TRUE; break; + case OPT_ICE_REGULAR: + cfg->media_cfg.ice_opt.aggressive = PJ_FALSE; + break; + case OPT_USE_TURN: cfg->media_cfg.enable_turn = PJ_TRUE; break; - case OPT_ICE_NO_HOST: - cfg->media_cfg.ice_no_host_cands = PJ_TRUE; + case OPT_ICE_MAX_HOSTS: + cfg->media_cfg.ice_max_host_cands = my_atoi(pj_optarg); break; case OPT_ICE_NO_RTCP: @@ -1644,11 +1650,17 @@ static int write_settings(const struct app_config *config, if (config->media_cfg.enable_ice) pj_strcat2(&cfg, "--use-ice\n"); + if (config->media_cfg.ice_opt.aggressive == PJ_FALSE) + pj_strcat2(&cfg, "--ice-regular\n"); + if (config->media_cfg.enable_turn) pj_strcat2(&cfg, "--use-turn\n"); - if (config->media_cfg.ice_no_host_cands) - pj_strcat2(&cfg, "--ice-no-host\n"); + if (config->media_cfg.ice_max_host_cands >= 0) { + pj_ansi_sprintf(line, "--ice_max_host_cands %d\n", + config->media_cfg.ice_max_host_cands); + pj_strcat2(&cfg, line); + } if (config->media_cfg.ice_no_rtcp) pj_strcat2(&cfg, "--ice-no-rtcp\n"); @@ -1885,7 +1897,7 @@ static int write_settings(const struct app_config *config, pj_strcat2(&cfg, "--use-compact-form\n"); } - if (config->cfg.force_lr) { + if (!config->cfg.force_lr) { pj_strcat2(&cfg, "--no-force-lr\n"); } @@ -3687,7 +3699,9 @@ void console_app_main(const pj_str_t *uri_to_call) pj_list_push_back(&msg_data.hdr_list, &refer_sub); } - pjsua_call_xfer_replaces(call, dst_call, 0, &msg_data); + pjsua_call_xfer_replaces(call, dst_call, + PJSUA_XFER_NO_REQUIRE_REPLACES, + &msg_data); } break; @@ -4718,19 +4732,62 @@ static pj_status_t create_ipv6_media_transports(void) for (i=0; i<app_config.cfg.max_calls; ++i) { enum { MAX_RETRY = 10 }; + pj_sock_t sock[2]; + pjmedia_sock_info si; unsigned j; /* Get rid of uninitialized var compiler warning with MSVC */ status = PJ_SUCCESS; for (j=0; j<MAX_RETRY; ++j) { - status = pjmedia_transport_udp_create3(pjsua_get_pjmedia_endpt(), - pj_AF_INET6(), - NULL, - &app_config.rtp_cfg.bound_addr, - port, - 0, &tp[i].transport); + unsigned k; + + for (k=0; k<2; ++k) { + pj_sockaddr bound_addr; + + status = pj_sock_socket(pj_AF_INET6(), pj_SOCK_DGRAM(), 0, &sock[k]); + if (status != PJ_SUCCESS) + break; + + status = pj_sockaddr_init(pj_AF_INET6(), &bound_addr, + &app_config.rtp_cfg.bound_addr, + (unsigned short)(port+k)); + if (status != PJ_SUCCESS) + break; + status = pj_sock_bind(sock[k], &bound_addr, + pj_sockaddr_get_len(&bound_addr)); + if (status != PJ_SUCCESS) + break; + } + if (status != PJ_SUCCESS) { + if (k==1) + pj_sock_close(sock[0]); + + if (port != 0) + port += 10; + else + break; + + continue; + } + + pj_bzero(&si, sizeof(si)); + si.rtp_sock = sock[0]; + si.rtcp_sock = sock[1]; + + pj_sockaddr_init(pj_AF_INET6(), &si.rtp_addr_name, + &app_config.rtp_cfg.public_addr, + (unsigned short)(port)); + pj_sockaddr_init(pj_AF_INET6(), &si.rtcp_addr_name, + &app_config.rtp_cfg.public_addr, + (unsigned short)(port+1)); + + status = pjmedia_transport_udp_attach(pjsua_get_pjmedia_endpt(), + NULL, + &si, + 0, + &tp[i].transport); if (port != 0) port += 10; else diff --git a/pjsip-apps/src/samples/debug.c b/pjsip-apps/src/samples/debug.c index 1a145b9a..5417a79a 100644 --- a/pjsip-apps/src/samples/debug.c +++ b/pjsip-apps/src/samples/debug.c @@ -28,5 +28,5 @@ * E.g.: * #include "playfile.c" */ -#include "auddemo.c" +#include "icedemo.c" diff --git a/pjsip-apps/src/samples/icedemo.c b/pjsip-apps/src/samples/icedemo.c index 1c13cf33..f3f7c6c3 100644 --- a/pjsip-apps/src/samples/icedemo.c +++ b/pjsip-apps/src/samples/icedemo.c @@ -39,7 +39,8 @@ static struct app_t { unsigned comp_cnt; pj_str_t ns; - pj_bool_t no_host; + int max_host; + pj_bool_t regular; pj_str_t stun_srv; pj_str_t turn_srv; pj_bool_t turn_tcp; @@ -306,8 +307,15 @@ static pj_status_t icedemo_init(void) /* -= Start initializing ICE stream transport config =- */ - /* Disable host candidates? */ - icedemo.ice_cfg.stun.no_host_cands = icedemo.opt.no_host; + /* Maximum number of host candidates */ + if (icedemo.opt.max_host != -1) + icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host; + + /* Nomination strategy */ + if (icedemo.opt.regular) + icedemo.ice_cfg.opt.aggressive = PJ_FALSE; + else + icedemo.ice_cfg.opt.aggressive = PJ_TRUE; /* Configure STUN/srflx candidate resolution */ if (icedemo.opt.stun_srv.slen) { @@ -960,7 +968,7 @@ static void icedemo_send_data(unsigned comp_id, const char *data) } */ - if (comp_id > pj_ice_strans_get_running_comp_cnt(icedemo.icest)) { + if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) { PJ_LOG(1,(THIS_FILE, "Error: invalid component ID")); return; } @@ -1137,7 +1145,8 @@ static void icedemo_usage() puts(" --comp-cnt, -c N Component count (default=1)"); puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV"); puts(" resolution"); - puts(" --no-host, -H Disable host candidates"); + puts(" --max-host, -H N Set max number of host candidates to N"); + puts(" --regular, -R Use regular nomination (default aggressive)"); puts(" --help, -h Display this screen."); puts(""); puts("STUN related options:"); @@ -1165,21 +1174,23 @@ int main(int argc, char *argv[]) struct pj_getopt_option long_options[] = { { "comp-cnt", 1, 0, 'c'}, { "nameserver", 1, 0, 'n'}, - { "no-host", 0, 0, 'H'}, + { "max-host", 1, 0, 'H'}, { "help", 0, 0, 'h'}, { "stun-srv", 1, 0, 's'}, { "turn-srv", 1, 0, 't'}, { "turn-tcp", 0, 0, 'T'}, { "turn-username", 1, 0, 'u'}, { "turn-password", 1, 0, 'p'}, - { "turn-fingerprint", 0, 0, 'F'} + { "turn-fingerprint", 0, 0, 'F'}, + { "regular", 0, 0, 'R'} }; int c, opt_id; pj_status_t status; icedemo.opt.comp_cnt = 1; + icedemo.opt.max_host = -1; - while((c=pj_getopt_long(argc,argv, "n:s:t:u:p:HhTF", long_options, &opt_id))!=-1) { + while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:hTFR", long_options, &opt_id))!=-1) { switch (c) { case 'c': icedemo.opt.comp_cnt = atoi(pj_optarg); @@ -1192,7 +1203,7 @@ int main(int argc, char *argv[]) icedemo.opt.ns = pj_str(pj_optarg); break; case 'H': - icedemo.opt.no_host = PJ_TRUE; + icedemo.opt.max_host = atoi(pj_optarg); break; case 'h': icedemo_usage(); @@ -1215,6 +1226,9 @@ int main(int argc, char *argv[]) case 'F': icedemo.opt.turn_fingerprint = PJ_TRUE; break; + case 'R': + icedemo.opt.regular = PJ_TRUE; + break; default: printf("Argument \"%s\" is not valid. Use -h to see help", argv[pj_optind]); diff --git a/pjsip-apps/src/samples/siprtp_report.c b/pjsip-apps/src/samples/siprtp_report.c index e63ef949..84c00724 100644 --- a/pjsip-apps/src/samples/siprtp_report.c +++ b/pjsip-apps/src/samples/siprtp_report.c @@ -77,7 +77,7 @@ static void print_call(int call_index) /* Call identification */ len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); - if (len < 1) + if (len < 0) pj_ansi_strcpy(userinfo, "<--uri too long-->"); else userinfo[len] = '\0'; diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h index 1db144c0..a171a096 100644 --- a/pjsip/include/pjsip/sip_config.h +++ b/pjsip/include/pjsip/sip_config.h @@ -227,6 +227,21 @@ PJ_INLINE(pjsip_cfg_t*) pjsip_cfg(void) /** + * RFC 3261 section 18.1.1: + * If a request is within 200 bytes of the path MTU, or if it is larger + * than 1300 bytes and the path MTU is unknown, the request MUST be sent + * using an RFC 2914 [43] congestion controlled transport protocol, such + * as TCP. + * + * This setting controls the threshold of the UDP packet, which if it's + * larger than this value the request will be sent with TCP. Default is + * 1300 bytes. + */ +#ifndef PJSIP_UDP_SIZE_THRESHOLD +# define PJSIP_UDP_SIZE_THRESHOLD 1300 +#endif + +/** * Encode SIP headers in their short forms to reduce size. By default, * SIP headers in outgoing messages will be encoded in their full names. * If this option is enabled, then SIP headers for outgoing messages diff --git a/pjsip/include/pjsip/sip_transport.h b/pjsip/include/pjsip/sip_transport.h index 502a0b04..275196bf 100644 --- a/pjsip/include/pjsip/sip_transport.h +++ b/pjsip/include/pjsip/sip_transport.h @@ -580,6 +580,17 @@ PJ_DECL(void) pjsip_tx_data_add_ref( pjsip_tx_data *tdata ); PJ_DECL(pj_status_t) pjsip_tx_data_dec_ref( pjsip_tx_data *tdata ); /** + * Print the SIP message to transmit data buffer's internal buffer. This + * may allocate memory for the buffer, if the buffer has not been allocated + * yet, and encode the SIP message to that buffer. + * + * @param tdata The transmit buffer. + * + * @return PJ_SUCCESS on success of the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsip_tx_data_encode(pjsip_tx_data *tdata); + +/** * Check if transmit data buffer contains a valid message. * * @param tdata The transmit buffer. diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index cb21ea07..95f5bf41 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -4260,9 +4260,16 @@ struct pjsua_media_config pj_bool_t enable_ice; /** - * Disable ICE host candidates. + * Set the maximum number of host candidates. + * + * Default: -1 (maximum not set) + */ + int ice_max_host_cands; + + /** + * ICE session options. */ - pj_bool_t ice_no_host_cands; + pj_ice_sess_options ice_opt; /** * Disable RTCP component. diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c index 53146b22..7ef55746 100644 --- a/pjsip/src/pjsip/sip_msg.c +++ b/pjsip/src/pjsip/sip_msg.c @@ -456,16 +456,18 @@ PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg, /* Print each of the headers. */ for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) { - len = (*hdr->vptr->print_on)(hdr, p, end-p); - if (len < 1) + len = pjsip_hdr_print_on(hdr, p, end-p); + if (len < 0) return -1; - p += len; - if (p+3 >= end) - return -1; + if (len > 0) { + p += len; + if (p+3 >= end) + return -1; - *p++ = '\r'; - *p++ = '\n'; + *p++ = '\r'; + *p++ = '\n'; + } } /* Process message body. */ @@ -1601,6 +1603,25 @@ static int pjsip_routing_hdr_print( pjsip_routing_hdr *hdr, char *startbuf = buf; char *endbuf = buf + size; const pjsip_parser_const_t *pc = pjsip_parser_const(); + pjsip_sip_uri *sip_uri; + pjsip_param *p; + + /* Check the proprietary param 'hide', don't print this header + * if it exists in the route URI. + */ + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(hdr->name_addr.uri); + p = sip_uri->other_param.next; + while (p != &sip_uri->other_param) { + const pj_str_t st_hide = {"hide", 4}; + + if (pj_stricmp(&p->name, &st_hide) == 0) { + /* Check if param 'hide' is specified without 'lr'. */ + pj_assert(sip_uri->lr_param != 0); + return 0; + } + p = p->next; + } + /* Route and Record-Route don't compact forms */ copy_advance(buf, hdr->name); diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c index 525f9e37..d10906db 100644 --- a/pjsip/src/pjsip/sip_transaction.c +++ b/pjsip/src/pjsip/sip_transaction.c @@ -1725,6 +1725,19 @@ static void send_msg_callback( pjsip_send_state *send_state, "will try next server. Err=%d (%s)", pjsip_tx_data_get_info(send_state->tdata), -sent, pj_strerror(-sent, errmsg, sizeof(errmsg)).ptr)); + + /* Reset retransmission count */ + tsx->retransmit_count = 0; + + /* And reset timeout timer */ + if (tsx->timeout_timer.id) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = TIMER_INACTIVE; + + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout_timer_val); + } } } diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c index 84b45a67..f34699be 100644 --- a/pjsip/src/pjsip/sip_transport.c +++ b/pjsip/src/pjsip/sip_transport.c @@ -434,6 +434,45 @@ PJ_DEF(void) pjsip_tx_data_invalidate_msg( pjsip_tx_data *tdata ) tdata->info = NULL; } +/* + * Print the SIP message to transmit data buffer's internal buffer. + */ +PJ_DEF(pj_status_t) pjsip_tx_data_encode(pjsip_tx_data *tdata) +{ + /* Allocate buffer if necessary. */ + if (tdata->buf.start == NULL) { + PJ_USE_EXCEPTION; + + PJ_TRY { + tdata->buf.start = (char*) + pj_pool_alloc(tdata->pool, PJSIP_MAX_PKT_LEN); + } + PJ_CATCH_ANY { + return PJ_ENOMEM; + } + PJ_END + + tdata->buf.cur = tdata->buf.start; + tdata->buf.end = tdata->buf.start + PJSIP_MAX_PKT_LEN; + } + + /* Do we need to reprint? */ + if (!pjsip_tx_data_is_valid(tdata)) { + pj_ssize_t size; + + size = pjsip_msg_print( tdata->msg, tdata->buf.start, + tdata->buf.end - tdata->buf.start); + if (size < 0) { + return PJSIP_EMSGTOOLONG; + } + pj_assert(size != 0); + tdata->buf.cur[size] = '\0'; + tdata->buf.cur += size; + } + + return PJ_SUCCESS; +} + PJ_DEF(pj_bool_t) pjsip_tx_data_is_valid( pjsip_tx_data *tdata ) { return tdata->buf.cur != tdata->buf.start; @@ -567,38 +606,7 @@ static void transport_send_callback(pjsip_transport *transport, */ static pj_status_t mod_on_tx_msg(pjsip_tx_data *tdata) { - /* Allocate buffer if necessary. */ - if (tdata->buf.start == NULL) { - PJ_USE_EXCEPTION; - - PJ_TRY { - tdata->buf.start = (char*) - pj_pool_alloc(tdata->pool, PJSIP_MAX_PKT_LEN); - } - PJ_CATCH_ANY { - return PJ_ENOMEM; - } - PJ_END - - tdata->buf.cur = tdata->buf.start; - tdata->buf.end = tdata->buf.start + PJSIP_MAX_PKT_LEN; - } - - /* Do we need to reprint? */ - if (!pjsip_tx_data_is_valid(tdata)) { - pj_ssize_t size; - - size = pjsip_msg_print( tdata->msg, tdata->buf.start, - tdata->buf.end - tdata->buf.start); - if (size < 0) { - return PJSIP_EMSGTOOLONG; - } - pj_assert(size != 0); - tdata->buf.cur[size] = '\0'; - tdata->buf.cur += size; - } - - return PJ_SUCCESS; + return pjsip_tx_data_encode(tdata); } /* diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c index 7a7e2d53..5145142b 100644 --- a/pjsip/src/pjsip/sip_ua_layer.c +++ b/pjsip/src/pjsip/sip_ua_layer.c @@ -907,7 +907,7 @@ static void print_dialog( const char *title, char userinfo[128]; len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); - if (len < 1) + if (len < 0) pj_ansi_strcpy(userinfo, "<--uri too long-->"); else userinfo[len] = '\0'; diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c index 381ccade..faf8bdb6 100644 --- a/pjsip/src/pjsip/sip_util.c +++ b/pjsip/src/pjsip/sip_util.c @@ -1135,6 +1135,8 @@ static void stateless_send_transport_cb( void *token, via->sent_by = stateless_data->cur_transport->local_name; via->rport_param = 0; + pjsip_tx_data_invalidate_msg(tdata); + /* Send message using this transport. */ status = pjsip_transport_send( stateless_data->cur_transport, tdata, @@ -1181,6 +1183,51 @@ stateless_send_resolver_callback( pj_status_t status, /* Copy server addresses */ pj_memcpy( &stateless_data->addr, addr, sizeof(pjsip_server_addresses)); + /* RFC 3261 section 18.1.1: + * If a request is within 200 bytes of the path MTU, or if it is larger + * than 1300 bytes and the path MTU is unknown, the request MUST be sent + * using an RFC 2914 [43] congestion controlled transport protocol, such + * as TCP. + */ + if (stateless_data->tdata->msg->type == PJSIP_REQUEST_MSG && + addr->count > 0 && + addr->entry[0].type == PJSIP_TRANSPORT_UDP) + { + int len; + + /* Encode the request */ + status = pjsip_tx_data_encode(stateless_data->tdata); + if (status != PJ_SUCCESS) { + if (stateless_data->app_cb) { + pj_bool_t cont = PJ_FALSE; + (*stateless_data->app_cb)(stateless_data, -status, &cont); + } + pjsip_tx_data_dec_ref(stateless_data->tdata); + return; + } + + /* Check if request message is larger than 1300 bytes. */ + len = stateless_data->tdata->buf.cur - + stateless_data->tdata->buf.start; + if (len >= PJSIP_UDP_SIZE_THRESHOLD) { + int i; + int count = stateless_data->addr.count; + + /* Insert "TCP version" of resolved UDP addresses at the + * beginning. + */ + if (count * 2 > PJSIP_MAX_RESOLVED_ADDRESSES) + count = PJSIP_MAX_RESOLVED_ADDRESSES / 2; + for (i = 0; i < count; ++i) { + pj_memcpy(&stateless_data->addr.entry[i+count], + &stateless_data->addr.entry[i], + sizeof(stateless_data->addr.entry[0])); + stateless_data->addr.entry[i].type = PJSIP_TRANSPORT_TCP; + } + stateless_data->addr.count = count * 2; + } + } + /* Process the addresses. */ stateless_send_transport_cb( stateless_data, stateless_data->tdata, -PJ_EPENDING); diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 5a34d0ea..6e9a9395 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -2694,7 +2694,7 @@ void print_call(const char *title, /* Dump invite sesion info. */ len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); - if (len < 1) + if (len < 0) pj_ansi_strcpy(userinfo, "<--uri too long-->"); else userinfo[len] = '\0'; diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index b57b81e3..4cf8cd0e 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -182,6 +182,9 @@ PJ_DEF(void) pjsua_media_config_default(pjsua_media_config *cfg) cfg->jb_init = cfg->jb_min_pre = cfg->jb_max_pre = cfg->jb_max = -1; cfg->snd_auto_close_time = 1; + cfg->ice_max_host_cands = -1; + pj_ice_sess_options_default(&cfg->ice_opt); + cfg->turn_conn_type = PJ_TURN_TP_UDP; } @@ -647,7 +650,8 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, /* Initialize logging first so that info/errors can be captured */ if (log_cfg) { status = pjsua_reconfigure_logging(log_cfg); - PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + if (status != PJ_SUCCESS) + return status; } /* If nameserver is configured, create DNS resolver instance and diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index 8d9643b3..b4d7f0dd 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -804,13 +804,16 @@ static pj_status_t create_ice_media_transports(void) ice_cfg.af = pj_AF_INET(); ice_cfg.resolver = pjsua_var.resolver; + ice_cfg.opt = pjsua_var.media_cfg.ice_opt; + /* Configure STUN settings */ if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) { pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0); ice_cfg.stun.server = pj_str(stunip); ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv); } - ice_cfg.stun.no_host_cands = pjsua_var.media_cfg.ice_no_host_cands; + if (pjsua_var.media_cfg.ice_max_host_cands >= 0) + ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands; /* Configure TURN settings */ if (pjsua_var.media_cfg.enable_turn) { diff --git a/pjsip/src/test/msg_test.c b/pjsip/src/test/msg_test.c index d4a7f14b..3bb58f69 100644 --- a/pjsip/src/test/msg_test.c +++ b/pjsip/src/test/msg_test.c @@ -365,16 +365,16 @@ parse_msg: hdr2 = ref_msg->hdr.next; while (hdr1 != &parsed_msg->hdr && hdr2 != &ref_msg->hdr) { - len = hdr1->vptr->print_on(hdr1, str1.ptr, BUFLEN); - if (len < 1) { + len = pjsip_hdr_print_on(hdr1, str1.ptr, BUFLEN); + if (len < 0) { status = -40; goto on_return; } str1.ptr[len] = '\0'; str1.slen = len; - len = hdr2->vptr->print_on(hdr2, str2.ptr, BUFLEN); - if (len < 1) { + len = pjsip_hdr_print_on(hdr2, str2.ptr, BUFLEN); + if (len < 0) { status = -50; goto on_return; } @@ -1944,7 +1944,7 @@ static int hdr_test(void) /* Print the parsed header*/ output = (char*) pj_pool_alloc(pool, 1024); len = pjsip_hdr_print_on(parsed_hdr1, output, 1024); - if (len < 1 || len >= 1024) { + if (len < 0 || len >= 1024) { PJ_LOG(3,(THIS_FILE, " header too long: %s: %s", test->hname, test->hcontent)); return -530; } |