From 8df6724ddca5de98cfdc21316fb5d7e5de95d726 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Wed, 12 Aug 2009 11:03:23 +0000 Subject: Ticket #866: Allow application to specify more than one STUN servers for more robustness, and continue application startup if STUN resolution fails PJSUA-LIB: - New fields in pjsua_config to specify more than one STUN servers (the stun_srv_cnt and stun_srv array) - The existing stun_host and stun_domain fields are deprecated, but backward compatibility is maintained. If stun_srv_cnt is zero, the library will import the entries from stun_host and stun_domain - The library will now resolve the STUN server entries one by one and test it before using it - New auxiliary API pjsua_resolve_stun_servers() to perform resolution and test against array of STUN servers pjsua application: - The "stun-srv" command line options can now be specified more than once git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2864 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/src/pjsua-lib/pjsua_core.c | 436 +++++++++++++++++++++++++++----------- pjsip/src/pjsua-lib/pjsua_media.c | 4 +- 2 files changed, 315 insertions(+), 125 deletions(-) (limited to 'pjsip/src/pjsua-lib') diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index c714be77..c4f38d9b 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -24,6 +24,10 @@ #define THIS_FILE "pjsua_core.c" +/* Internal prototypes */ +static void resolve_stun_entry(pjsua_stun_resolve *sess); + + /* PJSUA application instance. */ struct pjsua_data pjsua_var; @@ -59,6 +63,7 @@ static void init_data() pjsua_var.stun_status = PJ_EUNKNOWN; pjsua_var.nat_status = PJ_EPENDING; + pj_list_init(&pjsua_var.stun_res); } @@ -92,6 +97,7 @@ PJ_DEF(void) pjsua_config_default(pjsua_config *cfg) cfg->max_calls = ((PJSUA_MAX_CALLS) < 4) ? (PJSUA_MAX_CALLS) : 4; cfg->thread_cnt = 1; cfg->nat_type_in_sdp = 1; + cfg->stun_ignore_failure = PJ_TRUE; cfg->force_lr = PJ_TRUE; #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) cfg->use_srtp = PJSUA_DEFAULT_USE_SRTP; @@ -122,6 +128,10 @@ PJ_DEF(void) pjsua_config_dup(pj_pool_t *pool, pj_strdup_with_null(pool, &dst->user_agent, &src->user_agent); pj_strdup_with_null(pool, &dst->stun_domain, &src->stun_domain); pj_strdup_with_null(pool, &dst->stun_host, &src->stun_host); + + for (i=0; istun_srv_cnt; ++i) { + pj_strdup_with_null(pool, &dst->stun_srv[i], &src->stun_srv[i]); + } } PJ_DEF(void) pjsua_msg_data_init(pjsua_msg_data *msg_data) @@ -761,10 +771,20 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, if (status != PJ_SUCCESS) goto on_error; + /* Convert deprecated STUN settings */ + if (pjsua_var.ua_cfg.stun_srv_cnt==0) { + if (pjsua_var.ua_cfg.stun_domain.slen) { + pjsua_var.ua_cfg.stun_srv[pjsua_var.ua_cfg.stun_srv_cnt++] = + pjsua_var.ua_cfg.stun_domain; + } + if (pjsua_var.ua_cfg.stun_host.slen) { + pjsua_var.ua_cfg.stun_srv[pjsua_var.ua_cfg.stun_srv_cnt++] = + pjsua_var.ua_cfg.stun_host; + } + } /* Start resolving STUN server */ - - status = pjsua_resolve_stun_server(PJ_FALSE); + status = resolve_stun_server(PJ_FALSE); if (status != PJ_SUCCESS && status != PJ_EPENDING) { pjsua_perror(THIS_FILE, "Error resolving STUN server", status); return status; @@ -859,154 +879,308 @@ static void busy_sleep(unsigned msec) } while (PJ_TIME_VAL_LT(now, timeout)); } +/* Internal function to destroy STUN resolution session + * (pj_stun_resolve). + */ +static void destroy_stun_resolve(pjsua_stun_resolve *sess) +{ + PJSUA_LOCK(); + pj_list_erase(sess); + PJSUA_UNLOCK(); + + pj_assert(sess->stun_sock==NULL); + pj_pool_release(sess->pool); +} -/* - * Callback function to receive notification from the resolver - * when the resolution process completes. +/* This is the internal function to be called when STUN resolution + * session (pj_stun_resolve) has completed. */ -static void stun_dns_srv_resolver_cb(void *user_data, - pj_status_t status, - const pj_dns_srv_record *rec) +static void stun_resolve_complete(pjsua_stun_resolve *sess) { - unsigned i; + pj_stun_resolve_result result; + + pj_bzero(&result, sizeof(result)); + result.token = sess->token; + result.status = sess->status; + result.name = sess->srv[sess->idx]; + pj_memcpy(&result.addr, &sess->addr, sizeof(result.addr)); + + if (result.status == PJ_SUCCESS) { + char addr[PJ_INET6_ADDRSTRLEN+10]; + pj_sockaddr_print(&result.addr, addr, sizeof(addr), 3); + PJ_LOG(4,(THIS_FILE, + "STUN resolution success, using %.*s, address is %s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr, + addr)); + } else { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(result.status, errmsg, sizeof(errmsg)); + PJ_LOG(1,(THIS_FILE, "STUN resolution failed: %s", errmsg)); + } - PJ_UNUSED_ARG(user_data); + sess->cb(&result); + + if (!sess->blocking) { + destroy_stun_resolve(sess); + } +} + +/* This is the callback called by the STUN socket (pj_stun_sock) + * to report it's state. We use this as part of testing the + * STUN server. + */ +static pj_bool_t test_stun_on_status(pj_stun_sock *stun_sock, + pj_stun_sock_op op, + pj_status_t status) +{ + pjsua_stun_resolve *sess; + + sess = pj_stun_sock_get_user_data(stun_sock); + pj_assert(stun_sock == sess->stun_sock); - pjsua_var.stun_status = status; - if (status != PJ_SUCCESS) { - /* DNS SRV resolution failed. If stun_host is specified, resolve - * it with gethostbyname() - */ - if (pjsua_var.ua_cfg.stun_host.slen) { - pjsua_var.stun_status = - pj_sockaddr_parse(pj_AF_INET(), 0, - &pjsua_var.ua_cfg.stun_host, - &pjsua_var.stun_srv); - if (pjsua_var.stun_status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Invalid STUN server", - pjsua_var.stun_status); - } else { - if (pj_sockaddr_get_port(&pjsua_var.stun_srv)==0) - pj_sockaddr_set_port(&pjsua_var.stun_srv, 3478); - - PJ_LOG(3,(THIS_FILE, - "STUN server %.*s resolved, address is %s:%d", - (int)pjsua_var.ua_cfg.stun_host.slen, - pjsua_var.ua_cfg.stun_host.ptr, - pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr), - (int)pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port))); - } - } else { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + + PJ_LOG(4,(THIS_FILE, "STUN resolution for %.*s failed: %s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr, errmsg)); + + sess->status = status; + + pj_stun_sock_destroy(stun_sock); + sess->stun_sock = NULL; + + ++sess->idx; + resolve_stun_entry(sess); + + return PJ_FALSE; + + } else if (op == PJ_STUN_SOCK_BINDING_OP) { + pj_stun_sock_info ssi; + + pj_stun_sock_get_info(stun_sock, &ssi); + pj_memcpy(&sess->addr, &ssi.srv_addr, sizeof(sess->addr)); + + sess->status = PJ_SUCCESS; + pj_stun_sock_destroy(stun_sock); + sess->stun_sock = NULL; + + stun_resolve_complete(sess); + + return PJ_FALSE; + + } else + return PJ_TRUE; + +} + +/* This is an internal function to resolve and test current + * server entry in pj_stun_resolve session. It is called by + * pjsua_resolve_stun_servers() and test_stun_on_status() above + */ +static void resolve_stun_entry(pjsua_stun_resolve *sess) +{ + /* Loop while we have entry to try */ + for (; sess->idx < sess->count; ++sess->idx) { + const int af = pj_AF_INET(); + pj_str_t hostpart; + pj_uint16_t port; + pj_stun_sock_cb stun_sock_cb; + + pj_assert(sess->idx < sess->count); + + /* Parse the server entry into host:port */ + sess->status = pj_sockaddr_parse2(af, 0, &sess->srv[sess->idx], + &hostpart, &port, NULL); + if (sess->status != PJ_SUCCESS) { + PJ_LOG(2,(THIS_FILE, "Invalid STUN server entry %.*s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr)); + continue; + } + + /* Use default port if not specified */ + if (port == 0) + port = PJ_STUN_PORT; + + pj_assert(sess->stun_sock == NULL); + + PJ_LOG(4,(THIS_FILE, "Trying STUN server %.*s (%d of %d)..", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr, + sess->idx+1, sess->count)); + + /* Use STUN_sock to test this entry */ + pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb)); + stun_sock_cb.on_status = &test_stun_on_status; + sess->status = pj_stun_sock_create(&pjsua_var.stun_cfg, "stunresolve", + pj_AF_INET(), &stun_sock_cb, + NULL, sess, &sess->stun_sock); + if (sess->status != PJ_SUCCESS) { char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(sess->status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(THIS_FILE, + "Error creating STUN socket for %.*s: %s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr, errmsg)); + + continue; + } - pj_strerror(status, errmsg, sizeof(errmsg)); - PJ_LOG(1,(THIS_FILE, - "DNS SRV resolution failed for STUN server %.*s: %s", - (int)pjsua_var.ua_cfg.stun_domain.slen, - pjsua_var.ua_cfg.stun_domain.ptr, - errmsg)); + sess->status = pj_stun_sock_start(sess->stun_sock, &hostpart, + port, pjsua_var.resolver); + if (sess->status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(sess->status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(THIS_FILE, + "Error starting STUN socket for %.*s: %s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr, errmsg)); + + pj_stun_sock_destroy(sess->stun_sock); + sess->stun_sock = NULL; + continue; } + + /* Done for now, testing will resume/complete asynchronously in + * stun_sock_cb() + */ return; } - pj_assert(rec->count != 0 && rec->entry[0].server.addr_count != 0); - pj_sockaddr_in_init(&pjsua_var.stun_srv.ipv4, NULL, - rec->entry[0].port); - pjsua_var.stun_srv.ipv4.sin_addr.s_addr = - rec->entry[0].server.addr[0].s_addr; + if (sess->idx >= sess->count) { + /* No more entries to try */ + PJ_ASSERT_ON_FAIL(sess->status != PJ_SUCCESS, + sess->status = PJ_EUNKNOWN); + stun_resolve_complete(sess); + } +} + + +/* + * Resolve STUN server. + */ +PJ_DEF(pj_status_t) pjsua_resolve_stun_servers( unsigned count, + pj_str_t srv[], + pj_bool_t wait, + void *token, + pj_stun_resolve_cb cb) +{ + pj_pool_t *pool; + pjsua_stun_resolve *sess; + pj_status_t status; + unsigned i; + + PJ_ASSERT_RETURN(count && srv && cb, PJ_EINVAL); + + pool = pjsua_pool_create("stunres", 256, 256); + if (!pool) + return PJ_ENOMEM; + + sess = PJ_POOL_ZALLOC_T(pool, pjsua_stun_resolve); + sess->pool = pool; + sess->token = token; + sess->cb = cb; + sess->count = count; + sess->blocking = wait; + sess->status = PJ_EPENDING; + sess->srv = (pj_str_t*) pj_pool_calloc(pool, count, sizeof(pj_str_t)); + for (i=0; isrv[i], &srv[i]); + } + + PJSUA_LOCK(); + pj_list_push_back(&pjsua_var.stun_res, sess); + PJSUA_UNLOCK(); - PJ_LOG(3,(THIS_FILE, "_stun._udp.%.*s resolved, found %d entry(s):", - (int)pjsua_var.ua_cfg.stun_domain.slen, - pjsua_var.ua_cfg.stun_domain.ptr, - rec->count)); + resolve_stun_entry(sess); + + if (!wait) + return PJ_SUCCESS; - for (i=0; icount; ++i) { - PJ_LOG(3,(THIS_FILE, - " %d: prio=%d, weight=%d %s:%d", - i, rec->entry[i].priority, rec->entry[i].weight, - pj_inet_ntoa(rec->entry[i].server.addr[0]), - (int)rec->entry[i].port)); + while (sess->status == PJ_EPENDING) { + pjsua_handle_events(50); } + status = sess->status; + destroy_stun_resolve(sess); + + return status; +} + +/* + * Cancel pending STUN resolution. + */ +PJ_DEF(pj_status_t) pjsua_cancel_stun_resolution( void *token, + pj_bool_t notify_cb) +{ + pjsua_stun_resolve *sess; + unsigned cancelled_count = 0; + + PJSUA_LOCK(); + sess = pjsua_var.stun_res.next; + while (sess != &pjsua_var.stun_res) { + pjsua_stun_resolve *next = sess->next; + + if (sess->token == token) { + if (notify_cb) { + pj_stun_resolve_result result; + + pj_bzero(&result, sizeof(result)); + result.token = token; + result.status = PJ_ECANCELLED; + + sess->cb(&result); + } + + destroy_stun_resolve(sess); + ++cancelled_count; + } + + sess = next; + } + PJSUA_UNLOCK(); + + return cancelled_count ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +static void internal_stun_resolve_cb(const pj_stun_resolve_result *result) +{ + pjsua_var.stun_status = result->status; + if (result->status == PJ_SUCCESS) { + pj_memcpy(&pjsua_var.stun_srv, &result->addr, sizeof(result->addr)); + } } /* * Resolve STUN server. */ -pj_status_t pjsua_resolve_stun_server(pj_bool_t wait) +pj_status_t resolve_stun_server(pj_bool_t wait) { if (pjsua_var.stun_status == PJ_EUNKNOWN) { + pj_status_t status; + /* Initialize STUN configuration */ pj_stun_config_init(&pjsua_var.stun_cfg, &pjsua_var.cp.factory, 0, pjsip_endpt_get_ioqueue(pjsua_var.endpt), pjsip_endpt_get_timer_heap(pjsua_var.endpt)); /* Start STUN server resolution */ - - pjsua_var.stun_status = PJ_EPENDING; - - /* If stun_domain is specified, resolve STUN servers with DNS - * SRV resolution. - */ - if (pjsua_var.ua_cfg.stun_domain.slen) { - pj_str_t res_type; - pj_status_t status; - - /* Fail if resolver is not configured */ - if (pjsua_var.resolver == NULL) { - PJ_LOG(1,(THIS_FILE, "Nameserver must be configured when " - "stun_domain is specified")); - pjsua_var.stun_status = PJLIB_UTIL_EDNSNONS; - return PJLIB_UTIL_EDNSNONS; - } - res_type = pj_str("_stun._udp"); - status = - pj_dns_srv_resolve(&pjsua_var.ua_cfg.stun_domain, &res_type, - 3478, pjsua_var.pool, pjsua_var.resolver, - 0, NULL, &stun_dns_srv_resolver_cb, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error starting DNS SRV resolution", - pjsua_var.stun_status); + if (pjsua_var.ua_cfg.stun_srv_cnt) { + pjsua_var.stun_status = PJ_EPENDING; + status = pjsua_resolve_stun_servers(pjsua_var.ua_cfg.stun_srv_cnt, + pjsua_var.ua_cfg.stun_srv, + wait, NULL, + &internal_stun_resolve_cb); + if (wait || status != PJ_SUCCESS) { pjsua_var.stun_status = status; - return pjsua_var.stun_status; - } else { - pjsua_var.stun_status = PJ_EPENDING; } - } - /* Otherwise if stun_host is specified, resolve STUN server with - * gethostbyname(). - */ - else if (pjsua_var.ua_cfg.stun_host.slen) { - pjsua_var.stun_status = - pj_sockaddr_parse(pj_AF_INET(), 0, - &pjsua_var.ua_cfg.stun_host, - &pjsua_var.stun_srv); - if (pjsua_var.stun_status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Invalid STUN server", - pjsua_var.stun_status); - return pjsua_var.stun_status; - } - - if (pj_sockaddr_get_port(&pjsua_var.stun_srv)==0) - pj_sockaddr_set_port(&pjsua_var.stun_srv, 3478); - - PJ_LOG(3,(THIS_FILE, - "STUN server %.*s resolved, address is %s:%d", - (int)pjsua_var.ua_cfg.stun_host.slen, - pjsua_var.ua_cfg.stun_host.ptr, - pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr), - (int)pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port))); - - } - /* Otherwise disable STUN. */ - else { + } else { pjsua_var.stun_status = PJ_SUCCESS; } - - return pjsua_var.stun_status; - } else if (pjsua_var.stun_status == PJ_EPENDING) { /* STUN server resolution has been started, wait for the * result. @@ -1015,13 +1189,18 @@ pj_status_t pjsua_resolve_stun_server(pj_bool_t wait) while (pjsua_var.stun_status == PJ_EPENDING) pjsua_handle_events(10); } + } - return pjsua_var.stun_status; - - } else { - /* STUN server has been resolved, return the status */ - return pjsua_var.stun_status; + if (pjsua_var.stun_status != PJ_EPENDING && + pjsua_var.stun_status != PJ_SUCCESS && + pjsua_var.ua_cfg.stun_ignore_failure) + { + PJ_LOG(2,(THIS_FILE, + "Ignoring STUN resolution failure (by setting)")); + pjsua_var.stun_status = PJ_SUCCESS; } + + return pjsua_var.stun_status; } /* @@ -1074,6 +1253,17 @@ PJ_DEF(pj_status_t) pjsua_destroy(void) /* Destroy endpoint. */ if (pjsua_var.endpt) { + + /* Terminate any pending STUN resolution */ + if (!pj_list_empty(&pjsua_var.stun_res)) { + pjsua_stun_resolve *sess = pjsua_var.stun_res.next; + while (sess != &pjsua_var.stun_res) { + pjsua_stun_resolve *next = sess->next; + destroy_stun_resolve(sess); + sess = next; + } + } + /* Wait for some time to allow unregistration and ICE/TURN * transports shutdown to complete: */ @@ -1270,7 +1460,7 @@ static pj_status_t create_sip_udp_sock(int af, pj_status_t status; /* Make sure STUN server resolution has completed */ - status = pjsua_resolve_stun_server(PJ_TRUE); + status = resolve_stun_server(PJ_TRUE); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error resolving STUN server", status); return status; @@ -1982,7 +2172,7 @@ PJ_DEF(pj_status_t) pjsua_detect_nat_type() return PJ_SUCCESS; /* Make sure STUN server resolution has completed */ - status = pjsua_resolve_stun_server(PJ_TRUE); + status = resolve_stun_server(PJ_TRUE); if (status != PJ_SUCCESS) { pjsua_var.nat_status = status; pjsua_var.nat_type = PJ_STUN_NAT_TYPE_ERR_UNKNOWN; diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index b4d39711..7b95a137 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -359,7 +359,7 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, pj_sock_t sock[2]; /* Make sure STUN server resolution has completed */ - status = pjsua_resolve_stun_server(PJ_TRUE); + status = resolve_stun_server(PJ_TRUE); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error resolving STUN server", status); return status; @@ -860,7 +860,7 @@ static pj_status_t create_ice_media_transports(void) pj_status_t status; /* Make sure STUN server resolution has completed */ - status = pjsua_resolve_stun_server(PJ_TRUE); + status = resolve_stun_server(PJ_TRUE); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error resolving STUN server", status); return status; -- cgit v1.2.3