diff options
-rw-r--r-- | pjnath/include/pjnath/errno.h | 5 | ||||
-rw-r--r-- | pjnath/include/pjnath/nat_detect.h | 18 | ||||
-rw-r--r-- | pjnath/src/pjnath/errno.c | 1 | ||||
-rw-r--r-- | pjnath/src/pjnath/nat_detect.c | 28 | ||||
-rw-r--r-- | pjsip-apps/src/pjsua/pjsua_app.c | 46 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua.h | 61 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua_internal.h | 5 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_core.c | 55 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_media.c | 27 |
9 files changed, 189 insertions, 57 deletions
diff --git a/pjnath/include/pjnath/errno.h b/pjnath/include/pjnath/errno.h index 403f191d..1f763fc1 100644 --- a/pjnath/include/pjnath/errno.h +++ b/pjnath/include/pjnath/errno.h @@ -110,6 +110,11 @@ */ #define PJNATH_ESTUNIPV6NOTSUPP (PJNATH_ERRNO_START+41) /* 370041 */ +/** + * @hideinitializer + * Invalid STUN server or server not configured. + */ +#define PJNATH_ESTUNINSERVER (PJNATH_ERRNO_START+42) /* 370042 */ diff --git a/pjnath/include/pjnath/nat_detect.h b/pjnath/include/pjnath/nat_detect.h index e79ffb14..a4bc5fec 100644 --- a/pjnath/include/pjnath/nat_detect.h +++ b/pjnath/include/pjnath/nat_detect.h @@ -48,11 +48,17 @@ PJ_BEGIN_DECL typedef enum pj_stun_nat_type { /** - * NAT type is unknown, because the detection has failed. + * NAT type is unknown because the detection has not been performed. */ PJ_STUN_NAT_TYPE_UNKNOWN, /** + * NAT type is unknown because there is failure in the detection + * process, possibly because server does not support RFC 3489. + */ + PJ_STUN_NAT_TYPE_ERR_UNKNOWN, + + /** * This specifies that the client has open access to Internet (or * at least, its behind a firewall that behaves like a full-cone NAT, * but without the translation) @@ -152,6 +158,16 @@ typedef void pj_stun_nat_detect_cb(void *user_data, /** + * Get the NAT name from the specified NAT type. + * + * @param type NAT type. + * + * @return NAT name. + */ +PJ_DECL(const char*) pj_stun_get_nat_name(pj_stun_nat_type type); + + +/** * Perform NAT classification function according to the procedures * specified in RFC 3489. Once this function returns successfully, * the procedure will run in the "background" and will complete diff --git a/pjnath/src/pjnath/errno.c b/pjnath/src/pjnath/errno.c index 21a42d4e..af636fcd 100644 --- a/pjnath/src/pjnath/errno.c +++ b/pjnath/src/pjnath/errno.c @@ -49,6 +49,7 @@ static const struct PJ_BUILD_ERR( PJNATH_ESTUNNOMAPPEDADDR, "STUN (XOR-)MAPPED-ADDRESS attribute not found"), PJ_BUILD_ERR( PJNATH_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"), + PJ_BUILD_ERR( PJNATH_ESTUNINSERVER, "Invalid STUN server or server not configured"), /* ICE related errors */ PJ_BUILD_ERR( PJNATH_ENOICE, "ICE session not available"), diff --git a/pjnath/src/pjnath/nat_detect.c b/pjnath/src/pjnath/nat_detect.c index cf3ceed4..368c86fb 100644 --- a/pjnath/src/pjnath/nat_detect.c +++ b/pjnath/src/pjnath/nat_detect.c @@ -32,7 +32,8 @@ static const char *nat_type_names[] = { "Unknown", - "Open Internet", + "ErrUnknown", + "Open", "Blocked", "Symmetric UDP", "Full Cone", @@ -129,6 +130,17 @@ static void on_sess_timer(pj_timer_heap_t *th, static void sess_destroy(nat_detect_session *sess); +/* + * Get the NAT name from the specified NAT type. + */ +PJ_DEF(const char*) pj_stun_get_nat_name(pj_stun_nat_type type) +{ + PJ_ASSERT_RETURN(type >= 0 && type <= PJ_STUN_NAT_TYPE_PORT_RESTRICTED, + "*Invalid*"); + + return nat_type_names[type]; +} + static int test_executed(nat_detect_session *sess) { unsigned i, count; @@ -387,7 +399,7 @@ static void on_read_complete(pj_ioqueue_key_t *key, -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) { /* Permanent error */ - end_session(sess, -bytes_read, PJ_STUN_NAT_TYPE_UNKNOWN); + end_session(sess, -bytes_read, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); goto on_return; } @@ -406,7 +418,7 @@ static void on_read_complete(pj_ioqueue_key_t *key, if (status != PJ_EPENDING) { pj_assert(status != PJ_SUCCESS); - end_session(sess, status, PJ_STUN_NAT_TYPE_UNKNOWN); + end_session(sess, status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); } on_return: @@ -627,7 +639,7 @@ static void on_request_complete(pj_stun_session *stun_sess, * We've got other error with Test 2. */ end_session(sess, sess->result[ST_TEST_2].status, - PJ_STUN_NAT_TYPE_UNKNOWN); + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } } else { @@ -690,7 +702,7 @@ static void on_request_complete(pj_stun_session *stun_sess, * Got other error with test 3. */ end_session(sess, sess->result[ST_TEST_3].status, - PJ_STUN_NAT_TYPE_UNKNOWN); + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } } @@ -707,7 +719,7 @@ static void on_request_complete(pj_stun_session *stun_sess, * Got other error with test 1B. */ end_session(sess, sess->result[ST_TEST_1B].status, - PJ_STUN_NAT_TYPE_UNKNOWN); + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } break; @@ -716,7 +728,7 @@ static void on_request_complete(pj_stun_session *stun_sess, * We've got other error with Test 2. */ end_session(sess, sess->result[ST_TEST_2].status, - PJ_STUN_NAT_TYPE_UNKNOWN); + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } } @@ -726,7 +738,7 @@ static void on_request_complete(pj_stun_session *stun_sess, * We've got other error with Test 1. */ end_session(sess, sess->result[ST_TEST_1].status, - PJ_STUN_NAT_TYPE_UNKNOWN); + PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index 80dac5da..31281afc 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -1885,6 +1885,19 @@ static void on_call_replaced(pjsua_call_id old_call_id, /* + * NAT type detection callback. + */ +static void on_nat_detect(const pj_stun_nat_detect_result *res) +{ + if (res->status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "NAT detection failed", res->status); + } else { + PJ_LOG(3, (THIS_FILE, "NAT detected as %s", res->nat_type_name)); + } +} + + +/* * Print buddy list. */ static void print_buddy_list(void) @@ -2312,33 +2325,6 @@ static void manage_codec_prio(void) } -/* Callback upon NAT detection completion */ -static void nat_detect_cb(void *user_data, - const pj_stun_nat_detect_result *res) -{ - PJ_UNUSED_ARG(user_data); - - if (res->status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "NAT detection failed", res->status); - } else { - PJ_LOG(3, (THIS_FILE, "NAT detected as %s", res->nat_type_name)); - } -} - - -/* - * Detect NAT type. - */ -static void detect_nat_type(void) -{ - pj_status_t status; - - status = pjsua_detect_nat_type(NULL, &nat_detect_cb); - if (status != PJ_SUCCESS) - pjsua_perror(THIS_FILE, "Error", status); -} - - /* * Main "user interface" loop. */ @@ -2453,7 +2439,7 @@ void console_app_main(const pj_str_t *uri_to_call) break; case 'n': - detect_nat_type(); + pjsua_detect_nat_type(); break; case 'i': @@ -3307,6 +3293,7 @@ pj_status_t app_init(int argc, char *argv[]) app_config.cfg.cb.on_typing = &on_typing; app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status; app_config.cfg.cb.on_call_replaced = &on_call_replaced; + app_config.cfg.cb.on_nat_detect = &on_nat_detect; /* Initialize pjsua */ status = pjsua_init(&app_config.cfg, &app_config.log_cfg, @@ -3509,9 +3496,6 @@ pj_status_t app_main(void) return status; } - if (app_config.cfg.stun_domain.slen || app_config.cfg.stun_host.slen) - detect_nat_type(); - console_app_main(&uri_arg); return PJ_SUCCESS; diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index ea16d6b9..93aaefda 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -565,7 +565,9 @@ typedef struct pjsua_callback /** * Notify application when media state in the call has changed. * Normal application would need to implement this callback, e.g. - * to connect the call's media to sound device. + * to connect the call's media to sound device. When ICE is used, + * this callback will also be called to report ICE negotiation + * failure. * * @param call_id The call index. * @@ -891,6 +893,14 @@ typedef struct pjsua_callback const pj_str_t *to, const pj_str_t *contact, pj_bool_t is_typing); + /** + * Callback when the library has finished performing NAT type + * detection. + * + * @param res NAT detection result. + */ + void (*on_nat_detect)(const pj_stun_nat_detect_result *res); + } pjsua_callback; @@ -979,6 +989,17 @@ typedef struct pjsua_config pj_str_t stun_relay_host; /** + * Include local endpoint's NAT type in the SDP to assist troubleshooting. + * The valid values are: + * - 0: no information will be added in SDP. + * - 1: only the NAT type number is added. + * - 2: add both NAT type number and name. + * + * Default: 2 + */ + int nat_type_in_sdp; + + /** * Specify whether support for reliable provisional response (100rel and * PRACK) should be required by default. Note that this setting can be * further customized in account configuration (#pjsua_acc_config). @@ -1315,19 +1336,37 @@ PJ_DECL(pj_pool_factory*) pjsua_get_pool_factory(void); /** * This is a utility function to detect NAT type in front of this * endpoint. Once invoked successfully, this function will complete - * asynchronously and report the result in the callback. + * asynchronously and report the result in \a on_nat_detect() callback + * of pjsua_callback. + * + * After NAT has been detected and the callback is called, application can + * get the detected NAT type by calling #pjsua_get_nat_type(). Application + * can also perform NAT detection by calling #pjsua_detect_nat_type() + * again at later time. + * + * Note that STUN must be enabled to run this function successfully. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_detect_nat_type(void); + + +/** + * Get the NAT type as detected by #pjsua_detect_nat_type() function. + * This function will only return useful NAT type after #pjsua_detect_nat_type() + * has completed successfully and \a on_nat_detect() callback has been called. * - * @param srv_port Optional STUN server and port, in "SERVER[:PORT]" - * format. If this option is NULL, the function will use - * the STUN server that has been set in the pjsua - * configuration. - * @param user_data User data to be returned back in the callback. - * @param cb Optional callback to report the detection result. + * @param type NAT type. * - * @return PJ_SUCCESS if detection is started successfully. + * @return When detection is in progress, this function will + * return PJ_EPENDING and \a type will be set to + * PJ_STUN_NAT_TYPE_UNKNOWN. After NAT type has been + * detected successfully, this function will return + * PJ_SUCCESS and \a type will be set to the correct + * value. Other return values indicate error and + * \a type will be set to PJ_STUN_NAT_TYPE_ERR_UNKNOWN. */ -PJ_DECL(pj_status_t) pjsua_detect_nat_type(void *user_data, - pj_stun_nat_detect_cb *cb); +PJ_DECL(pj_status_t) pjsua_get_nat_type(pj_stun_nat_type *type); /** diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h index a83e506a..53a978f4 100644 --- a/pjsip/include/pjsua-lib/pjsua_internal.h +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -194,6 +194,11 @@ struct pjsua_data pj_status_t stun_status; /**< STUN server status. */ pj_dns_resolver *resolver; /**< DNS resolver. */ + /* Detected NAT type */ + pj_stun_nat_type nat_type; /**< NAT type. */ + pj_status_t nat_status; /**< Detection status. */ + pj_bool_t nat_in_progress; /**< Detection in progress */ + /* Account: */ unsigned acc_cnt; /**< Number of accounts. */ pjsua_acc_id default_acc; /**< Default account ID */ diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 321d3405..95ad7aa8 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -51,6 +51,7 @@ static void init_data() pjsua_var.tpdata[i].index = i; pjsua_var.stun_status = PJ_EUNKNOWN; + pjsua_var.nat_status = PJ_EPENDING; } @@ -79,6 +80,7 @@ PJ_DEF(void) pjsua_config_default(pjsua_config *cfg) cfg->max_calls = 4; cfg->thread_cnt = 1; + cfg->nat_type_in_sdp = 2; } PJ_DEF(void) pjsua_config_dup(pj_pool_t *pool, @@ -1858,28 +1860,69 @@ void pjsua_init_tpselector(pjsua_transport_id tp_id, } +/* Callback upon NAT detection completion */ +static void nat_detect_cb(void *user_data, + const pj_stun_nat_detect_result *res) +{ + PJ_UNUSED_ARG(user_data); + + pjsua_var.nat_in_progress = PJ_FALSE; + pjsua_var.nat_status = res->status; + pjsua_var.nat_type = res->nat_type; + + if (pjsua_var.ua_cfg.cb.on_nat_detect) { + (*pjsua_var.ua_cfg.cb.on_nat_detect)(res); + } +} + + /* * Detect NAT type. */ -PJ_DEF(pj_status_t) pjsua_detect_nat_type( void *user_data, - pj_stun_nat_detect_cb *cb) +PJ_DEF(pj_status_t) pjsua_detect_nat_type() { pj_status_t status; + if (pjsua_var.nat_in_progress) + return PJ_SUCCESS; + /* Make sure STUN server resolution has completed */ status = pjsua_resolve_stun_server(PJ_TRUE); if (status != PJ_SUCCESS) { + pjsua_var.nat_status = status; + pjsua_var.nat_type = PJ_STUN_NAT_TYPE_ERR_UNKNOWN; return status; } /* Make sure we have STUN */ if (pjsua_var.stun_srv.ipv4.sin_family == 0) { - return PJ_EINVALIDOP; + pjsua_var.nat_status = PJNATH_ESTUNINSERVER; + return PJNATH_ESTUNINSERVER; + } + + status = pj_stun_detect_nat_type(&pjsua_var.stun_srv.ipv4, + &pjsua_var.stun_cfg, + NULL, &nat_detect_cb); + + if (status != PJ_SUCCESS) { + pjsua_var.nat_status = status; + pjsua_var.nat_type = PJ_STUN_NAT_TYPE_ERR_UNKNOWN; + return status; } - return pj_stun_detect_nat_type(&pjsua_var.stun_srv.ipv4, - &pjsua_var.stun_cfg, - user_data, cb); + pjsua_var.nat_in_progress = PJ_TRUE; + + return PJ_SUCCESS; +} + + +/* + * Get NAT type. + */ +PJ_DEF(pj_status_t) pjsua_get_nat_type(pj_stun_nat_type *type) +{ + *type = pjsua_var.nat_type; + return pjsua_var.nat_status; } diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index 0f4547b9..f6424353 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -194,6 +194,9 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) &pjsua_var.null_port); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + /* Perform NAT detection */ + pjsua_detect_nat_type(); + return PJ_SUCCESS; } @@ -759,6 +762,30 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, if (status != PJ_SUCCESS) goto on_error; + /* Add NAT info in the SDP */ + if (pjsua_var.ua_cfg.nat_type_in_sdp) { + pjmedia_sdp_attr *a; + pj_str_t value; + char nat_info[80]; + + value.ptr = nat_info; + if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) { + value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info), + "%d", pjsua_var.nat_type); + } else { + const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type); + value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info), + "%d %s", + pjsua_var.nat_type, + type_name); + } + + a = pjmedia_sdp_attr_create(pool, "X-nat", &value); + + pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a); + + } + if (pjsua_var.media_cfg.enable_ice) { status = pjmedia_ice_modify_sdp(call->med_tp, pool, sdp); if (status != PJ_SUCCESS) |