2 Copyright (c) 2003-2006 by Juliusz Chroboczek
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 #ifndef NO_STANDARD_RESOLVER
26 #ifndef NO_FANCY_RESOLVER
27 int dnsUseGethostbyname
= 1;
29 const int dnsUseGethostbyname
= 3;
32 #ifndef NO_FANCY_RESOLVER
33 const int dnsUseGethostbyname
= 0;
35 #error use no resolver at all?
39 #ifndef NO_FANCY_RESOLVER
40 AtomPtr dnsNameServer
= NULL
;
41 int dnsMaxTimeout
= 60;
44 #ifndef NO_STANDARD_RESOLVER
45 int dnsGethostbynameTtl
= 1200;
48 int dnsNegativeTtl
= 120;
53 const int dnsQueryIPv6
= 0;
56 typedef struct _DnsQuery
{
64 TimeEventHandlerPtr timeout_handler
;
65 struct _DnsQuery
*next
;
66 } DnsQueryRec
, *DnsQueryPtr
;
70 struct sockaddr_in sin
;
72 struct sockaddr_in6 sin6
;
74 } nameserverAddress_storage
;
76 #ifndef NO_FANCY_RESOLVER
77 static AtomPtr atomLocalhost
, atomLocalhostDot
;
79 #define nameserverAddress nameserverAddress_storage.sa
81 static DnsQueryPtr inFlightDnsQueries
;
82 static DnsQueryPtr inFlightDnsQueriesLast
;
85 static int really_do_gethostbyname(AtomPtr name
, ObjectPtr object
);
86 static int really_do_dns(AtomPtr name
, ObjectPtr object
);
88 #ifndef NO_FANCY_RESOLVER
89 static int stringToLabels(char *buf
, int offset
, int n
, char *string
);
90 static int labelsToString(char *buf
, int offset
, int n
, char *d
,
91 int m
, int *j_return
);
92 static int dnsBuildQuery(int id
, char *buf
, int offset
, int n
,
93 AtomPtr name
, int af
);
94 static int dnsReplyHandler(int abort
, FdEventHandlerPtr event
);
95 static int dnsReplyId(char *buf
, int offset
, int n
, int *id_return
);
96 static int dnsDecodeReply(char *buf
, int offset
, int n
,
98 AtomPtr
*name_return
, AtomPtr
*value_return
,
99 int *af_return
, unsigned *ttl_return
);
100 static int dnsHandler(int status
, ConditionHandlerPtr chandler
);
101 static int dnsGethostbynameFallback(int id
, AtomPtr message
);
102 static int sendQuery(DnsQueryPtr query
);
107 #ifndef NO_FANCY_RESOLVER
109 parseResolvConf(char *filename
)
115 AtomPtr nameserver
= NULL
;
117 f
= fopen(filename
, "r");
119 do_log_error(L_ERROR
, errno
, "DNS: couldn't open %s", filename
);
124 p
= fgets(buf
, 512, f
);
129 if(buf
[n
- 1] != '\n') {
131 do_log(L_WARN
, "DNS: overly long line in %s -- skipping.\n",
142 while(*p
== ' ' || *p
== '\t')
144 if(strcasecmp_n("nameserver", p
, 10) != 0)
147 while(*p
== ' ' || *p
== '\t')
150 while(*q
== '.' || *q
== ':' || digit(*q
) || letter(*q
))
152 if(*q
!= ' ' && *q
!= '\t' && *q
!= '\r' && *q
!= '\n') {
153 do_log(L_WARN
, "DNS: couldn't parse line in %s -- skipping.\n",
157 nameserver
= internAtomLowerN(p
, q
- p
);
163 dnsNameServer
= nameserver
;
178 assert(sizeof(struct in_addr
) == 4);
180 assert(sizeof(struct in6_addr
) == 16);
183 #ifndef NO_STANDARD_RESOLVER
184 CONFIG_VARIABLE(dnsGethostbynameTtl
, CONFIG_TIME
,
185 "TTL for gethostbyname addresses.");
189 fd
= socket(PF_INET6
, SOCK_STREAM
, 0);
191 if(errno
== EPROTONOSUPPORT
|| errno
== EAFNOSUPPORT
) {
194 do_log_error(L_WARN
, errno
, "DNS: couldn't create socket");
201 #ifndef NO_FANCY_RESOLVER
202 parseResolvConf("/etc/resolv.conf");
203 if(dnsNameServer
== NULL
|| dnsNameServer
->string
[0] == '\0')
204 dnsNameServer
= internAtom("127.0.0.1");
205 CONFIG_VARIABLE(dnsMaxTimeout
, CONFIG_TIME
,
206 "Max timeout for DNS queries.");
207 CONFIG_VARIABLE(dnsNegativeTtl
, CONFIG_TIME
,
208 "TTL for negative DNS replies with no TTL.");
209 CONFIG_VARIABLE(dnsNameServer
, CONFIG_ATOM_LOWER
,
210 "The name server to use.");
211 #ifndef NO_STANDARD_RESOLVER
212 CONFIG_VARIABLE(dnsUseGethostbyname
, CONFIG_TETRASTATE
,
213 "Use the system resolver.");
218 CONFIG_VARIABLE(dnsQueryIPv6
, CONFIG_TETRASTATE
,
219 "Query for IPv6 addresses.");
226 #ifndef NO_FANCY_RESOLVER
229 struct sockaddr_in
*sin
= (struct sockaddr_in
*)&nameserverAddress
;
231 struct sockaddr_in6
*sin6
= (struct sockaddr_in6
*)&nameserverAddress
;
234 atomLocalhost
= internAtom("localhost");
235 atomLocalhostDot
= internAtom("localhost.");
236 inFlightDnsQueries
= NULL
;
237 inFlightDnsQueriesLast
= NULL
;
239 gettimeofday(&t
, NULL
);
240 idSeed
= t
.tv_usec
& 0xFFFF;
241 sin
->sin_family
= AF_INET
;
242 sin
->sin_port
= htons(53);
243 rc
= inet_aton(dnsNameServer
->string
, &sin
->sin_addr
);
246 sin6
->sin6_family
= AF_INET6
;
247 sin6
->sin6_port
= htons(53);
248 rc
= inet_pton(AF_INET6
, dnsNameServer
->string
, &sin6
->sin6_addr
);
252 do_log(L_ERROR
, "DNS: couldn't parse name server %s.\n",
253 dnsNameServer
->string
);
260 do_gethostbyname(char *origname
,
262 int (*handler
)(int, GethostbynameRequestPtr
),
266 int n
= strlen(origname
);
268 GethostbynameRequestRec request
;
271 memset(&request
, 0, sizeof(request
));
274 request
.error_message
= NULL
;
275 request
.count
= count
;
276 request
.handler
= handler
;
279 if(n
<= 0 || n
> 131) {
281 request
.error_message
= internAtom("empty name");
282 do_log(L_ERROR
, "Empty DNS name.\n");
283 done
= handler(-EINVAL
, &request
);
285 request
.error_message
= internAtom("name too long");
286 do_log(L_ERROR
, "DNS name too long.\n");
287 done
= handler(-ENAMETOOLONG
, &request
);
290 releaseAtom(request
.error_message
);
294 if(origname
[n
- 1] == '.')
297 name
= internAtomLowerN(origname
, n
);
300 request
.error_message
= internAtom("couldn't allocate name");
301 do_log(L_ERROR
, "Couldn't allocate DNS name.\n");
302 done
= handler(-ENOMEM
, &request
);
304 releaseAtom(request
.error_message
);
310 request
.error_message
= NULL
;
311 request
.count
= count
;
312 request
.object
= NULL
;
313 request
.handler
= handler
;
316 object
= findObject(OBJECT_DNS
, name
->string
, name
->length
);
317 if(object
== NULL
|| objectMustRevalidate(object
, NULL
)) {
319 privatiseObject(object
, 0);
320 releaseObject(object
);
322 object
= makeObject(OBJECT_DNS
, name
->string
, name
->length
, 1, 0,
325 request
.error_message
= internAtom("Couldn't allocate object");
326 do_log(L_ERROR
, "Couldn't allocate DNS object.\n");
327 done
= handler(-ENOMEM
, &request
);
330 releaseAtom(request
.error_message
);
335 if((object
->flags
& (OBJECT_INITIAL
| OBJECT_INPROGRESS
)) ==
337 if(dnsUseGethostbyname
>= 3)
338 rc
= really_do_gethostbyname(name
, object
);
340 rc
= really_do_dns(name
, object
);
342 assert(!(object
->flags
& (OBJECT_INITIAL
| OBJECT_INPROGRESS
)));
347 if(dnsUseGethostbyname
>= 3)
348 assert(!(object
->flags
& OBJECT_INITIAL
));
350 #ifndef NO_FANCY_RESOLVER
351 if(object
->flags
& OBJECT_INITIAL
) {
352 ConditionHandlerPtr chandler
;
353 assert(object
->flags
& OBJECT_INPROGRESS
);
354 request
.object
= object
;
355 chandler
= conditionWait(&object
->condition
, dnsHandler
,
356 sizeof(request
), &request
);
357 if(chandler
== NULL
) {
365 if(object
->headers
&& object
->headers
->length
> 0) {
366 if(object
->headers
->string
[0] == DNS_A
)
367 assert(((object
->headers
->length
- 1) %
368 sizeof(HostAddressRec
)) == 0);
370 assert(object
->headers
->string
[0] == DNS_CNAME
);
371 request
.addr
= retainAtom(object
->headers
);
372 } else if(object
->message
) {
373 request
.error_message
= retainAtom(object
->message
);
376 releaseObject(object
);
378 if(request
.addr
&& request
.addr
->length
> 0)
379 done
= handler(1, &request
);
381 done
= handler(-EDNS_HOST_NOT_FOUND
, &request
);
384 releaseAtom(request
.addr
); request
.addr
= NULL
;
385 releaseAtom(request
.name
); request
.name
= NULL
;
386 releaseAtom(request
.error_message
); request
.error_message
= NULL
;
390 releaseNotifyObject(object
);
391 done
= handler(-errno
, &request
);
398 dnsDelayedErrorNotifyHandler(TimeEventHandlerPtr event
)
401 GethostbynameRequestRec request
=
402 *(GethostbynameRequestPtr
)event
->data
;
403 done
= request
.handler(-EDNS_HOST_NOT_FOUND
, &request
);
405 releaseAtom(request
.name
); request
.name
= NULL
;
406 releaseAtom(request
.addr
); request
.addr
= NULL
;
407 releaseAtom(request
.error_message
); request
.error_message
= NULL
;
412 dnsDelayedDoneNotifyHandler(TimeEventHandlerPtr event
)
415 GethostbynameRequestRec request
= *(GethostbynameRequestPtr
)event
->data
;
416 done
= request
.handler(1, &request
);
418 releaseAtom(request
.name
); request
.name
= NULL
;
419 releaseAtom(request
.addr
); request
.addr
= NULL
;
420 releaseAtom(request
.error_message
); request
.error_message
= NULL
;
425 dnsDelayedNotify(int error
, GethostbynameRequestPtr request
)
427 TimeEventHandlerPtr handler
;
430 handler
= scheduleTimeEvent(0,
431 dnsDelayedErrorNotifyHandler
,
432 sizeof(*request
), request
);
434 handler
= scheduleTimeEvent(0,
435 dnsDelayedDoneNotifyHandler
,
436 sizeof(*request
), request
);
437 if(handler
== NULL
) {
438 do_log(L_ERROR
, "Couldn't schedule DNS notification.\n");
446 rfc2732(AtomPtr name
)
452 if(name
->length
< 38 &&
453 name
->string
[0] == '[' && name
->string
[name
->length
- 1] == ']') {
454 struct in6_addr in6a
;
455 memcpy(buf
, name
->string
+ 1, name
->length
- 2);
456 buf
[name
->length
- 2] = '\0';
457 rc
= inet_pton(AF_INET6
, buf
, &in6a
);
459 char s
[1 + sizeof(HostAddressRec
)];
460 memset(s
, 0, sizeof(s
));
463 memcpy(s
+ 2, &in6a
, 16);
464 a
= internAtomN(s
, 1 + sizeof(HostAddressRec
));
472 /* Used for sorting host addresses depending on the value of dnsQueryIPv6 */
474 compare_hostaddr(const void *av
, const void *bv
)
476 const HostAddressRec
*a
= av
, *b
= bv
;
489 if(dnsQueryIPv6
>= 2)
495 #ifndef NO_STANDARD_RESOLVER
497 really_do_gethostbyname(AtomPtr name
, ObjectPtr object
)
499 struct addrinfo
*ai
, *entry
, hints
;
508 object
->age
= current_time
.tv_sec
;
509 object
->expires
= current_time
.tv_sec
+ 240;
510 object
->flags
&= ~(OBJECT_INITIAL
| OBJECT_INPROGRESS
);
511 notifyObject(object
);
515 memset(&hints
, 0, sizeof(hints
));
516 hints
.ai_protocol
= IPPROTO_TCP
;
517 if(dnsQueryIPv6
<= 0)
518 hints
.ai_family
= AF_INET
;
519 else if(dnsQueryIPv6
>= 3)
520 hints
.ai_family
= AF_INET6
;
522 rc
= getaddrinfo(name
->string
, NULL
, &hints
, &ai
);
525 case 0: error
= 0; break;
527 #ifdef EAI_ADDRFAMILY
531 error
= EAFNOSUPPORT
; break;
532 case EAI_BADFLAGS
: error
= EINVAL
; break;
533 case EAI_SERVICE
: error
= EDNS_NO_RECOVERY
; break;
540 error
= EDNS_NO_ADDRESS
; break;
541 case EAI_FAIL
: error
= EDNS_NO_RECOVERY
; break;
542 case EAI_AGAIN
: error
= EDNS_TRY_AGAIN
; break;
544 case EAI_MEMORY
: error
= ENOMEM
; break;
546 case EAI_SYSTEM
: error
= errno
; break;
547 default: error
= EUNKNOWN
;
550 if(error
== EDNS_NO_ADDRESS
) {
551 object
->headers
= NULL
;
552 object
->age
= current_time
.tv_sec
;
553 object
->expires
= current_time
.tv_sec
+ dnsNegativeTtl
;
554 object
->flags
&= ~(OBJECT_INITIAL
| OBJECT_INPROGRESS
);
555 notifyObject(object
);
558 do_log_error(L_ERROR
, error
, "Getaddrinfo failed");
559 object
->flags
&= ~OBJECT_INPROGRESS
;
560 abortObject(object
, 404,
561 internAtomError(error
, "Getaddrinfo failed"));
562 notifyObject(object
);
572 if(entry
->ai_family
== AF_INET
&& entry
->ai_protocol
== IPPROTO_TCP
) {
573 if(dnsQueryIPv6
< 3) {
575 memset(host
.data
, 0, sizeof(host
.data
));
577 &((struct sockaddr_in
*)entry
->ai_addr
)->sin_addr
,
581 } else if(entry
->ai_family
== AF_INET6
&&
582 entry
->ai_protocol
== IPPROTO_TCP
) {
583 if(dnsQueryIPv6
> 0) {
585 memset(&host
.data
, 0, sizeof(host
.data
));
587 &((struct sockaddr_in6
*)entry
->ai_addr
)->sin6_addr
,
593 if(i
>= 1024 / sizeof(HostAddressRec
) - 2) {
594 do_log(L_ERROR
, "Too many addresses for host %s\n",
598 memcpy(buf
+ 1 + i
* sizeof(HostAddressRec
),
599 &host
, sizeof(HostAddressRec
));
602 entry
= entry
->ai_next
;
606 do_log(L_ERROR
, "Getaddrinfo returned no useful addresses\n");
607 object
->flags
&= ~OBJECT_INPROGRESS
;
608 abortObject(object
, 404,
609 internAtom("Getaddrinfo returned no useful addresses"));
610 notifyObject(object
);
614 if(1 <= dnsQueryIPv6
&& dnsQueryIPv6
<= 2)
615 qsort(buf
+ 1, i
, sizeof(HostAddressRec
), compare_hostaddr
);
617 a
= internAtomN(buf
, 1 + i
* sizeof(HostAddressRec
));
619 object
->flags
&= ~OBJECT_INPROGRESS
;
620 abortObject(object
, 501, internAtom("Couldn't allocate address"));
621 notifyObject(object
);
625 object
->age
= current_time
.tv_sec
;
626 object
->expires
= current_time
.tv_sec
+ dnsGethostbynameTtl
;
627 object
->flags
&= ~(OBJECT_INITIAL
| OBJECT_INPROGRESS
);
628 notifyObject(object
);
635 #ifndef NO_STANDARD_RESOLVER
637 really_do_gethostbyname(AtomPtr name
, ObjectPtr object
)
639 struct hostent
*host
;
645 host
= gethostbyname(name
->string
);
648 case HOST_NOT_FOUND
: error
= EDNS_HOST_NOT_FOUND
; break;
649 case NO_ADDRESS
: error
= EDNS_NO_ADDRESS
; break;
650 case NO_RECOVERY
: error
= EDNS_NO_RECOVERY
; break;
651 case TRY_AGAIN
: error
= EDNS_TRY_AGAIN
; break;
652 default: error
= EUNKNOWN
; break;
654 if(error
== EDNS_HOST_NOT_FOUND
) {
655 object
->headers
= NULL
;
656 object
->age
= current_time
.tv_sec
;
657 object
->expires
= current_time
.tv_sec
+ dnsNegativeTtl
;
658 object
->flags
&= ~(OBJECT_INITIAL
| OBJECT_INPROGRESS
);
659 object
->flags
&= ~OBJECT_INPROGRESS
;
660 notifyObject(object
);
663 do_log_error(L_ERROR
, error
, "Gethostbyname failed");
664 abortObject(object
, 404,
665 internAtomError(error
, "Gethostbyname failed"));
666 object
->flags
&= ~OBJECT_INPROGRESS
;
667 notifyObject(object
);
671 if(host
->h_addrtype
!= AF_INET
) {
672 do_log(L_ERROR
, "Address is not AF_INET.\n");
673 object
->flags
&= ~OBJECT_INPROGRESS
;
674 abortObject(object
, 404, internAtom("Address is not AF_INET"));
675 notifyObject(object
);
678 if(host
->h_length
!= sizeof(struct in_addr
)) {
679 do_log(L_ERROR
, "Address size inconsistent.\n");
680 object
->flags
&= ~OBJECT_INPROGRESS
;
681 abortObject(object
, 404, internAtom("Address size inconsistent"));
682 notifyObject(object
);
686 while(host
->h_addr_list
[i
] != NULL
) i
++;
687 s
= malloc(1 + i
* sizeof(HostAddressRec
));
691 memset(s
, 0, 1 + i
* sizeof(HostAddressRec
));
693 for(j
= 0; j
< i
; j
++) {
694 s
[j
* sizeof(HostAddressRec
) + 1] = 4;
695 memcpy(&s
[j
* sizeof(HostAddressRec
) + 2], host
->h_addr_list
[j
],
696 sizeof(struct in_addr
));
698 a
= internAtomN(s
, i
* sizeof(HostAddressRec
) + 1);
702 object
->flags
&= ~OBJECT_INPROGRESS
;
703 abortObject(object
, 501, internAtom("Couldn't allocate address"));
704 notifyObject(object
);
708 object
->age
= current_time
.tv_sec
;
709 object
->expires
= current_time
.tv_sec
+ dnsGethostbynameTtl
;
710 object
->flags
&= ~(OBJECT_INITIAL
| OBJECT_INPROGRESS
);
711 notifyObject(object
);
719 #ifdef NO_STANDARD_RESOLVER
721 really_do_gethostbyname(AtomPtr name
, ObjectPtr object
)
727 #ifndef NO_FANCY_RESOLVER
729 static int dnsSocket
= -1;
730 static FdEventHandlerPtr dnsSocketHandler
= NULL
;
733 dnsHandler(int status
, ConditionHandlerPtr chandler
)
735 GethostbynameRequestRec request
= *(GethostbynameRequestPtr
)chandler
->data
;
736 ObjectPtr object
= request
.object
;
738 assert(!(object
->flags
& OBJECT_INPROGRESS
));
740 if(object
->headers
) {
741 request
.addr
= retainAtom(object
->headers
);
742 dnsDelayedNotify(0, &request
);
745 request
.error_message
= retainAtom(object
->message
);
746 dnsDelayedNotify(1, &request
);
748 releaseObject(object
);
753 queryInFlight(DnsQueryPtr query
)
756 other
= inFlightDnsQueries
;
766 removeQuery(DnsQueryPtr query
)
768 DnsQueryPtr previous
;
769 if(query
== inFlightDnsQueries
) {
770 inFlightDnsQueries
= query
->next
;
771 if(inFlightDnsQueries
== NULL
)
772 inFlightDnsQueriesLast
= NULL
;
774 previous
= inFlightDnsQueries
;
775 while(previous
->next
) {
776 if(previous
->next
== query
)
778 previous
= previous
->next
;
780 assert(previous
->next
!= NULL
);
781 previous
->next
= query
->next
;
782 if(previous
->next
== NULL
)
783 inFlightDnsQueriesLast
= previous
;
788 insertQuery(DnsQueryPtr query
)
790 if(inFlightDnsQueriesLast
)
791 inFlightDnsQueriesLast
->next
= query
;
793 inFlightDnsQueries
= query
;
794 inFlightDnsQueriesLast
= query
;
798 findQuery(int id
, AtomPtr name
)
801 query
= inFlightDnsQueries
;
803 if(query
->id
== id
&& (name
== NULL
|| query
->name
== name
))
811 dnsTimeoutHandler(TimeEventHandlerPtr event
)
813 DnsQueryPtr query
= *(DnsQueryPtr
*)event
->data
;
814 ObjectPtr object
= query
->object
;
817 /* People are reporting that this does happen. And I have no idea why. */
818 if(!queryInFlight(query
)) {
819 do_log(L_ERROR
, "BUG: timing out martian query (%s, flags: 0x%x).\n",
820 scrub(query
->name
->string
), (unsigned)object
->flags
);
824 query
->timeout
= MAX(10, query
->timeout
* 2);
826 if(query
->timeout
> dnsMaxTimeout
) {
827 abortObject(object
, 501, internAtom("Timeout"));
830 rc
= sendQuery(query
);
832 if(rc
!= -EWOULDBLOCK
&& rc
!= -EAGAIN
&& rc
!= -ENOBUFS
) {
833 abortObject(object
, 501,
835 "Couldn't send DNS query"));
838 /* else let it timeout */
840 query
->timeout_handler
=
841 scheduleTimeEvent(query
->timeout
, dnsTimeoutHandler
,
842 sizeof(query
), &query
);
843 if(query
->timeout_handler
== NULL
) {
844 do_log(L_ERROR
, "Couldn't schedule DNS timeout handler.\n");
845 abortObject(object
, 501,
846 internAtom("Couldn't schedule DNS timeout handler"));
854 object
->flags
&= ~OBJECT_INPROGRESS
;
855 if(query
->inet4
) releaseAtom(query
->inet4
);
856 if(query
->inet6
) releaseAtom(query
->inet6
);
858 releaseNotifyObject(object
);
867 int inet6
= (nameserverAddress
.sa_family
== AF_INET6
);
868 int pf
= inet6
? PF_INET6
: PF_INET
;
870 inet6
? sizeof(struct sockaddr_in6
) : sizeof(struct sockaddr_in
);
873 int sa_size
= sizeof(struct sockaddr_in
);
877 assert(!dnsSocketHandler
);
878 dnsSocket
= socket(pf
, SOCK_DGRAM
, 0);
880 do_log_error(L_ERROR
, errno
, "Couldn't create DNS socket");
884 rc
= connect(dnsSocket
, &nameserverAddress
, sa_size
);
888 do_log_error(L_ERROR
, errno
, "Couldn't create DNS \"connection\"");
893 if(!dnsSocketHandler
) {
895 registerFdEvent(dnsSocket
, POLLIN
, dnsReplyHandler
, 0, NULL
);
896 if(dnsSocketHandler
== NULL
) {
897 do_log(L_ERROR
, "Couldn't register DNS socket handler.\n");
908 sendQuery(DnsQueryPtr query
)
919 if(dnsQueryIPv6
<= 0) {
920 af
[0] = 4; af
[1] = 0;
921 } else if(dnsQueryIPv6
<= 2) {
922 af
[0] = 4; af
[1] = 6;
924 af
[0] = 6; af
[1] = 0;
927 for(i
= 0; i
< 2; i
++) {
930 if(af
[i
] == 4 && query
->inet4
)
932 else if(af
[i
] == 6 && query
->inet6
)
935 buflen
= dnsBuildQuery(query
->id
, buf
, 0, 512, query
->name
, af
[i
]);
937 do_log(L_ERROR
, "Couldn't build DNS query.\n");
941 rc
= send(dnsSocket
, buf
, buflen
, 0);
944 do_log(L_ERROR
, "Couldn't send DNS query: partial send.\n");
947 do_log_error(L_ERROR
, errno
, "Couldn't send DNS query");
956 really_do_dns(AtomPtr name
, ObjectPtr object
)
960 AtomPtr message
= NULL
;
965 if(name
== atomLocalhost
|| name
== atomLocalhostDot
) {
966 char s
[1 + sizeof(HostAddressRec
)];
967 memset(s
, 0, sizeof(s
));
974 a
= internAtomN(s
, 1 + sizeof(HostAddressRec
));
976 abortObject(object
, 501,
977 internAtom("Couldn't allocate address"));
978 notifyObject(object
);
987 rc
= inet_aton(name
->string
, &ina
);
989 char s
[1 + sizeof(HostAddressRec
)];
990 memset(s
, 0, sizeof(s
));
993 memcpy(s
+ 2, &ina
, 4);
994 a
= internAtomN(s
, 1 + sizeof(HostAddressRec
));
996 abortObject(object
, 501,
997 internAtom("Couldn't allocate address"));
998 notifyObject(object
);
1010 object
->headers
= a
;
1011 object
->age
= current_time
.tv_sec
;
1012 object
->expires
= current_time
.tv_sec
+ 240;
1013 object
->flags
&= ~(OBJECT_INITIAL
| OBJECT_INPROGRESS
);
1014 notifyObject(object
);
1018 rc
= establishDnsSocket();
1020 do_log_error(L_ERROR
, -rc
, "Couldn't establish DNS socket.\n");
1021 message
= internAtomError(-rc
, "Couldn't establish DNS socket");
1025 /* The id is used to speed up detecting replies to queries that
1026 are no longer current -- see dnsReplyHandler. */
1027 id
= (idSeed
++) & 0xFFFF;
1029 query
= malloc(sizeof(DnsQueryRec
));
1031 do_log(L_ERROR
, "Couldn't allocate DNS query.\n");
1032 message
= internAtom("Couldn't allocate DNS query");
1036 query
->inet4
= NULL
;
1037 query
->inet6
= NULL
;
1039 query
->time
= current_time
.tv_sec
;
1040 query
->object
= retainObject(object
);
1042 query
->timeout_handler
= NULL
;
1045 query
->timeout_handler
=
1046 scheduleTimeEvent(query
->timeout
, dnsTimeoutHandler
,
1047 sizeof(query
), &query
);
1048 if(query
->timeout_handler
== NULL
) {
1049 do_log(L_ERROR
, "Couldn't schedule DNS timeout handler.\n");
1050 message
= internAtom("Couldn't schedule DNS timeout handler");
1055 object
->flags
|= OBJECT_INPROGRESS
;
1056 rc
= sendQuery(query
);
1058 if(rc
!= -EWOULDBLOCK
&& rc
!= -EAGAIN
&& rc
!= -ENOBUFS
) {
1059 object
->flags
&= ~OBJECT_INPROGRESS
;
1060 message
= internAtomError(-rc
, "Couldn't send DNS query");
1061 goto remove_fallback
;
1063 /* else let it timeout */
1065 releaseAtom(message
);
1071 releaseObject(query
->object
);
1072 cancelTimeEvent(query
->timeout_handler
);
1075 if(dnsUseGethostbyname
>= 1) {
1076 releaseAtom(message
);
1077 do_log(L_WARN
, "Falling back on gethostbyname.\n");
1078 return really_do_gethostbyname(name
, object
);
1080 abortObject(object
, 501, message
);
1081 notifyObject(object
);
1087 dnsReplyHandler(int abort
, FdEventHandlerPtr event
)
1094 AtomPtr name
, value
, message
= NULL
;
1098 AtomPtr cname
= NULL
;
1101 dnsSocketHandler
= NULL
;
1102 rc
= establishDnsSocket();
1104 do_log(L_ERROR
, "Couldn't reestablish DNS socket.\n");
1105 /* At this point, we should abort all in-flight
1106 DNS requests. Oh, well, they'll timeout anyway. */
1111 len
= recv(fd
, buf
, 2048, 0);
1113 if(errno
== EINTR
|| errno
== EAGAIN
) return 0;
1114 /* This is where we get ECONNREFUSED for an ICMP port unreachable */
1115 do_log_error(L_ERROR
, errno
, "DNS: recv failed");
1116 dnsGethostbynameFallback(-1, message
);
1120 /* This could be a late reply to a query that timed out and was
1121 resent, a reply to a query that timed out, or a reply to an
1122 AAAA query when we already got a CNAME reply to the associated
1123 A. We filter such replies straight away, without trying to
1125 rc
= dnsReplyId(buf
, 0, len
, &id
);
1127 do_log(L_WARN
, "Short DNS reply.\n");
1130 if(!findQuery(id
, NULL
)) {
1134 rc
= dnsDecodeReply(buf
, 0, len
, &id
, &name
, &value
, &af
, &ttl
);
1136 assert(value
== NULL
);
1137 /* We only want to fallback on gethostbyname if we received a
1138 reply that we could not understand. What about truncated
1141 do_log_error(L_WARN
, -rc
, "DNS");
1142 if(dnsUseGethostbyname
>= 2 ||
1143 (dnsUseGethostbyname
&&
1144 (rc
!= -EDNS_HOST_NOT_FOUND
&& rc
!= -EDNS_NO_RECOVERY
&&
1145 rc
!= -EDNS_FORMAT
))) {
1146 dnsGethostbynameFallback(id
, message
);
1149 message
= internAtomError(-rc
, NULL
);
1152 assert(name
!= NULL
&& id
>= 0 && af
>= 0);
1156 query
= findQuery(id
, name
);
1158 /* Duplicate id ? */
1164 /* We're going to use the information in this reply. If it was an
1165 error, construct an empty atom to distinguish it from information
1166 we're still waiting for. */
1168 value
= internAtom("");
1172 if(query
->inet4
== NULL
) {
1173 query
->inet4
= value
;
1174 query
->ttl4
= current_time
.tv_sec
+ ttl
;
1177 } else if(af
== 6) {
1178 if(query
->inet6
== NULL
) {
1179 query
->inet6
= value
;
1180 query
->ttl6
= current_time
.tv_sec
+ ttl
;
1183 } else if(af
== 0) {
1184 /* Ignore errors in this case. */
1185 if(query
->inet4
&& query
->inet4
->length
== 0) {
1186 releaseAtom(query
->inet4
);
1187 query
->inet4
= NULL
;
1189 if(query
->inet6
&& query
->inet6
->length
== 0) {
1190 releaseAtom(query
->inet6
);
1191 query
->inet6
= NULL
;
1193 if(query
->inet4
|| query
->inet6
) {
1194 do_log(L_WARN
, "Host %s has both %s and CNAME -- "
1195 "ignoring CNAME.\n", scrub(query
->name
->string
),
1196 query
->inet4
? "A" : "AAAA");
1198 value
= internAtom("");
1199 af
= query
->inet4
? 4 : 6;
1206 if(rc
>= 0 && !cname
&&
1207 ((dnsQueryIPv6
< 3 && query
->inet4
== NULL
) ||
1208 (dnsQueryIPv6
> 0 && query
->inet6
== NULL
)))
1211 /* This query is complete */
1213 cancelTimeEvent(query
->timeout_handler
);
1214 object
= query
->object
;
1216 if(object
->flags
& OBJECT_INITIAL
) {
1217 assert(!object
->headers
);
1219 assert(query
->inet4
== NULL
&& query
->inet6
== NULL
);
1220 object
->headers
= cname
;
1221 object
->expires
= current_time
.tv_sec
+ ttl
;
1222 } else if((!query
->inet4
|| query
->inet4
->length
== 0) &&
1223 (!query
->inet6
|| query
->inet6
->length
== 0)) {
1224 releaseAtom(query
->inet4
);
1225 releaseAtom(query
->inet6
);
1226 object
->expires
= current_time
.tv_sec
+ dnsNegativeTtl
;
1227 abortObject(object
, 500, retainAtom(message
));
1228 } else if(!query
->inet4
|| query
->inet4
->length
== 0) {
1229 object
->headers
= query
->inet6
;
1230 object
->expires
= query
->ttl6
;
1231 releaseAtom(query
->inet4
);
1232 } else if(!query
->inet6
|| query
->inet6
->length
== 0) {
1233 object
->headers
= query
->inet4
;
1234 object
->expires
= query
->ttl4
;
1235 releaseAtom(query
->inet6
);
1237 /* need to merge results */
1239 if(query
->inet4
->length
+ query
->inet6
->length
> 1024) {
1240 releaseAtom(query
->inet4
);
1241 releaseAtom(query
->inet6
);
1242 abortObject(object
, 500, internAtom("DNS reply too long"));
1244 if(dnsQueryIPv6
<= 1) {
1245 memcpy(buf
, query
->inet4
->string
, query
->inet4
->length
);
1246 memcpy(buf
+ query
->inet4
->length
,
1247 query
->inet6
->string
+ 1, query
->inet6
->length
- 1);
1249 memcpy(buf
, query
->inet6
->string
, query
->inet6
->length
);
1250 memcpy(buf
+ query
->inet6
->length
,
1251 query
->inet4
->string
+ 1, query
->inet4
->length
- 1);
1255 query
->inet4
->length
+
1256 query
->inet6
->length
- 1);
1257 if(object
->headers
== NULL
)
1258 abortObject(object
, 500,
1259 internAtom("Couldn't allocate DNS atom"));
1261 object
->expires
= MIN(query
->ttl4
, query
->ttl6
);
1263 object
->age
= current_time
.tv_sec
;
1264 object
->flags
&= ~(OBJECT_INITIAL
| OBJECT_INPROGRESS
);
1266 do_log(L_WARN
, "DNS object ex nihilo for %s.\n",
1267 scrub(query
->name
->string
));
1274 releaseAtom(message
);
1275 releaseNotifyObject(object
);
1280 dnsGethostbynameFallback(int id
, AtomPtr message
)
1282 DnsQueryPtr query
, previous
;
1285 if(inFlightDnsQueries
== NULL
) {
1286 releaseAtom(message
);
1291 if(id
< 0 || inFlightDnsQueries
->id
== id
) {
1293 query
= inFlightDnsQueries
;
1295 previous
= inFlightDnsQueries
;
1296 while(previous
->next
) {
1297 if(previous
->next
->id
== id
) {
1298 query
= previous
->next
;
1301 previous
= previous
->next
;
1305 query
= inFlightDnsQueries
;
1309 if(previous
== NULL
) {
1310 inFlightDnsQueries
= query
->next
;
1311 if(inFlightDnsQueries
== NULL
)
1312 inFlightDnsQueriesLast
= NULL
;
1314 previous
->next
= query
->next
;
1315 if(query
->next
== NULL
)
1316 inFlightDnsQueriesLast
= NULL
;
1319 object
= makeObject(OBJECT_DNS
, query
->name
->string
, query
->name
->length
,
1322 do_log(L_ERROR
, "Couldn't make DNS object.\n");
1323 releaseAtom(query
->name
);
1324 releaseAtom(message
);
1325 releaseObject(query
->object
);
1326 cancelTimeEvent(query
->timeout_handler
);
1330 if(dnsUseGethostbyname
>= 1) {
1331 releaseAtom(message
);
1332 do_log(L_WARN
, "Falling back to using system resolver.\n");
1333 really_do_gethostbyname(retainAtom(query
->name
), object
);
1335 releaseAtom(object
->message
);
1336 object
->message
= message
;
1337 object
->flags
&= ~OBJECT_INPROGRESS
;
1338 releaseNotifyObject(object
);
1340 cancelTimeEvent(query
->timeout_handler
);
1341 releaseAtom(query
->name
);
1342 if(query
->inet4
) releaseAtom(query
->inet4
);
1343 if(query
->inet6
) releaseAtom(query
->inet6
);
1344 releaseObject(query
->object
);
1350 stringToLabels(char *buf
, int offset
, int n
, char *string
)
1355 while(string
[k
] != '.' && string
[k
] != '\0')
1357 if(k
>= j
+ 256) return -1;
1358 buf
[i
] = (unsigned char)(k
- j
); i
++; if(i
>= n
) return -1;
1360 buf
[i
] = string
[j
]; i
++; j
++; if(i
>= n
) return -1;
1362 if(string
[j
] == '\0') {
1364 i
++; if(i
>= n
) return -1;
1373 #ifdef UNALIGNED_ACCESS
1374 #define DO_NTOHS(_d, _s) _d = ntohs(*(short*)(_s));
1375 #define DO_NTOHL(_d, _s) _d = ntohl(*(unsigned*)(_s))
1376 #define DO_HTONS(_d, _s) *(short*)(_d) = htons(_s);
1377 #define DO_HTONL(_d, _s) *(unsigned*)(_d) = htonl(_s)
1379 #define DO_NTOHS(_d, _s) \
1381 memcpy(&(_dd), (_s), sizeof(short)); \
1382 _d = ntohs(_dd); } while(0)
1383 #define DO_NTOHL(_d, _s) \
1384 do { unsigned _dd; \
1385 memcpy(&(_dd), (_s), sizeof(unsigned)); \
1386 _d = ntohl(_dd); } while(0)
1387 #define DO_HTONS(_d, _s) \
1388 do { unsigned short _dd; \
1390 memcpy((_d), &(_dd), sizeof(unsigned short)); } while(0);
1391 #define DO_HTONL(_d, _s) \
1392 do { unsigned _dd; \
1394 memcpy((_d), &(_dd), sizeof(unsigned)); } while(0);
1398 labelsToString(char *buf
, int offset
, int n
, char *d
, int m
, int *j_return
)
1400 int i
= offset
, j
, k
;
1405 if(i
>= n
) return -1;
1406 ll
= *(unsigned char*)&buf
[i
]; i
++;
1410 if((ll
& (3 << 6)) == (3 << 6)) {
1411 /* RFC 1035, 4.1.4 */
1413 if(i
>= n
) return -1;
1414 o
= (ll
& ~(3 << 6)) << 8 | *(unsigned char*)&buf
[i
];
1416 labelsToString(buf
, o
, n
, &d
[j
], m
- j
, &k
);
1419 } else if((ll
& (3 << 6)) == 0) {
1420 for(k
= 0; k
< ll
; k
++) {
1421 if(i
>= n
|| j
>= m
) return -1;
1424 if(i
>= n
) return -1;
1425 if(buf
[i
] != '\0') {
1426 if(j
>= m
) return -1;
1438 dnsBuildQuery(int id
, char *buf
, int offset
, int n
, AtomPtr name
, int af
)
1443 case 4: type
= 1; break;
1444 case 6: type
= 28; break;
1445 default: return -EINVAL
;
1448 if(i
+ 12 >= n
) return -1;
1449 DO_HTONS(&buf
[i
], id
); i
+= 2;
1450 DO_HTONS(&buf
[i
], 1<<8); i
+= 2;
1451 DO_HTONS(&buf
[i
], 1); i
+= 2;
1452 DO_HTONS(&buf
[i
], 0); i
+= 2;
1453 DO_HTONS(&buf
[i
], 0); i
+= 2;
1454 DO_HTONS(&buf
[i
], 0); i
+= 2;
1456 i
= stringToLabels(buf
, i
, n
, name
->string
);
1457 if(i
< 0) return -ENAMETOOLONG
;
1459 if(i
+ 4 >= n
) return -ENAMETOOLONG
;
1460 DO_HTONS(&buf
[i
], type
); i
+= 2;
1461 DO_HTONS(&buf
[i
], 1); i
+= 2;
1466 dnsReplyId(char *buf
, int offset
, int n
, int *id_return
)
1470 DO_NTOHS(*id_return
, &buf
[offset
]);
1475 dnsDecodeReply(char *buf
, int offset
, int n
, int *id_return
,
1476 AtomPtr
*name_return
, AtomPtr
*value_return
,
1477 int *af_return
, unsigned *ttl_return
)
1479 int i
= offset
, j
, m
;
1480 int id
= -1, b23
, qdcount
, ancount
, nscount
, arcount
, rdlength
;
1485 AtomPtr name
= NULL
, value
;
1486 char addresses
[1024];
1488 int error
= EDNS_NO_ADDRESS
;
1489 unsigned final_ttl
= 7 * 24 * 3600;
1493 error
= EDNS_INVALID
;
1497 DO_NTOHS(id
, &buf
[i
]); i
+= 2;
1498 DO_NTOHS(b23
, &buf
[i
]); i
+= 2;
1499 DO_NTOHS(qdcount
, &buf
[i
]); i
+= 2;
1500 DO_NTOHS(ancount
, &buf
[i
]); i
+= 2;
1501 DO_NTOHS(nscount
, &buf
[i
]); i
+= 2;
1502 DO_NTOHS(arcount
, &buf
[i
]); i
+= 2;
1505 "DNS id %d, b23 0x%x, qdcount %d, ancount %d, "
1506 "nscount %d, arcount %d\n",
1507 id
, b23
, qdcount
, ancount
, nscount
, arcount
);
1509 if((b23
& (0xF870)) != 0x8000) {
1510 do_log(L_ERROR
, "Incorrect DNS reply (b23 = 0x%x).\n", b23
);
1511 error
= EDNS_INVALID
;
1515 dnserror
= b23
& 0xF;
1518 do_log(L_WARN
, "Truncated DNS reply (b23 = 0x%x).\n", b23
);
1521 if(dnserror
|| qdcount
!= 1) {
1524 "Unexpected number %d of DNS questions.\n", qdcount
);
1526 error
= EDNS_FORMAT
;
1527 else if(dnserror
== 2)
1528 error
= EDNS_NO_RECOVERY
;
1529 else if(dnserror
== 3)
1530 error
= EDNS_HOST_NOT_FOUND
;
1531 else if(dnserror
== 4 || dnserror
== 5)
1532 error
= EDNS_REFUSED
;
1533 else if(dnserror
== 0)
1534 error
= EDNS_INVALID
;
1540 /* We do this early, so that we can return the address family to
1541 the caller in case of error. */
1542 i
= labelsToString(buf
, i
, n
, b
, 2048, &m
);
1544 error
= EDNS_FORMAT
;
1547 DO_NTOHS(type
, &buf
[i
]); i
+= 2;
1548 DO_NTOHS(class, &buf
[i
]); i
+= 2;
1555 error
= EDNS_FORMAT
;
1559 do_log(D_DNS
, "DNS q: ");
1560 do_log_n(D_DNS
, b
, m
);
1561 do_log(D_DNS
, " (%d, %d)\n", type
, class);
1562 name
= internAtomLowerN(b
, m
);
1569 error
= EDNS_FORMAT
;
1573 #define PARSE_ANSWER(kind, label) \
1575 i = labelsToString(buf, i, 1024, b, 2048, &m); \
1576 if(i < 0) goto label; \
1577 DO_NTOHS(type, &buf[i]); i += 2; if(i > 1024) goto label; \
1578 DO_NTOHS(class, &buf[i]); i += 2; if(i > 1024) goto label; \
1579 DO_NTOHL(ttl, &buf[i]); i += 4; if(i > 1024) goto label; \
1580 DO_NTOHS(rdlength, &buf[i]); i += 2; if(i > 1024) goto label; \
1581 do_log(D_DNS, "DNS " kind ": "); \
1582 do_log_n(D_DNS, b, m); \
1583 do_log(D_DNS, " (%d, %d): %d bytes, ttl %u\n", \
1584 type, class, rdlength, ttl); \
1588 for(j
= 0; j
< ancount
; j
++) {
1589 PARSE_ANSWER("an", fail
);
1590 if(strcasecmp_n(name
->string
, b
, m
) == 0) {
1592 do_log(D_DNS
, "DNS: %s: unknown class %d.\n",
1593 name
->string
, class);
1594 error
= EDNS_UNSUPPORTED
;
1597 if(type
== 1 || type
== 28) {
1598 if((type
== 1 && rdlength
!= 4) ||
1599 (type
== 28 && rdlength
!= 16)) {
1601 "DNS: %s: unexpected length %d of %s record.\n",
1602 scrub(name
->string
),
1603 rdlength
, type
== 1 ? "A" : "AAAA");
1604 error
= EDNS_INVALID
;
1605 if(rdlength
<= 0 || rdlength
>= 32)
1610 do_log(L_WARN
, "DNS: %s: host has both A and CNAME -- "
1611 "ignoring CNAME.\n", scrub(name
->string
));
1619 do_log(L_WARN
, "Unexpected AAAA reply.\n");
1626 do_log(L_WARN
, "Unexpected A reply.\n");
1631 if(addr_index
== 0) {
1632 addresses
[0] = DNS_A
;
1635 if(addr_index
> 1000) {
1636 error
= EDNS_INVALID
;
1640 assert(addresses
[0] == DNS_A
);
1643 memset(&addresses
[addr_index
], 0, sizeof(HostAddressRec
));
1645 addresses
[addr_index
] = 4;
1646 memcpy(addresses
+ addr_index
+ 1, buf
+ i
, 4);
1648 addresses
[addr_index
] = 6;
1649 memcpy(addresses
+ addr_index
+ 1, buf
+ i
, 16);
1651 addr_index
+= sizeof(HostAddressRec
);
1652 } else if(type
== 5) {
1654 if(af
!= 0 && addr_index
> 0) {
1655 do_log(L_WARN
, "DNS: host has both CNAME and A -- "
1656 "ignoring CNAME.\n");
1661 if(addr_index
!= 0) {
1662 /* Only warn if the CNAMEs are not identical */
1663 char tmp
[512]; int jj
, kk
;
1664 assert(addresses
[0] == DNS_CNAME
);
1665 jj
= labelsToString(buf
, i
, n
,
1668 kk
!= strlen(addresses
+ 1) ||
1669 memcmp(addresses
+ 1, tmp
, kk
) != 0) {
1670 do_log(L_WARN
, "DNS: "
1671 "%s: host has multiple CNAMEs -- "
1672 "ignoring subsequent.\n",
1673 scrub(name
->string
));
1678 addresses
[0] = DNS_CNAME
;
1680 j
= labelsToString(buf
, i
, n
,
1681 addresses
+ 1, 1020, &k
);
1684 error
= ENAMETOOLONG
;
1689 error
= EDNS_NO_ADDRESS
;
1699 #if (LOGGING_MAX & D_DNS)
1700 for(j
= 0; j
< nscount
; j
++) {
1701 PARSE_ANSWER("ns", nofail
);
1705 for(j
= 0; j
< arcount
; j
++) {
1706 PARSE_ANSWER("ar", nofail
);
1715 do_log(D_DNS
, "DNS: %d bytes\n", addr_index
);
1719 value
= internAtomN(addresses
, addr_index
);
1727 *name_return
= name
;
1728 *value_return
= value
;
1730 *ttl_return
= final_ttl
;
1735 *name_return
= name
;
1736 *value_return
= NULL
;
1744 really_do_dns(AtomPtr name
, ObjectPtr object
)