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-apps/src/pjsua/pjsua_app.c | 29 +- pjsip/include/pjsua-lib/pjsua.h | 133 ++++++++++ pjsip/include/pjsua-lib/pjsua_internal.h | 28 +- pjsip/src/pjsua-lib/pjsua_core.c | 436 ++++++++++++++++++++++--------- pjsip/src/pjsua-lib/pjsua_media.c | 4 +- 5 files changed, 483 insertions(+), 147 deletions(-) diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index 6e7c8b81..883d6704 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -190,7 +190,7 @@ static void usage(void) puts (" --password=string Set authentication password"); puts (" --publish Send presence PUBLISH for this account"); puts (" --use-100rel Require reliable provisional response (100rel)"); - puts (" --use-timer Require session timers"); + puts (" --use-timer Require SIP session timers"); puts (" --timer-se Session timers expiration period, in secs (def:1800)"); puts (" --timer-min-se Session timers minimum expiration period, in secs (def:90)"); puts (" --auto-update-nat=N Where N is 0 or 1 to enable/disable SIP traversal behind"); @@ -216,7 +216,8 @@ static void usage(void) puts (" This option can be specified multiple times."); puts (" --outbound=url Set the URL of global outbound proxy server"); puts (" May be specified multiple times"); - puts (" --stun-srv=name Set STUN server host or domain"); + puts (" --stun-srv=FORMAT Set STUN server host or domain. This option may be"); + puts (" specified more than once. FORMAT is hostdom[:PORT]"); puts (""); puts ("TLS Options:"); puts (" --use-tls Enable TLS transport (default=no)"); @@ -477,7 +478,7 @@ static pj_status_t parse_args(int argc, char *argv[], OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT, OPT_BOUND_ADDR, OPT_CONTACT_PARAMS, OPT_CONTACT_URI_PARAMS, OPT_100REL, OPT_USE_IMS, OPT_REALM, OPT_USERNAME, OPT_PASSWORD, - OPT_NAMESERVER, OPT_STUN_DOMAIN, OPT_STUN_SRV, + OPT_NAMESERVER, OPT_STUN_SRV, 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, @@ -543,7 +544,6 @@ static pj_status_t parse_args(int argc, char *argv[], { "username", 1, 0, OPT_USERNAME}, { "password", 1, 0, OPT_PASSWORD}, { "nameserver", 1, 0, OPT_NAMESERVER}, - { "stun-domain",1, 0, OPT_STUN_DOMAIN}, { "stun-srv", 1, 0, OPT_STUN_SRV}, { "add-buddy", 1, 0, OPT_ADD_BUDDY}, { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG}, @@ -959,12 +959,13 @@ static pj_status_t parse_args(int argc, char *argv[], } break; - case OPT_STUN_DOMAIN: /* STUN domain */ - cfg->cfg.stun_domain = pj_str(pj_optarg); - break; - case OPT_STUN_SRV: /* STUN server */ cfg->cfg.stun_host = pj_str(pj_optarg); + if (cfg->cfg.stun_srv_cnt==PJ_ARRAY_SIZE(cfg->cfg.stun_srv)) { + PJ_LOG(1,(THIS_FILE, "Error: too many STUN servers")); + return PJ_ETOOMANY; + } + cfg->cfg.stun_srv[cfg->cfg.stun_srv_cnt++] = pj_str(pj_optarg); break; case OPT_ADD_BUDDY: /* Add to buddy list. */ @@ -1615,16 +1616,10 @@ static int write_settings(const struct app_config *config, } /* STUN */ - if (config->cfg.stun_domain.slen) { - pj_ansi_sprintf(line, "--stun-domain %.*s\n", - (int)config->cfg.stun_domain.slen, - config->cfg.stun_domain.ptr); - pj_strcat2(&cfg, line); - } - if (config->cfg.stun_host.slen) { + for (i=0; icfg.stun_srv_cnt; ++i) { pj_ansi_sprintf(line, "--stun-srv %.*s\n", - (int)config->cfg.stun_host.slen, - config->cfg.stun_host.ptr); + (int)config->cfg.stun_srv[i].slen, + config->cfg.stun_srv[i].ptr); pj_strcat2(&cfg, line); } diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 43db66ea..cd86a524 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -891,6 +891,10 @@ typedef struct pjsua_config pj_str_t outbound_proxy[4]; /** + * Warning: deprecated, please use \a stun_srv field instead. To maintain + * backward compatibility, if \a stun_srv_cnt is zero then the value of + * this field will be copied to \a stun_srv field, if present. + * * Specify domain name to be resolved with DNS SRV resolution to get the * address of the STUN server. Alternatively application may specify * \a stun_host instead. @@ -901,11 +905,51 @@ typedef struct pjsua_config pj_str_t stun_domain; /** + * Warning: deprecated, please use \a stun_srv field instead. To maintain + * backward compatibility, if \a stun_srv_cnt is zero then the value of + * this field will be copied to \a stun_srv field, if present. + * * Specify STUN server to be used, in "HOST[:PORT]" format. If port is * not specified, default port 3478 will be used. */ pj_str_t stun_host; + /** + * Number of STUN server entries in \a stun_srv array. + */ + unsigned stun_srv_cnt; + + /** + * Array of STUN servers to try. The library will try to resolve and + * contact each of the STUN server entry until it finds one that is + * usable. Each entry may be a domain name, host name, IP address, and + * it may contain an optional port number. For example: + * - "pjsip.org" (domain name) + * - "sip.pjsip.org" (host name) + * - "pjsip.org:33478" (domain name and a non-standard port number) + * - "10.0.0.1:3478" (IP address and port number) + * + * When nameserver is configured in the \a pjsua_config.nameserver field, + * if entry is not an IP address, it will be resolved with DNS SRV + * resolution first, and it will fallback to use DNS A resolution if this + * fails. Port number may be specified even if the entry is a domain name, + * in case the DNS SRV resolution should fallback to a non-standard port. + * + * When nameserver is not configured, entries will be resolved with + * #pj_gethostbyname() if it's not an IP address. Port number may be + * specified if the server is not listening in standard STUN port. + */ + pj_str_t stun_srv[8]; + + /** + * This specifies if the library startup should ignore failure with the + * STUN servers. If this is set to PJ_FALSE, the library will refuse to + * start if it fails to resolve or contact any of the STUN servers. + * + * Default: PJ_TRUE + */ + pj_bool_t stun_ignore_failure; + /** * Support for adding and parsing NAT type in the SDP to assist * troubleshooting. The valid values are: @@ -1214,6 +1258,46 @@ PJ_DECL(pj_pool_factory*) pjsua_get_pool_factory(void); * */ +/** + * This structure is used to represent the result of the STUN server + * resolution and testing, the #pjsua_resolve_stun_servers() function. + * This structure will be passed in #pj_stun_resolve_cb callback. + */ +typedef struct pj_stun_resolve_result +{ + /** + * Arbitrary data that was passed to #pjsua_resolve_stun_servers() + * function. + */ + void *token; + + /** + * This will contain PJ_SUCCESS if at least one usable STUN server + * is found, otherwise it will contain the last error code during + * the operation. + */ + pj_status_t status; + + /** + * The server name that yields successful result. This will only + * contain value if status is successful. + */ + pj_str_t name; + + /** + * The server IP address. This will only contain value if status + * is successful. + */ + pj_sockaddr addr; + +} pj_stun_resolve_result; + + +/** + * Typedef of callback to be registered to #pjsua_resolve_stun_servers(). + */ +typedef void (*pj_stun_resolve_cb)(const pj_stun_resolve_result *result); + /** * This is a utility function to detect NAT type in front of this * endpoint. Once invoked successfully, this function will complete @@ -1252,6 +1336,55 @@ PJ_DECL(pj_status_t) pjsua_detect_nat_type(void); PJ_DECL(pj_status_t) pjsua_get_nat_type(pj_stun_nat_type *type); +/** + * Auxiliary function to resolve and contact each of the STUN server + * entries (sequentially) to find which is usable. The #pjsua_init() must + * have been called before calling this function. + * + * @param count Number of STUN server entries to try. + * @param srv Array of STUN server entries to try. Please see + * the \a stun_srv field in the #pjsua_config + * documentation about the format of this entry. + * @param wait Specify non-zero to make the function block until + * it gets the result. In this case, the function + * will block while the resolution is being done, + * and the callback will be called before this function + * returns. + * @param token Arbitrary token to be passed back to application + * in the callback. + * @param cb Callback to be called to notify the result of + * the function. + * + * @return If \a wait parameter is non-zero, this will return + * PJ_SUCCESS if one usable STUN server is found. + * Otherwise it will always return PJ_SUCCESS, and + * application will be notified about the result in + * the callback. + */ +PJ_DECL(pj_status_t) pjsua_resolve_stun_servers(unsigned count, + pj_str_t srv[], + pj_bool_t wait, + void *token, + pj_stun_resolve_cb cb); + +/** + * Cancel pending STUN resolution which match the specified token. + * + * @param token The token to match. This token was given to + * #pjsua_resolve_stun_servers() + * @param notify_cb Boolean to control whether the callback should + * be called for cancelled resolutions. When the + * callback is called, the status in the result + * will be set as PJ_ECANCELLED. + * + * @return PJ_SUCCESS if there is at least one pending STUN + * resolution cancelled, or PJ_ENOTFOUND if there is + * no matching one, or other error. + */ +PJ_DECL(pj_status_t) pjsua_cancel_stun_resolution(void *token, + pj_bool_t notify_cb); + + /** * This is a utility function to verify that valid SIP url is given. If the * URL is valid, PJ_SUCCESS will be returned. diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h index d80f5d1d..03c6627c 100644 --- a/pjsip/include/pjsua-lib/pjsua_internal.h +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -212,6 +212,22 @@ typedef struct pjsua_conf_setting unsigned bits_per_sample; } pjsua_conf_setting; +typedef struct pjsua_stun_resolve +{ + PJ_DECL_LIST_MEMBER(struct pjsua_stun_resolve); + + pj_pool_t *pool; /**< Pool */ + unsigned count; /**< # of entries */ + pj_str_t *srv; /**< Array of entries */ + unsigned idx; /**< Current index */ + void *token; /**< App token */ + pj_stun_resolve_cb cb; /**< App callback */ + pj_bool_t blocking; /**< Blocking? */ + pj_status_t status; /**< Session status */ + pj_sockaddr addr; /**< Result */ + pj_stun_sock *stun_sock; /**< Testing STUN sock */ +} pjsua_stun_resolve; + /** * Global pjsua application data. @@ -241,6 +257,7 @@ struct pjsua_data pj_stun_config stun_cfg; /**< Global STUN settings. */ pj_sockaddr stun_srv; /**< Resolved STUN server address */ pj_status_t stun_status; /**< STUN server status. */ + pjsua_stun_resolve stun_res; /**< List of pending STUN resolution*/ pj_dns_resolver *resolver; /**< DNS resolver. */ /* Detected NAT type */ @@ -350,17 +367,18 @@ PJ_INLINE(pjsua_im_data*) pjsua_im_data_dup(pj_pool_t *pool, #define PJSUA_UNLOCK() #endif +/****** + * STUN resolution + */ +/* Resolve the STUN server */ +pj_status_t resolve_stun_server(pj_bool_t wait); + /** * Normalize route URI (check for ";lr" and append one if it doesn't * exist and pjsua_config.force_lr is set. */ pj_status_t normalize_route_uri(pj_pool_t *pool, pj_str_t *uri); -/** - * Resolve STUN server. - */ -pj_status_t pjsua_resolve_stun_server(pj_bool_t wait); - /** * Handle incoming invite request. */ 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