ff5b09c97b
- import udns library (http://www.corpit.ru/mjt/udns.html) - initial draft for a DNSClient (derived from HTTPClient) Enable compile and test by adding -D ENABLE_DNS=Yes to cmake flags
1324 lines
41 KiB
C
1324 lines
41 KiB
C
/* udns_resolver.c
|
|
resolver stuff (main module)
|
|
|
|
Copyright (C) 2005 Michael Tokarev <mjt@corpit.ru>
|
|
This file is part of UDNS library, an async DNS stub resolver.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library, in file named COPYING.LGPL; if not,
|
|
write to the Free Software Foundation, Inc., 59 Temple Place,
|
|
Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
#ifdef WINDOWS
|
|
# include <winsock2.h> /* includes <windows.h> */
|
|
# include <ws2tcpip.h> /* needed for struct in6_addr */
|
|
#else
|
|
# include <sys/types.h>
|
|
# include <sys/socket.h>
|
|
# include <netinet/in.h>
|
|
# include <unistd.h>
|
|
# include <fcntl.h>
|
|
# include <sys/time.h>
|
|
# ifdef HAVE_POLL
|
|
# include <sys/poll.h>
|
|
# else
|
|
# ifdef HAVE_SYS_SELECT_H
|
|
# include <sys/select.h>
|
|
# endif
|
|
# endif
|
|
# ifdef HAVE_TIMES
|
|
# include <sys/times.h>
|
|
# endif
|
|
# define closesocket(sock) close(sock)
|
|
#endif /* !WINDOWS */
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include "udns.h"
|
|
|
|
#ifndef EAFNOSUPPORT
|
|
# define EAFNOSUPPORT EINVAL
|
|
#endif
|
|
#ifndef MSG_DONTWAIT
|
|
# define MSG_DONTWAIT 0
|
|
#endif
|
|
|
|
struct dns_qlist {
|
|
struct dns_query *head, *tail;
|
|
};
|
|
|
|
struct dns_query {
|
|
struct dns_query *dnsq_next; /* double-linked list */
|
|
struct dns_query *dnsq_prev;
|
|
unsigned dnsq_origdnl0; /* original query DN len w/o last 0 */
|
|
unsigned dnsq_flags; /* control flags for this query */
|
|
unsigned dnsq_servi; /* index of next server to try */
|
|
unsigned dnsq_servwait; /* bitmask: servers left to wait */
|
|
unsigned dnsq_servskip; /* bitmask: servers to skip */
|
|
unsigned dnsq_servnEDNS0; /* bitmask: servers refusing EDNS0 */
|
|
unsigned dnsq_try; /* number of tries made so far */
|
|
dnscc_t *dnsq_nxtsrch; /* next search pointer @dnsc_srchbuf */
|
|
time_t dnsq_deadline; /* when current try will expire */
|
|
dns_parse_fn *dnsq_parse; /* parse: raw => application */
|
|
dns_query_fn *dnsq_cbck; /* the callback to call when done */
|
|
void *dnsq_cbdata; /* user data for the callback */
|
|
#ifndef NDEBUG
|
|
struct dns_ctx *dnsq_ctx; /* the resolver context */
|
|
#endif
|
|
/* char fields at the end to avoid padding */
|
|
dnsc_t dnsq_id[2]; /* query ID */
|
|
dnsc_t dnsq_typcls[4]; /* requested RR type+class */
|
|
dnsc_t dnsq_dn[DNS_MAXDN+DNS_DNPAD]; /* the query DN +alignment */
|
|
};
|
|
|
|
/* working with dns_query lists */
|
|
|
|
static __inline void qlist_init(struct dns_qlist *list) {
|
|
list->head = list->tail = NULL;
|
|
}
|
|
|
|
static __inline void qlist_remove(struct dns_qlist *list, struct dns_query *q) {
|
|
if (q->dnsq_prev) q->dnsq_prev->dnsq_next = q->dnsq_next;
|
|
else list->head = q->dnsq_next;
|
|
if (q->dnsq_next) q->dnsq_next->dnsq_prev = q->dnsq_prev;
|
|
else list->tail = q->dnsq_prev;
|
|
}
|
|
|
|
static __inline void
|
|
qlist_add_head(struct dns_qlist *list, struct dns_query *q) {
|
|
q->dnsq_next = list->head;
|
|
if (list->head) list->head->dnsq_prev = q;
|
|
else list->tail = q;
|
|
list->head = q;
|
|
q->dnsq_prev = NULL;
|
|
}
|
|
|
|
static __inline void
|
|
qlist_insert_after(struct dns_qlist *list,
|
|
struct dns_query *q, struct dns_query *prev) {
|
|
if ((q->dnsq_prev = prev) != NULL) {
|
|
if ((q->dnsq_next = prev->dnsq_next) != NULL)
|
|
q->dnsq_next->dnsq_prev = q;
|
|
else
|
|
list->tail = q;
|
|
prev->dnsq_next = q;
|
|
}
|
|
else
|
|
qlist_add_head(list, q);
|
|
}
|
|
|
|
union sockaddr_ns {
|
|
struct sockaddr sa;
|
|
struct sockaddr_in sin;
|
|
#ifdef HAVE_IPv6
|
|
struct sockaddr_in6 sin6;
|
|
#endif
|
|
};
|
|
|
|
#define sin_eq(a,b) \
|
|
((a).sin_port == (b).sin_port && \
|
|
(a).sin_addr.s_addr == (b).sin_addr.s_addr)
|
|
#define sin6_eq(a,b) \
|
|
((a).sin6_port == (b).sin6_port && \
|
|
memcmp(&(a).sin6_addr, &(b).sin6_addr, sizeof(struct in6_addr)) == 0)
|
|
|
|
struct dns_ctx { /* resolver context */
|
|
/* settings */
|
|
unsigned dnsc_flags; /* various flags */
|
|
unsigned dnsc_timeout; /* timeout (base value) for queries */
|
|
unsigned dnsc_ntries; /* number of retries */
|
|
unsigned dnsc_ndots; /* ndots to assume absolute name */
|
|
unsigned dnsc_port; /* default port (DNS_PORT) */
|
|
unsigned dnsc_udpbuf; /* size of UDP buffer */
|
|
/* array of nameserver addresses */
|
|
union sockaddr_ns dnsc_serv[DNS_MAXSERV];
|
|
unsigned dnsc_nserv; /* number of nameservers */
|
|
unsigned dnsc_salen; /* length of socket addresses */
|
|
dnsc_t dnsc_srchbuf[1024]; /* buffer for searchlist */
|
|
dnsc_t *dnsc_srchend; /* current end of srchbuf */
|
|
|
|
dns_utm_fn *dnsc_utmfn; /* register/cancel timer events */
|
|
void *dnsc_utmctx; /* user timer context for utmfn() */
|
|
time_t dnsc_utmexp; /* when user timer expires */
|
|
|
|
dns_dbgfn *dnsc_udbgfn; /* debugging function */
|
|
|
|
/* dynamic data */
|
|
struct udns_jranctx dnsc_jran; /* random number generator state */
|
|
unsigned dnsc_nextid; /* next queue ID to use if !0 */
|
|
int dnsc_udpsock; /* UDP socket */
|
|
struct dns_qlist dnsc_qactive; /* active list sorted by deadline */
|
|
int dnsc_nactive; /* number entries in dnsc_qactive */
|
|
dnsc_t *dnsc_pbuf; /* packet buffer (udpbuf size) */
|
|
int dnsc_qstatus; /* last query status value */
|
|
};
|
|
|
|
static const struct {
|
|
const char *name;
|
|
enum dns_opt opt;
|
|
unsigned offset;
|
|
unsigned min, max;
|
|
} dns_opts[] = {
|
|
#define opt(name,opt,field,min,max) \
|
|
{name,opt,offsetof(struct dns_ctx,field),min,max}
|
|
opt("retrans", DNS_OPT_TIMEOUT, dnsc_timeout, 1,300),
|
|
opt("timeout", DNS_OPT_TIMEOUT, dnsc_timeout, 1,300),
|
|
opt("retry", DNS_OPT_NTRIES, dnsc_ntries, 1,50),
|
|
opt("attempts", DNS_OPT_NTRIES, dnsc_ntries, 1,50),
|
|
opt("ndots", DNS_OPT_NDOTS, dnsc_ndots, 0,1000),
|
|
opt("port", DNS_OPT_PORT, dnsc_port, 1,0xffff),
|
|
opt("udpbuf", DNS_OPT_UDPSIZE, dnsc_udpbuf, DNS_MAXPACKET,65536),
|
|
#undef opt
|
|
};
|
|
#define dns_ctxopt(ctx,idx) (*((unsigned*)(((char*)ctx)+dns_opts[idx].offset)))
|
|
|
|
#define ISSPACE(x) (x == ' ' || x == '\t' || x == '\r' || x == '\n')
|
|
|
|
struct dns_ctx dns_defctx;
|
|
|
|
#define SETCTX(ctx) if (!ctx) ctx = &dns_defctx
|
|
#define SETCTXINITED(ctx) SETCTX(ctx); assert(CTXINITED(ctx))
|
|
#define CTXINITED(ctx) (ctx->dnsc_flags & DNS_INITED)
|
|
#define SETCTXFRESH(ctx) SETCTXINITED(ctx); assert(!CTXOPEN(ctx))
|
|
#define SETCTXINACTIVE(ctx) \
|
|
SETCTXINITED(ctx); assert(!ctx->dnsc_nactive)
|
|
#define SETCTXOPEN(ctx) SETCTXINITED(ctx); assert(CTXOPEN(ctx))
|
|
#define CTXOPEN(ctx) (ctx->dnsc_udpsock >= 0)
|
|
|
|
#if defined(NDEBUG) || !defined(DEBUG)
|
|
#define dns_assert_ctx(ctx)
|
|
#else
|
|
static void dns_assert_ctx(const struct dns_ctx *ctx) {
|
|
int nactive = 0;
|
|
const struct dns_query *q;
|
|
for(q = ctx->dnsc_qactive.head; q; q = q->dnsq_next) {
|
|
assert(q->dnsq_ctx == ctx);
|
|
assert(q == (q->dnsq_next ?
|
|
q->dnsq_next->dnsq_prev : ctx->dnsc_qactive.tail));
|
|
assert(q == (q->dnsq_prev ?
|
|
q->dnsq_prev->dnsq_next : ctx->dnsc_qactive.head));
|
|
++nactive;
|
|
}
|
|
assert(nactive == ctx->dnsc_nactive);
|
|
}
|
|
#endif
|
|
|
|
enum {
|
|
DNS_INTERNAL = 0xffff, /* internal flags mask */
|
|
DNS_INITED = 0x0001, /* the context is initialized */
|
|
DNS_ASIS_DONE = 0x0002, /* search: skip the last as-is query */
|
|
DNS_SEEN_NODATA = 0x0004, /* search: NODATA has been received */
|
|
};
|
|
|
|
int dns_add_serv(struct dns_ctx *ctx, const char *serv) {
|
|
union sockaddr_ns *sns;
|
|
SETCTXFRESH(ctx);
|
|
if (!serv)
|
|
return (ctx->dnsc_nserv = 0);
|
|
if (ctx->dnsc_nserv >= DNS_MAXSERV)
|
|
return errno = ENFILE, -1;
|
|
sns = &ctx->dnsc_serv[ctx->dnsc_nserv];
|
|
memset(sns, 0, sizeof(*sns));
|
|
if (dns_pton(AF_INET, serv, &sns->sin.sin_addr) > 0) {
|
|
sns->sin.sin_family = AF_INET;
|
|
return ++ctx->dnsc_nserv;
|
|
}
|
|
#ifdef HAVE_IPv6
|
|
if (dns_pton(AF_INET6, serv, &sns->sin6.sin6_addr) > 0) {
|
|
sns->sin6.sin6_family = AF_INET6;
|
|
return ++ctx->dnsc_nserv;
|
|
}
|
|
#endif
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
int dns_add_serv_s(struct dns_ctx *ctx, const struct sockaddr *sa) {
|
|
SETCTXFRESH(ctx);
|
|
if (!sa)
|
|
return (ctx->dnsc_nserv = 0);
|
|
if (ctx->dnsc_nserv >= DNS_MAXSERV)
|
|
return errno = ENFILE, -1;
|
|
#ifdef HAVE_IPv6
|
|
else if (sa->sa_family == AF_INET6)
|
|
ctx->dnsc_serv[ctx->dnsc_nserv].sin6 = *(struct sockaddr_in6*)sa;
|
|
#endif
|
|
else if (sa->sa_family == AF_INET)
|
|
ctx->dnsc_serv[ctx->dnsc_nserv].sin = *(struct sockaddr_in*)sa;
|
|
else
|
|
return errno = EAFNOSUPPORT, -1;
|
|
return ++ctx->dnsc_nserv;
|
|
}
|
|
|
|
int dns_set_opts(struct dns_ctx *ctx, const char *opts) {
|
|
unsigned i, v;
|
|
int err = 0;
|
|
SETCTXINACTIVE(ctx);
|
|
for(;;) {
|
|
while(ISSPACE(*opts)) ++opts;
|
|
if (!*opts) break;
|
|
for(i = 0; ; ++i) {
|
|
if (i >= sizeof(dns_opts)/sizeof(dns_opts[0])) { ++err; break; }
|
|
v = strlen(dns_opts[i].name);
|
|
if (strncmp(dns_opts[i].name, opts, v) != 0 ||
|
|
(opts[v] != ':' && opts[v] != '='))
|
|
continue;
|
|
opts += v + 1;
|
|
v = 0;
|
|
if (*opts < '0' || *opts > '9') { ++err; break; }
|
|
do v = v * 10 + (*opts++ - '0');
|
|
while (*opts >= '0' && *opts <= '9');
|
|
if (v < dns_opts[i].min) v = dns_opts[i].min;
|
|
if (v > dns_opts[i].max) v = dns_opts[i].max;
|
|
dns_ctxopt(ctx, i) = v;
|
|
break;
|
|
}
|
|
while(*opts && !ISSPACE(*opts)) ++opts;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int dns_set_opt(struct dns_ctx *ctx, enum dns_opt opt, int val) {
|
|
int prev;
|
|
unsigned i;
|
|
SETCTXINACTIVE(ctx);
|
|
for(i = 0; i < sizeof(dns_opts)/sizeof(dns_opts[0]); ++i) {
|
|
if (dns_opts[i].opt != opt) continue;
|
|
prev = dns_ctxopt(ctx, i);
|
|
if (val >= 0) {
|
|
unsigned v = val;
|
|
if (v < dns_opts[i].min || v > dns_opts[i].max) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
dns_ctxopt(ctx, i) = v;
|
|
}
|
|
return prev;
|
|
}
|
|
if (opt == DNS_OPT_FLAGS) {
|
|
prev = ctx->dnsc_flags & ~DNS_INTERNAL;
|
|
if (val >= 0)
|
|
ctx->dnsc_flags =
|
|
(ctx->dnsc_flags & DNS_INTERNAL) | (val & ~DNS_INTERNAL);
|
|
return prev;
|
|
}
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
int dns_add_srch(struct dns_ctx *ctx, const char *srch) {
|
|
int dnl;
|
|
SETCTXINACTIVE(ctx);
|
|
if (!srch) {
|
|
memset(ctx->dnsc_srchbuf, 0, sizeof(ctx->dnsc_srchbuf));
|
|
ctx->dnsc_srchend = ctx->dnsc_srchbuf;
|
|
return 0;
|
|
}
|
|
dnl =
|
|
sizeof(ctx->dnsc_srchbuf) - (ctx->dnsc_srchend - ctx->dnsc_srchbuf) - 1;
|
|
dnl = dns_sptodn(srch, ctx->dnsc_srchend, dnl);
|
|
if (dnl > 0)
|
|
ctx->dnsc_srchend += dnl;
|
|
ctx->dnsc_srchend[0] = '\0'; /* we ensure the list is always ends at . */
|
|
if (dnl > 0)
|
|
return 0;
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
static void dns_drop_utm(struct dns_ctx *ctx) {
|
|
if (ctx->dnsc_utmfn)
|
|
ctx->dnsc_utmfn(NULL, -1, ctx->dnsc_utmctx);
|
|
ctx->dnsc_utmctx = NULL;
|
|
ctx->dnsc_utmexp = -1;
|
|
}
|
|
|
|
static void
|
|
_dns_request_utm(struct dns_ctx *ctx, time_t now) {
|
|
struct dns_query *q;
|
|
time_t deadline;
|
|
int timeout;
|
|
q = ctx->dnsc_qactive.head;
|
|
if (!q)
|
|
deadline = -1, timeout = -1;
|
|
else if (!now || q->dnsq_deadline <= now)
|
|
deadline = 0, timeout = 0;
|
|
else
|
|
deadline = q->dnsq_deadline, timeout = (int)(deadline - now);
|
|
if (ctx->dnsc_utmexp == deadline)
|
|
return;
|
|
ctx->dnsc_utmfn(ctx, timeout, ctx->dnsc_utmctx);
|
|
ctx->dnsc_utmexp = deadline;
|
|
}
|
|
|
|
static __inline void
|
|
dns_request_utm(struct dns_ctx *ctx, time_t now) {
|
|
if (ctx->dnsc_utmfn)
|
|
_dns_request_utm(ctx, now);
|
|
}
|
|
|
|
void dns_set_dbgfn(struct dns_ctx *ctx, dns_dbgfn *dbgfn) {
|
|
SETCTXINITED(ctx);
|
|
ctx->dnsc_udbgfn = dbgfn;
|
|
}
|
|
|
|
void
|
|
dns_set_tmcbck(struct dns_ctx *ctx, dns_utm_fn *fn, void *data) {
|
|
SETCTXINITED(ctx);
|
|
dns_drop_utm(ctx);
|
|
ctx->dnsc_utmfn = fn;
|
|
ctx->dnsc_utmctx = data;
|
|
if (CTXOPEN(ctx))
|
|
dns_request_utm(ctx, 0);
|
|
}
|
|
|
|
static unsigned dns_nonrandom_32(void) {
|
|
#ifdef WINDOWS
|
|
FILETIME ft;
|
|
GetSystemTimeAsFileTime(&ft);
|
|
return ft.dwLowDateTime;
|
|
#else
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
return tv.tv_usec;
|
|
#endif
|
|
}
|
|
|
|
/* This is historic deprecated API */
|
|
UDNS_API unsigned dns_random16(void);
|
|
unsigned dns_random16(void) {
|
|
unsigned x = dns_nonrandom_32();
|
|
return (x ^ (x >> 16)) & 0xffff;
|
|
}
|
|
|
|
static void dns_init_rng(struct dns_ctx *ctx) {
|
|
udns_jraninit(&ctx->dnsc_jran, dns_nonrandom_32());
|
|
ctx->dnsc_nextid = 0;
|
|
}
|
|
|
|
void dns_close(struct dns_ctx *ctx) {
|
|
struct dns_query *q, *p;
|
|
SETCTX(ctx);
|
|
if (CTXINITED(ctx)) {
|
|
if (ctx->dnsc_udpsock >= 0)
|
|
closesocket(ctx->dnsc_udpsock);
|
|
ctx->dnsc_udpsock = -1;
|
|
if (ctx->dnsc_pbuf)
|
|
free(ctx->dnsc_pbuf);
|
|
ctx->dnsc_pbuf = NULL;
|
|
q = ctx->dnsc_qactive.head;
|
|
while((p = q) != NULL) {
|
|
q = q->dnsq_next;
|
|
free(p);
|
|
}
|
|
qlist_init(&ctx->dnsc_qactive);
|
|
ctx->dnsc_nactive = 0;
|
|
dns_drop_utm(ctx);
|
|
}
|
|
}
|
|
|
|
void dns_reset(struct dns_ctx *ctx) {
|
|
SETCTX(ctx);
|
|
dns_close(ctx);
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
ctx->dnsc_timeout = 4;
|
|
ctx->dnsc_ntries = 3;
|
|
ctx->dnsc_ndots = 1;
|
|
ctx->dnsc_udpbuf = DNS_EDNS0PACKET;
|
|
ctx->dnsc_port = DNS_PORT;
|
|
ctx->dnsc_udpsock = -1;
|
|
ctx->dnsc_srchend = ctx->dnsc_srchbuf;
|
|
qlist_init(&ctx->dnsc_qactive);
|
|
dns_init_rng(ctx);
|
|
ctx->dnsc_flags = DNS_INITED;
|
|
}
|
|
|
|
struct dns_ctx *dns_new(const struct dns_ctx *copy) {
|
|
struct dns_ctx *ctx;
|
|
SETCTXINITED(copy);
|
|
dns_assert_ctx(copy);
|
|
ctx = malloc(sizeof(*ctx));
|
|
if (!ctx)
|
|
return NULL;
|
|
*ctx = *copy;
|
|
ctx->dnsc_udpsock = -1;
|
|
qlist_init(&ctx->dnsc_qactive);
|
|
ctx->dnsc_nactive = 0;
|
|
ctx->dnsc_pbuf = NULL;
|
|
ctx->dnsc_qstatus = 0;
|
|
ctx->dnsc_srchend = ctx->dnsc_srchbuf +
|
|
(copy->dnsc_srchend - copy->dnsc_srchbuf);
|
|
ctx->dnsc_utmfn = NULL;
|
|
ctx->dnsc_utmctx = NULL;
|
|
dns_init_rng(ctx);
|
|
return ctx;
|
|
}
|
|
|
|
void dns_free(struct dns_ctx *ctx) {
|
|
assert(ctx != NULL && ctx != &dns_defctx);
|
|
dns_reset(ctx);
|
|
free(ctx);
|
|
}
|
|
|
|
int dns_open(struct dns_ctx *ctx) {
|
|
int sock;
|
|
unsigned i;
|
|
int port;
|
|
union sockaddr_ns *sns;
|
|
#ifdef HAVE_IPv6
|
|
unsigned have_inet6 = 0;
|
|
#endif
|
|
|
|
SETCTXINITED(ctx);
|
|
assert(!CTXOPEN(ctx));
|
|
|
|
port = htons((unsigned short)ctx->dnsc_port);
|
|
/* ensure we have at least one server */
|
|
if (!ctx->dnsc_nserv) {
|
|
sns = ctx->dnsc_serv;
|
|
sns->sin.sin_family = AF_INET;
|
|
sns->sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
ctx->dnsc_nserv = 1;
|
|
}
|
|
|
|
for (i = 0; i < ctx->dnsc_nserv; ++i) {
|
|
sns = &ctx->dnsc_serv[i];
|
|
/* set port for each sockaddr */
|
|
#ifdef HAVE_IPv6
|
|
if (sns->sa.sa_family == AF_INET6) {
|
|
if (!sns->sin6.sin6_port) sns->sin6.sin6_port = (unsigned short)port;
|
|
++have_inet6;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
assert(sns->sa.sa_family == AF_INET);
|
|
if (!sns->sin.sin_port) sns->sin.sin_port = (unsigned short)port;
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_IPv6
|
|
if (have_inet6 && have_inet6 < ctx->dnsc_nserv) {
|
|
/* convert all IPv4 addresses to IPv6 V4MAPPED */
|
|
struct sockaddr_in6 sin6;
|
|
memset(&sin6, 0, sizeof(sin6));
|
|
sin6.sin6_family = AF_INET6;
|
|
/* V4MAPPED: ::ffff:1.2.3.4 */
|
|
sin6.sin6_addr.s6_addr[10] = 0xff;
|
|
sin6.sin6_addr.s6_addr[11] = 0xff;
|
|
for(i = 0; i < ctx->dnsc_nserv; ++i) {
|
|
sns = &ctx->dnsc_serv[i];
|
|
if (sns->sa.sa_family == AF_INET) {
|
|
sin6.sin6_port = sns->sin.sin_port;
|
|
memcpy(sin6.sin6_addr.s6_addr + 4*3, &sns->sin.sin_addr, 4);
|
|
sns->sin6 = sin6;
|
|
}
|
|
}
|
|
}
|
|
|
|
ctx->dnsc_salen = have_inet6 ?
|
|
sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
|
|
|
|
if (have_inet6)
|
|
sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
else
|
|
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
#else /* !HAVE_IPv6 */
|
|
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
ctx->dnsc_salen = sizeof(struct sockaddr_in);
|
|
#endif /* HAVE_IPv6 */
|
|
|
|
if (sock < 0) {
|
|
ctx->dnsc_qstatus = DNS_E_TEMPFAIL;
|
|
return -1;
|
|
}
|
|
#ifdef WINDOWS
|
|
{ unsigned long on = 1;
|
|
if (ioctlsocket(sock, FIONBIO, &on) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
ctx->dnsc_qstatus = DNS_E_TEMPFAIL;
|
|
return -1;
|
|
}
|
|
}
|
|
#else /* !WINDOWS */
|
|
if (fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK) < 0 ||
|
|
fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
|
|
closesocket(sock);
|
|
ctx->dnsc_qstatus = DNS_E_TEMPFAIL;
|
|
return -1;
|
|
}
|
|
#endif /* WINDOWS */
|
|
/* allocate the packet buffer */
|
|
if ((ctx->dnsc_pbuf = malloc(ctx->dnsc_udpbuf)) == NULL) {
|
|
closesocket(sock);
|
|
ctx->dnsc_qstatus = DNS_E_NOMEM;
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
ctx->dnsc_udpsock = sock;
|
|
dns_request_utm(ctx, 0);
|
|
return sock;
|
|
}
|
|
|
|
int dns_sock(const struct dns_ctx *ctx) {
|
|
SETCTXINITED(ctx);
|
|
return ctx->dnsc_udpsock;
|
|
}
|
|
|
|
int dns_active(const struct dns_ctx *ctx) {
|
|
SETCTXINITED(ctx);
|
|
dns_assert_ctx(ctx);
|
|
return ctx->dnsc_nactive;
|
|
}
|
|
|
|
int dns_status(const struct dns_ctx *ctx) {
|
|
SETCTX(ctx);
|
|
return ctx->dnsc_qstatus;
|
|
}
|
|
void dns_setstatus(struct dns_ctx *ctx, int status) {
|
|
SETCTX(ctx);
|
|
ctx->dnsc_qstatus = status;
|
|
}
|
|
|
|
/* End the query: disconnect it from the active list, free it,
|
|
* and return the result to the caller.
|
|
*/
|
|
static void
|
|
dns_end_query(struct dns_ctx *ctx, struct dns_query *q,
|
|
int status, void *result) {
|
|
dns_query_fn *cbck = q->dnsq_cbck;
|
|
void *cbdata = q->dnsq_cbdata;
|
|
ctx->dnsc_qstatus = status;
|
|
assert((status < 0 && result == 0) || (status >= 0 && result != 0));
|
|
assert(cbck != 0); /*XXX callback may be NULL */
|
|
assert(ctx->dnsc_nactive > 0);
|
|
--ctx->dnsc_nactive;
|
|
qlist_remove(&ctx->dnsc_qactive, q);
|
|
/* force the query to be unconnected */
|
|
/*memset(q, 0, sizeof(*q));*/
|
|
#ifndef NDEBUG
|
|
q->dnsq_ctx = NULL;
|
|
#endif
|
|
free(q);
|
|
cbck(ctx, result, cbdata);
|
|
}
|
|
|
|
#define DNS_DBG(ctx, code, sa, slen, pkt, plen) \
|
|
do { \
|
|
if (ctx->dnsc_udbgfn) \
|
|
ctx->dnsc_udbgfn(code, (sa), slen, pkt, plen, 0, 0); \
|
|
} while(0)
|
|
#define DNS_DBGQ(ctx, q, code, sa, slen, pkt, plen) \
|
|
do { \
|
|
if (ctx->dnsc_udbgfn) \
|
|
ctx->dnsc_udbgfn(code, (sa), slen, pkt, plen, q, q->dnsq_cbdata); \
|
|
} while(0)
|
|
|
|
static void dns_newid(struct dns_ctx *ctx, struct dns_query *q) {
|
|
/* this is how we choose an identifier for a new query (qID).
|
|
* For now, it's just sequential number, incremented for every query, and
|
|
* thus obviously trivial to guess.
|
|
* There are two choices:
|
|
* a) use sequential numbers. It is plain insecure. In DNS, there are two
|
|
* places where random numbers are (or can) be used to increase security:
|
|
* random qID and random source port number. Without this randomness
|
|
* (udns uses fixed port for all queries), or when the randomness is weak,
|
|
* it's trivial to spoof query replies. With randomness however, it
|
|
* becomes a bit more difficult task. Too bad we only have 16 bits for
|
|
* our security, as qID is only two bytes. It isn't a security per se,
|
|
* to rely on those 16 bits - an attacker can just flood us with fake
|
|
* replies with all possible qIDs (only 65536 of them), and in this case,
|
|
* even if we'll use true random qIDs, we'll be in trouble (not protected
|
|
* against spoofing). Yes, this is only possible on a high-speed network
|
|
* (probably on the LAN only, since usually a border router for a LAN
|
|
* protects internal machines from packets with spoofed local addresses
|
|
* from outside, and usually a nameserver resides on LAN), but it's
|
|
* still very well possible to send us fake replies.
|
|
* In other words: there's nothing a DNS (stub) resolver can do against
|
|
* spoofing attacks, unless DNSSEC is in use, which helps here alot.
|
|
* Too bad that DNSSEC isn't widespread, so relying on it isn't an
|
|
* option in almost all cases...
|
|
* b) use random qID, based on some random-number generation mechanism.
|
|
* This way, we increase our protection a bit (see above - it's very weak
|
|
* still), but we also increase risk of qID reuse and matching late replies
|
|
* that comes to queries we've sent before against new queries. There are
|
|
* some more corner cases around that, as well - for example, normally,
|
|
* udns tries to find the query for a given reply by qID, *and* by
|
|
* verifying that the query DN and other parameters are also the same
|
|
* (so if the new query is against another domain name, old reply will
|
|
* be ignored automatically). But certain types of replies which we now
|
|
* handle - for example, FORMERR reply from servers which refuses to
|
|
* process EDNS0-enabled packets - comes without all the query parameters
|
|
* but the qID - so we're forced to use qID only when determining which
|
|
* query the given reply corresponds to. This makes us even more
|
|
* vulnerable to spoofing attacks, because an attacker don't even need to
|
|
* know which queries we perform to spoof the replies - he only needs to
|
|
* flood us with fake FORMERR "replies".
|
|
*
|
|
* That all to say: using sequential (or any other trivially guessable)
|
|
* numbers for qIDs is insecure, but the whole thing is inherently insecure
|
|
* as well, and this "extra weakness" that comes from weak qID choosing
|
|
* algorithm adds almost nothing to the underlying problem.
|
|
*
|
|
* It CAN NOT be made secure. Period. That's it.
|
|
* Unless we choose to implement DNSSEC, which is a whole different story.
|
|
* Forcing TCP mode makes it better, but who uses TCP for DNS anyway?
|
|
* (and it's hardly possible because of huge impact on the recursive
|
|
* nameservers).
|
|
*
|
|
* Note that ALL stub resolvers (again, unless they implement and enforce
|
|
* DNSSEC) suffers from this same problem.
|
|
*
|
|
* Here, I use a pseudo-random number generator for qIDs, instead of a
|
|
* simpler sequential IDs. This is _not_ more secure than sequential
|
|
* ID, but some found random IDs more enjoyeable for some reason. So
|
|
* here it goes.
|
|
*/
|
|
|
|
/* Use random number and check if it's unique.
|
|
* If it's not, try again up to 5 times.
|
|
*/
|
|
unsigned loop;
|
|
dnsc_t c0, c1;
|
|
for(loop = 0; loop < 5; ++loop) {
|
|
const struct dns_query *c;
|
|
if (!ctx->dnsc_nextid)
|
|
ctx->dnsc_nextid = udns_jranval(&ctx->dnsc_jran);
|
|
c0 = ctx->dnsc_nextid & 0xff;
|
|
c1 = (ctx->dnsc_nextid >> 8) & 0xff;
|
|
ctx->dnsc_nextid >>= 16;
|
|
for(c = ctx->dnsc_qactive.head; c; c = c->dnsq_next)
|
|
if (c->dnsq_id[0] == c0 && c->dnsq_id[1] == c1)
|
|
break; /* found such entry, try again */
|
|
if (!c)
|
|
break;
|
|
}
|
|
q->dnsq_id[0] = c0; q->dnsq_id[1] = c1;
|
|
|
|
/* reset all parameters relevant for previous query lifetime */
|
|
q->dnsq_try = 0;
|
|
q->dnsq_servi = 0;
|
|
/*XXX probably should keep dnsq_servnEDNS0 bits?
|
|
* See also comments in dns_ioevent() about FORMERR case */
|
|
q->dnsq_servwait = q->dnsq_servskip = q->dnsq_servnEDNS0 = 0;
|
|
}
|
|
|
|
/* Find next search suffix and fills in q->dnsq_dn.
|
|
* Return 0 if no more to try. */
|
|
static int dns_next_srch(struct dns_ctx *ctx, struct dns_query *q) {
|
|
unsigned dnl;
|
|
|
|
for(;;) {
|
|
if (q->dnsq_nxtsrch > ctx->dnsc_srchend)
|
|
return 0;
|
|
dnl = dns_dnlen(q->dnsq_nxtsrch);
|
|
if (dnl + q->dnsq_origdnl0 <= DNS_MAXDN &&
|
|
(*q->dnsq_nxtsrch || !(q->dnsq_flags & DNS_ASIS_DONE)))
|
|
break;
|
|
q->dnsq_nxtsrch += dnl;
|
|
}
|
|
memcpy(q->dnsq_dn + q->dnsq_origdnl0, q->dnsq_nxtsrch, dnl);
|
|
if (!*q->dnsq_nxtsrch)
|
|
q->dnsq_flags |= DNS_ASIS_DONE;
|
|
q->dnsq_nxtsrch += dnl;
|
|
dns_newid(ctx, q); /* new ID for new qDN */
|
|
return 1;
|
|
}
|
|
|
|
/* find the server to try for current iteration.
|
|
* Note that current dnsq_servi may point to a server we should skip --
|
|
* in that case advance to the next server.
|
|
* Return true if found, false if all tried.
|
|
*/
|
|
static int dns_find_serv(const struct dns_ctx *ctx, struct dns_query *q) {
|
|
while(q->dnsq_servi < ctx->dnsc_nserv) {
|
|
if (!(q->dnsq_servskip & (1 << q->dnsq_servi)))
|
|
return 1;
|
|
++q->dnsq_servi;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* format and send the query to a given server.
|
|
* In case of network problem (sendto() fails), return -1,
|
|
* else return 0.
|
|
*/
|
|
static int
|
|
dns_send_this(struct dns_ctx *ctx, struct dns_query *q,
|
|
unsigned servi, time_t now) {
|
|
unsigned qlen;
|
|
unsigned tries;
|
|
|
|
{ /* format the query buffer */
|
|
dnsc_t *p = ctx->dnsc_pbuf;
|
|
memset(p, 0, DNS_HSIZE);
|
|
if (!(q->dnsq_flags & DNS_NORD)) p[DNS_H_F1] |= DNS_HF1_RD;
|
|
if (q->dnsq_flags & DNS_AAONLY) p[DNS_H_F1] |= DNS_HF1_AA;
|
|
if (q->dnsq_flags & DNS_SET_CD) p[DNS_H_F2] |= DNS_HF2_CD;
|
|
p[DNS_H_QDCNT2] = 1;
|
|
memcpy(p + DNS_H_QID, q->dnsq_id, 2);
|
|
p = dns_payload(p);
|
|
/* copy query dn */
|
|
p += dns_dntodn(q->dnsq_dn, p, DNS_MAXDN);
|
|
/* query type and class */
|
|
memcpy(p, q->dnsq_typcls, 4); p += 4;
|
|
/* add EDNS0 record. DO flag requires it */
|
|
if (q->dnsq_flags & DNS_SET_DO ||
|
|
(ctx->dnsc_udpbuf > DNS_MAXPACKET &&
|
|
!(q->dnsq_servnEDNS0 & (1 << servi)))) {
|
|
*p++ = 0; /* empty (root) DN */
|
|
p = dns_put16(p, DNS_T_OPT);
|
|
p = dns_put16(p, ctx->dnsc_udpbuf);
|
|
/* EDNS0 RCODE & VERSION; rest of the TTL field; RDLEN */
|
|
memset(p, 0, 2+2+2);
|
|
if (q->dnsq_flags & DNS_SET_DO) p[2] |= DNS_EF1_DO;
|
|
p += 2+2+2;
|
|
ctx->dnsc_pbuf[DNS_H_ARCNT2] = 1;
|
|
}
|
|
qlen = p - ctx->dnsc_pbuf;
|
|
assert(qlen <= ctx->dnsc_udpbuf);
|
|
}
|
|
|
|
/* send the query */
|
|
tries = 10;
|
|
while (sendto(ctx->dnsc_udpsock, (void*)ctx->dnsc_pbuf, qlen, 0,
|
|
&ctx->dnsc_serv[servi].sa, ctx->dnsc_salen) < 0) {
|
|
/*XXX just ignore the sendto() error for now and try again.
|
|
* In the future, it may be possible to retrieve the error code
|
|
* and find which operation/query failed.
|
|
*XXX try the next server too? (if ENETUNREACH is returned immediately)
|
|
*/
|
|
if (--tries) continue;
|
|
/* if we can't send the query, fail it. */
|
|
dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0);
|
|
return -1;
|
|
}
|
|
DNS_DBGQ(ctx, q, 1,
|
|
&ctx->dnsc_serv[servi].sa, sizeof(union sockaddr_ns),
|
|
ctx->dnsc_pbuf, qlen);
|
|
q->dnsq_servwait |= 1 << servi; /* expect reply from this ns */
|
|
|
|
q->dnsq_deadline = now +
|
|
(dns_find_serv(ctx, q) ? 1 : ctx->dnsc_timeout << q->dnsq_try);
|
|
|
|
/* move the query to the proper place, according to the new deadline */
|
|
qlist_remove(&ctx->dnsc_qactive, q);
|
|
{ /* insert from the tail */
|
|
struct dns_query *p;
|
|
for(p = ctx->dnsc_qactive.tail; p; p = p->dnsq_prev)
|
|
if (p->dnsq_deadline <= q->dnsq_deadline)
|
|
break;
|
|
qlist_insert_after(&ctx->dnsc_qactive, q, p);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* send the query out using next available server
|
|
* and add it to the active list, or, if no servers available,
|
|
* end it.
|
|
*/
|
|
static void
|
|
dns_send(struct dns_ctx *ctx, struct dns_query *q, time_t now) {
|
|
|
|
/* if we can't send the query, return TEMPFAIL even when searching:
|
|
* we can't be sure whenever the name we tried to search exists or not,
|
|
* so don't continue searching, or we may find the wrong name. */
|
|
|
|
if (!dns_find_serv(ctx, q)) {
|
|
/* no more servers in this iteration. Try the next cycle */
|
|
q->dnsq_servi = 0; /* reset */
|
|
q->dnsq_try++; /* next try */
|
|
if (q->dnsq_try >= ctx->dnsc_ntries ||
|
|
!dns_find_serv(ctx, q)) {
|
|
/* no more servers and tries, fail the query */
|
|
/* return TEMPFAIL even when searching: no more tries for this
|
|
* searchlist, and no single definitive reply (handled in dns_ioevent()
|
|
* in NOERROR or NXDOMAIN cases) => all nameservers failed to process
|
|
* current search list element, so we don't know whenever the name exists.
|
|
*/
|
|
dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
dns_send_this(ctx, q, q->dnsq_servi++, now);
|
|
}
|
|
|
|
static void dns_dummy_cb(struct dns_ctx *ctx, void *result, void *data) {
|
|
if (result) free(result);
|
|
data = ctx = 0; /* used */
|
|
}
|
|
|
|
/* The (only, main, real) query submission routine.
|
|
* Allocate new query structure, initialize it, check validity of
|
|
* parameters, and add it to the head of the active list, without
|
|
* trying to send it (to be picked up on next event).
|
|
* Error return (without calling the callback routine) -
|
|
* no memory or wrong parameters.
|
|
*XXX The `no memory' case probably should go to the callback anyway...
|
|
*/
|
|
struct dns_query *
|
|
dns_submit_dn(struct dns_ctx *ctx,
|
|
dnscc_t *dn, int qcls, int qtyp, int flags,
|
|
dns_parse_fn *parse, dns_query_fn *cbck, void *data) {
|
|
struct dns_query *q;
|
|
SETCTXOPEN(ctx);
|
|
dns_assert_ctx(ctx);
|
|
|
|
q = calloc(sizeof(*q), 1);
|
|
if (!q) {
|
|
ctx->dnsc_qstatus = DNS_E_NOMEM;
|
|
return NULL;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
q->dnsq_ctx = ctx;
|
|
#endif
|
|
q->dnsq_parse = parse;
|
|
q->dnsq_cbck = cbck ? cbck : dns_dummy_cb;
|
|
q->dnsq_cbdata = data;
|
|
|
|
q->dnsq_origdnl0 = dns_dntodn(dn, q->dnsq_dn, sizeof(q->dnsq_dn));
|
|
assert(q->dnsq_origdnl0 > 0);
|
|
--q->dnsq_origdnl0; /* w/o the trailing 0 */
|
|
dns_put16(q->dnsq_typcls+0, qtyp);
|
|
dns_put16(q->dnsq_typcls+2, qcls);
|
|
q->dnsq_flags = (flags | ctx->dnsc_flags) & ~DNS_INTERNAL;
|
|
|
|
if (flags & DNS_NOSRCH ||
|
|
dns_dnlabels(q->dnsq_dn) > ctx->dnsc_ndots) {
|
|
q->dnsq_nxtsrch = flags & DNS_NOSRCH ?
|
|
ctx->dnsc_srchend /* end of the search list if no search requested */ :
|
|
ctx->dnsc_srchbuf /* beginning of the list, but try as-is first */;
|
|
q->dnsq_flags |= DNS_ASIS_DONE;
|
|
dns_newid(ctx, q);
|
|
}
|
|
else {
|
|
q->dnsq_nxtsrch = ctx->dnsc_srchbuf;
|
|
dns_next_srch(ctx, q);
|
|
}
|
|
|
|
/* q->dnsq_deadline is set to 0 (calloc above): the new query is
|
|
* "already expired" when first inserted into queue, so it's safe
|
|
* to insert it into the head of the list. Next call to dns_timeouts()
|
|
* will actually send it.
|
|
*/
|
|
qlist_add_head(&ctx->dnsc_qactive, q);
|
|
++ctx->dnsc_nactive;
|
|
dns_request_utm(ctx, 0);
|
|
|
|
return q;
|
|
}
|
|
|
|
struct dns_query *
|
|
dns_submit_p(struct dns_ctx *ctx,
|
|
const char *name, int qcls, int qtyp, int flags,
|
|
dns_parse_fn *parse, dns_query_fn *cbck, void *data) {
|
|
int isabs;
|
|
SETCTXOPEN(ctx);
|
|
if (dns_ptodn(name, 0, ctx->dnsc_pbuf, DNS_MAXDN, &isabs) <= 0) {
|
|
ctx->dnsc_qstatus = DNS_E_BADQUERY;
|
|
return NULL;
|
|
}
|
|
if (isabs)
|
|
flags |= DNS_NOSRCH;
|
|
return
|
|
dns_submit_dn(ctx, ctx->dnsc_pbuf, qcls, qtyp, flags, parse, cbck, data);
|
|
}
|
|
|
|
/* process readable fd condition.
|
|
* To be usable in edge-triggered environment, the routine
|
|
* should consume all input so it should loop over.
|
|
* Note it isn't really necessary to loop here, because
|
|
* an application may perform the loop just fine by it's own,
|
|
* but in this case we should return some sensitive result,
|
|
* to indicate when to stop calling and error conditions.
|
|
* Note also we may encounter all sorts of recvfrom()
|
|
* errors which aren't fatal, and at the same time we may
|
|
* loop forever if an error IS fatal.
|
|
*/
|
|
void dns_ioevent(struct dns_ctx *ctx, time_t now) {
|
|
int r;
|
|
unsigned servi;
|
|
struct dns_query *q;
|
|
dnsc_t *pbuf;
|
|
dnscc_t *pend, *pcur;
|
|
void *result;
|
|
union sockaddr_ns sns;
|
|
socklen_t slen;
|
|
|
|
SETCTX(ctx);
|
|
if (!CTXOPEN(ctx))
|
|
return;
|
|
dns_assert_ctx(ctx);
|
|
pbuf = ctx->dnsc_pbuf;
|
|
|
|
if (!now) now = time(NULL);
|
|
|
|
again: /* receive the reply */
|
|
|
|
slen = sizeof(sns);
|
|
r = recvfrom(ctx->dnsc_udpsock, (void*)pbuf, ctx->dnsc_udpbuf,
|
|
MSG_DONTWAIT, &sns.sa, &slen);
|
|
if (r < 0) {
|
|
/*XXX just ignore recvfrom() errors for now.
|
|
* in the future it may be possible to determine which
|
|
* query failed and requeue it.
|
|
* Note there may be various error conditions, triggered
|
|
* by both local problems and remote problems. It isn't
|
|
* quite trivial to determine whenever an error is local
|
|
* or remote. On local errors, we should stop, while
|
|
* remote errors should be ignored (for now anyway).
|
|
*/
|
|
#ifdef WINDOWS
|
|
if (WSAGetLastError() == WSAEWOULDBLOCK)
|
|
#else
|
|
if (errno == EAGAIN)
|
|
#endif
|
|
{
|
|
dns_request_utm(ctx, now);
|
|
return;
|
|
}
|
|
goto again;
|
|
}
|
|
|
|
pend = pbuf + r;
|
|
pcur = dns_payload(pbuf);
|
|
|
|
/* check reply header */
|
|
if (pcur > pend || dns_numqd(pbuf) > 1 || dns_opcode(pbuf) != 0) {
|
|
DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r);
|
|
goto again;
|
|
}
|
|
|
|
/* find the matching query, by qID */
|
|
for (q = ctx->dnsc_qactive.head; ; q = q->dnsq_next) {
|
|
if (!q) {
|
|
/* no more requests: old reply? */
|
|
DNS_DBG(ctx, -5/*no matching query*/, &sns.sa, slen, pbuf, r);
|
|
goto again;
|
|
}
|
|
if (pbuf[DNS_H_QID1] == q->dnsq_id[0] &&
|
|
pbuf[DNS_H_QID2] == q->dnsq_id[1])
|
|
break;
|
|
}
|
|
|
|
/* if we have numqd, compare with our query qDN */
|
|
if (dns_numqd(pbuf)) {
|
|
/* decode the qDN */
|
|
dnsc_t dn[DNS_MAXDN];
|
|
if (dns_getdn(pbuf, &pcur, pend, dn, sizeof(dn)) < 0 ||
|
|
pcur + 4 > pend) {
|
|
DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r);
|
|
goto again;
|
|
}
|
|
if (!dns_dnequal(dn, q->dnsq_dn) ||
|
|
memcmp(pcur, q->dnsq_typcls, 4) != 0) {
|
|
/* not this query */
|
|
DNS_DBG(ctx, -5/*no matching query*/, &sns.sa, slen, pbuf, r);
|
|
goto again;
|
|
}
|
|
/* here, query match, and pcur points past qDN in query section in pbuf */
|
|
}
|
|
/* if no numqd, we only allow FORMERR rcode */
|
|
else if (dns_rcode(pbuf) != DNS_R_FORMERR) {
|
|
/* treat it as bad reply if !FORMERR */
|
|
DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r);
|
|
goto again;
|
|
}
|
|
else {
|
|
/* else it's FORMERR, handled below */
|
|
}
|
|
|
|
/* find server */
|
|
#ifdef HAVE_IPv6
|
|
if (sns.sa.sa_family == AF_INET6 && slen >= sizeof(sns.sin6)) {
|
|
for(servi = 0; servi < ctx->dnsc_nserv; ++servi)
|
|
if (sin6_eq(ctx->dnsc_serv[servi].sin6, sns.sin6))
|
|
break;
|
|
}
|
|
else
|
|
#endif
|
|
if (sns.sa.sa_family == AF_INET && slen >= sizeof(sns.sin)) {
|
|
for(servi = 0; servi < ctx->dnsc_nserv; ++servi)
|
|
if (sin_eq(ctx->dnsc_serv[servi].sin, sns.sin))
|
|
break;
|
|
}
|
|
else
|
|
servi = ctx->dnsc_nserv;
|
|
|
|
/* check if we expect reply from this server.
|
|
* Note we can receive reply from first try if we're already at next */
|
|
if (!(q->dnsq_servwait & (1 << servi))) { /* if ever asked this NS */
|
|
DNS_DBG(ctx, -2/*wrong server*/, &sns.sa, slen, pbuf, r);
|
|
goto again;
|
|
}
|
|
|
|
/* we got (some) reply for our query */
|
|
|
|
DNS_DBGQ(ctx, q, 0, &sns.sa, slen, pbuf, r);
|
|
q->dnsq_servwait &= ~(1 << servi); /* don't expect reply from this serv */
|
|
|
|
/* process the RCODE */
|
|
switch(dns_rcode(pbuf)) {
|
|
|
|
case DNS_R_NOERROR:
|
|
if (dns_tc(pbuf)) {
|
|
/* possible truncation. We can't deal with it. */
|
|
/*XXX for now, treat TC bit the same as SERVFAIL.
|
|
* It is possible to:
|
|
* a) try to decode the reply - may be ANSWER section is ok;
|
|
* b) check if server understands EDNS0, and if it is, and
|
|
* answer still don't fit, end query.
|
|
*/
|
|
break;
|
|
}
|
|
if (!dns_numan(pbuf)) { /* no data of requested type */
|
|
if (dns_next_srch(ctx, q)) {
|
|
/* if we're searching, try next searchlist element,
|
|
* but remember NODATA reply. */
|
|
q->dnsq_flags |= DNS_SEEN_NODATA;
|
|
dns_send(ctx, q, now);
|
|
}
|
|
else
|
|
/* else - nothing to search any more - finish the query.
|
|
* It will be NODATA since we've seen a NODATA reply. */
|
|
dns_end_query(ctx, q, DNS_E_NODATA, 0);
|
|
}
|
|
/* we've got a positive reply here */
|
|
else if (q->dnsq_parse) {
|
|
/* if we have parsing routine, call it and return whatever it returned */
|
|
/* don't try to re-search if NODATA here. For example,
|
|
* if we asked for A but only received CNAME. Unless we'll
|
|
* someday do recursive queries. And that's problematic too, since
|
|
* we may be dealing with specific AA-only nameservers for a given
|
|
* domain, but CNAME points elsewhere...
|
|
*/
|
|
r = q->dnsq_parse(q->dnsq_dn, pbuf, pcur, pend, &result);
|
|
dns_end_query(ctx, q, r, r < 0 ? NULL : result);
|
|
}
|
|
/* else just malloc+copy the raw DNS reply */
|
|
else if ((result = malloc(r)) == NULL)
|
|
dns_end_query(ctx, q, DNS_E_NOMEM, NULL);
|
|
else {
|
|
memcpy(result, pbuf, r);
|
|
dns_end_query(ctx, q, r, result);
|
|
}
|
|
goto again;
|
|
|
|
case DNS_R_NXDOMAIN: /* Non-existing domain. */
|
|
if (dns_next_srch(ctx, q))
|
|
/* more search entries exists, try them. */
|
|
dns_send(ctx, q, now);
|
|
else
|
|
/* nothing to search anymore. End the query, returning either NODATA
|
|
* if we've seen it before, or NXDOMAIN if not. */
|
|
dns_end_query(ctx, q,
|
|
q->dnsq_flags & DNS_SEEN_NODATA ? DNS_E_NODATA : DNS_E_NXDOMAIN, 0);
|
|
goto again;
|
|
|
|
case DNS_R_FORMERR:
|
|
case DNS_R_NOTIMPL:
|
|
/* for FORMERR and NOTIMPL rcodes, if we tried EDNS0-enabled query,
|
|
* try w/o EDNS0. */
|
|
if (ctx->dnsc_udpbuf > DNS_MAXPACKET &&
|
|
!(q->dnsq_servnEDNS0 & (1 << servi))) {
|
|
/* we always trying EDNS0 first if enabled, and retry a given query
|
|
* if not available. Maybe it's better to remember inavailability of
|
|
* EDNS0 in ctx as a per-NS flag, and never try again for this NS.
|
|
* For long-running applications.. maybe they will change the nameserver
|
|
* while we're running? :) Also, since FORMERR is the only rcode we
|
|
* allow to be header-only, and in this case the only check we do to
|
|
* find a query it belongs to is qID (not qDN+qCLS+qTYP), it's much
|
|
* easier to spoof and to force us to perform non-EDNS0 queries only...
|
|
*/
|
|
q->dnsq_servnEDNS0 |= 1 << servi;
|
|
dns_send_this(ctx, q, servi, now);
|
|
goto again;
|
|
}
|
|
/* else we handle it the same as SERVFAIL etc */
|
|
|
|
case DNS_R_SERVFAIL:
|
|
case DNS_R_REFUSED:
|
|
/* for these rcodes, advance this request
|
|
* to the next server and reschedule */
|
|
default: /* unknown rcode? hmmm... */
|
|
break;
|
|
}
|
|
|
|
/* here, we received unexpected reply */
|
|
q->dnsq_servskip |= (1 << servi); /* don't retry this server */
|
|
|
|
/* we don't expect replies from this server anymore.
|
|
* But there may be other servers. Some may be still processing our
|
|
* query, and some may be left to try.
|
|
* We just ignore this reply and wait a bit more if some NSes haven't
|
|
* replied yet (dnsq_servwait != 0), and let the situation to be handled
|
|
* on next event processing. Timeout for this query is set correctly,
|
|
* if not taking into account the one-second difference - we can try
|
|
* next server in the same iteration sooner.
|
|
*/
|
|
|
|
/* try next server */
|
|
if (!q->dnsq_servwait) {
|
|
/* next retry: maybe some other servers will reply next time.
|
|
* dns_send() will end the query for us if no more servers to try.
|
|
* Note we can't continue with the next searchlist element here:
|
|
* we don't know if the current qdn exists or not, there's no definitive
|
|
* answer yet (which is seen in cases above).
|
|
*XXX standard resolver also tries as-is query in case all nameservers
|
|
* failed to process our query and if not tried before. We don't do it.
|
|
*/
|
|
dns_send(ctx, q, now);
|
|
}
|
|
else {
|
|
/* else don't do anything - not all servers replied yet */
|
|
}
|
|
goto again;
|
|
|
|
}
|
|
|
|
/* handle all timeouts */
|
|
int dns_timeouts(struct dns_ctx *ctx, int maxwait, time_t now) {
|
|
/* this is a hot routine */
|
|
struct dns_query *q;
|
|
|
|
SETCTX(ctx);
|
|
dns_assert_ctx(ctx);
|
|
|
|
/* Pick up first entry from query list.
|
|
* If its deadline has passed, (re)send it
|
|
* (dns_send() will move it next in the list).
|
|
* If not, this is the query which determines the closest deadline.
|
|
*/
|
|
|
|
q = ctx->dnsc_qactive.head;
|
|
if (!q)
|
|
return maxwait;
|
|
if (!now)
|
|
now = time(NULL);
|
|
do {
|
|
if (q->dnsq_deadline > now) { /* first non-expired query */
|
|
int w = (int)(q->dnsq_deadline - now);
|
|
if (maxwait < 0 || maxwait > w)
|
|
maxwait = w;
|
|
break;
|
|
}
|
|
else {
|
|
/* process expired deadline */
|
|
dns_send(ctx, q, now);
|
|
}
|
|
} while((q = ctx->dnsc_qactive.head) != NULL);
|
|
|
|
dns_request_utm(ctx, now); /* update timer with new deadline */
|
|
return maxwait;
|
|
}
|
|
|
|
struct dns_resolve_data {
|
|
int dnsrd_done;
|
|
void *dnsrd_result;
|
|
};
|
|
|
|
static void dns_resolve_cb(struct dns_ctx *ctx, void *result, void *data) {
|
|
struct dns_resolve_data *d = data;
|
|
d->dnsrd_result = result;
|
|
d->dnsrd_done = 1;
|
|
ctx = ctx;
|
|
}
|
|
|
|
void *dns_resolve(struct dns_ctx *ctx, struct dns_query *q) {
|
|
time_t now;
|
|
struct dns_resolve_data d;
|
|
int n;
|
|
SETCTXOPEN(ctx);
|
|
|
|
if (!q)
|
|
return NULL;
|
|
|
|
assert(ctx == q->dnsq_ctx);
|
|
dns_assert_ctx(ctx);
|
|
/* do not allow re-resolving syncronous queries */
|
|
assert(q->dnsq_cbck != dns_resolve_cb && "can't resolve syncronous query");
|
|
if (q->dnsq_cbck == dns_resolve_cb) {
|
|
ctx->dnsc_qstatus = DNS_E_BADQUERY;
|
|
return NULL;
|
|
}
|
|
q->dnsq_cbck = dns_resolve_cb;
|
|
q->dnsq_cbdata = &d;
|
|
d.dnsrd_done = 0;
|
|
|
|
now = time(NULL);
|
|
while(!d.dnsrd_done && (n = dns_timeouts(ctx, -1, now)) >= 0) {
|
|
#ifdef HAVE_POLL
|
|
struct pollfd pfd;
|
|
pfd.fd = ctx->dnsc_udpsock;
|
|
pfd.events = POLLIN;
|
|
n = poll(&pfd, 1, n * 1000);
|
|
#else
|
|
fd_set rfd;
|
|
struct timeval tv;
|
|
FD_ZERO(&rfd);
|
|
FD_SET(ctx->dnsc_udpsock, &rfd);
|
|
tv.tv_sec = n; tv.tv_usec = 0;
|
|
n = select(ctx->dnsc_udpsock + 1, &rfd, NULL, NULL, &tv);
|
|
#endif
|
|
now = time(NULL);
|
|
if (n > 0)
|
|
dns_ioevent(ctx, now);
|
|
}
|
|
|
|
return d.dnsrd_result;
|
|
}
|
|
|
|
void *dns_resolve_dn(struct dns_ctx *ctx,
|
|
dnscc_t *dn, int qcls, int qtyp, int flags,
|
|
dns_parse_fn *parse) {
|
|
return
|
|
dns_resolve(ctx,
|
|
dns_submit_dn(ctx, dn, qcls, qtyp, flags, parse, NULL, NULL));
|
|
}
|
|
|
|
void *dns_resolve_p(struct dns_ctx *ctx,
|
|
const char *name, int qcls, int qtyp, int flags,
|
|
dns_parse_fn *parse) {
|
|
return
|
|
dns_resolve(ctx,
|
|
dns_submit_p(ctx, name, qcls, qtyp, flags, parse, NULL, NULL));
|
|
}
|
|
|
|
int dns_cancel(struct dns_ctx *ctx, struct dns_query *q) {
|
|
SETCTX(ctx);
|
|
dns_assert_ctx(ctx);
|
|
assert(q->dnsq_ctx == ctx);
|
|
/* do not allow cancelling syncronous queries */
|
|
assert(q->dnsq_cbck != dns_resolve_cb && "can't cancel syncronous query");
|
|
if (q->dnsq_cbck == dns_resolve_cb)
|
|
return (ctx->dnsc_qstatus = DNS_E_BADQUERY);
|
|
qlist_remove(&ctx->dnsc_qactive, q);
|
|
--ctx->dnsc_nactive;
|
|
dns_request_utm(ctx, 0);
|
|
return 0;
|
|
}
|
|
|