diff options
Diffstat (limited to 'pjlib-util/src/pjlib-util/srv_resolver.c')
-rw-r--r-- | pjlib-util/src/pjlib-util/srv_resolver.c | 257 |
1 files changed, 197 insertions, 60 deletions
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; } |