From 36413704c1ff24a8e38e0fcf47c9a9c87621e71a Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Sun, 8 Oct 2006 12:39:34 +0000 Subject: 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 --- pjsip/src/test-pjsip/dns_test.c | 596 ++++++++++++++++++++++++++++++++++++++++ pjsip/src/test-pjsip/test.c | 10 + pjsip/src/test-pjsip/test.h | 4 +- 3 files changed, 609 insertions(+), 1 deletion(-) create mode 100644 pjsip/src/test-pjsip/dns_test.c (limited to 'pjsip/src/test-pjsip') 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 + * + * 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 +#include +#include + +/* 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; icount != result.servers.count) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 10: result count mismatch")); + return 10; + } + + for (i=0; icount; ++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; isin_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 (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 +#include #include #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 { -- cgit v1.2.3