diff options
Diffstat (limited to 'pjsip')
-rw-r--r-- | pjsip/build/Makefile | 3 | ||||
-rw-r--r-- | pjsip/build/test_pjsip.dsp | 4 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_config.h | 36 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_endpoint.h | 43 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_resolve.h | 193 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua.h | 14 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_endpoint.c | 47 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_resolve.c | 848 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_core.c | 39 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/dns_test.c | 596 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/test.c | 10 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/test.h | 4 |
12 files changed, 1766 insertions, 71 deletions
diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile index 81c2cb2e..dad39451 100644 --- a/pjsip/build/Makefile +++ b/pjsip/build/Makefile @@ -88,7 +88,8 @@ export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT # Defines for building test application # export TEST_SRCDIR = ../src/test-pjsip -export TEST_OBJS += dlg_core_test.o msg_err_test.o msg_logger.o msg_test.o \ +export TEST_OBJS += dlg_core_test.o dns_test.o msg_err_test.o \ + msg_logger.o msg_test.o \ test.o transport_loop_test.o transport_tcp_test.o \ transport_test.o transport_udp_test.o \ tsx_basic_test.o tsx_bench.o tsx_uac_test.o \ diff --git a/pjsip/build/test_pjsip.dsp b/pjsip/build/test_pjsip.dsp index 82f10ac3..27814458 100644 --- a/pjsip/build/test_pjsip.dsp +++ b/pjsip/build/test_pjsip.dsp @@ -93,6 +93,10 @@ SOURCE="..\src\test-pjsip\dlg_core_test.c" # End Source File
# Begin Source File
+SOURCE="..\src\test-pjsip\dns_test.c"
+# End Source File
+# Begin Source File
+
SOURCE="..\src\test-pjsip\main.c"
# End Source File
# Begin Source File
diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h index e4b402fb..420a2a01 100644 --- a/pjsip/include/pjsip/sip_config.h +++ b/pjsip/include/pjsip/sip_config.h @@ -199,6 +199,42 @@ #endif +/** + * This macro specifies whether full DNS resolution should be used. + * When enabled, #pjsip_resolve() will perform asynchronous DNS SRV and + * A (or AAAA, when IPv6 is supported) resolution to resolve the SIP + * domain. + * + * Note that even when this setting is enabled, asynchronous DNS resolution + * will only be done when application calls #pjsip_endpt_create_resolver(), + * configure the nameservers with #pj_dns_resolver_set_ns(), and configure + * the SIP endpoint's DNS resolver with #pjsip_endpt_set_resolver(). If + * these steps are not followed, the domain will be resolved with normal + * pj_gethostbyname() function. + * + * Turning off this setting will save the footprint by about 16KB, since + * it should also exclude dns.o and resolve.o from PJLIB-UTIL. + * + * Default: 1 (enabled) + */ +#ifndef PJSIP_HAS_RESOLVER +# define PJSIP_HAS_RESOLVER 1 +#endif + + +/** + * Maximum number of addresses returned by the resolver. The number here + * will slightly affect stack usage, since each entry will occupy about + * 32 bytes of stack memory. + * + * Default: 8 + */ +#ifndef PJSIP_MAX_RESOLVED_ADDRESSES +# define PJSIP_MAX_RESOLVED_ADDRESSES 8 +#endif + + + /* Endpoint. */ #define PJSIP_MAX_TIMER_COUNT (2*PJSIP_MAX_TSX_COUNT + 2*PJSIP_MAX_DIALOG_COUNT) #define PJSIP_POOL_LEN_ENDPT (4000) diff --git a/pjsip/include/pjsip/sip_endpoint.h b/pjsip/include/pjsip/sip_endpoint.h index aed4d8a2..6e166090 100644 --- a/pjsip/include/pjsip/sip_endpoint.h +++ b/pjsip/include/pjsip/sip_endpoint.h @@ -275,12 +275,51 @@ PJ_DECL(pj_status_t) pjsip_endpt_create_tdata( pjsip_endpoint *endpt, pjsip_tx_data **p_tdata); /** + * Create the DNS resolver instance. Application creates the DNS + * resolver instance, set the nameserver to be used by the DNS + * resolver, then set the DNS resolver to be used by the endpoint + * by calling #pjsip_endpt_set_resolver(). + * + * @param endpt The SIP endpoint instance. + * @param p_resv Pointer to receive the DNS resolver instance. + * + * @return PJ_SUCCESS on success, or the appropriate error + * code. + */ +PJ_DECL(pj_status_t) pjsip_endpt_create_resolver(pjsip_endpoint *endpt, + pj_dns_resolver **p_resv); + +/** + * Set DNS resolver to be used by the SIP resolver. Application can set + * the resolver instance to NULL to disable DNS resolution (perhaps + * temporarily). When DNS resolver is disabled, the endpoint will resolve + * hostnames with the normal pj_gethostbyname() function. + * + * @param endpt The SIP endpoint instance. + * @param resv The resolver instance to be used by the SIP + * endpoint. + * + * @return PJ_SUCCESS on success, or the appropriate error + * code. + */ +PJ_DECL(pj_status_t) pjsip_endpt_set_resolver(pjsip_endpoint *endpt, + pj_dns_resolver *resv); + +/** + * Get the DNS resolver being used by the SIP resolver. + * + * @param endpt The SIP endpoint instance. + * + * @return The DNS resolver instance currently being used + * by the SIP endpoint. + */ +PJ_DECL(pj_dns_resolver*) pjsip_endpt_get_resolver(pjsip_endpoint *endpt); + +/** * Asynchronously resolve a SIP target host or domain according to rule * specified in RFC 3263 (Locating SIP Servers). When the resolving operation * has completed, the callback will be called. * - * Note: at the moment we don't have implementation of RFC 3263 yet! - * * @param endpt The endpoint instance. * @param pool The pool to allocate resolver job. * @param target The target specification to be resolved. diff --git a/pjsip/include/pjsip/sip_resolve.h b/pjsip/include/pjsip/sip_resolve.h index 3ebface3..a71dd5b5 100644 --- a/pjsip/include/pjsip/sip_resolve.h +++ b/pjsip/include/pjsip/sip_resolve.h @@ -27,26 +27,141 @@ */ #include <pjsip/sip_types.h> +#include <pjlib-util/resolver.h> #include <pj/sock.h> PJ_BEGIN_DECL /** - * @defgroup PJSIP_RESOLVE Server Resolution + * @defgroup PJSIP_RESOLVE SIP SRV Server Resolution (RFC 3263 - Locating SIP Servers) * @ingroup PJSIP_TRANSPORT * @brief Framework to resolve SIP servers based on RFC 3263. * @{ - * This is the server resolution framework, which is modelled after - * RFC 3263 - Locating SIP Servers document. The server resolution + * \section PJSIP_RESOLVE_FEATURES Features + * + * This is the SIP server resolution framework, which is modelled after + * RFC 3263 - Locating SIP Servers document. The SIP server resolution * framework is asynchronous; callback will be called once the server * address has been resolved (successfully or with errors). + * + * \subsection PJSIP_RESOLVE_CONFORMANT Conformance to RFC 3263 + * + * The SIP server resolution framework is modelled after RFC 3263 (Locating + * SIP Servers) document, and it provides a single function (#pjsip_resolve()) + * to resolve a domain into actual IP addresses of the servers, by querying + * DNS SRV record and DNS A record where necessary. + * + * The #pjsip_resolve() function performs the server resolution according + * to RFC 3263 with some additional fallback mechanisms, as follows: + * - if the target name is an IP address, the callback will be called + * immediately with the IP address. If port number was specified, this + * port number will be used, otherwise the default port number for the + * transport will be used (5060 for TCP/UDP, 5061 for TLS) if the transport + * is specified. If the transport is not specified, UDP with port number + * 5060 will be used. + * - if target name is not an IP address but it contains port number, + * then the target name is resolved with DNS A (or AAAA, when IPv6 is + * supported in the future) query, and the port is taken from the + * port number argument. The callback will be called once the DNS A + * resolution completes. If the DNS A resolution returns multiple IP + * addresses, these IP addresses will be returned to the caller. + * - if target name is not an IP address and port number is not specified, + * DNS SRV resolution will be performed for the specified name and + * transport type (or UDP when transport is not specified), + * then followed by DNS A (or AAAA, when IPv6 is supported) + * resolution for each target in the SRV record. If DNS SRV + * resolution returns error, DNS A (or AAAA) resolution will be + * performed for the original target (it is assumed that the target domain + * does not support SRV records). Upon successful completion, + * application callback will be called with each IP address of the + * target selected based on the load-balancing and fail-over criteria + * below. + * + * The above server resolution procedure differs from RFC 3263 in these + * regards: + * - currently #pjsip_resolve() doesn't support DNS NAPTR record. + * - if transport is not specified, it is assumed to be UDP (the proper + * behavior is to query the NAPTR record, but we don't support this + * yet). + * + * + * \subsection PJSIP_SIP_RESOLVE_FAILOVER_LOADBALANCE Load-Balancing and Fail-Over + * + * When multiple targets are returned in the DNS SRV response, server entries + * are selected based on the following rule (which is described in RFC 2782): + * - targets will be sorted based on the priority first. + * - for targets with the same priority, #pjsip_resolve() will select + * only one target according to its weight. To select this one target, + * the function associates running-sum for all targets, and generates + * a random number between zero and the total running-sum (inclusive). + * The target selected is the first target with running-sum greater than + * or equal to this random number. + * + * The above procedure will select one target for each priority, allowing + * application to fail-over to the next target when the previous target fails. + * These targets are returned in the #pjsip_server_addresses structure + * argument of the callback. + * + * \subsection PJSIP_SIP_RESOLVE_SIP_FEATURES SIP SRV Resolver Features + * + * Some features of the SIP resolver: + * - DNS SRV entries are returned on sorted order based on priority + * to allow failover to the next appropriate server. + * - The procedure in RFC 2782 is used to select server with the same + * priority to load-balance the servers load. + * - A single function (#pjsip_resolve()) performs all server resolution + * works, from resolving the SRV records to getting the actual IP addresses + * of the servers with DNS A (or AAAA) resolution. + * - When multiple DNS SRV records are returned, parallel DNS A (or AAAA) + * queries will be issued simultaneously. + * - The PJLIB-UTIL DNS resolver provides additional functionality such as + * response caching, query aggregation, parallel nameservers, fallback + * nameserver, etc., which will be described below. + * + * + * \subsection PJSIP_RESOLVE_DNS_FEATURES DNS Resolver Features + * + * The PJSIP server resolution framework uses PJLIB-UTIL DNS resolver engine + * for performing the asynchronous DNS request. The PJLIB-UTIL DNS resolver + * has some useful features, such as: + * - queries are asynchronous with configurable timeout, + * - query aggregation to combine multiple pending queries to the same + * DNS target into a single DNS request (to save message round-trip and + * processing), + * - response caching with TTL negotiated between the minimum TTL found in + * the response and the maximum TTL allowed in the configuration, + * - multiple nameservers, with active nameserver is selected from nameserver + * which provides the best response time, + * - fallback nameserver, with periodic detection of which name servers are + * active or down. + * - etc. + * + * Please consult PJLIB-UTIL DNS resolver documentation for more details. + * + * + * \section PJSIP_RESOLVE_USING Using the Resolver + * + * To maintain backward compatibility, the resolver MUST be enabled manually. + * With the default settings, the resolver WILL NOT perform DNS SRV resolution, + * as it will just resolve the name with standard pj_gethostbyname() function. + * + * Application can enable the SRV resolver by creating the PJLIB-UTIL DNS + * resolver with #pjsip_endpt_create_resolver(), configure the + * nameservers of the PJLIB-UTIL DNS resolver object by calling + * pj_dns_resolver_set_ns() function, and pass the DNS resolver object to + * #pjsip_resolver_set_resolver() function. + * + * Once the resolver is set, it will be used automatically by PJSIP everytime + * PJSIP needs to send SIP request/response messages. + * + * + * \section PJSIP_RESOLVE_REFERENCE Reference + * + * Reference: + * - RFC 2782: A DNS RR for specifying the location of services (DNS SRV) + * - RFC 3263: Locating SIP Servers */ -/** - * Maximum number of addresses returned by the resolver. - */ -#define PJSIP_MAX_RESOLVED_ADDRESSES 8 - /** * The server addresses returned by the resolver. */ @@ -61,6 +176,12 @@ typedef struct pjsip_server_addresses /** Preferable transport to be used to contact this address. */ pjsip_transport_type_e type; + /** Server priority (the lower the higher the priority). */ + unsigned priority; + + /** Server weight (the higher the more load it can handle). */ + unsigned weight; + /** The server's address. */ pj_sockaddr addr; @@ -85,15 +206,56 @@ typedef void pjsip_resolver_callback(pj_status_t status, const struct pjsip_server_addresses *addr); /** - * Create resolver engine. + * Create SIP resolver engine. Note that this function is normally called + * internally by pjsip_endpoint instance. + * + * @param pf The Pool Factory. + * @param p_res Pointer to receive SIP resolver instance. + * + * @return PJ_SUCCESS when resolver can be successfully created. + */ +PJ_DECL(pj_status_t) pjsip_resolver_create(pj_pool_t *pool, + pjsip_resolver_t **p_res); + +/** + * Set the DNS resolver instance of the SIP resolver engine. Before the + * DNS resolver is set, the SIP resolver will use standard pj_gethostbyname() + * to resolve addresses. * - * @param pool The Pool. - * @return The resolver engine. + * Note that application normally will use #pjsip_endpt_set_resolver() instead + * since it does not normally have access to the SIP resolver instance. + * + * @param res The SIP resolver engine. + * @param dns_res The DNS resolver instance to be used by the SIP resolver. + * This argument can be NULL to reset the internal DNS + * instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsip_resolver_set_resolver(pjsip_resolver_t *res, + pj_dns_resolver *dns_res); + + +/** + * Get the DNS resolver instance of the SIP resolver engine. + * + * Note that application normally will use #pjsip_endpt_get_resolver() instead + * since it does not normally have access to the SIP resolver instance. + * + * @param res The SIP resolver engine. + * + * @return The DNS resolver instance (may be NULL) */ -PJ_DECL(pjsip_resolver_t*) pjsip_resolver_create(pj_pool_t *pool); +PJ_DECL(pj_dns_resolver*) pjsip_resolver_get_resolver(pjsip_resolver_t *res); /** - * Destroy resolver engine. + * Destroy resolver engine. Note that this will also destroy the internal + * DNS resolver inside the engine. If application doesn't want the internal + * DNS resolver to be destroyed, it should set the internal DNS resolver + * to NULL before calling this function. + * + * Note that this function will normally called by the SIP endpoint instance + * when the SIP endpoint instance is destroyed. * * @param resolver The resolver. */ @@ -104,7 +266,8 @@ PJ_DECL(void) pjsip_resolver_destroy(pjsip_resolver_t *resolver); * specified in RFC 3263 (Locating SIP Servers). When the resolving operation * has completed, the callback will be called. * - * Note: at the moment we don't have implementation of RFC 3263 yet! + * Note that application normally will use #pjsip_endpt_resolve() instead + * since it does not normally have access to the SIP resolver instance. * * @param resolver The resolver engine. * @param pool The pool to allocate resolver job. @@ -114,7 +277,7 @@ PJ_DECL(void) pjsip_resolver_destroy(pjsip_resolver_t *resolver); */ PJ_DECL(void) pjsip_resolve( pjsip_resolver_t *resolver, pj_pool_t *pool, - pjsip_host_info *target, + const pjsip_host_info *target, void *token, pjsip_resolver_callback *cb); diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 9ded3f15..2b11df01 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -388,6 +388,20 @@ typedef struct pjsua_config unsigned thread_cnt; /** + * Number of nameservers. If no name server is configured, the SIP SRV + * resolution would be disabled, and domain will be resolved with + * standard pj_gethostbyname() function. + */ + unsigned nameserver_count; + + /** + * Array of nameservers to be used by the SIP resolver subsystem. + * The order of the name server specifies the priority (first name + * server will be used first, unless it is not reachable). + */ + pj_str_t nameserver[4]; + + /** * Number of outbound proxies in the array. */ unsigned outbound_proxy_cnt; diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c index 1fa39831..571ee11c 100644 --- a/pjsip/src/pjsip/sip_endpoint.c +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -484,8 +484,8 @@ PJ_DEF(pj_status_t) pjsip_endpt_create(pj_pool_factory *pf, } /* Create asynchronous DNS resolver. */ - endpt->resolver = pjsip_resolver_create(endpt->pool); - if (!endpt->resolver) { + status = pjsip_resolver_create(endpt->pool, &endpt->resolver); + if (status != PJ_SUCCESS) { PJ_LOG(4, (THIS_FILE, "Error creating resolver instance")); goto on_error; } @@ -926,6 +926,42 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_tdata( pjsip_endpoint *endpt, } /* + * Create the DNS resolver instance. + */ +PJ_DEF(pj_status_t) pjsip_endpt_create_resolver(pjsip_endpoint *endpt, + pj_dns_resolver **p_resv) +{ +#if PJSIP_HAS_RESOLVER + PJ_ASSERT_RETURN(endpt && p_resv, PJ_EINVAL); + return pj_dns_resolver_create( endpt->pf, NULL, 0, endpt->timer_heap, + endpt->ioqueue, p_resv); +#else + PJ_UNUSED_ARG(endpt); + PJ_UNUSED_ARG(p_resv); + pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)"); + return PJ_EINVALIDOP; +#endif +} + +/* + * Set DNS resolver to be used by the SIP resolver. + */ +PJ_DEF(pj_status_t) pjsip_endpt_set_resolver( pjsip_endpoint *endpt, + pj_dns_resolver *resv) +{ + return pjsip_resolver_set_resolver(endpt->resolver, resv); +} + +/* + * Get the DNS resolver being used by the SIP resolver. + */ +PJ_DEF(pj_dns_resolver*) pjsip_endpt_get_resolver(pjsip_endpoint *endpt) +{ + PJ_ASSERT_RETURN(endpt, NULL); + return pjsip_resolver_get_resolver(endpt->resolver); +} + +/* * Resolve */ PJ_DEF(void) pjsip_endpt_resolve( pjsip_endpoint *endpt, @@ -1036,6 +1072,13 @@ PJ_DEF(void) pjsip_endpt_dump( pjsip_endpoint *endpt, pj_bool_t detail ) pj_pool_get_capacity(endpt->pool), pj_pool_get_used_size(endpt->pool))); + /* Resolver */ +#if PJSIP_HAS_RESOLVER + if (pjsip_endpt_get_resolver(endpt)) { + pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), detail); + } +#endif + /* Transports. */ pjsip_tpmgr_dump_transports( endpt->transport_mgr ); diff --git a/pjsip/src/pjsip/sip_resolve.c b/pjsip/src/pjsip/sip_resolve.c index 5b920be5..8f28f9e8 100644 --- a/pjsip/src/pjsip/sip_resolve.c +++ b/pjsip/src/pjsip/sip_resolve.c @@ -19,30 +19,141 @@ #include <pjsip/sip_resolve.h> #include <pjsip/sip_transport.h> #include <pjsip/sip_errno.h> -#include <pj/pool.h> -#include <pj/ctype.h> +#include <pjlib-util/errno.h> +#include <pj/array.h> #include <pj/assert.h> +#include <pj/ctype.h> #include <pj/log.h> +#include <pj/pool.h> +#include <pj/rand.h> +#include <pj/string.h> + #define THIS_FILE "sip_resolve.c" +#define ADDR_MAX_COUNT 8 + +struct naptr_target +{ + pj_str_t target_name; /**< NAPTR target name. */ + pjsip_transport_type_e type; /**< Transport type. */ + unsigned order; /**< Order */ + unsigned pref; /**< Preference. */ +}; + +struct srv_target +{ + pjsip_transport_type_e type; + pj_str_t target_name; + char target_buf[PJ_MAX_HOSTNAME]; + unsigned port; + unsigned priority; + unsigned weight; + unsigned sum; + unsigned addr_cnt; + pj_in_addr addr[ADDR_MAX_COUNT]; +}; + +struct query +{ + char objname[PJ_MAX_OBJ_NAME]; + + pjsip_resolver_t *resolver; /**< Resolver SIP instance. */ + pj_dns_type dns_state; /**< DNS type being resolved. */ + void *token; + pjsip_resolver_callback *cb; + pj_dns_async_query *object; + pj_status_t last_error; + + /* Original request: */ + struct { + pjsip_host_info target; + } req; + + /* NAPTR records: */ + unsigned naptr_cnt; + struct naptr_target naptr[8]; + + /* SRV records and their resolved IP addresses: */ + unsigned srv_cnt; + struct srv_target srv[PJSIP_MAX_RESOLVED_ADDRESSES]; + + /* Number of hosts in SRV records that the IP address has been resolved */ + unsigned host_resolved; +}; + + struct pjsip_resolver_t { - void *dummy; + pj_dns_resolver *res; + unsigned job_id; }; -PJ_DEF(pjsip_resolver_t*) pjsip_resolver_create(pj_pool_t *pool) +static void dns_callback(void *user_data, + pj_status_t status, + pj_dns_parsed_packet *response); + + +/* + * Public API to create the resolver. + */ +PJ_DEF(pj_status_t) pjsip_resolver_create( pj_pool_t *pool, + pjsip_resolver_t **p_res) { pjsip_resolver_t *resolver; - resolver = (pjsip_resolver_t*) pj_pool_calloc(pool, 1, sizeof(*resolver)); - return resolver; + + PJ_ASSERT_RETURN(pool && p_res, PJ_EINVAL); + resolver = pj_pool_zalloc(pool, sizeof(*resolver)); + *p_res = resolver; + + return PJ_SUCCESS; +} + + +/* + * Public API to set the DNS resolver instance for the SIP resolver. + */ +PJ_DEF(pj_status_t) pjsip_resolver_set_resolver(pjsip_resolver_t *res, + pj_dns_resolver *dns_res) +{ +#if PJSIP_HAS_RESOLVER + res->res = dns_res; + return PJ_SUCCESS; +#else + PJ_UNUSED_ARG(res); + PJ_UNUSED_ARG(dns_res); + pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)"); + return PJ_EINVALIDOP; +#endif; +} + + +/* + * Public API to get the internal DNS resolver. + */ +PJ_DEF(pj_dns_resolver*) pjsip_resolver_get_resolver(pjsip_resolver_t *res) +{ + return res->res; } + +/* + * Public API to create destroy the resolver + */ PJ_DEF(void) pjsip_resolver_destroy(pjsip_resolver_t *resolver) { - PJ_UNUSED_ARG(resolver); + if (resolver->res) { +#if PJSIP_HAS_RESOLVER + pj_dns_resolver_destroy(resolver->res, PJ_FALSE); +#endif + resolver->res = NULL; + } } +/* + * Internal: + * determine if an address is a valid IP address. + */ static int is_str_ip(const pj_str_t *host) { const char *p = host->ptr; @@ -58,29 +169,23 @@ static int is_str_ip(const pj_str_t *host) return 1; } + +/* + * This is the main function for performing server resolution. + */ PJ_DEF(void) pjsip_resolve( pjsip_resolver_t *resolver, pj_pool_t *pool, - pjsip_host_info *target, + const pjsip_host_info *target, void *token, pjsip_resolver_callback *cb) { - struct pjsip_server_addresses svr_addr; - pj_status_t status; + pjsip_server_addresses svr_addr; + pj_status_t status = PJ_SUCCESS; int is_ip_addr; + struct query *query; + pj_str_t srv_name; pjsip_transport_type_e type = target->type; - PJ_UNUSED_ARG(resolver); - PJ_UNUSED_ARG(pool); - - PJ_LOG(5,(THIS_FILE, "Resolving server '%.*s:%d' type=%s", - target->addr.host.slen, - target->addr.host.ptr, - target->addr.port, - pjsip_transport_get_type_name(type))); - - /* We only do synchronous resolving at this moment. */ - PJ_TODO(SUPPORT_RFC3263_SERVER_RESOLUTION) - /* Is it IP address or hostname?. */ is_ip_addr = is_str_ip(&target->addr.host); @@ -109,50 +214,693 @@ PJ_DEF(void) pjsip_resolve( pjsip_resolver_t *resolver, */ type = PJSIP_TRANSPORT_UDP; } + } + + + /* If target is an IP address, or if resolver is not configured, + * we can just finish the resolution now using pj_gethostbyname() + */ + if (is_ip_addr || resolver->res == NULL) { + + pj_in_addr ip_addr; + pj_uint16_t srv_port; + + if (!is_ip_addr) { + PJ_LOG(5,(THIS_FILE, + "DNS resolver not available, target '%.*s:%d' type=%s " + "will be resolved with gethostbyname()", + target->addr.host.slen, + target->addr.host.ptr, + target->addr.port, + pjsip_transport_get_type_name(target->type))); + } + + /* Set the port number if not specified. */ + if (target->addr.port == 0) { + srv_port = (pj_uint16_t) + pjsip_transport_get_default_port_for_type(type); + } else { + srv_port = (pj_uint16_t)target->addr.port; + } + + /* This will eventually call pj_gethostbyname() if the host + * is not an IP address. + */ + status = pj_sockaddr_in_init((pj_sockaddr_in*)&svr_addr.entry[0].addr, + &target->addr.host, srv_port); + if (status != PJ_SUCCESS) + goto on_error; + + /* Call the callback. */ + ip_addr = ((pj_sockaddr_in*)&svr_addr.entry[0].addr)->sin_addr; + PJ_LOG(5,(THIS_FILE, + "Target '%.*s:%d' type=%s resolved to " + "'%s:%d' type=%s", + (int)target->addr.host.slen, + target->addr.host.ptr, + target->addr.port, + pjsip_transport_get_type_name(target->type), + pj_inet_ntoa(ip_addr), + srv_port, + pjsip_transport_get_type_name(type))); + svr_addr.count = 1; + svr_addr.entry[0].priority = 0; + svr_addr.entry[0].weight = 0; + svr_addr.entry[0].type = type; + svr_addr.entry[0].addr_len = sizeof(pj_sockaddr_in); + (*cb)(status, token, &svr_addr); + /* Done. */ + return; } - /* Set the port number if not specified. */ - if (target->addr.port == 0) { - target->addr.port = pjsip_transport_get_default_port_for_type(type); + /* Target is not an IP address so we need to resolve it. */ +#if PJSIP_HAS_RESOLVER + + /* Build the query state */ + query = pj_pool_zalloc(pool, sizeof(struct query)); + pj_ansi_snprintf(query->objname, sizeof(query->objname), "rsvjob%X", + resolver->job_id++); + query->resolver = resolver; + query->token = token; + query->cb = cb; + query->req.target = *target; + pj_strdup(pool, &query->req.target.addr.host, &target->addr.host); + + /* If port is not specified, start with SRV resolution + * (should be with NAPTR, but we'll do that later) + */ + PJ_TODO(SUPPORT_DNS_NAPTR); + + /* Build dummy NAPTR entry */ + query->naptr_cnt = 1; + pj_bzero(&query->naptr[0], sizeof(query->naptr[0])); + query->naptr[0].order = 0; + query->naptr[0].pref = 0; + query->naptr[0].type = type; + query->naptr[0].target_name.ptr = + pj_pool_alloc(pool, target->addr.host.slen + 12); + + if (type == PJSIP_TRANSPORT_TLS) + pj_strcpy2(&query->naptr[0].target_name, "_sips._tcp."); + else if (type == PJSIP_TRANSPORT_TCP) + pj_strcpy2(&query->naptr[0].target_name, "_sip._tcp."); + else if (type == PJSIP_TRANSPORT_UDP) + pj_strcpy2(&query->naptr[0].target_name, "_sip._udp."); + else { + pj_assert(!"Unknown transport type"); + pj_strcpy2(&query->naptr[0].target_name, "_sip._udp."); } + pj_strcat(&query->naptr[0].target_name, &target->addr.host); + + + /* Start DNS SRV or A resolution, depending on whether port is specified */ + if (target->addr.port == 0) { + query->dns_state = PJ_DNS_TYPE_SRV; + srv_name = query->naptr[0].target_name; - /* Resolve hostname. */ - if (!is_ip_addr) { - status = pj_sockaddr_in_init((pj_sockaddr_in*)&svr_addr.entry[0].addr, - &target->addr.host, - (pj_uint16_t)target->addr.port); } else { - status = pj_sockaddr_in_init((pj_sockaddr_in*)&svr_addr.entry[0].addr, - &target->addr.host, - (pj_uint16_t)target->addr.port); + /* Otherwise if port is specified, start with A (or AAAA) host + * resolution + */ + query->dns_state = PJ_DNS_TYPE_A; + + /* Since we don't perform SRV resolution, pretend that we'ee already + * done so by inserting a dummy SRV record. + */ + + query->srv_cnt = 1; + pj_bzero(&query->srv[0], sizeof(query->srv[0])); + query->srv[0].target_name = query->req.target.addr.host; + query->srv[0].type = type; + query->srv[0].port = query->req.target.addr.port; + query->srv[0].priority = 0; + query->srv[0].weight = 0; + + srv_name = query->srv[0].target_name; } + /* Start the asynchronous query */ + PJ_LOG(5, (query->objname, + "Starting async DNS %s query: target=%.*s, transport=%s, " + "port=%d", + pj_dns_get_type_name(query->dns_state), + (int)srv_name.slen, srv_name.ptr, + pjsip_transport_get_type_name(target->type), + target->addr.port)); + + status = pj_dns_resolver_start_query(resolver->res, &srv_name, + query->dns_state, 0, &dns_callback, + query, &query->object); + if (status != PJ_SUCCESS) + goto on_error; + + return; + +#else /* PJSIP_HAS_RESOLVER */ + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(query); + PJ_UNUSED_ARG(srv_name); +#endif /* PJSIP_HAS_RESOLVER */ + +on_error: if (status != PJ_SUCCESS) { char errmsg[PJ_ERR_MSG_SIZE]; PJ_LOG(4,(THIS_FILE, "Failed to resolve '%.*s'. Err=%d (%s)", - target->addr.host.slen, + (int)target->addr.host.slen, target->addr.host.ptr, status, pj_strerror(status,errmsg,sizeof(errmsg)).ptr)); - (*cb)(status, token, &svr_addr); + (*cb)(status, token, NULL); + return; + } +} + +/* + * The rest of the code should only get compiled when resolver is enabled + */ +#if PJSIP_HAS_RESOLVER + +#define SWAP(type,ptr1,ptr2) if (ptr1 != ptr2) { \ + type tmp; \ + pj_memcpy(&tmp, ptr1, sizeof(type)); \ + pj_memcpy(ptr1, ptr2, sizeof(type)); \ + (ptr1)->target_name.ptr = (ptr1)->target_buf; \ + pj_memcpy(ptr2, &tmp, sizeof(type)); \ + (ptr2)->target_name.ptr = (ptr2)->target_buf; \ + } else {} + +/* Build server entries in the query based on received SRV response */ +static void build_server_entries(struct query *query, + pj_dns_parsed_packet *response) +{ + unsigned i; + unsigned naptr_id; + + /* Find NAPTR target which corresponds to this SRV target */ + for (naptr_id=0; naptr_id < query->naptr_cnt; ++naptr_id) { + if (pj_stricmp(&query->naptr[naptr_id].target_name, + &response->ans[0].name)==0) + break; + } + if (naptr_id == query->naptr_cnt) { + PJ_LOG(4,(query->objname, + "Unable to find NAPTR record for SRV name %.*s!", + (int)response->ans[0].name.slen, + response->ans[0].name.ptr)); return; } - /* Call the callback. */ - PJ_LOG(5,(THIS_FILE, "Server resolved: '%.*s:%d' type=%s has %d entries, " - "entry[0]=%s:%d type=%s", - target->addr.host.slen, - target->addr.host.ptr, - target->addr.port, - pjsip_transport_get_type_name(type), - 1, - pj_inet_ntoa(((pj_sockaddr_in*)&svr_addr.entry[0].addr)->sin_addr), - target->addr.port, - pjsip_transport_get_type_name(type))); - svr_addr.count = (status == PJ_SUCCESS) ? 1 : 0; - svr_addr.entry[0].type = type; - svr_addr.entry[0].addr_len = sizeof(pj_sockaddr_in); - (*cb)(status, token, &svr_addr); + + /* Save the Resource Records in DNS answer into SRV targets. */ + query->srv_cnt = 0; + for (i=0; i<response->hdr.anscount && + query->srv_cnt < PJSIP_MAX_RESOLVED_ADDRESSES; ++i) + { + pj_dns_parsed_rr *rr = &response->ans[i]; + struct srv_target *srv = &query->srv[query->srv_cnt]; + + if (rr->type != PJ_DNS_TYPE_SRV) { + PJ_LOG(4,(query->objname, + "Received non SRV answer for SRV query!")); + continue; + } + + if (rr->rdata.srv.target.slen > PJ_MAX_HOSTNAME) { + PJ_LOG(4,(query->objname, "Hostname is too long!")); + continue; + } + + /* Build the SRV entry for RR */ + pj_bzero(srv, sizeof(*srv)); + pj_memcpy(srv->target_buf, rr->rdata.srv.target.ptr, + rr->rdata.srv.target.slen); + srv->target_name.ptr = srv->target_buf; + srv->target_name.slen = rr->rdata.srv.target.slen; + srv->type = query->naptr[naptr_id].type; + srv->port = rr->rdata.srv.port; + srv->priority = rr->rdata.srv.prio; + srv->weight = rr->rdata.srv.weight; + + ++query->srv_cnt; + } + + /* First pass: + * order the entries based on priority. + */ + for (i=0; i<query->srv_cnt-1; ++i) { + unsigned min = i, j; + for (j=i+1; j<query->srv_cnt; ++j) { + if (query->srv[j].priority < query->srv[min].priority) + min = j; + } + SWAP(struct srv_target, &query->srv[i], &query->srv[min]); + } + + /* Second pass: + * pick one host among hosts with the same priority, according + * to its weight. The idea is when one server fails, client should + * contact the next server with higher priority rather than contacting + * server with the same priority as the failed one. + * + * The algorithm for selecting server among servers with the same + * priority is described in RFC 2782. + */ + for (i=0; i<query->srv_cnt; ++i) { + unsigned j, count=1, sum; + + /* Calculate running sum for servers with the same priority */ + sum = query->srv[i].sum = query->srv[i].weight; + for (j=i+1; j<query->srv_cnt && + query->srv[j].priority == query->srv[i].priority; ++j) + { + sum += query->srv[j].weight; + query->srv[j].sum = sum; + ++count; + } + + if (count > 1) { + unsigned r; + + /* Elect one random number between zero and the total sum of + * weight (inclusive). + */ + r = pj_rand() % (sum + 1); + + /* Select the first server which running sum is greater than or + * equal to the random number. + */ + for (j=i; j<i+count; ++j) { + if (query->srv[j].sum >= r) + break; + } + + /* Must have selected one! */ + pj_assert(j != i+count); + + /* Put this entry in front (of entries with same priority) */ + SWAP(struct srv_target, &query->srv[i], &query->srv[j]); + + /* Remove all other entries (of the same priority) */ + while (count > 1) { + pj_array_erase(query->srv, sizeof(struct srv_target), + query->srv_cnt, i+1); + --count; + --query->srv_cnt; + } + } + } + + /* Since we've been moving around SRV entries, update the pointers + * in target_name. + */ + for (i=0; i<query->srv_cnt; ++i) { + query->srv[i].target_name.ptr = query->srv[i].target_buf; + } + + /* Check for Additional Info section if A records are available, and + * fill in the IP address (so that we won't need to resolve the A + * record with another DNS query). + */ + for (i=0; i<response->hdr.arcount; ++i) { + pj_dns_parsed_rr *rr = &response->arr[i]; + unsigned j; + + if (rr->type != PJ_DNS_TYPE_A) + continue; + + /* Yippeaiyee!! There is an "A" record! + * Update the IP address of the corresponding SRV record. + */ + for (j=0; j<query->srv_cnt; ++j) { + if (pj_stricmp(&rr->name, &query->srv[j].target_name)==0) { + unsigned cnt = query->srv[j].addr_cnt; + query->srv[j].addr[cnt] = pj_inet_addr(&rr->rdata.a.ip_addr); + ++query->srv[j].addr_cnt; + ++query->host_resolved; + break; + } + } + + /* Not valid message; SRV entry might have been deleted in + * server selection process. + */ + /* + if (j == query->srv_cnt) { + PJ_LOG(4,(query->objname, + "Received DNS SRV answer with A record, but " + "couldn't find matching name (name=%.*s)", + (int)rr->name.slen, + rr->name.ptr)); + } + */ + } + + /* Rescan again the name specified in the SRV record to see if IP + * address is specified as the target name (unlikely, but well, who + * knows..). + */ + for (i=0; i<query->srv_cnt; ++i) { + pj_in_addr addr; + + if (query->srv[i].addr_cnt != 0) { + /* IP address already resolved */ + continue; + } + + if (pj_inet_aton(&query->srv[i].target_name, &addr) != 0) { + query->srv[i].addr[query->srv[i].addr_cnt++] = addr; + ++query->host_resolved; + } + } + + /* Print resolved entries to the log */ + PJ_LOG(5,(query->objname, + "SRV query for %.*s completed, " + "%d of %d total entries selected%c", + (int)query->naptr[naptr_id].target_name.slen, + query->naptr[naptr_id].target_name.ptr, + query->srv_cnt, + response->hdr.anscount, + (query->srv_cnt ? ':' : ' '))); + + for (i=0; i<query->srv_cnt; ++i) { + const char *addr; + + if (query->srv[i].addr_cnt != 0) + addr = pj_inet_ntoa(query->srv[i].addr[0]); + else + addr = "-"; + + PJ_LOG(5,(query->objname, + " %d: SRV %d %d %d %.*s (%s)", + i, query->srv[i].priority, + query->srv[i].weight, + query->srv[i].port, + (int)query->srv[i].target_name.slen, + query->srv[i].target_name.ptr, + addr)); + } } + +/* Start DNS A record queries for all SRV records in the query structure */ +static pj_status_t resolve_hostnames(struct query *query) +{ + unsigned i; + pj_status_t err=PJ_SUCCESS, status; + + query->dns_state = PJ_DNS_TYPE_A; + for (i=0; i<query->srv_cnt; ++i) { + PJ_LOG(5, (query->objname, + "Starting async DNS A query for %.*s", + (int)query->srv[i].target_name.slen, + query->srv[i].target_name.ptr)); + + status = pj_dns_resolver_start_query(query->resolver->res, + &query->srv[i].target_name, + PJ_DNS_TYPE_A, 0, + &dns_callback, + query, NULL); + if (status != PJ_SUCCESS) { + query->host_resolved++; + err = status; + } + } + + return (query->host_resolved == query->srv_cnt) ? err : PJ_SUCCESS; +} + +/* + * This callback is called by PJLIB-UTIL DNS resolver when asynchronous + * query has completed (successfully or with error). + */ +static void dns_callback(void *user_data, + pj_status_t status, + pj_dns_parsed_packet *pkt) +{ + struct query *query = user_data; + unsigned i; + + /* Proceed to next stage */ + + if (query->dns_state == PJ_DNS_TYPE_SRV) { + + /* We are getting SRV response */ + + if (status == PJ_SUCCESS && pkt->hdr.anscount != 0) { + /* Got SRV response, build server entry. If A records are available + * in additional records section of the DNS response, save them too. + */ + build_server_entries(query, pkt); + + } else if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + unsigned naptr_id; + + /* Update query last error */ + query->last_error = status; + + /* Find which NAPTR target has not got SRV records */ + for (naptr_id=0; naptr_id < query->naptr_cnt; ++naptr_id) { + for (i=0; i<query->srv_cnt; ++i) { + if (query->srv[i].type == query->naptr[naptr_id].type) + break; + } + if (i == query->srv_cnt) + break; + } + if (naptr_id == query->naptr_cnt) { + /* Strangely all NAPTR records seem to already have SRV + * records! This is quite unexpected, by anyway lets set + * the naptr_id to zero just in case. + */ + pj_assert(!"Strange"); + naptr_id = 0; + + } + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(query->objname, + "DNS SRV resolution failed for %.*s: %s", + (int)query->naptr[naptr_id].target_name.slen, + query->naptr[naptr_id].target_name.ptr, + errmsg)); + } + + /* If we can't build SRV record, assume the original target is + * an A record. + */ + if (query->srv_cnt == 0) { + /* Looks like we aren't getting any SRV responses. + * Resolve the original target as A record by creating a + * single "dummy" srv record and start the hostname resolution. + */ + unsigned naptr_id; + + /* Find which NAPTR target has not got SRV records */ + for (naptr_id=0; naptr_id < query->naptr_cnt; ++naptr_id) { + for (i=0; i<query->srv_cnt; ++i) { + if (query->srv[i].type == query->naptr[naptr_id].type) + break; + } + if (i == query->srv_cnt) + break; + } + if (naptr_id == query->naptr_cnt) { + /* Strangely all NAPTR records seem to already have SRV + * records! This is quite unexpected, by anyway lets set + * the naptr_id to zero just in case. + */ + pj_assert(!"Strange"); + naptr_id = 0; + + } + + PJ_LOG(4, (query->objname, + "DNS SRV resolution failed for %.*s, trying " + "resolving A record for %.*s", + (int)query->naptr[naptr_id].target_name.slen, + query->naptr[naptr_id].target_name.ptr, + (int)query->req.target.addr.host.slen, + query->req.target.addr.host.ptr)); + + /* Create a "dummy" srv record using the original target */ + i = query->srv_cnt++; + pj_bzero(&query->srv[i], sizeof(query->srv[i])); + query->srv[i].target_name = query->req.target.addr.host; + query->srv[i].type = query->naptr[naptr_id].type; + query->srv[i].priority = 0; + query->srv[i].weight = 0; + + query->srv[i].port = query->req.target.addr.port; + if (query->srv[i].port == 0) { + query->srv[i].port = (pj_uint16_t) + pjsip_transport_get_default_port_for_type(query->srv[i].type); + } + } + + + /* Resolve server hostnames (DNS A record) for hosts which don't have + * A record yet. + */ + if (query->host_resolved != query->srv_cnt) { + status = resolve_hostnames(query); + if (status != PJ_SUCCESS) + goto on_error; + + /* Must return now. Callback may have been called and query + * may have been destroyed. + */ + return; + } + + } else if (query->dns_state == PJ_DNS_TYPE_A) { + + /* Check that we really have answer */ + if (status==PJ_SUCCESS && pkt->hdr.anscount != 0) { + + /* Update IP address of the corresponding hostname */ + for (i=0; i<query->srv_cnt; ++i) { + if (pj_stricmp(&pkt->ans[0].name, + &query->srv[i].target_name)==0) + { + break; + } + } + + if (i == query->srv_cnt) { + PJ_LOG(4,(query->objname, + "Received answer to DNS A request with no matching " + "SRV record! The unknown name is %.*s", + (int)pkt->ans[0].name.slen, pkt->ans[0].name.ptr)); + } else { + unsigned j; + + query->srv[i].addr[query->srv[i].addr_cnt++] = + pj_inet_addr(&pkt->ans[0].rdata.a.ip_addr); + + PJ_LOG(5,(query->objname, + "DNS A for %.*s: %.*s", + (int)query->srv[i].target_name.slen, + query->srv[i].target_name.ptr, + (int)pkt->ans[0].rdata.a.ip_addr.slen, + pkt->ans[0].rdata.a.ip_addr.ptr)); + + /* Check for multiple IP addresses */ + for (j=1; j<pkt->hdr.anscount && + query->srv[i].addr_cnt < ADDR_MAX_COUNT; ++j) + { + query->srv[i].addr[query->srv[i].addr_cnt++] = + pj_inet_addr(&pkt->ans[j].rdata.a.ip_addr); + + PJ_LOG(5,(query->objname, + "Additional DNS A for %.*s: %.*s", + (int)query->srv[i].target_name.slen, + query->srv[i].target_name.ptr, + (int)pkt->ans[j].rdata.a.ip_addr.slen, + pkt->ans[j].rdata.a.ip_addr.ptr)); + } + } + + } else if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + /* Update last error */ + query->last_error = status; + + /* Log error */ + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(query->objname, "DNS A record resolution failed: %s", + errmsg)); + } + + ++query->host_resolved; + + } else { + pj_assert(!"Unexpected state!"); + query->last_error = status = PJ_EINVALIDOP; + goto on_error; + } + + /* Check if all hosts have been resolved */ + if (query->host_resolved == query->srv_cnt) { + /* Got all answers, build server addresses */ + pjsip_server_addresses svr_addr; + + svr_addr.count = 0; + for (i=0; i<query->srv_cnt; ++i) { + unsigned j; + + /* Do we have IP address for this server? */ + /* This log is redundant really. + if (query->srv[i].addr_cnt == 0) { + PJ_LOG(5,(query->objname, + " SRV target %.*s:%d does not have IP address!", + (int)query->srv[i].target_name.slen, + query->srv[i].target_name.ptr, + query->srv[i].port)); + continue; + } + */ + + for (j=0; j<query->srv[i].addr_cnt; ++j) { + unsigned idx = svr_addr.count; + pj_sockaddr_in *addr; + + svr_addr.entry[idx].type = query->srv[i].type; + svr_addr.entry[idx].priority = query->srv[i].priority; + svr_addr.entry[idx].weight = query->srv[i].weight; + svr_addr.entry[idx].addr_len = sizeof(pj_sockaddr_in); + + addr = (pj_sockaddr_in*)&svr_addr.entry[idx].addr; + pj_bzero(addr, sizeof(pj_sockaddr_in)); + addr->sin_family = PJ_AF_INET; + addr->sin_addr = query->srv[i].addr[j]; + addr->sin_port = pj_htons((pj_uint16_t)query->srv[i].port); + + ++svr_addr.count; + } + } + + PJ_LOG(5,(query->objname, + "Server resolution complete, %d server entry(s) found", + svr_addr.count)); + + + if (svr_addr.count > 0) + status = PJ_SUCCESS; + else { + status = query->last_error; + if (status == PJ_SUCCESS) + status = PJLIB_UTIL_EDNSNOANSWERREC; + } + + /* Call the callback */ + (*query->cb)(status, query->token, &svr_addr); + } + + + return; + +on_error: + /* Check for failure */ + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + PJ_LOG(4,(query->objname, + "DNS %s record resolution error for '%.*s'." + " Err=%d (%s)", + pj_dns_get_type_name(query->dns_state), + (int)query->req.target.addr.host.slen, + query->req.target.addr.host.ptr, + status, + pj_strerror(status,errmsg,sizeof(errmsg)).ptr)); + (*query->cb)(status, query->token, NULL); + return; + } +} + +#endif /* PJSIP_HAS_RESOLVER */ + + + diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 6b39fdbb..b4f4325f 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -456,6 +456,45 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); } + /* If nameserver is configured, create DNS resolver instance and + * set it to be used by SIP resolver. + */ + if (ua_cfg->nameserver_count) { +#if PJSIP_HAS_RESOLVER + pj_dns_resolver *resv; + unsigned i; + + /* Create DNS resolver */ + status = pjsip_endpt_create_resolver(pjsua_var.endpt, &resv); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Configure nameserver for the DNS resolver */ + status = pj_dns_resolver_set_ns(resv, ua_cfg->nameserver_count, + ua_cfg->nameserver, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error setting nameserver", status); + return status; + } + + /* Set this DNS resolver to be used by the SIP resolver */ + status = pjsip_endpt_set_resolver(pjsua_var.endpt, resv); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error setting DNS resolver", status); + return status; + } + + /* Print nameservers */ + for (i=0; i<ua_cfg->nameserver_count; ++i) { + PJ_LOG(4,(THIS_FILE, "Nameserver %.*s added", + (int)ua_cfg->nameserver[i].slen, + ua_cfg->nameserver[i].ptr)); + } +#else + PJ_LOG(2,(THIS_FILE, + "DNS resolver is disabled (PJSIP_HAS_RESOLVER==0)")); +#endif + } + /* Init SIP UA: */ /* Initialize transaction layer: */ diff --git a/pjsip/src/test-pjsip/dns_test.c b/pjsip/src/test-pjsip/dns_test.c new file mode 100644 index 00000000..a088e5d9 --- /dev/null +++ b/pjsip/src/test-pjsip/dns_test.c @@ -0,0 +1,596 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> +#include <pjlib-util.h> + +/* For logging purpose. */ +#define THIS_FILE "dns_test.c" + +struct result +{ + pj_status_t status; + pjsip_server_addresses servers; +}; + + +static void cb(pj_status_t status, + void *token, + const struct pjsip_server_addresses *addr) +{ + struct result *result = token; + + result->status = status; + if (status == PJ_SUCCESS) + pj_memcpy(&result->servers, addr, sizeof(*addr)); +} + + +static void add_dns_entries(pj_dns_resolver *resv) +{ + /* Inject DNS SRV entry */ + pj_dns_parsed_packet pkt; + pj_dns_parsed_rr ans[4]; + pj_dns_parsed_rr ar[5]; + unsigned i; + + /* + * This is answer to SRV query to "example.com" domain, and + * the answer contains full reference to the A records of + * the server. The full DNS records is : + + _sip._udp.example.com 3600 IN SRV 0 0 5060 sip01.example.com. + _sip._udp.example.com 3600 IN SRV 0 20 5060 sip02.example.com. + _sip._udp.example.com 3600 IN SRV 0 10 5060 sip03.example.com. + _sip._udp.example.com 3600 IN SRV 1 0 5060 sip04.example.com. + + sip01.example.com. 3600 IN A 1.1.1.1 + sip02.example.com. 3600 IN A 2.2.2.2 + sip03.example.com. 3600 IN A 3.3.3.3 + sip04.example.com. 3600 IN A 4.4.4.4 + + ; Additionally, add A record for "example.com" + example.com. 3600 IN A 5.5.5.5 + + */ + pj_bzero(&pkt, sizeof(pkt)); + pj_bzero(ans, sizeof(ans)); + pj_bzero(ar, sizeof(ar)); + + pkt.hdr.flags = PJ_DNS_SET_QR(1); + pkt.hdr.anscount = PJ_ARRAY_SIZE(ans); + pkt.hdr.arcount = 0; + pkt.ans = ans; + pkt.arr = ar; + + ans[0].name = pj_str("_sip._udp.example.com"); + ans[0].type = PJ_DNS_TYPE_SRV; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.srv.prio = 0; + ans[0].rdata.srv.weight = 0; + ans[0].rdata.srv.port = 5060; + ans[0].rdata.srv.target = pj_str("sip01.example.com"); + + ans[1].name = pj_str("_sip._udp.example.com"); + ans[1].type = PJ_DNS_TYPE_SRV; + ans[1].dnsclass = PJ_DNS_CLASS_IN; + ans[1].ttl = 3600; + ans[1].rdata.srv.prio = 0; + ans[1].rdata.srv.weight = 20; + ans[1].rdata.srv.port = 5060; + ans[1].rdata.srv.target = pj_str("sip02.example.com"); + + ans[2].name = pj_str("_sip._udp.example.com"); + ans[2].type = PJ_DNS_TYPE_SRV; + ans[2].dnsclass = PJ_DNS_CLASS_IN; + ans[2].ttl = 3600; + ans[2].rdata.srv.prio = 0; + ans[2].rdata.srv.weight = 10; + ans[2].rdata.srv.port = 5060; + ans[2].rdata.srv.target = pj_str("sip03.example.com"); + + ans[3].name = pj_str("_sip._udp.example.com"); + ans[3].type = PJ_DNS_TYPE_SRV; + ans[3].dnsclass = PJ_DNS_CLASS_IN; + ans[3].ttl = 3600; + ans[3].rdata.srv.prio = 1; + ans[3].rdata.srv.weight = 0; + ans[3].rdata.srv.port = 5060; + ans[3].rdata.srv.target = pj_str("sip04.example.com"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + ar[0].name = pj_str("sip01.example.com"); + ar[0].type = PJ_DNS_TYPE_A; + ar[0].dnsclass = PJ_DNS_CLASS_IN; + ar[0].ttl = 3600; + ar[0].rdata.a.ip_addr = pj_str("1.1.1.1"); + + ar[1].name = pj_str("sip02.example.com"); + ar[1].type = PJ_DNS_TYPE_A; + ar[1].dnsclass = PJ_DNS_CLASS_IN; + ar[1].ttl = 3600; + ar[1].rdata.a.ip_addr = pj_str("2.2.2.2"); + + ar[2].name = pj_str("sip03.example.com"); + ar[2].type = PJ_DNS_TYPE_A; + ar[2].dnsclass = PJ_DNS_CLASS_IN; + ar[2].ttl = 3600; + ar[2].rdata.a.ip_addr = pj_str("3.3.3.3"); + + ar[3].name = pj_str("sip04.example.com"); + ar[3].type = PJ_DNS_TYPE_A; + ar[3].dnsclass = PJ_DNS_CLASS_IN; + ar[3].ttl = 3600; + ar[3].rdata.a.ip_addr = pj_str("4.4.4.4"); + + ar[4].name = pj_str("example.com"); + ar[4].type = PJ_DNS_TYPE_A; + ar[4].dnsclass = PJ_DNS_CLASS_IN; + ar[4].ttl = 3600; + ar[4].rdata.a.ip_addr = pj_str("5.5.5.5"); + + /* + * Create individual A records for all hosts in "example.com" domain. + */ + for (i=0; i<PJ_ARRAY_SIZE(ar); ++i) { + pj_bzero(&pkt, sizeof(pkt)); + pkt.hdr.anscount = 1; + pkt.hdr.flags = PJ_DNS_SET_QR(1); + pkt.ans = &ar[i]; + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + } + + /* + * Simulate DNS error response by creating these answers. + * Sample of invalid SRV records: _sip._udp.sip01.example.com. + */ + for (i=0; i<PJ_ARRAY_SIZE(ans); ++i) { + pj_dns_parsed_query q; + char buf[128]; + char *services[] = { "_sip._udp.", "_sip._tcp.", "_sips._tcp."}; + unsigned j; + + for (j=0; j<PJ_ARRAY_SIZE(services); ++j) { + q.dnsclass = PJ_DNS_CLASS_IN; + q.type = PJ_DNS_TYPE_SRV; + + q.name.ptr = buf; + pj_bzero(buf, sizeof(buf)); + pj_strcpy2(&q.name, services[j]); + pj_strcat(&q.name, &ans[i].rdata.srv.target); + + pj_bzero(&pkt, sizeof(pkt)); + pkt.hdr.qdcount = 1; + pkt.hdr.flags = PJ_DNS_SET_QR(1) | + PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN); + pkt.q = &q; + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + } + } + + + /* + * ANOTHER DOMAIN. + * + * This time we let SRV and A get answered in different DNS + * query. + */ + + /* The "domain.com" DNS records (note the different the port): + + _sip._tcp.domain.com 3600 IN SRV 1 0 50060 sip06.domain.com. + _sip._tcp.domain.com 3600 IN SRV 2 0 50060 sip07.domain.com. + + sip06.domain.com. 3600 IN A 6.6.6.6 + sip07.domain.com. 3600 IN A 7.7.7.7 + */ + + pj_bzero(&pkt, sizeof(pkt)); + pj_bzero(&ans, sizeof(ans)); + pkt.hdr.flags = PJ_DNS_SET_QR(1); + pkt.hdr.anscount = 2; + pkt.ans = ans; + + /* Add the SRV records, with reverse priority (to test that sorting + * works. + */ + ans[0].name = pj_str("_sip._tcp.domain.com"); + ans[0].type = PJ_DNS_TYPE_SRV; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.srv.prio = 2; + ans[0].rdata.srv.weight = 0; + ans[0].rdata.srv.port = 50060; + ans[0].rdata.srv.target = pj_str("SIP07.DOMAIN.COM"); + + ans[1].name = pj_str("_sip._tcp.domain.com"); + ans[1].type = PJ_DNS_TYPE_SRV; + ans[1].dnsclass = PJ_DNS_CLASS_IN; + ans[1].ttl = 3600; + ans[1].rdata.srv.prio = 1; + ans[1].rdata.srv.weight = 0; + ans[1].rdata.srv.port = 50060; + ans[1].rdata.srv.target = pj_str("SIP06.DOMAIN.COM"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + /* From herein there is only one answer */ + pkt.hdr.anscount = 1; + + /* Add a single SRV for UDP */ + ans[0].name = pj_str("_sip._udp.domain.com"); + ans[0].type = PJ_DNS_TYPE_SRV; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.srv.prio = 0; + ans[0].rdata.srv.weight = 0; + ans[0].rdata.srv.port = 50060; + ans[0].rdata.srv.target = pj_str("SIP06.DOMAIN.COM"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + + /* Add the A record for sip06.domain.com */ + ans[0].name = pj_str("sip06.domain.com"); + ans[0].type = PJ_DNS_TYPE_A; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.a.ip_addr = pj_str("6.6.6.6"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + /* Add the A record for sip07.domain.com */ + ans[0].name = pj_str("sip07.domain.com"); + ans[0].type = PJ_DNS_TYPE_A; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.a.ip_addr = pj_str("7.7.7.7"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); +} + + +/* + * Perform server resolution where the results are expected to + * come in strict order. + */ +static int test_resolve(const char *title, + pj_pool_t *pool, + pjsip_transport_type_e type, + char *name, + int port, + pjsip_server_addresses *ref) +{ + pjsip_host_info dest; + struct result result; + + PJ_LOG(3,(THIS_FILE, " test_resolve(): %s", title)); + + dest.type = type; + dest.flag = pjsip_transport_get_flag_from_type(type); + dest.addr.host = pj_str(name); + dest.addr.port = port; + + result.status = 0x12345678; + + pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb); + + while (result.status == 0x12345678) { + int i = 0; + pj_time_val timeout = { 1, 0 }; + pjsip_endpt_handle_events(endpt, &timeout); + if (i == 1) + pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE); + } + + if (result.status != PJ_SUCCESS) { + app_perror(" pjsip_endpt_resolve() error", result.status); + return result.status; + } + + if (ref) { + unsigned i; + + if (ref->count != result.servers.count) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 10: result count mismatch")); + return 10; + } + + for (i=0; i<ref->count; ++i) { + pj_sockaddr_in *ra = (pj_sockaddr_in *)&ref->entry[i].addr; + pj_sockaddr_in *rb = (pj_sockaddr_in *)&result.servers.entry[i].addr; + + if (ra->sin_addr.s_addr != rb->sin_addr.s_addr) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 20: IP address mismatch")); + return 20; + } + if (ra->sin_port != rb->sin_port) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 30: port mismatch")); + return 30; + } + if (ref->entry[i].addr_len != result.servers.entry[i].addr_len) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 40: addr_len mismatch")); + return 40; + } + if (ref->entry[i].type != result.servers.entry[i].type) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 50: transport type mismatch")); + return 50; + } + } + } + + return PJ_SUCCESS; +} + +/* + * Perform round-robin/load balance test. + */ +static int round_robin_test(pj_pool_t *pool) +{ + enum { COUNT = 400, PCT_ALLOWANCE = 5 }; + unsigned i; + struct server_hit + { + char *ip_addr; + unsigned percent; + unsigned hits; + } server_hit[] = + { + { "1.1.1.1", 3, 0 }, + { "2.2.2.2", 65, 0 }, + { "3.3.3.3", 32, 0 }, + { "4.4.4.4", 0, 0 } + }; + + PJ_LOG(3,(THIS_FILE, " Performing round-robin/load-balance test..")); + + /* Do multiple resolve request to "example.com". + * The resolver should select the server based on the weight proportion + * the the servers in the SRV entry. + */ + for (i=0; i<COUNT; ++i) { + pjsip_host_info dest; + struct result result; + unsigned j; + + dest.type = PJSIP_TRANSPORT_UDP; + dest.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP); + dest.addr.host = pj_str("example.com"); + dest.addr.port = 0; + + result.status = 0x12345678; + + pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb); + + while (result.status == 0x12345678) { + int i = 0; + pj_time_val timeout = { 1, 0 }; + pjsip_endpt_handle_events(endpt, &timeout); + if (i == 1) + pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE); + } + + /* Find which server was "hit" */ + for (j=0; j<PJ_ARRAY_SIZE(server_hit); ++j) { + pj_str_t tmp; + pj_in_addr a1; + pj_sockaddr_in *a2; + + tmp = pj_str(server_hit[j].ip_addr); + a1 = pj_inet_addr(&tmp); + a2 = (pj_sockaddr_in*) &result.servers.entry[0].addr; + + if (a1.s_addr == a2->sin_addr.s_addr) { + server_hit[j].hits++; + break; + } + } + + if (j == PJ_ARRAY_SIZE(server_hit)) { + PJ_LOG(1,(THIS_FILE, "..round_robin_test() error 10: returned address mismatch")); + return 10; + } + } + + /* Print the actual hit rate */ + for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) { + PJ_LOG(3,(THIS_FILE, " ..Server %s: weight=%d%%, hit %d%% times", + server_hit[i].ip_addr, server_hit[i].percent, + (server_hit[i].hits * 100) / COUNT)); + } + + /* Compare the actual hit with the weight proportion */ + for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) { + int actual_pct = (server_hit[i].hits * 100) / COUNT; + + if (actual_pct + PCT_ALLOWANCE < (int)server_hit[i].percent || + actual_pct - PCT_ALLOWANCE > (int)server_hit[i].percent) + { + PJ_LOG(1,(THIS_FILE, + "..round_robin_test() error 20: " + "hit rate difference for server %s (%d%%) is more than " + "tolerable allowance (%d%%)", + server_hit[i].ip_addr, + actual_pct - server_hit[i].percent, + PCT_ALLOWANCE)); + return 20; + } + } + + PJ_LOG(3,(THIS_FILE, + " Load balance test success, hit-rate is " + "within %d%% allowance", PCT_ALLOWANCE)); + return PJ_SUCCESS; +} + + +#define C(expr) status = expr; \ + if (status != PJ_SUCCESS) app_perror(THIS_FILE, "Error", status); + +static void add_ref(pjsip_server_addresses *r, + pjsip_transport_type_e type, + char *addr, + int port) +{ + pj_sockaddr_in *a; + pj_str_t tmp; + + r->entry[r->count].type = type; + r->entry[r->count].priority = 0; + r->entry[r->count].weight = 0; + r->entry[r->count].addr_len = sizeof(pj_sockaddr_in); + + a = (pj_sockaddr_in *)&r->entry[r->count].addr; + a->sin_family = PJ_AF_INET; + tmp = pj_str(addr); + a->sin_addr = pj_inet_addr(&tmp); + a->sin_port = pj_htons((pj_uint16_t)port); + + r->count++; +} + +static void create_ref(pjsip_server_addresses *r, + pjsip_transport_type_e type, + char *addr, + int port) +{ + r->count = 0; + add_ref(r, type, addr, port); +} + + +/* + * Main test entry. + */ +int resolve_test(void) +{ + pj_pool_t *pool; + pj_dns_resolver *resv; + pj_str_t nameserver; + pj_uint16_t port = 5353; + pj_status_t status; + + pool = pjsip_endpt_create_pool(endpt, NULL, 4000, 4000); + + status = pjsip_endpt_create_resolver(endpt, &resv); + + nameserver = pj_str("192.168.0.106"); + pj_dns_resolver_set_ns(resv, 1, &nameserver, &port); + pjsip_endpt_set_resolver(endpt, resv); + + add_dns_entries(resv); + + /* These all should be resolved as IP addresses (DNS A query) */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "1.1.1.1", 5060); + status = test_resolve("IP address without transport and port", pool, PJSIP_TRANSPORT_UNSPECIFIED, "1.1.1.1", 0, &ref); + if (status != PJ_SUCCESS) + return -100; + } + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "1.1.1.1", 5060); + status = test_resolve("IP address with explicit port", pool, PJSIP_TRANSPORT_UNSPECIFIED, "1.1.1.1", 5060, &ref); + if (status != PJ_SUCCESS) + return -110; + } + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TCP, "1.1.1.1", 5060); + status = test_resolve("IP address without port (TCP)", pool, PJSIP_TRANSPORT_TCP,"1.1.1.1", 0, &ref); + if (status != PJ_SUCCESS) + return -120; + } + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TLS, "1.1.1.1", 5061); + status = test_resolve("IP address without port (TLS)", pool, PJSIP_TRANSPORT_TLS, "1.1.1.1", 0, &ref); + if (status != PJ_SUCCESS) + return -130; + } + + /* This should be resolved as DNS A record (because port is present) */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "5.5.5.5", 5060); + status = test_resolve("domain name with port should resolve to A record", pool, PJSIP_TRANSPORT_UNSPECIFIED, "example.com", 5060, &ref); + if (status != PJ_SUCCESS) + return -140; + } + + /* This will fail to be resolved as SRV, resolver should fallback to + * resolving to A record. + */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "2.2.2.2", 5060); + status = test_resolve("failure with SRV fallback to A record", pool, PJSIP_TRANSPORT_UNSPECIFIED, "sip02.example.com", 0, &ref); + if (status != PJ_SUCCESS) + return -150; + } + + /* Same as above, but explicitly for TLS. */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TLS, "2.2.2.2", 5061); + status = test_resolve("failure with SRV fallback to A record (for TLS)", pool, PJSIP_TRANSPORT_TLS, "sip02.example.com", 0, &ref); + if (status != PJ_SUCCESS) + return -150; + } + + /* Standard DNS SRV followed by A recolution */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "6.6.6.6", 50060); + status = test_resolve("standard SRV resolution", pool, PJSIP_TRANSPORT_UNSPECIFIED, "domain.com", 0, &ref); + if (status != PJ_SUCCESS) + return -155; + } + + /* Standard DNS SRV followed by A recolution (explicit transport) */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TCP, "6.6.6.6", 50060); + add_ref(&ref, PJSIP_TRANSPORT_TCP, "7.7.7.7", 50060); + status = test_resolve("standard SRV resolution with explicit transport (TCP)", pool, PJSIP_TRANSPORT_TCP, "domain.com", 0, &ref); + if (status != PJ_SUCCESS) + return -160; + } + + + /* Round robin/load balance test */ + if (round_robin_test(pool) != 0) + return -170; + + /* Timeout test */ + { + status = test_resolve("timeout test", pool, PJSIP_TRANSPORT_UNSPECIFIED, "an.invalid.address", 0, NULL); + if (status == PJ_SUCCESS) + return -150; + } + + return 0; +} + diff --git a/pjsip/src/test-pjsip/test.c b/pjsip/src/test-pjsip/test.c index 502f049b..c7566bb2 100644 --- a/pjsip/src/test-pjsip/test.c +++ b/pjsip/src/test-pjsip/test.c @@ -20,6 +20,7 @@ #include "test.h" #include <pjlib.h> +#include <pjlib-util.h> #include <pjsip.h> #define THIS_FILE "test.c" @@ -240,6 +241,11 @@ int test_main(void) return rc; } + if ((rc=pjlib_util_init()) != PJ_SUCCESS) { + app_perror("pj_init", rc); + return rc; + } + status = init_report(); if (status != PJ_SUCCESS) return status; @@ -311,6 +317,10 @@ int test_main(void) DO_TEST(transport_tcp_test()); #endif +#if INCLUDE_RESOLVE_TEST + DO_TEST(resolve_test()); +#endif + #if INCLUDE_TSX_TEST status = pjsip_udp_transport_start(endpt, NULL, NULL, 1, &tp); diff --git a/pjsip/src/test-pjsip/test.h b/pjsip/src/test-pjsip/test.h index fc0901c1..bc8db5e4 100644 --- a/pjsip/src/test-pjsip/test.h +++ b/pjsip/src/test-pjsip/test.h @@ -38,7 +38,7 @@ extern pjsip_endpoint *endpt; #define INCLUDE_MESSAGING_GROUP 0 #define INCLUDE_TRANSPORT_GROUP 0 -#define INCLUDE_TSX_GROUP 1 +#define INCLUDE_TSX_GROUP 0 /* * Include tests that normally would fail under certain gcc @@ -56,6 +56,7 @@ extern pjsip_endpoint *endpt; #define INCLUDE_UDP_TEST INCLUDE_TRANSPORT_GROUP #define INCLUDE_LOOP_TEST INCLUDE_TRANSPORT_GROUP #define INCLUDE_TCP_TEST INCLUDE_TRANSPORT_GROUP +#define INCLUDE_RESOLVE_TEST 1 #define INCLUDE_TSX_TEST INCLUDE_TSX_GROUP @@ -69,6 +70,7 @@ int tsx_bench(void); int transport_udp_test(void); int transport_loop_test(void); int transport_tcp_test(void); +int resolve_test(void); struct tsx_test_param { |