summaryrefslogtreecommitdiff
path: root/pjsip
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-10-08 12:39:34 +0000
committerBenny Prijono <bennylp@teluu.com>2006-10-08 12:39:34 +0000
commit36413704c1ff24a8e38e0fcf47c9a9c87621e71a (patch)
tree4393a2f84da0f02f8b1374b0e3ff29d4dce50fb1 /pjsip
parentb1cdeda73284f461f15c68468369baf7556cb0e3 (diff)
Major addition to support DNS SRV resolution in PJSIP:
- added DNS asynchronous/caching resolver engine in PJLIB-UTIL (resolver.[hc]) - modified SIP resolver (sip_resolve.c) to properly perform DNS SRV/A resolution when DNS resolution is enabled. - added dns_test.c in PJSIP-TEST for testing the SIP resolver. - added nameserver configuration in PJSUA-LIB - added "--nameserver" option in PJSUA. - updated project/Makefiles and doxygen documentation. git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@753 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip')
-rw-r--r--pjsip/build/Makefile3
-rw-r--r--pjsip/build/test_pjsip.dsp4
-rw-r--r--pjsip/include/pjsip/sip_config.h36
-rw-r--r--pjsip/include/pjsip/sip_endpoint.h43
-rw-r--r--pjsip/include/pjsip/sip_resolve.h193
-rw-r--r--pjsip/include/pjsua-lib/pjsua.h14
-rw-r--r--pjsip/src/pjsip/sip_endpoint.c47
-rw-r--r--pjsip/src/pjsip/sip_resolve.c848
-rw-r--r--pjsip/src/pjsua-lib/pjsua_core.c39
-rw-r--r--pjsip/src/test-pjsip/dns_test.c596
-rw-r--r--pjsip/src/test-pjsip/test.c10
-rw-r--r--pjsip/src/test-pjsip/test.h4
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
{