1 /* $Id$ */
2 /*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20 #include <pjlib-util/srv_resolver.h>
21 #include <pjlib-util/errno.h>
22 #include <pj/array.h>
23 #include <pj/assert.h>
24 #include <pj/log.h>
25 #include <pj/os.h>
26 #include <pj/pool.h>
27 #include <pj/rand.h>
28 #include <pj/string.h>
29
30
31 #define THIS_FILE "srv_resolver.c"
32
33 #define ADDR_MAX_COUNT PJ_DNS_MAX_IP_IN_A_REC
34
35 struct common
36 {
37 pj_dns_type type; /**< Type of this structure.*/
38 };
39
40 #pragma pack(1)
41 struct srv_target
42 {
43 struct common common;
44 struct common common_aaaa;
45 pj_dns_srv_async_query *parent;
46 pj_str_t target_name;
47 pj_dns_async_query *q_a;
48 pj_dns_async_query *q_aaaa;
49 char target_buf[PJ_MAX_HOSTNAME];
50 pj_str_t cname;
51 char cname_buf[PJ_MAX_HOSTNAME];
52 pj_uint16_t port;
53 unsigned priority;
54 unsigned weight;
55 unsigned sum;
56 unsigned addr_cnt;
57 pj_sockaddr addr[ADDR_MAX_COUNT];/**< Address family and IP.*/
58 };
59 #pragma pack()
60
61 struct pj_dns_srv_async_query
62 {
63 struct common common;
64 char *objname;
65
66 pj_dns_type dns_state; /**< DNS type being resolved. */
67 pj_dns_resolver *resolver; /**< Resolver SIP instance. */
68 void *token;
69 pj_dns_async_query *q_srv;
70 pj_dns_srv_resolver_cb *cb;
71 pj_status_t last_error;
72
73 /* Original request: */
74 unsigned option;
75 pj_str_t full_name;
76 pj_str_t domain_part;
77 pj_uint16_t def_port;
78
79 /* SRV records and their resolved IP addresses: */
80 unsigned srv_cnt;
81 struct srv_target srv[PJ_DNS_SRV_MAX_ADDR];
82
83 /* Number of hosts in SRV records that the IP address has been resolved */
84 unsigned host_resolved;
85
86 };
87
88
89 /* Async resolver callback, forward decl. */
90 static void dns_callback(void *user_data,
91 pj_status_t status,
92 pj_dns_parsed_packet *pkt);
93
94
95
96 /*
97 * The public API to invoke DNS SRV resolution.
98 */
pj_dns_srv_resolve(const pj_str_t * domain_name,const pj_str_t * res_name,unsigned def_port,pj_pool_t * pool,pj_dns_resolver * resolver,unsigned option,void * token,pj_dns_srv_resolver_cb * cb,pj_dns_srv_async_query ** p_query)99 PJ_DEF(pj_status_t) pj_dns_srv_resolve( const pj_str_t *domain_name,
100 const pj_str_t *res_name,
101 unsigned def_port,
102 pj_pool_t *pool,
103 pj_dns_resolver *resolver,
104 unsigned option,
105 void *token,
106 pj_dns_srv_resolver_cb *cb,
107 pj_dns_srv_async_query **p_query)
108 {
109 pj_size_t len;
110 pj_str_t target_name;
111 pj_dns_srv_async_query *query_job;
112 pj_status_t status;
113
114 PJ_ASSERT_RETURN(domain_name && domain_name->slen &&
115 res_name && res_name->slen &&
116 pool && resolver && cb, PJ_EINVAL);
117
118 /* Build full name */
119 len = domain_name->slen + res_name->slen + 2;
120 target_name.ptr = (char*) pj_pool_alloc(pool, len);
121 pj_strcpy(&target_name, res_name);
122 if (res_name->ptr[res_name->slen-1] != '.')
123 pj_strcat2(&target_name, ".");
124 len = target_name.slen;
125 pj_strcat(&target_name, domain_name);
126 target_name.ptr[target_name.slen] = '\0';
127
128
129 /* Build the query_job state */
130 query_job = PJ_POOL_ZALLOC_T(pool, pj_dns_srv_async_query);
131 query_job->common.type = PJ_DNS_TYPE_SRV;
132 query_job->objname = target_name.ptr;
133 query_job->resolver = resolver;
134 query_job->token = token;
135 query_job->cb = cb;
136 query_job->option = option;
137 query_job->full_name = target_name;
138 query_job->domain_part.ptr = target_name.ptr + len;
139 query_job->domain_part.slen = target_name.slen - len;
140 query_job->def_port = (pj_uint16_t)def_port;
141
142 /* Normalize query job option PJ_DNS_SRV_RESOLVE_AAAA_ONLY */
143 if (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY)
144 query_job->option |= PJ_DNS_SRV_RESOLVE_AAAA;
145
146 /* Start the asynchronous query_job */
147
148 query_job->dns_state = PJ_DNS_TYPE_SRV;
149
150 PJ_LOG(5, (query_job->objname,
151 "Starting async DNS %s query_job: target=%.*s:%d",
152 pj_dns_get_type_name(query_job->dns_state),
153 (int)target_name.slen, target_name.ptr,
154 def_port));
155
156 status = pj_dns_resolver_start_query(resolver, &target_name,
157 query_job->dns_state, 0,
158 &dns_callback,
159 query_job, &query_job->q_srv);
160 if (status==PJ_SUCCESS && p_query)
161 *p_query = query_job;
162
163 return status;
164 }
165
166
167 /*
168 * Cancel pending query.
169 */
pj_dns_srv_cancel_query(pj_dns_srv_async_query * query,pj_bool_t notify)170 PJ_DEF(pj_status_t) pj_dns_srv_cancel_query(pj_dns_srv_async_query *query,
171 pj_bool_t notify)
172 {
173 pj_bool_t has_pending = PJ_FALSE;
174 unsigned i;
175
176 if (query->q_srv) {
177 pj_dns_resolver_cancel_query(query->q_srv, PJ_FALSE);
178 query->q_srv = NULL;
179 has_pending = PJ_TRUE;
180 }
181
182 for (i=0; i<query->srv_cnt; ++i) {
183 struct srv_target *srv = &query->srv[i];
184 if (srv->q_a) {
185 pj_dns_resolver_cancel_query(srv->q_a, PJ_FALSE);
186 srv->q_a = NULL;
187 has_pending = PJ_TRUE;
188 }
189 if (srv->q_aaaa) {
190 /* Check if it is a dummy query. */
191 if (srv->q_aaaa != (pj_dns_async_query*)0x1) {
192 pj_dns_resolver_cancel_query(srv->q_aaaa, PJ_FALSE);
193 has_pending = PJ_TRUE;
194 }
195 srv->q_aaaa = NULL;
196 }
197 }
198
199 if (has_pending && notify && query->cb) {
200 (*query->cb)(query->token, PJ_ECANCELLED, NULL);
201 }
202
203 return has_pending? PJ_SUCCESS : PJ_EINVALIDOP;
204 }
205
206
207 #define SWAP(type,ptr1,ptr2) if (ptr1 != ptr2) { \
208 type tmp; \
209 pj_memcpy(&tmp, ptr1, sizeof(type)); \
210 pj_memcpy(ptr1, ptr2, sizeof(type)); \
211 (ptr1)->target_name.ptr = (ptr1)->target_buf;\
212 pj_memcpy(ptr2, &tmp, sizeof(type)); \
213 (ptr2)->target_name.ptr = (ptr2)->target_buf;\
214 } else {}
215
216
217 /* Build server entries in the query_job based on received SRV response */
build_server_entries(pj_dns_srv_async_query * query_job,pj_dns_parsed_packet * response)218 static void build_server_entries(pj_dns_srv_async_query *query_job,
219 pj_dns_parsed_packet *response)
220 {
221 unsigned i;
222
223 /* Save the Resource Records in DNS answer into SRV targets. */
224 query_job->srv_cnt = 0;
225 for (i=0; i<response->hdr.anscount &&
226 query_job->srv_cnt < PJ_DNS_SRV_MAX_ADDR; ++i)
227 {
228 pj_dns_parsed_rr *rr = &response->ans[i];
229 struct srv_target *srv = &query_job->srv[query_job->srv_cnt];
230
231 if (rr->type != PJ_DNS_TYPE_SRV) {
232 PJ_LOG(4,(query_job->objname,
233 "Received non SRV answer for SRV query_job!"));
234 continue;
235 }
236
237 if (rr->rdata.srv.target.slen > PJ_MAX_HOSTNAME) {
238 PJ_LOG(4,(query_job->objname, "Hostname is too long!"));
239 continue;
240 }
241
242 if (rr->rdata.srv.target.slen == 0) {
243 PJ_LOG(4,(query_job->objname, "Hostname is empty!"));
244 continue;
245 }
246
247 /* Build the SRV entry for RR */
248 pj_bzero(srv, sizeof(*srv));
249 srv->target_name.ptr = srv->target_buf;
250 pj_strncpy(&srv->target_name, &rr->rdata.srv.target,
251 sizeof(srv->target_buf));
252 srv->port = rr->rdata.srv.port;
253 srv->priority = rr->rdata.srv.prio;
254 srv->weight = rr->rdata.srv.weight;
255
256 ++query_job->srv_cnt;
257 }
258
259 if (query_job->srv_cnt == 0) {
260 PJ_LOG(4,(query_job->objname,
261 "Could not find SRV record in DNS answer!"));
262 return;
263 }
264
265 /* First pass:
266 * order the entries based on priority.
267 */
268 for (i=0; i<query_job->srv_cnt-1; ++i) {
269 unsigned min = i, j;
270 for (j=i+1; j<query_job->srv_cnt; ++j) {
271 if (query_job->srv[j].priority < query_job->srv[min].priority)
272 min = j;
273 }
274 SWAP(struct srv_target, &query_job->srv[i], &query_job->srv[min]);
275 }
276
277 /* Second pass:
278 * Order the entry in a list.
279 *
280 * The algorithm for selecting server among servers with the same
281 * priority is described in RFC 2782.
282 */
283 for (i=0; i<query_job->srv_cnt; ++i) {
284 unsigned j, count=1, sum;
285
286 /* Calculate running sum for servers with the same priority */
287 sum = query_job->srv[i].sum = query_job->srv[i].weight;
288 for (j=i+1; j<query_job->srv_cnt &&
289 query_job->srv[j].priority == query_job->srv[i].priority; ++j)
290 {
291 sum += query_job->srv[j].weight;
292 query_job->srv[j].sum = sum;
293 ++count;
294 }
295
296 if (count > 1) {
297 unsigned r;
298
299 /* Elect one random number between zero and the total sum of
300 * weight (inclusive).
301 */
302 r = pj_rand() % (sum + 1);
303
304 /* Select the first server which running sum is greater than or
305 * equal to the random number.
306 */
307 for (j=i; j<i+count; ++j) {
308 if (query_job->srv[j].sum >= r)
309 break;
310 }
311
312 /* Must have selected one! */
313 pj_assert(j != i+count);
314
315 /* Put this entry in front (of entries with same priority) */
316 SWAP(struct srv_target, &query_job->srv[i], &query_job->srv[j]);
317
318 /* Remove all other entries (of the same priority) */
319 /* Don't need to do this.
320 * See https://trac.pjsip.org/repos/ticket/1719
321 while (count > 1) {
322 pj_array_erase(query_job->srv, sizeof(struct srv_target),
323 query_job->srv_cnt, i+1);
324 --count;
325 --query_job->srv_cnt;
326 }
327 */
328 }
329 }
330
331 /* Since we've been moving around SRV entries, update the pointers
332 * in target_name.
333 */
334 for (i=0; i<query_job->srv_cnt; ++i) {
335 query_job->srv[i].target_name.ptr = query_job->srv[i].target_buf;
336 }
337
338 /* Check for Additional Info section if A/AAAA records are available, and
339 * fill in the IP address (so that we won't need to resolve the A/AAAA
340 * record with another DNS query_job).
341 */
342 for (i=0; i<response->hdr.arcount; ++i) {
343 pj_dns_parsed_rr *rr = &response->arr[i];
344 unsigned j;
345
346 /* Skip non-A/AAAA record */
347 if (rr->type != PJ_DNS_TYPE_A && rr->type != PJ_DNS_TYPE_AAAA)
348 continue;
349
350 /* Also skip if:
351 * - it is A record and app only want AAAA record, or
352 * - it is AAAA record and app does not want AAAA record
353 */
354 if ((rr->type == PJ_DNS_TYPE_A &&
355 (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY)!=0) ||
356 (rr->type == PJ_DNS_TYPE_AAAA &&
357 (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA)==0))
358 {
359 continue;
360 }
361
362 /* Yippeaiyee!! There is an "A/AAAA" record!
363 * Update the IP address of the corresponding SRV record.
364 */
365 for (j=0; j<query_job->srv_cnt; ++j) {
366 if (pj_stricmp(&rr->name, &query_job->srv[j].target_name)==0
367 && query_job->srv[j].addr_cnt < ADDR_MAX_COUNT)
368 {
369 unsigned cnt = query_job->srv[j].addr_cnt;
370 if (rr->type == PJ_DNS_TYPE_A) {
371 pj_sockaddr_init(pj_AF_INET(),
372 &query_job->srv[j].addr[cnt], NULL,
373 query_job->srv[j].port);
374 query_job->srv[j].addr[cnt].ipv4.sin_addr =
375 rr->rdata.a.ip_addr;
376 } else {
377 pj_sockaddr_init(pj_AF_INET6(),
378 &query_job->srv[j].addr[cnt], NULL,
379 query_job->srv[j].port);
380 query_job->srv[j].addr[cnt].ipv6.sin6_addr =
381 rr->rdata.aaaa.ip_addr;
382 }
383
384 /* Only increment host_resolved once per SRV record */
385 if (query_job->srv[j].addr_cnt == 0)
386 ++query_job->host_resolved;
387
388 ++query_job->srv[j].addr_cnt;
389 break;
390 }
391 }
392
393 /* Not valid message; SRV entry might have been deleted in
394 * server selection process.
395 */
396 /*
397 if (j == query_job->srv_cnt) {
398 PJ_LOG(4,(query_job->objname,
399 "Received DNS SRV answer with A record, but "
400 "couldn't find matching name (name=%.*s)",
401 (int)rr->name.slen,
402 rr->name.ptr));
403 }
404 */
405
406 }
407
408 /* Rescan again the name specified in the SRV record to see if IP
409 * address is specified as the target name (unlikely, but well, who
410 * knows..).
411 */
412 for (i=0; i<query_job->srv_cnt; ++i) {
413 pj_in_addr addr;
414 pj_in6_addr addr6;
415 unsigned cnt = query_job->srv[i].addr_cnt;
416
417 if (cnt != 0) {
418 /* IP address already resolved */
419 continue;
420 }
421
422 if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY)==0 &&
423 pj_inet_pton(pj_AF_INET(), &query_job->srv[i].target_name,
424 &addr) == PJ_SUCCESS)
425 {
426 pj_sockaddr_init(pj_AF_INET(), &query_job->srv[i].addr[cnt],
427 NULL, query_job->srv[i].port);
428 query_job->srv[i].addr[cnt].ipv4.sin_addr = addr;
429 ++query_job->srv[i].addr_cnt;
430 ++query_job->host_resolved;
431 } else if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA)!=0 &&
432 pj_inet_pton(pj_AF_INET6(), &query_job->srv[i].target_name,
433 &addr6) == PJ_SUCCESS)
434 {
435 pj_sockaddr_init(pj_AF_INET6(), &query_job->srv[i].addr[cnt],
436 NULL, query_job->srv[i].port);
437 query_job->srv[i].addr[cnt].ipv6.sin6_addr = addr6;
438 ++query_job->srv[i].addr_cnt;
439 ++query_job->host_resolved;
440 }
441 }
442
443 /* Print resolved entries to the log */
444 PJ_LOG(5,(query_job->objname,
445 "SRV query_job for %.*s completed, "
446 "%d of %d total entries selected%c",
447 (int)query_job->full_name.slen,
448 query_job->full_name.ptr,
449 query_job->srv_cnt,
450 response->hdr.anscount,
451 (query_job->srv_cnt ? ':' : ' ')));
452
453 for (i=0; i<query_job->srv_cnt; ++i) {
454 char addr[PJ_INET6_ADDRSTRLEN];
455
456 if (query_job->srv[i].addr_cnt != 0) {
457 pj_sockaddr_print(&query_job->srv[i].addr[0],
458 addr, sizeof(addr), 2);
459 } else
460 pj_ansi_strcpy(addr, "-");
461
462 PJ_LOG(5,(query_job->objname,
463 " %d: SRV %d %d %d %.*s (%s)",
464 i, query_job->srv[i].priority,
465 query_job->srv[i].weight,
466 query_job->srv[i].port,
467 (int)query_job->srv[i].target_name.slen,
468 query_job->srv[i].target_name.ptr,
469 addr));
470 }
471 }
472
473
474 /* Start DNS A and/or AAAA record queries for all SRV records in
475 * the query_job structure.
476 */
resolve_hostnames(pj_dns_srv_async_query * query_job)477 static pj_status_t resolve_hostnames(pj_dns_srv_async_query *query_job)
478 {
479 unsigned i, err_cnt = 0;
480 pj_status_t err=PJ_SUCCESS, status;
481
482 query_job->dns_state = PJ_DNS_TYPE_A;
483
484 for (i=0; i<query_job->srv_cnt; ++i) {
485 struct srv_target *srv = &query_job->srv[i];
486
487 if (srv->addr_cnt != 0) {
488 /*
489 * This query is already counted as resolved because of the
490 * additional records in the SRV response or the target name
491 * is an IP address exception in build_server_entries().
492 */
493 continue;
494 }
495
496 PJ_LOG(5, (query_job->objname,
497 "Starting async DNS A query_job for %.*s",
498 (int)srv->target_name.slen,
499 srv->target_name.ptr));
500
501 srv->common.type = PJ_DNS_TYPE_A;
502 srv->common_aaaa.type = PJ_DNS_TYPE_AAAA;
503 srv->parent = query_job;
504 srv->q_a = NULL;
505 srv->q_aaaa = NULL;
506
507 status = PJ_SUCCESS;
508
509 /* Start DNS A record query */
510 if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) == 0)
511 {
512 if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) != 0) {
513 /* If there will be DNS AAAA query too, let's setup
514 * a dummy one here, otherwise app callback may be called
515 * immediately (before DNS AAAA query is sent) when
516 * DNS A record is available in the cache.
517 */
518 srv->q_aaaa = (pj_dns_async_query*)0x1;
519 }
520 status = pj_dns_resolver_start_query(query_job->resolver,
521 &srv->target_name,
522 PJ_DNS_TYPE_A, 0,
523 &dns_callback,
524 &srv->common, &srv->q_a);
525 }
526
527 /* Start DNS AAAA record query */
528 if (status == PJ_SUCCESS &&
529 (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) != 0)
530 {
531 status = pj_dns_resolver_start_query(query_job->resolver,
532 &srv->target_name,
533 PJ_DNS_TYPE_AAAA, 0,
534 &dns_callback,
535 &srv->common_aaaa, &srv->q_aaaa);
536 }
537
538 /* See also #1809: dns_callback() will be invoked synchronously when response
539 * is available in the cache, and var 'query_job->host_resolved' will get
540 * incremented within the dns_callback(), which will cause this function
541 * returning false error, so don't use that variable for counting errors.
542 */
543 if (status != PJ_SUCCESS) {
544 query_job->host_resolved++;
545 err_cnt++;
546 err = status;
547 }
548 }
549
550 return (err_cnt == query_job->srv_cnt) ? err : PJ_SUCCESS;
551 }
552
553 /*
554 * This callback is called by PJLIB-UTIL DNS resolver when asynchronous
555 * query_job has completed (successfully or with error).
556 */
dns_callback(void * user_data,pj_status_t status,pj_dns_parsed_packet * pkt)557 static void dns_callback(void *user_data,
558 pj_status_t status,
559 pj_dns_parsed_packet *pkt)
560 {
561 struct common *common = (struct common*) user_data;
562 pj_dns_srv_async_query *query_job;
563 struct srv_target *srv = NULL;
564 unsigned i;
565
566 if (common->type == PJ_DNS_TYPE_SRV) {
567 query_job = (pj_dns_srv_async_query*) common;
568 srv = NULL;
569 } else if (common->type == PJ_DNS_TYPE_A) {
570 srv = (struct srv_target*) common;
571 query_job = srv->parent;
572 } else if (common->type == PJ_DNS_TYPE_AAAA) {
573 srv = (struct srv_target*)((pj_int8_t*)common-sizeof(struct common));
574 query_job = srv->parent;
575 } else {
576 pj_assert(!"Unexpected user data!");
577 return;
578 }
579
580 /* Proceed to next stage */
581 if (query_job->dns_state == PJ_DNS_TYPE_SRV) {
582
583 /* We are getting SRV response */
584
585 /* Clear the outstanding job */
586 query_job->q_srv = NULL;
587
588 if (status == PJ_SUCCESS) {
589 if (PJ_DNS_GET_TC(pkt->hdr.flags)) {
590 /* Got truncated answer, the standard recommends to follow up
591 * the query using TCP. Since we currently don't support it,
592 * just return error.
593 */
594 PJ_LOG(4,(query_job->objname,
595 "Discard truncated DNS SRV response for %.*s",
596 (int)query_job->full_name.slen,
597 query_job->full_name.ptr));
598
599 status = PJ_EIGNORED;
600 query_job->last_error = status;
601 goto on_error;
602 } else if (pkt->hdr.anscount != 0) {
603 /* Got SRV response, build server entry. If A records are
604 * available in additional records section of the DNS response,
605 * save them too.
606 */
607 build_server_entries(query_job, pkt);
608 }
609
610 } else {
611 char errmsg[PJ_ERR_MSG_SIZE];
612
613 /* Update query_job last error */
614 query_job->last_error = status;
615
616 pj_strerror(status, errmsg, sizeof(errmsg));
617 PJ_LOG(4,(query_job->objname,
618 "DNS SRV resolution failed for %.*s: %s",
619 (int)query_job->full_name.slen,
620 query_job->full_name.ptr,
621 errmsg));
622
623 /* Trigger error when fallback is disabled */
624 if ((query_job->option &
625 (PJ_DNS_SRV_FALLBACK_A | PJ_DNS_SRV_FALLBACK_AAAA)) == 0)
626 {
627 goto on_error;
628 }
629 }
630
631 /* If we can't build SRV record, assume the original target is
632 * an A record and resolve with DNS A resolution.
633 */
634 if (query_job->srv_cnt == 0 && query_job->domain_part.slen > 0) {
635 unsigned new_option = 0;
636
637 /* Looks like we aren't getting any SRV responses.
638 * Resolve the original target as A record by creating a
639 * single "dummy" srv record and start the hostname resolution.
640 */
641 PJ_LOG(4, (query_job->objname,
642 "DNS SRV resolution failed for %.*s, trying "
643 "resolving A/AAAA record for %.*s",
644 (int)query_job->full_name.slen,
645 query_job->full_name.ptr,
646 (int)query_job->domain_part.slen,
647 query_job->domain_part.ptr));
648
649 /* Create a "dummy" srv record using the original target */
650 i = query_job->srv_cnt++;
651 pj_bzero(&query_job->srv[i], sizeof(query_job->srv[i]));
652 query_job->srv[i].target_name = query_job->domain_part;
653 query_job->srv[i].priority = 0;
654 query_job->srv[i].weight = 0;
655 query_job->srv[i].port = query_job->def_port;
656
657 /* Update query_job resolution option based on fallback option */
658 if (query_job->option & PJ_DNS_SRV_FALLBACK_AAAA)
659 new_option |= (PJ_DNS_SRV_RESOLVE_AAAA |
660 PJ_DNS_SRV_RESOLVE_AAAA_ONLY);
661 if (query_job->option & PJ_DNS_SRV_FALLBACK_A)
662 new_option &= (~PJ_DNS_SRV_RESOLVE_AAAA_ONLY);
663
664 query_job->option = new_option;
665 }
666
667
668 /* Resolve server hostnames (DNS A/AAAA record) for hosts which
669 * don't have A/AAAA record yet.
670 */
671 if (query_job->host_resolved != query_job->srv_cnt) {
672 status = resolve_hostnames(query_job);
673 if (status != PJ_SUCCESS)
674 goto on_error;
675
676 /* Must return now. Callback may have been called and query_job
677 * may have been destroyed.
678 */
679 return;
680 }
681
682 } else if (query_job->dns_state == PJ_DNS_TYPE_A) {
683 pj_bool_t is_type_a, srv_completed;
684 pj_dns_addr_record rec;
685
686 /* Avoid warning: potentially uninitialized local variable 'rec' */
687 rec.alias.slen = 0;
688 rec.addr_count = 0;
689
690 /* Clear outstanding job */
691 if (common->type == PJ_DNS_TYPE_A) {
692 srv_completed = (srv->q_aaaa == NULL);
693 srv->q_a = NULL;
694 } else if (common->type == PJ_DNS_TYPE_AAAA) {
695 srv_completed = (srv->q_a == NULL);
696 srv->q_aaaa = NULL;
697 } else {
698 pj_assert(!"Unexpected job type");
699 query_job->last_error = status = PJ_EINVALIDOP;
700 goto on_error;
701 }
702
703 is_type_a = (common->type == PJ_DNS_TYPE_A);
704
705 /* Parse response */
706 if (status==PJ_SUCCESS && pkt->hdr.anscount != 0) {
707 status = pj_dns_parse_addr_response(pkt, &rec);
708 if (status!=PJ_SUCCESS) {
709 PJ_PERROR(4,(query_job->objname, status,
710 "DNS %s record parse error for '%.*s'.",
711 (is_type_a ? "A" : "AAAA"),
712 (int)query_job->domain_part.slen,
713 query_job->domain_part.ptr));
714 }
715 }
716
717 /* Check that we really have answer */
718 if (status==PJ_SUCCESS && pkt->hdr.anscount != 0) {
719 char addr[PJ_INET6_ADDRSTRLEN];
720
721 pj_assert(rec.addr_count != 0);
722
723 /* Update CNAME alias, if present. */
724 if (srv->cname.slen==0 && rec.alias.slen) {
725 pj_assert(rec.alias.slen <= (int)sizeof(srv->cname_buf));
726 srv->cname.ptr = srv->cname_buf;
727 pj_strcpy(&srv->cname, &rec.alias);
728 //} else {
729 //srv->cname.slen = 0;
730 }
731
732 /* Update IP address of the corresponding hostname or CNAME */
733 for (i=0; i<rec.addr_count && srv->addr_cnt<ADDR_MAX_COUNT; ++i)
734 {
735 pj_bool_t added = PJ_FALSE;
736
737 if (is_type_a && rec.addr[i].af == pj_AF_INET()) {
738 pj_sockaddr_init(pj_AF_INET(), &srv->addr[srv->addr_cnt],
739 NULL, srv->port);
740 srv->addr[srv->addr_cnt].ipv4.sin_addr =
741 rec.addr[i].ip.v4;
742 added = PJ_TRUE;
743 } else if (!is_type_a && rec.addr[i].af == pj_AF_INET6()) {
744 pj_sockaddr_init(pj_AF_INET6(), &srv->addr[srv->addr_cnt],
745 NULL, srv->port);
746 srv->addr[srv->addr_cnt].ipv6.sin6_addr =
747 rec.addr[i].ip.v6;
748 added = PJ_TRUE;
749 } else {
750 /* Mismatched address family, e.g: getting IPv6 address in
751 * DNS A query resolution.
752 */
753 PJ_LOG(4,(query_job->objname,
754 "Bad address family in DNS %s query for %.*s",
755 (is_type_a? "A" : "AAAA"),
756 (int)srv->target_name.slen,
757 srv->target_name.ptr));
758 }
759
760 if (added) {
761 PJ_LOG(5,(query_job->objname,
762 "DNS %s for %.*s: %s",
763 (is_type_a? "A" : "AAAA"),
764 (int)srv->target_name.slen,
765 srv->target_name.ptr,
766 pj_sockaddr_print(&srv->addr[srv->addr_cnt],
767 addr, sizeof(addr), 2)));
768
769 ++srv->addr_cnt;
770 }
771 }
772
773 } else if (status != PJ_SUCCESS) {
774 /* Update last error */
775 query_job->last_error = status;
776
777 /* Log error */
778 PJ_PERROR(4,(query_job->objname, status,
779 "DNS %s record resolution failed",
780 (is_type_a? "A" : "AAAA")));
781 }
782
783 /* Increment host resolved count when both DNS A and AAAA record
784 * queries for this server are completed.
785 */
786 if (srv_completed)
787 ++query_job->host_resolved;
788
789 } else {
790 pj_assert(!"Unexpected state!");
791 query_job->last_error = status = PJ_EINVALIDOP;
792 goto on_error;
793 }
794
795 /* Check if all hosts have been resolved */
796 if (query_job->host_resolved == query_job->srv_cnt) {
797 /* Got all answers, build server addresses */
798 pj_dns_srv_record srv_rec;
799
800 srv_rec.count = 0;
801 for (i=0; i<query_job->srv_cnt; ++i) {
802 unsigned j;
803 struct srv_target *srv2 = &query_job->srv[i];
804 pj_dns_addr_record *s = &srv_rec.entry[srv_rec.count].server;
805
806 srv_rec.entry[srv_rec.count].priority = srv2->priority;
807 srv_rec.entry[srv_rec.count].weight = srv2->weight;
808 srv_rec.entry[srv_rec.count].port = (pj_uint16_t)srv2->port ;
809
810 srv_rec.entry[srv_rec.count].server.name = srv2->target_name;
811 srv_rec.entry[srv_rec.count].server.alias = srv2->cname;
812 srv_rec.entry[srv_rec.count].server.addr_count = 0;
813
814 pj_assert(srv2->addr_cnt <= PJ_DNS_MAX_IP_IN_A_REC);
815
816 for (j=0; j<srv2->addr_cnt; ++j) {
817 s->addr[j].af = srv2->addr[j].addr.sa_family;
818 if (s->addr[j].af == pj_AF_INET())
819 s->addr[j].ip.v4 = srv2->addr[j].ipv4.sin_addr;
820 else
821 s->addr[j].ip.v6 = srv2->addr[j].ipv6.sin6_addr;
822 ++s->addr_count;
823 }
824
825 if (srv2->addr_cnt > 0) {
826 ++srv_rec.count;
827 if (srv_rec.count == PJ_DNS_SRV_MAX_ADDR)
828 break;
829 }
830 }
831
832 PJ_LOG(5,(query_job->objname,
833 "Server resolution complete, %d server entry(s) found",
834 srv_rec.count));
835
836
837 if (srv_rec.count > 0)
838 status = PJ_SUCCESS;
839 else {
840 status = query_job->last_error;
841 if (status == PJ_SUCCESS)
842 status = PJLIB_UTIL_EDNSNOANSWERREC;
843 }
844
845 /* Call the callback */
846 (*query_job->cb)(query_job->token, status, &srv_rec);
847 }
848
849
850 return;
851
852 on_error:
853 /* Check for failure */
854 if (status != PJ_SUCCESS) {
855 PJ_PERROR(4,(query_job->objname, status,
856 "DNS %s record resolution error for '%.*s'.",
857 pj_dns_get_type_name(query_job->dns_state),
858 (int)query_job->domain_part.slen,
859 query_job->domain_part.ptr));
860
861 /* Cancel any pending query */
862 pj_dns_srv_cancel_query(query_job, PJ_FALSE);
863
864 (*query_job->cb)(query_job->token, status, NULL);
865 return;
866 }
867 }
868
869
870