diff options
author | Nanang Izzuddin <nanang@teluu.com> | 2016-06-20 10:10:42 +0000 |
---|---|---|
committer | Nanang Izzuddin <nanang@teluu.com> | 2016-06-20 10:10:42 +0000 |
commit | b80242b94843137edd58e9075a892c7971b7bf55 (patch) | |
tree | 7162251a3b656d1af18c48b1ce02332a54de351c /pjlib-util | |
parent | bddd40a4e4db55d1c6b046a4fe21b733e76f9fd5 (diff) |
Close #1927: IPv6 support in DNS SRV:
- support DNS A and AAAA resolution for each target in DNS SRV record
- support fallback to DNS A and DNS AAAA resolution when DNS SRV record is not available
- support IPv6 nameservers.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@5349 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjlib-util')
-rw-r--r-- | pjlib-util/include/pjlib-util/resolver.h | 56 | ||||
-rw-r--r-- | pjlib-util/include/pjlib-util/srv_resolver.h | 27 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util-test/resolver_test.c | 529 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util/resolver.c | 328 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util/srv_resolver.c | 257 |
5 files changed, 1062 insertions, 135 deletions
diff --git a/pjlib-util/include/pjlib-util/resolver.h b/pjlib-util/include/pjlib-util/resolver.h index 0611747b..fa22d046 100644 --- a/pjlib-util/include/pjlib-util/resolver.h +++ b/pjlib-util/include/pjlib-util/resolver.h @@ -226,6 +226,47 @@ typedef struct pj_dns_a_record /** + * This structure represents DNS address record, i.e: DNS A and DNS AAAA + * records, as the result of parsing DNS response packet using + * #pj_dns_parse_addr_response(). + */ +typedef struct pj_dns_addr_record +{ + /** The target name being queried. */ + pj_str_t name; + + /** If target name corresponds to a CNAME entry, the alias contains + * the value of the CNAME entry, otherwise it will be empty. + */ + pj_str_t alias; + + /** Number of IP addresses. */ + unsigned addr_count; + + /** IP addresses of the host found in the response */ + struct { + + /** IP address family */ + int af; + + /** IP address */ + union { + /** IPv4 address */ + pj_in_addr v4; + + /** IPv6 address */ + pj_in6_addr v6; + } ip; + + } addr[PJ_DNS_MAX_IP_IN_A_REC]; + + /** Internal buffer for hostname and alias. */ + char buf_[128]; + +} pj_dns_addr_record; + + +/** * Set default values to the DNS settings. * * @param s The DNS settings to be initialized. @@ -408,6 +449,21 @@ PJ_DECL(pj_status_t) pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt, /** + * A utility function to parse a DNS response containing AAAA records into + * DNS AAAA record. + * + * @param pkt The DNS response packet. + * @param rec The structure to be initialized with the parsed + * DNS AAAA record from the packet. + * + * @return PJ_SUCCESS if response can be parsed successfully. + */ +PJ_DECL(pj_status_t) pj_dns_parse_addr_response( + const pj_dns_parsed_packet *pkt, + pj_dns_addr_record *rec); + + +/** * Put the specified DNS packet into DNS cache. This function is mainly used * for testing the resolver, however it can also be used to inject entries * into the resolver. diff --git a/pjlib-util/include/pjlib-util/srv_resolver.h b/pjlib-util/include/pjlib-util/srv_resolver.h index 770a2a72..bcaf9843 100644 --- a/pjlib-util/include/pjlib-util/srv_resolver.h +++ b/pjlib-util/include/pjlib-util/srv_resolver.h @@ -84,27 +84,34 @@ typedef enum pj_dns_srv_option * Specify if the resolver should fallback with DNS A * resolution when the SRV resolution fails. This option may * be specified together with PJ_DNS_SRV_FALLBACK_AAAA to - * make the resolver fallback to AAAA if SRV resolution fails, - * and then to DNS A resolution if the AAAA resolution fails. + * make the resolver fallback to both DNS A and DNS AAAA + * resolutions if SRV resolution fails. */ PJ_DNS_SRV_FALLBACK_A = 1, /** * Specify if the resolver should fallback with DNS AAAA * resolution when the SRV resolution fails. This option may - * be specified together with PJ_DNS_SRV_FALLBACK_A to - * make the resolver fallback to AAAA if SRV resolution fails, - * and then to DNS A resolution if the AAAA resolution fails. + * be specified together with PJ_DNS_SRV_FALLBACK_AAAA to + * make the resolver fallback to both DNS A and DNS AAAA + * resolutions if SRV resolution fails. */ PJ_DNS_SRV_FALLBACK_AAAA = 2, /** * Specify if the resolver should try to resolve with DNS AAAA - * resolution first of each targets in the DNS SRV record. If - * this option is not specified, the SRV resolver will query - * the DNS A record for the target instead. + * resolution of each targets in the DNS SRV record. If this + * option is not specified, the SRV resolver will query the + * DNS A record for the target instead. + */ + PJ_DNS_SRV_RESOLVE_AAAA = 4, + + /** + * Specify if the resolver should try to resolve with DNS AAAA + * resolution only (i.e: without DNS A resolution) for each targets + * in the DNS SRV record. */ - PJ_DNS_SRV_RESOLVE_AAAA = 4 + PJ_DNS_SRV_RESOLVE_AAAA_ONLY = 8 } pj_dns_srv_option; @@ -131,7 +138,7 @@ typedef struct pj_dns_srv_record pj_uint16_t port; /** The host address. */ - pj_dns_a_record server; + pj_dns_addr_record server; } entry[PJ_DNS_SRV_MAX_ADDR]; diff --git a/pjlib-util/src/pjlib-util-test/resolver_test.c b/pjlib-util/src/pjlib-util-test/resolver_test.c index e68efe4f..9feae8d0 100644 --- a/pjlib-util/src/pjlib-util-test/resolver_test.c +++ b/pjlib-util/src/pjlib-util-test/resolver_test.c @@ -191,6 +191,20 @@ static int print_rr(pj_uint8_t *pkt, int size, pj_uint8_t *pos, p += 6; size -= 6; + } else if (rr->type == PJ_DNS_TYPE_AAAA) { + + if (size < 18) + return -1; + + /* RDLEN is 16 */ + write16(p, 16); + + /* Address */ + pj_memcpy(p+2, &rr->rdata.aaaa.ip_addr, 16); + + p += 18; + size -= 18; + } else if (rr->type == PJ_DNS_TYPE_CNAME || rr->type == PJ_DNS_TYPE_NS || rr->type == PJ_DNS_TYPE_PTR) { @@ -330,7 +344,7 @@ static int server_thread(void *p) while (!thread_quit) { pj_fd_set_t rset; pj_time_val timeout = {0, 500}; - pj_sockaddr_in src_addr; + pj_sockaddr src_addr; pj_dns_parsed_packet *req; char pkt[1024]; pj_ssize_t pkt_len; @@ -405,16 +419,21 @@ static int poll_worker_thread(void *p) static void destroy(void); -static int init(void) +static int init(pj_bool_t use_ipv6) { pj_status_t status; pj_str_t nameservers[2]; pj_uint16_t ports[2]; int i; - nameservers[0] = pj_str("127.0.0.1"); + if (use_ipv6) { + nameservers[0] = pj_str("::1"); + nameservers[1] = pj_str("::1"); + } else { + nameservers[0] = pj_str("127.0.0.1"); + nameservers[1] = pj_str("127.0.0.1"); + } ports[0] = 5553; - nameservers[1] = pj_str("127.0.0.1"); ports[1] = 5554; g_server[0].port = ports[0]; @@ -425,14 +444,18 @@ static int init(void) status = pj_sem_create(pool, NULL, 0, 2, &sem); pj_assert(status == PJ_SUCCESS); + thread_quit = PJ_FALSE; + for (i=0; i<2; ++i) { - pj_sockaddr_in addr; + pj_sockaddr addr; - status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &g_server[i].sock); + status = pj_sock_socket((use_ipv6? pj_AF_INET6() : pj_AF_INET()), + pj_SOCK_DGRAM(), 0, &g_server[i].sock); if (status != PJ_SUCCESS) return -10; - pj_sockaddr_in_init(&addr, NULL, (pj_uint16_t)g_server[i].port); + pj_sockaddr_init((use_ipv6? pj_AF_INET6() : pj_AF_INET()), + &addr, NULL, (pj_uint16_t)g_server[i].port); status = pj_sock_bind(g_server[i].sock, &addr, sizeof(addr)); if (status != PJ_SUCCESS) @@ -693,6 +716,215 @@ static int a_parser_test(void) //////////////////////////////////////////////////////////////////////////// +/* DNS A/AAAA parser tests */ +static int addr_parser_test(void) +{ + pj_dns_parsed_packet pkt; + pj_dns_addr_record rec; + pj_status_t rc; + + PJ_LOG(3,(THIS_FILE, " DNS A/AAAA record parser tests")); + + pkt.q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query); + pkt.ans = (pj_dns_parsed_rr*) + pj_pool_calloc(pool, 32, sizeof(pj_dns_parsed_rr)); + + /* Simple answer with direct A record, but with addition of + * a CNAME and another A to confuse the parser. + */ + PJ_LOG(3,(THIS_FILE, " A RR with duplicate CNAME/A")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 4; + + /* This is the RR corresponding to the query */ + pkt.ans[0].name = pj_str("ahost"); + pkt.ans[0].type = PJ_DNS_TYPE_A; + pkt.ans[0].dnsclass = 1; + pkt.ans[0].ttl = 1; + pkt.ans[0].rdata.a.ip_addr.s_addr = 0x01020304; + + /* CNAME to confuse the parser */ + pkt.ans[1].name = pj_str("ahost"); + pkt.ans[1].type = PJ_DNS_TYPE_CNAME; + pkt.ans[1].dnsclass = 1; + pkt.ans[1].ttl = 1; + pkt.ans[1].rdata.cname.name = pj_str("bhost"); + + /* DNS A RR to confuse the parser */ + pkt.ans[2].name = pj_str("bhost"); + pkt.ans[2].type = PJ_DNS_TYPE_A; + pkt.ans[2].dnsclass = 1; + pkt.ans[2].ttl = 1; + pkt.ans[2].rdata.a.ip_addr.s_addr = 0x0203; + + /* Additional RR corresponding to the query, DNS AAAA RR */ + pkt.ans[3].name = pj_str("ahost"); + pkt.ans[3].type = PJ_DNS_TYPE_AAAA; + pkt.ans[3].dnsclass = 1; + pkt.ans[3].ttl = 1; + pkt.ans[3].rdata.aaaa.ip_addr.u6_addr32[0] = 0x01020304; + + + rc = pj_dns_parse_addr_response(&pkt, &rec); + pj_assert(rc == PJ_SUCCESS); + pj_assert(pj_strcmp2(&rec.name, "ahost")==0); + pj_assert(rec.alias.slen == 0); + pj_assert(rec.addr_count == 2); + pj_assert(rec.addr[0].af==pj_AF_INET() && rec.addr[0].ip.v4.s_addr == 0x01020304); + pj_assert(rec.addr[1].af==pj_AF_INET6() && rec.addr[1].ip.v6.u6_addr32[0] == 0x01020304); + + /* Answer with the target corresponds to a CNAME entry, but not + * as the first record, and with additions of some CNAME and A + * entries to confuse the parser. + */ + PJ_LOG(3,(THIS_FILE, " CNAME RR with duplicate CNAME/A")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 4; + + /* This is the DNS A record for the alias */ + pkt.ans[0].name = pj_str("ahostalias"); + pkt.ans[0].type = PJ_DNS_TYPE_A; + pkt.ans[0].dnsclass = 1; + pkt.ans[0].ttl = 1; + pkt.ans[0].rdata.a.ip_addr.s_addr = 0x02020202; + + /* CNAME entry corresponding to the query */ + pkt.ans[1].name = pj_str("ahost"); + pkt.ans[1].type = PJ_DNS_TYPE_CNAME; + pkt.ans[1].dnsclass = 1; + pkt.ans[1].ttl = 1; + pkt.ans[1].rdata.cname.name = pj_str("ahostalias"); + + /* Another CNAME to confuse the parser */ + pkt.ans[2].name = pj_str("ahost"); + pkt.ans[2].type = PJ_DNS_TYPE_CNAME; + pkt.ans[2].dnsclass = 1; + pkt.ans[2].ttl = 1; + pkt.ans[2].rdata.cname.name = pj_str("ahostalias2"); + + /* Another DNS A to confuse the parser */ + pkt.ans[3].name = pj_str("ahostalias2"); + pkt.ans[3].type = PJ_DNS_TYPE_A; + pkt.ans[3].dnsclass = 1; + pkt.ans[3].ttl = 1; + pkt.ans[3].rdata.a.ip_addr.s_addr = 0x03030303; + + rc = pj_dns_parse_addr_response(&pkt, &rec); + pj_assert(rc == PJ_SUCCESS); + pj_assert(pj_strcmp2(&rec.name, "ahost")==0); + pj_assert(pj_strcmp2(&rec.alias, "ahostalias")==0); + pj_assert(rec.addr_count == 1); + pj_assert(rec.addr[0].ip.v4.s_addr == 0x02020202); + + /* + * No query section. + */ + PJ_LOG(3,(THIS_FILE, " No query section")); + pkt.hdr.qdcount = 0; + pkt.hdr.anscount = 0; + + rc = pj_dns_parse_addr_response(&pkt, &rec); + pj_assert(rc == PJLIB_UTIL_EDNSINANSWER); + + /* + * No answer section. + */ + PJ_LOG(3,(THIS_FILE, " No answer section")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 0; + + rc = pj_dns_parse_addr_response(&pkt, &rec); + pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC); + + /* + * Answer doesn't match query. + */ + PJ_LOG(3,(THIS_FILE, " Answer doesn't match query")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 1; + + /* An answer that doesn't match the query */ + pkt.ans[0].name = pj_str("ahostalias"); + pkt.ans[0].type = PJ_DNS_TYPE_A; + pkt.ans[0].dnsclass = 1; + pkt.ans[0].ttl = 1; + pkt.ans[0].rdata.a.ip_addr.s_addr = 0x02020202; + + rc = pj_dns_parse_addr_response(&pkt, &rec); + pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC); + + + /* + * DNS CNAME that doesn't have corresponding DNS A. + */ + PJ_LOG(3,(THIS_FILE, " CNAME with no matching DNS A RR (1)")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 1; + + /* The CNAME */ + pkt.ans[0].name = pj_str("ahost"); + pkt.ans[0].type = PJ_DNS_TYPE_CNAME; + pkt.ans[0].dnsclass = 1; + pkt.ans[0].ttl = 1; + pkt.ans[0].rdata.cname.name = pj_str("ahostalias"); + + rc = pj_dns_parse_addr_response(&pkt, &rec); + pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC); + + + /* + * DNS CNAME that doesn't have corresponding DNS A. + */ + PJ_LOG(3,(THIS_FILE, " CNAME with no matching DNS A RR (2)")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 2; + + /* The CNAME */ + pkt.ans[0].name = pj_str("ahost"); + pkt.ans[0].type = PJ_DNS_TYPE_CNAME; + pkt.ans[0].dnsclass = 1; + pkt.ans[0].ttl = 1; + pkt.ans[0].rdata.cname.name = pj_str("ahostalias"); + + /* DNS A record, but the name doesn't match */ + pkt.ans[1].name = pj_str("ahost"); + pkt.ans[1].type = PJ_DNS_TYPE_A; + pkt.ans[1].dnsclass = 1; + pkt.ans[1].ttl = 1; + pkt.ans[1].rdata.a.ip_addr.s_addr = 0x01020304; + + rc = pj_dns_parse_addr_response(&pkt, &rec); + pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC); + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////// /* Simple DNS test */ #define IP_ADDR0 0x00010203 @@ -1005,6 +1237,27 @@ static void action1_1(const pj_dns_parsed_packet *pkt, res->ans[1].ttl = 1; res->ans[1].name = pj_str(alias); res->ans[1].rdata.a.ip_addr.s_addr = IP_ADDR1; + + } else if (pkt->q[0].type == PJ_DNS_TYPE_AAAA) { + char *alias = "sipalias.somedomain.com"; + + pj_assert(pj_strcmp2(&res->q[0].name, target)==0); + + res->hdr.anscount = 2; + res->ans[0].type = PJ_DNS_TYPE_CNAME; + res->ans[0].dnsclass = 1; + res->ans[0].ttl = 1000; /* resolver should select minimum TTL */ + res->ans[0].name = res->q[0].name; + res->ans[0].rdata.cname.name = pj_str(alias); + + res->ans[1].type = PJ_DNS_TYPE_AAAA; + res->ans[1].dnsclass = 1; + res->ans[1].ttl = 1; + res->ans[1].name = pj_str(alias); + res->ans[1].rdata.aaaa.ip_addr.u6_addr32[0] = IP_ADDR1; + res->ans[1].rdata.aaaa.ip_addr.u6_addr32[1] = IP_ADDR1; + res->ans[1].rdata.aaaa.ip_addr.u6_addr32[2] = IP_ADDR1; + res->ans[1].rdata.aaaa.ip_addr.u6_addr32[3] = IP_ADDR1; } *p_res = res; @@ -1026,12 +1279,16 @@ static void srv_cb_1(void *user_data, return); PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias.somedomain.com")==0, return); - PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].s_addr == IP_ADDR1, return); + + /* IPv4 only */ + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr_count == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].ip.v4.s_addr == IP_ADDR1, return); PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT1, return); } + static void srv_cb_1b(void *user_data, pj_status_t status, const pj_dns_srv_record *rec) @@ -1045,6 +1302,61 @@ static void srv_cb_1b(void *user_data, PJ_ASSERT_ON_FAIL(rec->count == 0, return); } + +static void srv_cb_1c(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + PJ_UNUSED_ARG(user_data); + + pj_sem_post(sem); + + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return); + PJ_ASSERT_ON_FAIL(rec->count == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].priority == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].weight == 2, return); + + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.name, "sip.somedomain.com")==0, + return); + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias.somedomain.com")==0, + return); + PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT1, return); + + /* IPv4 and IPv6 */ + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr_count == 2, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].af == pj_AF_INET() && + rec->entry[0].server.addr[0].ip.v4.s_addr == IP_ADDR1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[1].af == pj_AF_INET6() && + rec->entry[0].server.addr[1].ip.v6.u6_addr32[0] == IP_ADDR1, return); +} + + +static void srv_cb_1d(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + PJ_UNUSED_ARG(user_data); + + pj_sem_post(sem); + + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return); + PJ_ASSERT_ON_FAIL(rec->count == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].priority == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].weight == 2, return); + + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.name, "sip.somedomain.com")==0, + return); + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias.somedomain.com")==0, + return); + PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT1, return); + + /* IPv6 only */ + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr_count == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].af == pj_AF_INET6() && + rec->entry[0].server.addr[0].ip.v6.u6_addr32[0] == IP_ADDR1, return); +} + + static int srv_resolver_test(void) { pj_status_t status; @@ -1078,6 +1390,48 @@ static int srv_resolver_test(void) pj_thread_sleep(1000 + ((set.qretr_count + 2) * set.qretr_delay)); + + /* DNS SRV option PJ_DNS_SRV_RESOLVE_AAAA */ + PJ_LOG(3,(THIS_FILE, " srv_resolve(): option PJ_DNS_SRV_RESOLVE_AAAA")); + + g_server[0].action = ACTION_CB; + g_server[0].action_cb = &action1_1; + g_server[1].action = ACTION_CB; + g_server[1].action_cb = &action1_1; + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver, + PJ_DNS_SRV_RESOLVE_AAAA, + NULL, &srv_cb_1c, NULL); + pj_assert(status == PJ_SUCCESS); + + pj_sem_wait(sem); + + pj_thread_sleep(1000); + + /* DNS SRV option PJ_DNS_SRV_RESOLVE_AAAA_ONLY */ + PJ_LOG(3,(THIS_FILE, " srv_resolve(): option PJ_DNS_SRV_RESOLVE_AAAA_ONLY")); + + g_server[0].action = ACTION_CB; + g_server[0].action_cb = &action1_1; + g_server[1].action = ACTION_CB; + g_server[1].action_cb = &action1_1; + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver, + PJ_DNS_SRV_RESOLVE_AAAA_ONLY, + NULL, &srv_cb_1d, NULL); + pj_assert(status == PJ_SUCCESS); + + pj_sem_wait(sem); + + pj_thread_sleep(1000); + + /* Successful scenario */ PJ_LOG(3,(THIS_FILE, " srv_resolve(): parallel queries")); g_server[0].pkt_count = 0; @@ -1102,7 +1456,6 @@ static int srv_resolver_test(void) /* Since TTL is one, subsequent queries should fail */ PJ_LOG(3,(THIS_FILE, " srv_resolve(): cache expires scenario")); - pj_thread_sleep(1000); g_server[0].action = PJ_DNS_RCODE_NXDOMAIN; @@ -1114,6 +1467,7 @@ static int srv_resolver_test(void) pj_sem_wait(sem); + return 0; } @@ -1171,6 +1525,27 @@ static void action2_1(const pj_dns_parsed_packet *pkt, res->ans[1].name = pj_str(alias); res->ans[1].ttl = 1; res->ans[1].rdata.a.ip_addr.s_addr = IP_ADDR2; + + } else if (pkt->q[0].type == PJ_DNS_TYPE_AAAA) { + char *alias = "sipalias01." TARGET; + + pj_assert(pj_strcmp2(&res->q[0].name, TARGET)==0); + + res->hdr.anscount = 2; + res->ans[0].type = PJ_DNS_TYPE_CNAME; + res->ans[0].dnsclass = 1; + res->ans[0].name = res->q[0].name; + res->ans[0].ttl = 1; + res->ans[0].rdata.cname.name = pj_str(alias); + + res->ans[1].type = PJ_DNS_TYPE_AAAA; + res->ans[1].dnsclass = 1; + res->ans[1].ttl = 1; + res->ans[1].name = pj_str(alias); + res->ans[1].rdata.aaaa.ip_addr.u6_addr32[0] = IP_ADDR2; + res->ans[1].rdata.aaaa.ip_addr.u6_addr32[1] = IP_ADDR2; + res->ans[1].rdata.aaaa.ip_addr.u6_addr32[2] = IP_ADDR2; + res->ans[1].rdata.aaaa.ip_addr.u6_addr32[3] = IP_ADDR2; } *p_res = res; @@ -1192,8 +1567,62 @@ static void srv_cb_2(void *user_data, return); PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias01." TARGET)==0, return); - PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].s_addr == IP_ADDR2, return); PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT2, return); + + /* IPv4 only */ + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr_count == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].af == pj_AF_INET() && + rec->entry[0].server.addr[0].ip.v4.s_addr == IP_ADDR2, return); +} + +static void srv_cb_2a(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + PJ_UNUSED_ARG(user_data); + + pj_sem_post(sem); + + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return); + PJ_ASSERT_ON_FAIL(rec->count == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].priority == 0, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].weight == 0, return); + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.name, TARGET)==0, + return); + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias01." TARGET)==0, + return); + PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT2, return); + + /* IPv4 and IPv6 */ + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr_count == 2, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].af == pj_AF_INET() && + rec->entry[0].server.addr[0].ip.v4.s_addr == IP_ADDR2, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[1].af == pj_AF_INET6() && + rec->entry[0].server.addr[1].ip.v6.u6_addr32[0] == IP_ADDR2, return); +} + +static void srv_cb_2b(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + PJ_UNUSED_ARG(user_data); + + pj_sem_post(sem); + + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return); + PJ_ASSERT_ON_FAIL(rec->count == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].priority == 0, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].weight == 0, return); + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.name, TARGET)==0, + return); + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias01." TARGET)==0, + return); + PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT2, return); + + /* IPv6 only */ + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr_count == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].af == pj_AF_INET6() && + rec->entry[0].server.addr[0].ip.v6.u6_addr32[0] == IP_ADDR2, return); } static int srv_resolver_fallback_test(void) @@ -1202,6 +1631,7 @@ static int srv_resolver_fallback_test(void) pj_str_t domain = pj_str(TARGET); pj_str_t res_name = pj_str("_sip._udp."); + /* Fallback test */ PJ_LOG(3,(THIS_FILE, " srv_resolve(): fallback test")); g_server[0].action = ACTION_CB; @@ -1235,6 +1665,51 @@ static int srv_resolver_fallback_test(void) pj_assert(g_server[0].pkt_count == 0); pj_assert(g_server[1].pkt_count == 0); + /* Clear cache */ + pj_thread_sleep(1000); + + /* Fallback with PJ_DNS_SRV_FALLBACK_A and PJ_DNS_SRV_FALLBACK_AAAA */ + PJ_LOG(3,(THIS_FILE, " srv_resolve(): fallback to DNS A and AAAA")); + + g_server[0].action = ACTION_CB; + g_server[0].action_cb = &action2_1; + g_server[1].action = ACTION_CB; + g_server[1].action_cb = &action2_1; + + status = pj_dns_srv_resolve(&domain, &res_name, PORT2, pool, resolver, + PJ_DNS_SRV_FALLBACK_A | PJ_DNS_SRV_FALLBACK_AAAA, + NULL, &srv_cb_2a, NULL); + if (status != PJ_SUCCESS) { + app_perror(" srv_resolve error", status); + pj_assert(status == PJ_SUCCESS); + } + + pj_sem_wait(sem); + + /* Clear cache */ + pj_thread_sleep(1000); + + /* Fallback with PJ_DNS_SRV_FALLBACK_AAAA only */ + PJ_LOG(3,(THIS_FILE, " srv_resolve(): fallback to DNS AAAA only")); + + g_server[0].action = ACTION_CB; + g_server[0].action_cb = &action2_1; + g_server[1].action = ACTION_CB; + g_server[1].action_cb = &action2_1; + + status = pj_dns_srv_resolve(&domain, &res_name, PORT2, pool, resolver, + PJ_DNS_SRV_FALLBACK_AAAA, + NULL, &srv_cb_2b, NULL); + if (status != PJ_SUCCESS) { + app_perror(" srv_resolve error", status); + pj_assert(status == PJ_SUCCESS); + } + + pj_sem_wait(sem); + + /* Clear cache */ + pj_thread_sleep(1000); + return 0; } @@ -1333,7 +1808,7 @@ static void srv_cb_3(void *user_data, pj_assert(rec->entry[i].server.addr_count == PJ_DNS_MAX_IP_IN_A_REC); for (j=0; j<PJ_DNS_MAX_IP_IN_A_REC; ++j) { - pj_assert(rec->entry[i].server.addr[j].s_addr == IP_ADDR3+j); + pj_assert(rec->entry[i].server.addr[j].ip.v4.s_addr == IP_ADDR3+j); } } @@ -1374,7 +1849,7 @@ int resolver_test(void) { int rc; - rc = init(); + rc = init(PJ_FALSE); if (rc != 0) goto on_error; @@ -1382,6 +1857,10 @@ int resolver_test(void) if (rc != 0) goto on_error; + rc = addr_parser_test(); + if (rc != 0) + goto on_error; + rc = simple_test(); if (rc != 0) goto on_error; @@ -1395,6 +1874,31 @@ int resolver_test(void) srv_resolver_many_test(); destroy(); + + +#if PJ_HAS_IPV6 + /* Similar tests using IPv6 socket and without parser tests */ + PJ_LOG(3,(THIS_FILE, " Re-run DNS resolution tests using IPv6 socket")); + + rc = init(PJ_TRUE); + if (rc != 0) + goto on_error; + + rc = simple_test(); + if (rc != 0) + goto on_error; + + rc = dns_test(); + if (rc != 0) + goto on_error; + + srv_resolver_test(); + srv_resolver_fallback_test(); + srv_resolver_many_test(); + + destroy(); +#endif + return 0; on_error: @@ -1402,4 +1906,3 @@ on_error: return rc; } - diff --git a/pjlib-util/src/pjlib-util/resolver.c b/pjlib-util/src/pjlib-util/resolver.c index 6d7a5903..202ad824 100644 --- a/pjlib-util/src/pjlib-util/resolver.c +++ b/pjlib-util/src/pjlib-util/resolver.c @@ -79,7 +79,7 @@ static const char *state_names[3] = */ struct nameserver { - pj_sockaddr_in addr; /**< Server address. */ + pj_sockaddr addr; /**< Server address. */ enum ns_state state; /**< Nameserver state. */ pj_time_val state_expiry; /**< Time set next state. */ @@ -179,13 +179,24 @@ struct pj_dns_resolver pj_sock_t udp_sock; /**< UDP socket. */ pj_ioqueue_key_t *udp_key; /**< UDP socket ioqueue key. */ unsigned char udp_rx_pkt[UDPSZ];/**< UDP receive buffer. */ - unsigned char udp_tx_pkt[UDPSZ];/**< UDP receive buffer. */ - pj_ssize_t udp_len; /**< Length of received packet. */ + unsigned char udp_tx_pkt[UDPSZ];/**< UDP transmit buffer. */ pj_ioqueue_op_key_t udp_op_rx_key; /**< UDP read operation key. */ pj_ioqueue_op_key_t udp_op_tx_key; /**< UDP write operation key. */ - pj_sockaddr_in udp_src_addr; /**< Source address of packet */ + pj_sockaddr udp_src_addr; /**< Source address of packet */ int udp_addr_len; /**< Source address length. */ +#if PJ_HAS_IPV6 + /* IPv6 socket */ + pj_sock_t udp6_sock; /**< UDP socket. */ + pj_ioqueue_key_t *udp6_key; /**< UDP socket ioqueue key. */ + unsigned char udp6_rx_pkt[UDPSZ];/**< UDP receive buffer. */ + //unsigned char udp6_tx_pkt[UDPSZ];/**< UDP transmit buffer. */ + pj_ioqueue_op_key_t udp6_op_rx_key;/**< UDP read operation key. */ + pj_ioqueue_op_key_t udp6_op_tx_key;/**< UDP write operation key. */ + pj_sockaddr udp6_src_addr; /**< Source address of packet */ + int udp6_addr_len; /**< Source address length. */ +#endif + /* Settings */ pj_dns_settings settings; /**< Resolver settings. */ @@ -237,6 +248,17 @@ static void close_sock(pj_dns_resolver *resv) pj_sock_close(resv->udp_sock); resv->udp_sock = PJ_INVALID_SOCKET; } + +#if PJ_HAS_IPV6 + if (resv->udp6_key != NULL) { + pj_ioqueue_unregister(resv->udp6_key); + resv->udp6_key = NULL; + resv->udp6_sock = PJ_INVALID_SOCKET; + } else if (resv->udp6_sock != PJ_INVALID_SOCKET) { + pj_sock_close(resv->udp6_sock); + resv->udp6_sock = PJ_INVALID_SOCKET; + } +#endif } @@ -244,6 +266,8 @@ static void close_sock(pj_dns_resolver *resv) static pj_status_t init_sock(pj_dns_resolver *resv) { pj_ioqueue_callback socket_cb; + pj_sockaddr bound_addr; + pj_ssize_t rx_pkt_size; pj_status_t status; /* Create the UDP socket */ @@ -269,15 +293,59 @@ static pj_status_t init_sock(pj_dns_resolver *resv) pj_ioqueue_op_key_init(&resv->udp_op_tx_key, sizeof(resv->udp_op_tx_key)); /* Start asynchronous read to the UDP socket */ - resv->udp_len = sizeof(resv->udp_rx_pkt); + rx_pkt_size = sizeof(resv->udp_rx_pkt); resv->udp_addr_len = sizeof(resv->udp_src_addr); status = pj_ioqueue_recvfrom(resv->udp_key, &resv->udp_op_rx_key, - resv->udp_rx_pkt, &resv->udp_len, + resv->udp_rx_pkt, &rx_pkt_size, PJ_IOQUEUE_ALWAYS_ASYNC, &resv->udp_src_addr, &resv->udp_addr_len); if (status != PJ_EPENDING) return status; + +#if PJ_HAS_IPV6 + /* Also setup IPv6 socket */ + + /* Create the UDP socket */ + status = pj_sock_socket(pj_AF_INET6(), pj_SOCK_DGRAM(), 0, + &resv->udp6_sock); + if (status != PJ_SUCCESS) + return status; + + /* Bind to any address/port */ + pj_sockaddr_init(pj_AF_INET6(), &bound_addr, NULL, 0); + status = pj_sock_bind(resv->udp6_sock, &bound_addr, + pj_sockaddr_get_len(&bound_addr)); + if (status != PJ_SUCCESS) + return status; + + /* Register to ioqueue */ + pj_bzero(&socket_cb, sizeof(socket_cb)); + socket_cb.on_read_complete = &on_read_complete; + status = pj_ioqueue_register_sock(resv->pool, resv->ioqueue, + resv->udp6_sock, resv, &socket_cb, + &resv->udp6_key); + if (status != PJ_SUCCESS) + return status; + + pj_ioqueue_op_key_init(&resv->udp6_op_rx_key, + sizeof(resv->udp6_op_rx_key)); + pj_ioqueue_op_key_init(&resv->udp6_op_tx_key, + sizeof(resv->udp6_op_tx_key)); + + /* Start asynchronous read to the UDP socket */ + rx_pkt_size = sizeof(resv->udp6_rx_pkt); + resv->udp6_addr_len = sizeof(resv->udp6_src_addr); + status = pj_ioqueue_recvfrom(resv->udp6_key, &resv->udp6_op_rx_key, + resv->udp6_rx_pkt, &rx_pkt_size, + PJ_IOQUEUE_ALWAYS_ASYNC, + &resv->udp6_src_addr, &resv->udp6_addr_len); + if (status != PJ_EPENDING) + return status; +#else + PJ_UNUSED_ARG(bound_addr); +#endif + return PJ_SUCCESS; } @@ -474,8 +542,11 @@ PJ_DEF(pj_status_t) pj_dns_resolver_set_ns( pj_dns_resolver *resolver, for (i=0; i<count; ++i) { struct nameserver *ns = &resolver->ns[i]; - status = pj_sockaddr_in_init(&ns->addr, &servers[i], - (pj_uint16_t)(ports ? ports[i] : PORT)); + status = pj_sockaddr_init(pj_AF_INET(), &ns->addr, &servers[i], + (pj_uint16_t)(ports ? ports[i] : PORT)); + if (status != PJ_SUCCESS) + status = pj_sockaddr_init(pj_AF_INET6(), &ns->addr, &servers[i], + (pj_uint16_t)(ports ? ports[i] : PORT)); if (status != PJ_SUCCESS) { pj_mutex_unlock(resolver->mutex); return PJLIB_UTIL_EDNSINNSADDR; @@ -612,7 +683,13 @@ static pj_status_t transmit_query(pj_dns_resolver *resolver, } /* Check if the socket is available for sending */ - if (pj_ioqueue_is_pending(resolver->udp_key, &resolver->udp_op_tx_key)) { + if (pj_ioqueue_is_pending(resolver->udp_key, &resolver->udp_op_tx_key) +#if PJ_HAS_IPV6 + || pj_ioqueue_is_pending(resolver->udp6_key, + &resolver->udp6_op_tx_key) +#endif + ) + { ++q->transmit_cnt; PJ_LOG(4,(resolver->name.ptr, "Socket busy in transmitting DNS %s query for %s%s", @@ -642,19 +719,29 @@ static pj_status_t transmit_query(pj_dns_resolver *resolver, pj_ssize_t sent = (pj_ssize_t) pkt_size; struct nameserver *ns = &resolver->ns[servers[i]]; - status = pj_ioqueue_sendto(resolver->udp_key, - &resolver->udp_op_tx_key, - resolver->udp_tx_pkt, &sent, 0, - &resolver->ns[servers[i]].addr, - sizeof(pj_sockaddr_in)); + if (ns->addr.addr.sa_family == pj_AF_INET()) { + status = pj_ioqueue_sendto(resolver->udp_key, + &resolver->udp_op_tx_key, + resolver->udp_tx_pkt, &sent, 0, + &ns->addr, + pj_sockaddr_get_len(&ns->addr)); + } +#if PJ_HAS_IPV6 + else { + status = pj_ioqueue_sendto(resolver->udp6_key, + &resolver->udp6_op_tx_key, + resolver->udp_tx_pkt, &sent, 0, + &ns->addr, + pj_sockaddr_get_len(&ns->addr)); + } +#endif PJ_PERROR(4,(resolver->name.ptr, status, "%s %d bytes to NS %d (%s:%d): DNS %s query for %s", (q->transmit_cnt==0? "Transmitting":"Re-transmitting"), (int)pkt_size, servers[i], - pj_inet_ntop2(pj_AF_INET(), &ns->addr.sin_addr, addr, - sizeof(addr)), - (int)pj_ntohs(ns->addr.sin_port), + pj_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), + pj_sockaddr_get_port(&ns->addr), pj_dns_get_type_name(q->key.qtype), q->key.name)); @@ -1028,6 +1115,132 @@ PJ_DEF(pj_status_t) pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt, } +/* + * DNS response containing A and/or AAAA packet. + */ +PJ_DEF(pj_status_t) pj_dns_parse_addr_response( + const pj_dns_parsed_packet *pkt, + pj_dns_addr_record *rec) +{ + enum { MAX_SEARCH = 20 }; + pj_str_t hostname, alias = {NULL, 0}, *resname; + pj_size_t bufstart = 0; + pj_size_t bufleft; + unsigned i, ansidx, cnt=0; + + PJ_ASSERT_RETURN(pkt && rec, PJ_EINVAL); + + /* Init the record */ + pj_bzero(rec, sizeof(*rec)); + + bufleft = sizeof(rec->buf_); + + /* Return error if there's error in the packet. */ + if (PJ_DNS_GET_RCODE(pkt->hdr.flags)) + return PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(pkt->hdr.flags)); + + /* Return error if there's no query section */ + if (pkt->hdr.qdcount == 0) + return PJLIB_UTIL_EDNSINANSWER; + + /* Return error if there's no answer */ + if (pkt->hdr.anscount == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + /* Get the hostname from the query. */ + hostname = pkt->q[0].name; + + /* Copy hostname to the record */ + if (hostname.slen > (int)bufleft) { + return PJ_ENAMETOOLONG; + } + + pj_memcpy(&rec->buf_[bufstart], hostname.ptr, hostname.slen); + rec->name.ptr = &rec->buf_[bufstart]; + rec->name.slen = hostname.slen; + + bufstart += hostname.slen; + bufleft -= hostname.slen; + + /* Find the first RR which name matches the hostname. */ + for (ansidx=0; ansidx < pkt->hdr.anscount; ++ansidx) { + if (pj_stricmp(&pkt->ans[ansidx].name, &hostname)==0) + break; + } + + if (ansidx == pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + resname = &hostname; + + /* Keep following CNAME records. */ + while (pkt->ans[ansidx].type == PJ_DNS_TYPE_CNAME && + cnt++ < MAX_SEARCH) + { + resname = &pkt->ans[ansidx].rdata.cname.name; + + if (!alias.slen) + alias = *resname; + + for (i=0; i < pkt->hdr.anscount; ++i) { + if (pj_stricmp(resname, &pkt->ans[i].name)==0) + break; + } + + if (i==pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + ansidx = i; + } + + if (cnt >= MAX_SEARCH) + return PJLIB_UTIL_EDNSINANSWER; + + if (pkt->ans[ansidx].type != PJ_DNS_TYPE_A && + pkt->ans[ansidx].type != PJ_DNS_TYPE_AAAA) + { + return PJLIB_UTIL_EDNSINANSWER; + } + + /* Copy alias to the record, if present. */ + if (alias.slen) { + if (alias.slen > (int)bufleft) + return PJ_ENAMETOOLONG; + + pj_memcpy(&rec->buf_[bufstart], alias.ptr, alias.slen); + rec->alias.ptr = &rec->buf_[bufstart]; + rec->alias.slen = alias.slen; + + bufstart += alias.slen; + bufleft -= alias.slen; + } + + /* Get the IP addresses. */ + cnt = 0; + for (i=0; i < pkt->hdr.anscount && cnt < PJ_DNS_MAX_IP_IN_A_REC ; ++i) { + if ((pkt->ans[i].type == PJ_DNS_TYPE_A || + pkt->ans[i].type == PJ_DNS_TYPE_AAAA) && + pj_stricmp(&pkt->ans[i].name, resname)==0) + { + if (pkt->ans[i].type == PJ_DNS_TYPE_A) { + rec->addr[cnt].af = pj_AF_INET(); + rec->addr[cnt].ip.v4 = pkt->ans[i].rdata.a.ip_addr; + } else { + rec->addr[cnt].af = pj_AF_INET6(); + rec->addr[cnt].ip.v6 = pkt->ans[i].rdata.aaaa.ip_addr; + } + ++cnt; + } + } + rec->addr_count = cnt; + + if (cnt == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + return PJ_SUCCESS; +} + + /* Set nameserver state */ static void set_nameserver_state(pj_dns_resolver *resolver, unsigned index, @@ -1036,7 +1249,7 @@ static void set_nameserver_state(pj_dns_resolver *resolver, { struct nameserver *ns = &resolver->ns[index]; enum ns_state old_state = ns->state; - char addr[PJ_INET_ADDRSTRLEN]; + char addr[PJ_INET6_ADDRSTRLEN]; ns->state = state; ns->state_expiry = *now; @@ -1050,9 +1263,8 @@ static void set_nameserver_state(pj_dns_resolver *resolver, ns->state_expiry.sec += resolver->settings.bad_ns_ttl; PJ_LOG(5, (resolver->name.ptr, "Nameserver %s:%d state changed %s --> %s", - pj_inet_ntop2(pj_AF_INET(), &ns->addr.sin_addr, addr, - sizeof(addr)), - (int)pj_ntohs(ns->addr.sin_port), + pj_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), + pj_sockaddr_get_port(&ns->addr), state_names[old_state], state_names[state])); } @@ -1131,7 +1343,7 @@ static pj_status_t select_nameservers(pj_dns_resolver *resolver, /* Update name server status */ static void report_nameserver_status(pj_dns_resolver *resolver, - const pj_sockaddr_in *ns_addr, + const pj_sockaddr *ns_addr, const pj_dns_parsed_packet *pkt) { unsigned i; @@ -1168,10 +1380,7 @@ static void report_nameserver_status(pj_dns_resolver *resolver, for (i=0; i<resolver->ns_count; ++i) { struct nameserver *ns = &resolver->ns[i]; - if (ns->addr.sin_addr.s_addr == ns_addr->sin_addr.s_addr && - ns->addr.sin_port == ns_addr->sin_port && - ns->addr.sin_family == ns_addr->sin_family) - { + if (pj_sockaddr_cmp(&ns->addr, ns_addr) == 0) { if (q_id == ns->q_id) { /* Calculate response time */ pj_time_val rt = now; @@ -1394,12 +1603,33 @@ static void on_read_complete(pj_ioqueue_key_t *key, pj_pool_t *pool = NULL; pj_dns_parsed_packet *dns_pkt; pj_dns_async_query *q; - char addr[PJ_INET_ADDRSTRLEN]; + char addr[PJ_INET6_ADDRSTRLEN]; + pj_sockaddr *src_addr; + int *src_addr_len; + unsigned char *rx_pkt; + pj_ssize_t rx_pkt_size; pj_status_t status; PJ_USE_EXCEPTION; resolver = (pj_dns_resolver *) pj_ioqueue_get_user_data(key); + pj_assert(resolver); + +#if PJ_HAS_IPV6 + if (key == resolver->udp6_key) { + src_addr = &resolver->udp6_src_addr; + src_addr_len = &resolver->udp6_addr_len; + rx_pkt = resolver->udp6_rx_pkt; + rx_pkt_size = sizeof(resolver->udp6_rx_pkt); + } else +#endif + { + src_addr = &resolver->udp_src_addr; + src_addr_len = &resolver->udp_addr_len; + rx_pkt = resolver->udp_rx_pkt; + rx_pkt_size = sizeof(resolver->udp_rx_pkt); + } + pj_mutex_lock(resolver->mutex); @@ -1411,9 +1641,8 @@ static void on_read_complete(pj_ioqueue_key_t *key, pj_strerror(status, errmsg, sizeof(errmsg)); PJ_LOG(4,(resolver->name.ptr, "DNS resolver read error from %s:%d: %s", - pj_inet_ntop2(pj_AF_INET(), &resolver->udp_src_addr.sin_addr, - addr, sizeof(addr)), - pj_ntohs(resolver->udp_src_addr.sin_port), + pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), + pj_sockaddr_get_port(src_addr), errmsg)); goto read_next_packet; @@ -1422,9 +1651,8 @@ static void on_read_complete(pj_ioqueue_key_t *key, PJ_LOG(5,(resolver->name.ptr, "Received %d bytes DNS response from %s:%d", (int)bytes_read, - pj_inet_ntop2(pj_AF_INET(), &resolver->udp_src_addr.sin_addr, - addr, sizeof(addr)), - pj_ntohs(resolver->udp_src_addr.sin_port))); + pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), + pj_sockaddr_get_port(src_addr))); /* Check for zero packet */ @@ -1439,7 +1667,7 @@ static void on_read_complete(pj_ioqueue_key_t *key, status = -1; dns_pkt = NULL; PJ_TRY { - status = pj_dns_parse_packet(pool, resolver->udp_rx_pkt, + status = pj_dns_parse_packet(pool, rx_pkt, (unsigned)bytes_read, &dns_pkt); } PJ_CATCH_ANY { @@ -1448,7 +1676,7 @@ static void on_read_complete(pj_ioqueue_key_t *key, PJ_END; /* Update nameserver status */ - report_nameserver_status(resolver, &resolver->udp_src_addr, dns_pkt); + report_nameserver_status(resolver, src_addr, dns_pkt); /* Handle parse error */ if (status != PJ_SUCCESS) { @@ -1457,9 +1685,8 @@ static void on_read_complete(pj_ioqueue_key_t *key, pj_strerror(status, errmsg, sizeof(errmsg)); PJ_LOG(3,(resolver->name.ptr, "Error parsing DNS response from %s:%d: %s", - pj_inet_ntop2(pj_AF_INET(), &resolver->udp_src_addr.sin_addr, - addr, sizeof(addr)), - pj_ntohs(resolver->udp_src_addr.sin_port), + pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), + pj_sockaddr_get_port(src_addr), errmsg)); goto read_next_packet; } @@ -1471,9 +1698,8 @@ static void on_read_complete(pj_ioqueue_key_t *key, if (!q) { PJ_LOG(5,(resolver->name.ptr, "DNS response from %s:%d id=%d discarded", - pj_inet_ntop2(pj_AF_INET(), &resolver->udp_src_addr.sin_addr, - addr, sizeof(addr)), - pj_ntohs(resolver->udp_src_addr.sin_port), + pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), + pj_sockaddr_get_port(src_addr), (unsigned)dns_pkt->hdr.id)); goto read_next_packet; } @@ -1536,13 +1762,11 @@ read_next_packet: /* needed just in case PJ_HAS_POOL_ALT_API is set */ pj_pool_release(pool); } - bytes_read = sizeof(resolver->udp_rx_pkt); - resolver->udp_addr_len = sizeof(resolver->udp_src_addr); - status = pj_ioqueue_recvfrom(resolver->udp_key, op_key, - resolver->udp_rx_pkt, - &bytes_read, PJ_IOQUEUE_ALWAYS_ASYNC, - &resolver->udp_src_addr, - &resolver->udp_addr_len); + + status = pj_ioqueue_recvfrom(key, op_key, rx_pkt, &rx_pkt_size, + PJ_IOQUEUE_ALWAYS_ASYNC, + src_addr, src_addr_len); + if (status != PJ_EPENDING) { char errmsg[PJ_ERR_MSG_SIZE]; @@ -1642,14 +1866,14 @@ PJ_DEF(void) pj_dns_resolver_dump(pj_dns_resolver *resolver, PJ_LOG(3,(resolver->name.ptr, " Name servers:")); for (i=0; i<resolver->ns_count; ++i) { - char addr[PJ_INET_ADDRSTRLEN]; + char addr[PJ_INET6_ADDRSTRLEN]; struct nameserver *ns = &resolver->ns[i]; PJ_LOG(3,(resolver->name.ptr, " NS %d: %s:%d (state=%s until %ds, rtt=%d ms)", - i, pj_inet_ntop2(pj_AF_INET(), &ns->addr.sin_addr, addr, - sizeof(addr)), - pj_ntohs(ns->addr.sin_port), + i, + pj_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), + pj_sockaddr_get_port(&ns->addr), state_names[ns->state], ns->state_expiry.sec - now.sec, PJ_TIME_VAL_MSEC(ns->rt_delay))); diff --git a/pjlib-util/src/pjlib-util/srv_resolver.c b/pjlib-util/src/pjlib-util/srv_resolver.c index 6e325b43..339e5de6 100644 --- a/pjlib-util/src/pjlib-util/srv_resolver.c +++ b/pjlib-util/src/pjlib-util/srv_resolver.c @@ -37,22 +37,26 @@ struct common pj_dns_type type; /**< Type of this structure.*/ }; +#pragma pack(1) struct srv_target { struct common common; + struct common common_aaaa; pj_dns_srv_async_query *parent; pj_str_t target_name; pj_dns_async_query *q_a; + pj_dns_async_query *q_aaaa; char target_buf[PJ_MAX_HOSTNAME]; pj_str_t cname; char cname_buf[PJ_MAX_HOSTNAME]; - unsigned port; + pj_uint16_t port; unsigned priority; unsigned weight; unsigned sum; unsigned addr_cnt; - pj_in_addr addr[ADDR_MAX_COUNT]; + pj_sockaddr addr[ADDR_MAX_COUNT];/**< Address family and IP.*/ }; +#pragma pack() struct pj_dns_srv_async_query { @@ -135,6 +139,10 @@ PJ_DEF(pj_status_t) pj_dns_srv_resolve( const pj_str_t *domain_name, query_job->domain_part.slen = target_name.slen - len; query_job->def_port = (pj_uint16_t)def_port; + /* Normalize query job option PJ_DNS_SRV_RESOLVE_AAAA_ONLY */ + if (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) + query_job->option |= PJ_DNS_SRV_RESOLVE_AAAA; + /* Start the asynchronous query_job */ query_job->dns_state = PJ_DNS_TYPE_SRV; @@ -178,6 +186,11 @@ PJ_DEF(pj_status_t) pj_dns_srv_cancel_query(pj_dns_srv_async_query *query, srv->q_a = NULL; has_pending = PJ_TRUE; } + if (srv->q_aaaa) { + pj_dns_resolver_cancel_query(srv->q_aaaa, PJ_FALSE); + srv->q_aaaa = NULL; + has_pending = PJ_TRUE; + } } if (has_pending && notify && query->cb) { @@ -314,29 +327,56 @@ static void build_server_entries(pj_dns_srv_async_query *query_job, query_job->srv[i].target_name.ptr = query_job->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 + /* Check for Additional Info section if A/AAAA records are available, and + * fill in the IP address (so that we won't need to resolve the A/AAAA * record with another DNS query_job). */ 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) + /* Skip non-A/AAAA record */ + if (rr->type != PJ_DNS_TYPE_A && rr->type != PJ_DNS_TYPE_AAAA) + continue; + + /* Also skip if: + * - it is A record and app only want AAAA record, or + * - it is AAAA record and app does not want AAAA record + */ + if ((rr->type == PJ_DNS_TYPE_A && + (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY)!=0) || + (rr->type == PJ_DNS_TYPE_AAAA && + (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA)==0)) + { continue; + } - /* Yippeaiyee!! There is an "A" record! + /* Yippeaiyee!! There is an "A/AAAA" record! * Update the IP address of the corresponding SRV record. */ for (j=0; j<query_job->srv_cnt; ++j) { - if (pj_stricmp(&rr->name, &query_job->srv[j].target_name)==0 && - query_job->srv[j].addr_cnt < ADDR_MAX_COUNT) - { + if (pj_stricmp(&rr->name, &query_job->srv[j].target_name)==0 + && query_job->srv[j].addr_cnt < ADDR_MAX_COUNT) + { unsigned cnt = query_job->srv[j].addr_cnt; - query_job->srv[j].addr[cnt].s_addr = rr->rdata.a.ip_addr.s_addr; + if (rr->type == PJ_DNS_TYPE_A) { + pj_sockaddr_init(pj_AF_INET(), + &query_job->srv[j].addr[cnt], NULL, + query_job->srv[j].port); + query_job->srv[j].addr[cnt].ipv4.sin_addr = + rr->rdata.a.ip_addr; + } else { + pj_sockaddr_init(pj_AF_INET6(), + &query_job->srv[j].addr[cnt], NULL, + query_job->srv[j].port); + query_job->srv[j].addr[cnt].ipv6.sin6_addr = + rr->rdata.aaaa.ip_addr; + } + /* Only increment host_resolved once per SRV record */ if (query_job->srv[j].addr_cnt == 0) ++query_job->host_resolved; + ++query_job->srv[j].addr_cnt; break; } @@ -354,6 +394,7 @@ static void build_server_entries(pj_dns_srv_async_query *query_job, rr->name.ptr)); } */ + } /* Rescan again the name specified in the SRV record to see if IP @@ -362,16 +403,32 @@ static void build_server_entries(pj_dns_srv_async_query *query_job, */ for (i=0; i<query_job->srv_cnt; ++i) { pj_in_addr addr; + pj_in6_addr addr6; if (query_job->srv[i].addr_cnt != 0) { /* IP address already resolved */ continue; } - if (pj_inet_pton(pj_AF_INET(), &query_job->srv[i].target_name, + if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY)==0 && + pj_inet_pton(pj_AF_INET(), &query_job->srv[i].target_name, &addr) == PJ_SUCCESS) { - query_job->srv[i].addr[query_job->srv[i].addr_cnt++] = addr; + unsigned cnt = query_job->srv[i].addr_cnt; + pj_sockaddr_init(pj_AF_INET(), &query_job->srv[i].addr[cnt], + NULL, query_job->srv[i].port); + query_job->srv[i].addr[cnt].ipv4.sin_addr = addr; + ++query_job->srv[i].addr_cnt; + ++query_job->host_resolved; + } else if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA)!=0 && + pj_inet_pton(pj_AF_INET6(), &query_job->srv[i].target_name, + &addr6) == PJ_SUCCESS) + { + unsigned cnt = query_job->srv[i].addr_cnt; + pj_sockaddr_init(pj_AF_INET6(), &query_job->srv[i].addr[cnt], + NULL, query_job->srv[i].port); + query_job->srv[i].addr[cnt].ipv6.sin6_addr = addr6; + ++query_job->srv[i].addr_cnt; ++query_job->host_resolved; } } @@ -387,11 +444,11 @@ static void build_server_entries(pj_dns_srv_async_query *query_job, (query_job->srv_cnt ? ':' : ' '))); for (i=0; i<query_job->srv_cnt; ++i) { - char addr[PJ_INET_ADDRSTRLEN]; + char addr[PJ_INET6_ADDRSTRLEN]; if (query_job->srv[i].addr_cnt != 0) { - pj_inet_ntop(pj_AF_INET(), &query_job->srv[i].addr[0], - addr, sizeof(addr)); + pj_sockaddr_print(&query_job->srv[i].addr[0], + addr, sizeof(addr), 2); } else pj_ansi_strcpy(addr, "-"); @@ -407,13 +464,16 @@ static void build_server_entries(pj_dns_srv_async_query *query_job, } -/* Start DNS A record queries for all SRV records in the query_job structure */ +/* Start DNS A and/or AAAA record queries for all SRV records in + * the query_job structure. + */ static pj_status_t resolve_hostnames(pj_dns_srv_async_query *query_job) { unsigned i, err_cnt = 0; pj_status_t err=PJ_SUCCESS, status; query_job->dns_state = PJ_DNS_TYPE_A; + for (i=0; i<query_job->srv_cnt; ++i) { struct srv_target *srv = &query_job->srv[i]; @@ -423,18 +483,37 @@ static pj_status_t resolve_hostnames(pj_dns_srv_async_query *query_job) srv->target_name.ptr)); srv->common.type = PJ_DNS_TYPE_A; + srv->common_aaaa.type = PJ_DNS_TYPE_AAAA; srv->parent = query_job; + status = PJ_SUCCESS; + + /* Start DNA A record query */ + if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) == 0) + { + status = pj_dns_resolver_start_query(query_job->resolver, + &srv->target_name, + PJ_DNS_TYPE_A, 0, + &dns_callback, + &srv->common, &srv->q_a); + } + + /* Start DNA AAAA record query */ + if (status == PJ_SUCCESS && + (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) != 0) + { + status = pj_dns_resolver_start_query(query_job->resolver, + &srv->target_name, + PJ_DNS_TYPE_AAAA, 0, + &dns_callback, + &srv->common_aaaa, &srv->q_aaaa); + } + /* See also #1809: dns_callback() will be invoked synchronously when response * is available in the cache, and var 'query_job->host_resolved' will get * incremented within the dns_callback(), which will cause this function * returning false error, so don't use that variable for counting errors. */ - status = pj_dns_resolver_start_query(query_job->resolver, - &srv->target_name, - PJ_DNS_TYPE_A, 0, - &dns_callback, - srv, &srv->q_a); if (status != PJ_SUCCESS) { query_job->host_resolved++; err_cnt++; @@ -464,6 +543,9 @@ static void dns_callback(void *user_data, } else if (common->type == PJ_DNS_TYPE_A) { srv = (struct srv_target*) common; query_job = srv->parent; + } else if (common->type == PJ_DNS_TYPE_AAAA) { + srv = (struct srv_target*)((pj_int8_t*)common-sizeof(struct common)); + query_job = srv->parent; } else { pj_assert(!"Unexpected user data!"); return; @@ -474,6 +556,7 @@ static void dns_callback(void *user_data, /* We are getting SRV response */ + /* Clear the outstanding job */ query_job->q_srv = NULL; if (status == PJ_SUCCESS && pkt->hdr.anscount != 0) { @@ -507,13 +590,15 @@ static void dns_callback(void *user_data, * an A record and resolve with DNS A resolution. */ if (query_job->srv_cnt == 0) { + unsigned new_option = 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. */ PJ_LOG(4, (query_job->objname, "DNS SRV resolution failed for %.*s, trying " - "resolving A record for %.*s", + "resolving A/AAAA record for %.*s", (int)query_job->full_name.slen, query_job->full_name.ptr, (int)query_job->domain_part.slen, @@ -526,11 +611,20 @@ static void dns_callback(void *user_data, query_job->srv[i].priority = 0; query_job->srv[i].weight = 0; query_job->srv[i].port = query_job->def_port; - } + + /* Update query_job resolution option based on fallback option */ + if (query_job->option & PJ_DNS_SRV_FALLBACK_AAAA) + new_option |= (PJ_DNS_SRV_RESOLVE_AAAA | + PJ_DNS_SRV_RESOLVE_AAAA_ONLY); + if (query_job->option & PJ_DNS_SRV_FALLBACK_A) + new_option &= (~PJ_DNS_SRV_RESOLVE_AAAA_ONLY); + + query_job->option = new_option; + } - /* Resolve server hostnames (DNS A record) for hosts which don't have - * A record yet. + /* Resolve server hostnames (DNS A/AAAA record) for hosts which + * don't have A/AAAA record yet. */ if (query_job->host_resolved != query_job->srv_cnt) { status = resolve_hostnames(query_job); @@ -544,54 +638,83 @@ static void dns_callback(void *user_data, } } else if (query_job->dns_state == PJ_DNS_TYPE_A) { + pj_bool_t is_type_a, srv_completed; - /* Clear the outstanding job */ - srv->q_a = NULL; + /* Clear outstanding job */ + if (common->type == PJ_DNS_TYPE_A) { + srv_completed = (srv->q_aaaa == NULL); + srv->q_a = NULL; + } else if (common->type == PJ_DNS_TYPE_AAAA) { + srv_completed = (srv->q_a == NULL); + srv->q_aaaa = NULL; + } else { + pj_assert(!"Unexpected job type"); + query_job->last_error = status = PJ_EINVALIDOP; + goto on_error; + } + + is_type_a = (common->type == PJ_DNS_TYPE_A); /* Check that we really have answer */ if (status==PJ_SUCCESS && pkt->hdr.anscount != 0) { - char addr[PJ_INET_ADDRSTRLEN]; - pj_dns_a_record rec; + char addr[PJ_INET6_ADDRSTRLEN]; + pj_dns_addr_record rec; /* Parse response */ - status = pj_dns_parse_a_response(pkt, &rec); + status = pj_dns_parse_addr_response(pkt, &rec); if (status != PJ_SUCCESS) goto on_error; pj_assert(rec.addr_count != 0); /* Update CNAME alias, if present. */ - if (rec.alias.slen) { + if (srv->cname.slen==0 && rec.alias.slen) { pj_assert(rec.alias.slen <= (int)sizeof(srv->cname_buf)); srv->cname.ptr = srv->cname_buf; pj_strcpy(&srv->cname, &rec.alias); - } else { - srv->cname.slen = 0; + //} else { + //srv->cname.slen = 0; } /* Update IP address of the corresponding hostname or CNAME */ - if (srv->addr_cnt < ADDR_MAX_COUNT) { - srv->addr[srv->addr_cnt++].s_addr = rec.addr[0].s_addr; - - PJ_LOG(5,(query_job->objname, - "DNS A for %.*s: %s", - (int)srv->target_name.slen, - srv->target_name.ptr, - pj_inet_ntop2(pj_AF_INET(), &rec.addr[0], - addr, sizeof(addr)))); - } - - /* Check for multiple IP addresses */ - for (i=1; i<rec.addr_count && srv->addr_cnt < ADDR_MAX_COUNT; ++i) + for (i=0; i<rec.addr_count && srv->addr_cnt<ADDR_MAX_COUNT; ++i) { - srv->addr[srv->addr_cnt++].s_addr = rec.addr[i].s_addr; - - PJ_LOG(5,(query_job->objname, - "Additional DNS A for %.*s: %s", - (int)srv->target_name.slen, - srv->target_name.ptr, - pj_inet_ntop2(pj_AF_INET(), &rec.addr[i], - addr, sizeof(addr)))); + pj_bool_t added = PJ_FALSE; + + if (is_type_a && rec.addr[i].af == pj_AF_INET()) { + pj_sockaddr_init(pj_AF_INET(), &srv->addr[srv->addr_cnt], + NULL, srv->port); + srv->addr[srv->addr_cnt].ipv4.sin_addr = + rec.addr[i].ip.v4; + added = PJ_TRUE; + } else if (!is_type_a && rec.addr[i].af == pj_AF_INET6()) { + pj_sockaddr_init(pj_AF_INET6(), &srv->addr[srv->addr_cnt], + NULL, srv->port); + srv->addr[srv->addr_cnt].ipv6.sin6_addr = + rec.addr[i].ip.v6; + added = PJ_TRUE; + } else { + /* Mismatched address family, e.g: getting IPv6 address in + * DNS A query resolution. + */ + PJ_LOG(4,(query_job->objname, + "Bad address family in DNS %s query for %.*s", + (is_type_a? "A" : "AAAA"), + (int)srv->target_name.slen, + srv->target_name.ptr)); + } + + if (added) { + PJ_LOG(5,(query_job->objname, + "DNS %s for %.*s: %s", + (is_type_a? "A" : "AAAA"), + (int)srv->target_name.slen, + srv->target_name.ptr, + pj_sockaddr_print(&srv->addr[srv->addr_cnt], + addr, sizeof(addr), 2))); + + ++srv->addr_cnt; + } } } else if (status != PJ_SUCCESS) { @@ -602,11 +725,17 @@ static void dns_callback(void *user_data, /* Log error */ pj_strerror(status, errmsg, sizeof(errmsg)); - PJ_LOG(4,(query_job->objname, "DNS A record resolution failed: %s", + PJ_LOG(4,(query_job->objname, + "DNS %s record resolution failed: %s", + (is_type_a? "A" : "AAAA"), errmsg)); } - ++query_job->host_resolved; + /* Increment host resolved count when both DNS A and AAAA record + * queries for this server are completed. + */ + if (srv_completed) + ++query_job->host_resolved; } else { pj_assert(!"Unexpected state!"); @@ -623,6 +752,7 @@ static void dns_callback(void *user_data, for (i=0; i<query_job->srv_cnt; ++i) { unsigned j; struct srv_target *srv2 = &query_job->srv[i]; + pj_dns_addr_record *s = &srv_rec.entry[srv_rec.count].server; srv_rec.entry[srv_rec.count].priority = srv2->priority; srv_rec.entry[srv_rec.count].weight = srv2->weight; @@ -634,10 +764,13 @@ static void dns_callback(void *user_data, pj_assert(srv2->addr_cnt <= PJ_DNS_MAX_IP_IN_A_REC); - for (j=0; j<srv2->addr_cnt; ++j) { - srv_rec.entry[srv_rec.count].server.addr[j].s_addr = - srv2->addr[j].s_addr; - ++srv_rec.entry[srv_rec.count].server.addr_count; + for (j=0; j<srv2->addr_cnt; ++j) { + s->addr[j].af = srv2->addr[j].addr.sa_family; + if (s->addr[j].af == pj_AF_INET()) + s->addr[j].ip.v4 = srv2->addr[j].ipv4.sin_addr; + else + s->addr[j].ip.v6 = srv2->addr[j].ipv6.sin6_addr; + ++s->addr_count; } if (srv2->addr_cnt > 0) { @@ -680,6 +813,10 @@ on_error: query_job->domain_part.ptr, status, pj_strerror(status,errmsg,sizeof(errmsg)).ptr)); + + /* Cancel any pending query */ + pj_dns_srv_cancel_query(query_job, PJ_FALSE); + (*query_job->cb)(query_job->token, status, NULL); return; } |