2014-06-02 02:13:57 -04:00
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <poll.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <pthread.h>
|
|
|
|
#include "stdio_impl.h"
|
|
|
|
#include "syscall.h"
|
2014-06-04 16:53:39 -04:00
|
|
|
#include "lookup.h"
|
2014-06-02 02:13:57 -04:00
|
|
|
|
|
|
|
static void cleanup(void *p)
|
|
|
|
{
|
|
|
|
__syscall(SYS_close, (intptr_t)p);
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned long mtime()
|
|
|
|
{
|
|
|
|
struct timespec ts;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
|
|
return (unsigned long)ts.tv_sec * 1000
|
|
|
|
+ ts.tv_nsec / 1000000;
|
|
|
|
}
|
|
|
|
|
2016-01-28 20:51:31 -05:00
|
|
|
int __res_msend_rc(int nqueries, const unsigned char *const *queries,
|
|
|
|
const int *qlens, unsigned char *const *answers, int *alens, int asize,
|
|
|
|
const struct resolvconf *conf)
|
2014-06-02 02:13:57 -04:00
|
|
|
{
|
|
|
|
int fd;
|
2016-01-28 18:24:34 -05:00
|
|
|
int timeout, attempts, retry_interval, servfail_retry;
|
2014-06-02 02:13:57 -04:00
|
|
|
union {
|
|
|
|
struct sockaddr_in sin;
|
|
|
|
struct sockaddr_in6 sin6;
|
2016-01-28 18:24:34 -05:00
|
|
|
} sa = {0}, ns[MAXNS] = {{0}};
|
2014-06-02 02:13:57 -04:00
|
|
|
socklen_t sl = sizeof sa.sin;
|
|
|
|
int nns = 0;
|
|
|
|
int family = AF_INET;
|
|
|
|
int rlen;
|
|
|
|
int next;
|
|
|
|
int i, j;
|
|
|
|
int cs;
|
|
|
|
struct pollfd pfd;
|
|
|
|
unsigned long t0, t1, t2;
|
|
|
|
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
|
|
|
|
|
2016-01-28 20:51:31 -05:00
|
|
|
timeout = 1000*conf->timeout;
|
|
|
|
attempts = conf->attempts;
|
2016-01-28 18:24:34 -05:00
|
|
|
|
2016-01-28 20:51:31 -05:00
|
|
|
nns = conf->nns;
|
|
|
|
for (nns=0; nns<conf->nns; nns++) {
|
|
|
|
const struct address *iplit = &conf->ns[nns];
|
2016-01-28 18:24:34 -05:00
|
|
|
if (iplit->family == AF_INET) {
|
|
|
|
memcpy(&ns[nns].sin.sin_addr, iplit->addr, 4);
|
|
|
|
ns[nns].sin.sin_port = htons(53);
|
|
|
|
ns[nns].sin.sin_family = AF_INET;
|
|
|
|
} else {
|
|
|
|
sl = sizeof sa.sin6;
|
|
|
|
memcpy(&ns[nns].sin6.sin6_addr, iplit->addr, 16);
|
|
|
|
ns[nns].sin6.sin6_port = htons(53);
|
|
|
|
ns[nns].sin6.sin6_scope_id = iplit->scopeid;
|
|
|
|
ns[nns].sin6.sin6_family = family = AF_INET6;
|
2014-06-02 02:13:57 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get local address and open/bind a socket */
|
|
|
|
sa.sin.sin_family = family;
|
|
|
|
fd = socket(family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
|
|
|
|
|
|
|
/* Handle case where system lacks IPv6 support */
|
|
|
|
if (fd < 0 && family == AF_INET6 && errno == EAFNOSUPPORT) {
|
|
|
|
fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
|
|
|
family = AF_INET;
|
|
|
|
}
|
|
|
|
if (fd < 0 || bind(fd, (void *)&sa, sl) < 0) return -1;
|
|
|
|
|
|
|
|
/* Past this point, there are no errors. Each individual query will
|
|
|
|
* yield either no reply (indicated by zero length) or an answer
|
|
|
|
* packet which is up to the caller to interpret. */
|
|
|
|
|
|
|
|
pthread_cleanup_push(cleanup, (void *)(intptr_t)fd);
|
|
|
|
pthread_setcancelstate(cs, 0);
|
|
|
|
|
|
|
|
/* Convert any IPv4 addresses in a mixed environment to v4-mapped */
|
|
|
|
if (family == AF_INET6) {
|
|
|
|
setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &(int){0}, sizeof 0);
|
|
|
|
for (i=0; i<nns; i++) {
|
|
|
|
if (ns[i].sin.sin_family != AF_INET) continue;
|
|
|
|
memcpy(ns[i].sin6.sin6_addr.s6_addr+12,
|
|
|
|
&ns[i].sin.sin_addr, 4);
|
|
|
|
memcpy(ns[i].sin6.sin6_addr.s6_addr,
|
|
|
|
"\0\0\0\0\0\0\0\0\0\0\xff\xff", 12);
|
|
|
|
ns[i].sin6.sin6_family = AF_INET6;
|
|
|
|
ns[i].sin6.sin6_flowinfo = 0;
|
|
|
|
ns[i].sin6.sin6_scope_id = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(alens, 0, sizeof *alens * nqueries);
|
|
|
|
|
|
|
|
pfd.fd = fd;
|
|
|
|
pfd.events = POLLIN;
|
|
|
|
retry_interval = timeout / attempts;
|
2014-06-02 04:27:54 -04:00
|
|
|
next = 0;
|
2014-06-02 02:13:57 -04:00
|
|
|
t0 = t2 = mtime();
|
|
|
|
t1 = t2 - retry_interval;
|
|
|
|
|
|
|
|
for (; t2-t0 < timeout; t2=mtime()) {
|
|
|
|
if (t2-t1 >= retry_interval) {
|
|
|
|
/* Query all configured namservers in parallel */
|
|
|
|
for (i=0; i<nqueries; i++)
|
|
|
|
if (!alens[i])
|
|
|
|
for (j=0; j<nns; j++)
|
|
|
|
sendto(fd, queries[i],
|
|
|
|
qlens[i], MSG_NOSIGNAL,
|
|
|
|
(void *)&ns[j], sl);
|
|
|
|
t1 = t2;
|
avoid spurious lookup failures from badly-behaved nameservers
the results of a dns query, whether it's performed as part of one of
the standard name-resolving functions or directly by res_send, should
be a function of the query, not of the particular nameserver that
responds to it. thus, all responses which indicate a failure or
refusal by the nameserver, as opposed to a positive or negative result
for the query, should be ignored.
the strategy used is to re-issue the query immediately (but with a
limit on the number of retries, in case the server is really broken)
when a response code of 2 (server failure, typically transient) is
seen, and otherwise take no action on bad responses (which generally
indicate a misconfigured nameserver or one which the client does not
have permission to use), allowing the normal retry interval to apply
and of course accepting responses from other nameservers queried in
parallel.
empirically this matches the traditional resolver behavior for
nameservers that respond with a code of 2 in the case where there is
just a single nameserver configured. the behavior diverges when
multiple nameservers are available, since musl is querying them in
parallel. in this case we are mildly more aggressive at retrying.
2014-06-07 04:09:21 -04:00
|
|
|
servfail_retry = 2 * nqueries;
|
2014-06-02 02:13:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Wait for a response, or until time to retry */
|
|
|
|
if (poll(&pfd, 1, t1+retry_interval-t2) <= 0) continue;
|
|
|
|
|
|
|
|
while ((rlen = recvfrom(fd, answers[next], asize, 0,
|
|
|
|
(void *)&sa, (socklen_t[1]){sl})) >= 0) {
|
|
|
|
|
avoid spurious lookup failures from badly-behaved nameservers
the results of a dns query, whether it's performed as part of one of
the standard name-resolving functions or directly by res_send, should
be a function of the query, not of the particular nameserver that
responds to it. thus, all responses which indicate a failure or
refusal by the nameserver, as opposed to a positive or negative result
for the query, should be ignored.
the strategy used is to re-issue the query immediately (but with a
limit on the number of retries, in case the server is really broken)
when a response code of 2 (server failure, typically transient) is
seen, and otherwise take no action on bad responses (which generally
indicate a misconfigured nameserver or one which the client does not
have permission to use), allowing the normal retry interval to apply
and of course accepting responses from other nameservers queried in
parallel.
empirically this matches the traditional resolver behavior for
nameservers that respond with a code of 2 in the case where there is
just a single nameserver configured. the behavior diverges when
multiple nameservers are available, since musl is querying them in
parallel. in this case we are mildly more aggressive at retrying.
2014-06-07 04:09:21 -04:00
|
|
|
/* Ignore non-identifiable packets */
|
|
|
|
if (rlen < 4) continue;
|
2014-06-02 02:13:57 -04:00
|
|
|
|
|
|
|
/* Ignore replies from addresses we didn't send to */
|
avoid spurious lookup failures from badly-behaved nameservers
the results of a dns query, whether it's performed as part of one of
the standard name-resolving functions or directly by res_send, should
be a function of the query, not of the particular nameserver that
responds to it. thus, all responses which indicate a failure or
refusal by the nameserver, as opposed to a positive or negative result
for the query, should be ignored.
the strategy used is to re-issue the query immediately (but with a
limit on the number of retries, in case the server is really broken)
when a response code of 2 (server failure, typically transient) is
seen, and otherwise take no action on bad responses (which generally
indicate a misconfigured nameserver or one which the client does not
have permission to use), allowing the normal retry interval to apply
and of course accepting responses from other nameservers queried in
parallel.
empirically this matches the traditional resolver behavior for
nameservers that respond with a code of 2 in the case where there is
just a single nameserver configured. the behavior diverges when
multiple nameservers are available, since musl is querying them in
parallel. in this case we are mildly more aggressive at retrying.
2014-06-07 04:09:21 -04:00
|
|
|
for (j=0; j<nns && memcmp(ns+j, &sa, sl); j++);
|
|
|
|
if (j==nns) continue;
|
2014-06-02 02:13:57 -04:00
|
|
|
|
|
|
|
/* Find which query this answer goes with, if any */
|
|
|
|
for (i=next; i<nqueries && (
|
|
|
|
answers[next][0] != queries[i][0] ||
|
|
|
|
answers[next][1] != queries[i][1] ); i++);
|
|
|
|
if (i==nqueries) continue;
|
|
|
|
if (alens[i]) continue;
|
|
|
|
|
avoid spurious lookup failures from badly-behaved nameservers
the results of a dns query, whether it's performed as part of one of
the standard name-resolving functions or directly by res_send, should
be a function of the query, not of the particular nameserver that
responds to it. thus, all responses which indicate a failure or
refusal by the nameserver, as opposed to a positive or negative result
for the query, should be ignored.
the strategy used is to re-issue the query immediately (but with a
limit on the number of retries, in case the server is really broken)
when a response code of 2 (server failure, typically transient) is
seen, and otherwise take no action on bad responses (which generally
indicate a misconfigured nameserver or one which the client does not
have permission to use), allowing the normal retry interval to apply
and of course accepting responses from other nameservers queried in
parallel.
empirically this matches the traditional resolver behavior for
nameservers that respond with a code of 2 in the case where there is
just a single nameserver configured. the behavior diverges when
multiple nameservers are available, since musl is querying them in
parallel. in this case we are mildly more aggressive at retrying.
2014-06-07 04:09:21 -04:00
|
|
|
/* Only accept positive or negative responses;
|
|
|
|
* retry immediately on server failure, and ignore
|
|
|
|
* all other codes such as refusal. */
|
|
|
|
switch (answers[next][3] & 15) {
|
|
|
|
case 0:
|
|
|
|
case 3:
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
if (servfail_retry && servfail_retry--)
|
|
|
|
sendto(fd, queries[i],
|
|
|
|
qlens[i], MSG_NOSIGNAL,
|
|
|
|
(void *)&ns[j], sl);
|
|
|
|
default:
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-06-02 02:13:57 -04:00
|
|
|
/* Store answer in the right slot, or update next
|
|
|
|
* available temp slot if it's already in place. */
|
|
|
|
alens[i] = rlen;
|
|
|
|
if (i == next)
|
|
|
|
for (; next<nqueries && alens[next]; next++);
|
|
|
|
else
|
|
|
|
memcpy(answers[i], answers[next], rlen);
|
|
|
|
|
|
|
|
if (next == nqueries) goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
pthread_cleanup_pop(1);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2016-01-28 20:51:31 -05:00
|
|
|
|
|
|
|
int __res_msend(int nqueries, const unsigned char *const *queries,
|
|
|
|
const int *qlens, unsigned char *const *answers, int *alens, int asize)
|
|
|
|
{
|
|
|
|
struct resolvconf conf;
|
|
|
|
if (__get_resolv_conf(&conf, 0, 0) < 0) return -1;
|
|
|
|
return __res_msend_rc(nqueries, queries, qlens, answers, alens, asize, &conf);
|
|
|
|
}
|