blob: 609c56e02b9d53060d6f23f8763437f8fe7d699b [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $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
35struct common
36{
37 pj_dns_type type; /**< Type of this structure.*/
38};
39
40struct srv_target
41{
42 struct common common;
43 pj_dns_srv_async_query *parent;
44 pj_str_t target_name;
45 pj_dns_async_query *q_a;
46 char target_buf[PJ_MAX_HOSTNAME];
47 pj_str_t cname;
48 char cname_buf[PJ_MAX_HOSTNAME];
49 unsigned port;
50 unsigned priority;
51 unsigned weight;
52 unsigned sum;
53 unsigned addr_cnt;
54 pj_in_addr addr[ADDR_MAX_COUNT];
55};
56
57struct pj_dns_srv_async_query
58{
59 struct common common;
60 char *objname;
61
62 pj_dns_type dns_state; /**< DNS type being resolved. */
63 pj_dns_resolver *resolver; /**< Resolver SIP instance. */
64 void *token;
65 pj_dns_async_query *q_srv;
66 pj_dns_srv_resolver_cb *cb;
67 pj_status_t last_error;
68
69 /* Original request: */
70 unsigned option;
71 pj_str_t full_name;
72 pj_str_t domain_part;
73 pj_uint16_t def_port;
74
75 /* SRV records and their resolved IP addresses: */
76 unsigned srv_cnt;
77 struct srv_target srv[PJ_DNS_SRV_MAX_ADDR];
78
79 /* Number of hosts in SRV records that the IP address has been resolved */
80 unsigned host_resolved;
81
82};
83
84
85/* Async resolver callback, forward decl. */
86static void dns_callback(void *user_data,
87 pj_status_t status,
88 pj_dns_parsed_packet *pkt);
89
90
91
92/*
93 * The public API to invoke DNS SRV resolution.
94 */
95PJ_DEF(pj_status_t) pj_dns_srv_resolve( const pj_str_t *domain_name,
96 const pj_str_t *res_name,
97 unsigned def_port,
98 pj_pool_t *pool,
99 pj_dns_resolver *resolver,
100 unsigned option,
101 void *token,
102 pj_dns_srv_resolver_cb *cb,
103 pj_dns_srv_async_query **p_query)
104{
105 pj_size_t len;
106 pj_str_t target_name;
107 pj_dns_srv_async_query *query_job;
108 pj_status_t status;
109
110 PJ_ASSERT_RETURN(domain_name && domain_name->slen &&
111 res_name && res_name->slen &&
112 pool && resolver && cb, PJ_EINVAL);
113
114 /* Build full name */
115 len = domain_name->slen + res_name->slen + 2;
116 target_name.ptr = (char*) pj_pool_alloc(pool, len);
117 pj_strcpy(&target_name, res_name);
118 if (res_name->ptr[res_name->slen-1] != '.')
119 pj_strcat2(&target_name, ".");
120 len = target_name.slen;
121 pj_strcat(&target_name, domain_name);
122 target_name.ptr[target_name.slen] = '\0';
123
124
125 /* Build the query_job state */
126 query_job = PJ_POOL_ZALLOC_T(pool, pj_dns_srv_async_query);
127 query_job->common.type = PJ_DNS_TYPE_SRV;
128 query_job->objname = target_name.ptr;
129 query_job->resolver = resolver;
130 query_job->token = token;
131 query_job->cb = cb;
132 query_job->option = option;
133 query_job->full_name = target_name;
134 query_job->domain_part.ptr = target_name.ptr + len;
135 query_job->domain_part.slen = target_name.slen - len;
136 query_job->def_port = (pj_uint16_t)def_port;
137
138 /* Start the asynchronous query_job */
139
140 query_job->dns_state = PJ_DNS_TYPE_SRV;
141
142 PJ_LOG(5, (query_job->objname,
143 "Starting async DNS %s query_job: target=%.*s:%d",
144 pj_dns_get_type_name(query_job->dns_state),
145 (int)target_name.slen, target_name.ptr,
146 def_port));
147
148 status = pj_dns_resolver_start_query(resolver, &target_name,
149 query_job->dns_state, 0,
150 &dns_callback,
151 query_job, &query_job->q_srv);
152 if (status==PJ_SUCCESS && p_query)
153 *p_query = query_job;
154
155 return status;
156}
157
158
159/*
160 * Cancel pending query.
161 */
162PJ_DEF(pj_status_t) pj_dns_srv_cancel_query(pj_dns_srv_async_query *query,
163 pj_bool_t notify)
164{
165 pj_bool_t has_pending = PJ_FALSE;
166 unsigned i;
167
168 if (query->q_srv) {
169 pj_dns_resolver_cancel_query(query->q_srv, PJ_FALSE);
170 query->q_srv = NULL;
171 has_pending = PJ_TRUE;
172 }
173
174 for (i=0; i<query->srv_cnt; ++i) {
175 struct srv_target *srv = &query->srv[i];
176 if (srv->q_a) {
177 pj_dns_resolver_cancel_query(srv->q_a, PJ_FALSE);
178 srv->q_a = NULL;
179 has_pending = PJ_TRUE;
180 }
181 }
182
183 if (has_pending && notify && query->cb) {
184 (*query->cb)(query->token, PJ_ECANCELLED, NULL);
185 }
186
187 return has_pending? PJ_SUCCESS : PJ_EINVALIDOP;
188}
189
190
191#define SWAP(type,ptr1,ptr2) if (ptr1 != ptr2) { \
192 type tmp; \
193 pj_memcpy(&tmp, ptr1, sizeof(type)); \
194 pj_memcpy(ptr1, ptr2, sizeof(type)); \
195 (ptr1)->target_name.ptr = (ptr1)->target_buf;\
196 pj_memcpy(ptr2, &tmp, sizeof(type)); \
197 (ptr2)->target_name.ptr = (ptr2)->target_buf;\
198 } else {}
199
200
201/* Build server entries in the query_job based on received SRV response */
202static void build_server_entries(pj_dns_srv_async_query *query_job,
203 pj_dns_parsed_packet *response)
204{
205 unsigned i;
206
207 /* Save the Resource Records in DNS answer into SRV targets. */
208 query_job->srv_cnt = 0;
209 for (i=0; i<response->hdr.anscount &&
210 query_job->srv_cnt < PJ_DNS_SRV_MAX_ADDR; ++i)
211 {
212 pj_dns_parsed_rr *rr = &response->ans[i];
213 struct srv_target *srv = &query_job->srv[query_job->srv_cnt];
214
215 if (rr->type != PJ_DNS_TYPE_SRV) {
216 PJ_LOG(4,(query_job->objname,
217 "Received non SRV answer for SRV query_job!"));
218 continue;
219 }
220
221 if (rr->rdata.srv.target.slen > PJ_MAX_HOSTNAME) {
222 PJ_LOG(4,(query_job->objname, "Hostname is too long!"));
223 continue;
224 }
225
226 /* Build the SRV entry for RR */
227 pj_bzero(srv, sizeof(*srv));
228 srv->target_name.ptr = srv->target_buf;
229 pj_strncpy(&srv->target_name, &rr->rdata.srv.target,
230 sizeof(srv->target_buf));
231 srv->port = rr->rdata.srv.port;
232 srv->priority = rr->rdata.srv.prio;
233 srv->weight = rr->rdata.srv.weight;
234
235 ++query_job->srv_cnt;
236 }
237
238 if (query_job->srv_cnt == 0) {
239 PJ_LOG(4,(query_job->objname,
240 "Could not find SRV record in DNS answer!"));
241 return;
242 }
243
244 /* First pass:
245 * order the entries based on priority.
246 */
247 for (i=0; i<query_job->srv_cnt-1; ++i) {
248 unsigned min = i, j;
249 for (j=i+1; j<query_job->srv_cnt; ++j) {
250 if (query_job->srv[j].priority < query_job->srv[min].priority)
251 min = j;
252 }
253 SWAP(struct srv_target, &query_job->srv[i], &query_job->srv[min]);
254 }
255
256 /* Second pass:
257 * pick one host among hosts with the same priority, according
258 * to its weight. The idea is when one server fails, client should
259 * contact the next server with higher priority rather than contacting
260 * server with the same priority as the failed one.
261 *
262 * The algorithm for selecting server among servers with the same
263 * priority is described in RFC 2782.
264 */
265 for (i=0; i<query_job->srv_cnt; ++i) {
266 unsigned j, count=1, sum;
267
268 /* Calculate running sum for servers with the same priority */
269 sum = query_job->srv[i].sum = query_job->srv[i].weight;
270 for (j=i+1; j<query_job->srv_cnt &&
271 query_job->srv[j].priority == query_job->srv[i].priority; ++j)
272 {
273 sum += query_job->srv[j].weight;
274 query_job->srv[j].sum = sum;
275 ++count;
276 }
277
278 if (count > 1) {
279 unsigned r;
280
281 /* Elect one random number between zero and the total sum of
282 * weight (inclusive).
283 */
284 r = pj_rand() % (sum + 1);
285
286 /* Select the first server which running sum is greater than or
287 * equal to the random number.
288 */
289 for (j=i; j<i+count; ++j) {
290 if (query_job->srv[j].sum >= r)
291 break;
292 }
293
294 /* Must have selected one! */
295 pj_assert(j != i+count);
296
297 /* Put this entry in front (of entries with same priority) */
298 SWAP(struct srv_target, &query_job->srv[i], &query_job->srv[j]);
299
300 /* Remove all other entries (of the same priority) */
301 while (count > 1) {
302 pj_array_erase(query_job->srv, sizeof(struct srv_target),
303 query_job->srv_cnt, i+1);
304 --count;
305 --query_job->srv_cnt;
306 }
307 }
308 }
309
310 /* Since we've been moving around SRV entries, update the pointers
311 * in target_name.
312 */
313 for (i=0; i<query_job->srv_cnt; ++i) {
314 query_job->srv[i].target_name.ptr = query_job->srv[i].target_buf;
315 }
316
317 /* Check for Additional Info section if A records are available, and
318 * fill in the IP address (so that we won't need to resolve the A
319 * record with another DNS query_job).
320 */
321 for (i=0; i<response->hdr.arcount; ++i) {
322 pj_dns_parsed_rr *rr = &response->arr[i];
323 unsigned j;
324
325 if (rr->type != PJ_DNS_TYPE_A)
326 continue;
327
328 /* Yippeaiyee!! There is an "A" record!
329 * Update the IP address of the corresponding SRV record.
330 */
331 for (j=0; j<query_job->srv_cnt; ++j) {
332 if (pj_stricmp(&rr->name, &query_job->srv[j].target_name)==0) {
333 unsigned cnt = query_job->srv[j].addr_cnt;
334 query_job->srv[j].addr[cnt].s_addr = rr->rdata.a.ip_addr.s_addr;
335 /* Only increment host_resolved once per SRV record */
336 if (query_job->srv[j].addr_cnt == 0)
337 ++query_job->host_resolved;
338 ++query_job->srv[j].addr_cnt;
339 break;
340 }
341 }
342
343 /* Not valid message; SRV entry might have been deleted in
344 * server selection process.
345 */
346 /*
347 if (j == query_job->srv_cnt) {
348 PJ_LOG(4,(query_job->objname,
349 "Received DNS SRV answer with A record, but "
350 "couldn't find matching name (name=%.*s)",
351 (int)rr->name.slen,
352 rr->name.ptr));
353 }
354 */
355 }
356
357 /* Rescan again the name specified in the SRV record to see if IP
358 * address is specified as the target name (unlikely, but well, who
359 * knows..).
360 */
361 for (i=0; i<query_job->srv_cnt; ++i) {
362 pj_in_addr addr;
363
364 if (query_job->srv[i].addr_cnt != 0) {
365 /* IP address already resolved */
366 continue;
367 }
368
369 if (pj_inet_aton(&query_job->srv[i].target_name, &addr) != 0) {
370 query_job->srv[i].addr[query_job->srv[i].addr_cnt++] = addr;
371 ++query_job->host_resolved;
372 }
373 }
374
375 /* Print resolved entries to the log */
376 PJ_LOG(5,(query_job->objname,
377 "SRV query_job for %.*s completed, "
378 "%d of %d total entries selected%c",
379 (int)query_job->full_name.slen,
380 query_job->full_name.ptr,
381 query_job->srv_cnt,
382 response->hdr.anscount,
383 (query_job->srv_cnt ? ':' : ' ')));
384
385 for (i=0; i<query_job->srv_cnt; ++i) {
386 const char *addr;
387
388 if (query_job->srv[i].addr_cnt != 0)
389 addr = pj_inet_ntoa(query_job->srv[i].addr[0]);
390 else
391 addr = "-";
392
393 PJ_LOG(5,(query_job->objname,
394 " %d: SRV %d %d %d %.*s (%s)",
395 i, query_job->srv[i].priority,
396 query_job->srv[i].weight,
397 query_job->srv[i].port,
398 (int)query_job->srv[i].target_name.slen,
399 query_job->srv[i].target_name.ptr,
400 addr));
401 }
402}
403
404
405/* Start DNS A record queries for all SRV records in the query_job structure */
406static pj_status_t resolve_hostnames(pj_dns_srv_async_query *query_job)
407{
408 unsigned i;
409 pj_status_t err=PJ_SUCCESS, status;
410
411 query_job->dns_state = PJ_DNS_TYPE_A;
412 for (i=0; i<query_job->srv_cnt; ++i) {
413 struct srv_target *srv = &query_job->srv[i];
414
415 PJ_LOG(5, (query_job->objname,
416 "Starting async DNS A query_job for %.*s",
417 (int)srv->target_name.slen,
418 srv->target_name.ptr));
419
420 srv->common.type = PJ_DNS_TYPE_A;
421 srv->parent = query_job;
422
423 status = pj_dns_resolver_start_query(query_job->resolver,
424 &srv->target_name,
425 PJ_DNS_TYPE_A, 0,
426 &dns_callback,
427 srv, &srv->q_a);
428 if (status != PJ_SUCCESS) {
429 query_job->host_resolved++;
430 err = status;
431 }
432 }
433
434 return (query_job->host_resolved == query_job->srv_cnt) ? err : PJ_SUCCESS;
435}
436
437/*
438 * This callback is called by PJLIB-UTIL DNS resolver when asynchronous
439 * query_job has completed (successfully or with error).
440 */
441static void dns_callback(void *user_data,
442 pj_status_t status,
443 pj_dns_parsed_packet *pkt)
444{
445 struct common *common = (struct common*) user_data;
446 pj_dns_srv_async_query *query_job;
447 struct srv_target *srv = NULL;
448 unsigned i;
449
450 if (common->type == PJ_DNS_TYPE_SRV) {
451 query_job = (pj_dns_srv_async_query*) common;
452 srv = NULL;
453 } else if (common->type == PJ_DNS_TYPE_A) {
454 srv = (struct srv_target*) common;
455 query_job = srv->parent;
456 } else {
457 pj_assert(!"Unexpected user data!");
458 return;
459 }
460
461 /* Proceed to next stage */
462 if (query_job->dns_state == PJ_DNS_TYPE_SRV) {
463
464 /* We are getting SRV response */
465
466 query_job->q_srv = NULL;
467
468 if (status == PJ_SUCCESS && pkt->hdr.anscount != 0) {
469 /* Got SRV response, build server entry. If A records are available
470 * in additional records section of the DNS response, save them too.
471 */
472 build_server_entries(query_job, pkt);
473
474 } else if (status != PJ_SUCCESS) {
475 char errmsg[PJ_ERR_MSG_SIZE];
476
477 /* Update query_job last error */
478 query_job->last_error = status;
479
480 pj_strerror(status, errmsg, sizeof(errmsg));
481 PJ_LOG(4,(query_job->objname,
482 "DNS SRV resolution failed for %.*s: %s",
483 (int)query_job->full_name.slen,
484 query_job->full_name.ptr,
485 errmsg));
486
487 /* Trigger error when fallback is disabled */
488 if ((query_job->option &
489 (PJ_DNS_SRV_FALLBACK_A | PJ_DNS_SRV_FALLBACK_AAAA)) == 0)
490 {
491 goto on_error;
492 }
493 }
494
495 /* If we can't build SRV record, assume the original target is
496 * an A record and resolve with DNS A resolution.
497 */
498 if (query_job->srv_cnt == 0) {
499 /* Looks like we aren't getting any SRV responses.
500 * Resolve the original target as A record by creating a
501 * single "dummy" srv record and start the hostname resolution.
502 */
503 PJ_LOG(4, (query_job->objname,
504 "DNS SRV resolution failed for %.*s, trying "
505 "resolving A record for %.*s",
506 (int)query_job->full_name.slen,
507 query_job->full_name.ptr,
508 (int)query_job->domain_part.slen,
509 query_job->domain_part.ptr));
510
511 /* Create a "dummy" srv record using the original target */
512 i = query_job->srv_cnt++;
513 pj_bzero(&query_job->srv[i], sizeof(query_job->srv[i]));
514 query_job->srv[i].target_name = query_job->domain_part;
515 query_job->srv[i].priority = 0;
516 query_job->srv[i].weight = 0;
517 query_job->srv[i].port = query_job->def_port;
518 }
519
520
521 /* Resolve server hostnames (DNS A record) for hosts which don't have
522 * A record yet.
523 */
524 if (query_job->host_resolved != query_job->srv_cnt) {
525 status = resolve_hostnames(query_job);
526 if (status != PJ_SUCCESS)
527 goto on_error;
528
529 /* Must return now. Callback may have been called and query_job
530 * may have been destroyed.
531 */
532 return;
533 }
534
535 } else if (query_job->dns_state == PJ_DNS_TYPE_A) {
536
537 /* Clear the outstanding job */
538 srv->q_a = NULL;
539
540 /* Check that we really have answer */
541 if (status==PJ_SUCCESS && pkt->hdr.anscount != 0) {
542 pj_dns_a_record rec;
543
544 /* Parse response */
545 status = pj_dns_parse_a_response(pkt, &rec);
546 if (status != PJ_SUCCESS)
547 goto on_error;
548
549 pj_assert(rec.addr_count != 0);
550
551 /* Update CNAME alias, if present. */
552 if (rec.alias.slen) {
553 pj_assert(rec.alias.slen <= (int)sizeof(srv->cname_buf));
554 srv->cname.ptr = srv->cname_buf;
555 pj_strcpy(&srv->cname, &rec.alias);
556 } else {
557 srv->cname.slen = 0;
558 }
559
560 /* Update IP address of the corresponding hostname or CNAME */
561 if (srv->addr_cnt < ADDR_MAX_COUNT) {
562 srv->addr[srv->addr_cnt++].s_addr = rec.addr[0].s_addr;
563
564 PJ_LOG(5,(query_job->objname,
565 "DNS A for %.*s: %s",
566 (int)srv->target_name.slen,
567 srv->target_name.ptr,
568 pj_inet_ntoa(rec.addr[0])));
569 }
570
571 /* Check for multiple IP addresses */
572 for (i=1; i<rec.addr_count && srv->addr_cnt < ADDR_MAX_COUNT; ++i)
573 {
574 srv->addr[srv->addr_cnt++].s_addr = rec.addr[i].s_addr;
575
576 PJ_LOG(5,(query_job->objname,
577 "Additional DNS A for %.*s: %s",
578 (int)srv->target_name.slen,
579 srv->target_name.ptr,
580 pj_inet_ntoa(rec.addr[i])));
581 }
582
583 } else if (status != PJ_SUCCESS) {
584 char errmsg[PJ_ERR_MSG_SIZE];
585
586 /* Update last error */
587 query_job->last_error = status;
588
589 /* Log error */
590 pj_strerror(status, errmsg, sizeof(errmsg));
591 PJ_LOG(4,(query_job->objname, "DNS A record resolution failed: %s",
592 errmsg));
593 }
594
595 ++query_job->host_resolved;
596
597 } else {
598 pj_assert(!"Unexpected state!");
599 query_job->last_error = status = PJ_EINVALIDOP;
600 goto on_error;
601 }
602
603 /* Check if all hosts have been resolved */
604 if (query_job->host_resolved == query_job->srv_cnt) {
605 /* Got all answers, build server addresses */
606 pj_dns_srv_record srv_rec;
607
608 srv_rec.count = 0;
609 for (i=0; i<query_job->srv_cnt; ++i) {
610 unsigned j;
611 struct srv_target *srv = &query_job->srv[i];
612
613 srv_rec.entry[srv_rec.count].priority = srv->priority;
614 srv_rec.entry[srv_rec.count].weight = srv->weight;
615 srv_rec.entry[srv_rec.count].port = (pj_uint16_t)srv->port ;
616
617 srv_rec.entry[srv_rec.count].server.name = srv->target_name;
618 srv_rec.entry[srv_rec.count].server.alias = srv->cname;
619 srv_rec.entry[srv_rec.count].server.addr_count = 0;
620
621 pj_assert(srv->addr_cnt <= PJ_DNS_MAX_IP_IN_A_REC);
622
623 for (j=0; j<srv->addr_cnt; ++j) {
624 srv_rec.entry[srv_rec.count].server.addr[j].s_addr =
625 srv->addr[j].s_addr;
626 ++srv_rec.entry[srv_rec.count].server.addr_count;
627 }
628
629 if (srv->addr_cnt > 0) {
630 ++srv_rec.count;
631 if (srv_rec.count == PJ_DNS_SRV_MAX_ADDR)
632 break;
633 }
634 }
635
636 PJ_LOG(5,(query_job->objname,
637 "Server resolution complete, %d server entry(s) found",
638 srv_rec.count));
639
640
641 if (srv_rec.count > 0)
642 status = PJ_SUCCESS;
643 else {
644 status = query_job->last_error;
645 if (status == PJ_SUCCESS)
646 status = PJLIB_UTIL_EDNSNOANSWERREC;
647 }
648
649 /* Call the callback */
650 (*query_job->cb)(query_job->token, status, &srv_rec);
651 }
652
653
654 return;
655
656on_error:
657 /* Check for failure */
658 if (status != PJ_SUCCESS) {
659 char errmsg[PJ_ERR_MSG_SIZE];
660 PJ_UNUSED_ARG(errmsg);
661 PJ_LOG(4,(query_job->objname,
662 "DNS %s record resolution error for '%.*s'."
663 " Err=%d (%s)",
664 pj_dns_get_type_name(query_job->dns_state),
665 (int)query_job->domain_part.slen,
666 query_job->domain_part.ptr,
667 status,
668 pj_strerror(status,errmsg,sizeof(errmsg)).ptr));
669 (*query_job->cb)(query_job->token, status, NULL);
670 return;
671 }
672}
673
674