forked from Mirrors/freeswitch
6c86bd9cc3
git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@6645 d0543943-73ff-0310-b7d9-9358b9ac24b2
1371 lines
39 KiB
C
1371 lines
39 KiB
C
/* $Id: udns_resolver.c,v 1.57 2006/11/29 01:17:43 mjt Exp $
|
|
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 WIN32
|
|
#ifdef _MSC_VER
|
|
#undef inline
|
|
#define inline __inline
|
|
#pragma warning(disable:4133)
|
|
#if (_MSC_VER >= 1400) // VC8+
|
|
#ifndef _CRT_SECURE_NO_DEPRECATE
|
|
#define _CRT_SECURE_NO_DEPRECATE
|
|
#endif
|
|
#ifndef _CRT_NONSTDC_NO_DEPRECATE
|
|
#define _CRT_NONSTDC_NO_DEPRECATE
|
|
#endif
|
|
#endif // VC8+
|
|
int udns_inet_pton(int, const char *, void *);
|
|
#include "process.h"
|
|
#else
|
|
#define udns_inet_pton inet_pton
|
|
#endif
|
|
# include <winsock2.h> /* includes <windows.h> */
|
|
# include <ws2tcpip.h> /* needed for struct in6_addr */
|
|
# include <iphlpapi.h> /* for dns server addresses etc */
|
|
# undef HAVE_POLL
|
|
#else
|
|
#define udns_inet_pton inet_pton
|
|
# include <sys/types.h>
|
|
# include <sys/socket.h>
|
|
# include <netinet/in.h>
|
|
# include <arpa/inet.h> /* for inet_pton() */
|
|
# include <unistd.h>
|
|
# include <fcntl.h>
|
|
# include <sys/time.h>
|
|
# ifdef HAVE_POLL
|
|
# include <sys/poll.h>
|
|
# endif
|
|
# define closesocket(sock) close(sock)
|
|
#endif /* !WIN32 */
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include "udns.h"
|
|
|
|
#define DNS_QEXTRA 16 /* size of extra buffer space */
|
|
#define DNS_QBUF DNS_HSIZE+DNS_MAXDN+DNS_QEXTRA
|
|
|
|
#if !defined(HAVE_INET6) && defined(AF_INET6)
|
|
# define HAVE_INET6 1
|
|
#endif
|
|
#ifdef NO_INET6
|
|
# undef HAVE_INET6
|
|
#endif
|
|
|
|
#ifndef EAFNOSUPPORT
|
|
# define EAFNOSUPPORT EINVAL
|
|
#endif
|
|
|
|
union usockaddr_ns {
|
|
struct sockaddr sa;
|
|
struct sockaddr_in sin;
|
|
#if HAVE_INET6
|
|
struct sockaddr_in6 sin6;
|
|
#endif
|
|
};
|
|
|
|
struct dns_qlink {
|
|
struct dns_query *next, *prev;
|
|
};
|
|
|
|
struct dns_query {
|
|
struct dns_qlink dnsq_link; /* list entry (should be first) */
|
|
dnsc_t dnsq_buf[DNS_QBUF]; /* the query buffer */
|
|
enum dns_class dnsq_cls; /* requested RR class */
|
|
enum dns_type dnsq_typ; /* requested RR type */
|
|
unsigned dnsq_len; /* length of the query packet */
|
|
unsigned dnsq_origdnl; /* original length of the dnsq_dn */
|
|
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_try; /* number of tries made so far */
|
|
unsigned dnsq_srchi; /* current search index */
|
|
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
|
|
};
|
|
|
|
/* working with dns_query lists */
|
|
|
|
static inline void qlist_init(struct dns_qlink *list) {
|
|
list->next = list->prev = (struct dns_query *)list;
|
|
}
|
|
|
|
static inline int qlist_empty(const struct dns_qlink *list) {
|
|
return list->next == (const struct dns_query *)list ? 1 : 0;
|
|
}
|
|
|
|
static inline struct dns_query *qlist_first(struct dns_qlink *list) {
|
|
return list->next == (struct dns_query *)list ? 0 : list->next;
|
|
}
|
|
|
|
static inline void qlist_remove(struct dns_query *q) {
|
|
q->dnsq_link.next->dnsq_link.prev = q->dnsq_link.prev;
|
|
q->dnsq_link.prev->dnsq_link.next = q->dnsq_link.next;
|
|
}
|
|
|
|
static inline struct dns_query *qlist_pop(struct dns_qlink *list) {
|
|
struct dns_query *q = list->next;
|
|
if (q == (struct dns_query *)list)
|
|
return NULL;
|
|
qlist_remove(q);
|
|
return q;
|
|
}
|
|
|
|
/* insert q between prev and next */
|
|
static inline void
|
|
qlist_insert(struct dns_query *q,
|
|
struct dns_query *prev, struct dns_query *next) {
|
|
q->dnsq_link.next = next;
|
|
q->dnsq_link.prev = prev;
|
|
prev->dnsq_link.next = next->dnsq_link.prev = q;
|
|
}
|
|
|
|
static inline void
|
|
qlist_insert_after(struct dns_query *q, struct dns_query *prev) {
|
|
qlist_insert(q, prev, prev->dnsq_link.next);
|
|
}
|
|
|
|
static inline void
|
|
qlist_insert_before(struct dns_query *q, struct dns_query *next) {
|
|
qlist_insert(q, next->dnsq_link.prev, next);
|
|
}
|
|
|
|
static inline void
|
|
qlist_add_tail(struct dns_query *q, struct dns_qlink *top) {
|
|
qlist_insert_before(q, (struct dns_query *)top);
|
|
}
|
|
|
|
static inline void
|
|
qlist_add_head(struct dns_query *q, struct dns_qlink *top) {
|
|
qlist_insert_after(q, (struct dns_query *)top);
|
|
}
|
|
|
|
#define QLIST_FIRST(list, direction) ((list)->direction)
|
|
#define QLIST_ISLAST(list, q) ((q) == (struct dns_query*)(list))
|
|
#define QLIST_NEXT(q, direction) ((q)->dnsq_link.direction)
|
|
|
|
#define QLIST_FOR_EACH(list, q, direction) \
|
|
for(q = QLIST_FIRST(list, direction); \
|
|
!QLIST_ISLAST(list, q); q = QLIST_NEXT(q, direction))
|
|
|
|
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 usockaddr_ns dnsc_serv[DNS_MAXSERV];
|
|
unsigned dnsc_nserv; /* number of nameservers */
|
|
unsigned dnsc_salen; /* length of socket addresses */
|
|
/* search list for unqualified names */
|
|
dnsc_t dnsc_srch[DNS_MAXSRCH][DNS_MAXDN];
|
|
unsigned dnsc_nsrch; /* number of srch[] */
|
|
|
|
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 */
|
|
unsigned short dnsc_nextid; /* next queue ID to use */
|
|
dns_socket dnsc_udpsock; /* UDP socket */
|
|
struct dns_qlink 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,offset) (*((unsigned*)(((char*)ctx)+offset)))
|
|
|
|
#define ISSPACE(x) (x == ' ' || x == '\t' || x == '\r' || x == '\n')
|
|
|
|
static const char space[] = " \t\r\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(qlist_empty(&ctx->dnsc_qactive))
|
|
#define SETCTXOPEN(ctx) SETCTXINITED(ctx); assert(CTXOPEN(ctx))
|
|
#ifdef WIN32
|
|
#define CTXOPEN(ctx) (ctx->dnsc_udpsock != INVALID_SOCKET )
|
|
#else
|
|
#define CTXOPEN(ctx) (ctx->dnsc_udpsock >= 0)
|
|
#ifndef INVALID_SOCKET
|
|
#define INVALID_SOCKET -1
|
|
#endif
|
|
#endif
|
|
|
|
#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;
|
|
QLIST_FOR_EACH(&ctx->dnsc_qactive, q, next) {
|
|
assert(q->dnsq_ctx == ctx);
|
|
assert(q->dnsq_link.next->dnsq_link.prev == q);
|
|
assert(q->dnsq_link.prev->dnsq_link.next == q);
|
|
++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 */
|
|
DNS_SEEN_FAIL = 0x0008, /* search: SERVFAIL has been received */
|
|
DNS_SEEN_WRONG = 0x0010, /* search: something wrong happened */
|
|
};
|
|
|
|
static int dns_add_serv_internal(struct dns_ctx *ctx, const char *serv) {
|
|
union usockaddr_ns *sns;
|
|
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 HAVE_INET6
|
|
{ struct in_addr addr;
|
|
struct in6_addr addr6;
|
|
if (udns_inet_pton(AF_INET, serv, &addr) > 0) {
|
|
sns->sin.sin_family = AF_INET;
|
|
sns->sin.sin_addr = addr;
|
|
return ++ctx->dnsc_nserv;
|
|
}
|
|
if (udns_inet_pton(AF_INET6, serv, &addr6) > 0) {
|
|
sns->sin6.sin6_family = AF_INET6;
|
|
sns->sin6.sin6_addr = addr6;
|
|
return ++ctx->dnsc_nserv;
|
|
}
|
|
}
|
|
#else
|
|
{ struct in_addr addr;
|
|
if (inet_aton(serv, &addr) > 0) {
|
|
sns->sin.sin_family = AF_INET;
|
|
sns->sin.sin_addr = addr;
|
|
return ++ctx->dnsc_nserv;
|
|
}
|
|
}
|
|
#endif
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
int dns_add_serv(struct dns_ctx *ctx, const char *serv) {
|
|
SETCTXFRESH(ctx);
|
|
return dns_add_serv_internal(ctx, serv);
|
|
}
|
|
|
|
static void dns_set_serv_internal(struct dns_ctx *ctx, char *serv) {
|
|
ctx->dnsc_nserv = 0;
|
|
for(serv = strtok(serv, space); serv; serv = strtok(NULL, space))
|
|
dns_add_serv_internal(ctx, serv);
|
|
}
|
|
|
|
static int
|
|
dns_add_serv_s_internal(struct dns_ctx *ctx, const struct sockaddr *sa) {
|
|
if (!sa)
|
|
return (ctx->dnsc_nserv = 0);
|
|
if (ctx->dnsc_nserv >= DNS_MAXSERV)
|
|
return errno = ENFILE, -1;
|
|
#if HAVE_INET6
|
|
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_add_serv_s(struct dns_ctx *ctx, const struct sockaddr *sa) {
|
|
SETCTXFRESH(ctx);
|
|
return dns_add_serv_s_internal(ctx, sa);
|
|
}
|
|
|
|
static void dns_set_opts_internal(struct dns_ctx *ctx, const char *opts) {
|
|
size_t i, v;
|
|
for(;;) {
|
|
while(ISSPACE(*opts)) ++opts;
|
|
if (!*opts) break;
|
|
for(i = 0; i < sizeof(dns_opts)/sizeof(dns_opts[0]); ++i) {
|
|
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') break;
|
|
do v = v * 10 + (*opts++ - '0');
|
|
while (*opts >= '0' && *opts <= '9');
|
|
if (dns_opts[i].min && v < dns_opts[i].min) v = dns_opts[i].min;
|
|
else if (v > dns_opts[i].max) v = dns_opts[i].max;
|
|
dns_ctxopt(ctx, dns_opts[i].offset) = (unsigned)v;
|
|
break;
|
|
}
|
|
while(*opts && !ISSPACE(*opts)) ++opts;
|
|
}
|
|
}
|
|
|
|
int dns_set_opts(struct dns_ctx *ctx, const char *opts) {
|
|
SETCTXINACTIVE(ctx);
|
|
dns_set_opts_internal(ctx, opts);
|
|
return 0;
|
|
}
|
|
|
|
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, dns_opts[i].offset);
|
|
if (val >= 0) {
|
|
unsigned v = val;
|
|
if (v < dns_opts[i].min || v > dns_opts[i].max) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
dns_ctxopt(ctx, dns_opts[i].offset) = 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;
|
|
}
|
|
|
|
static int dns_add_srch_internal(struct dns_ctx *ctx, const char *srch) {
|
|
if (!srch)
|
|
return (ctx->dnsc_nsrch = 0);
|
|
else if (ctx->dnsc_nsrch >= DNS_MAXSRCH)
|
|
return errno = ENFILE, -1;
|
|
else if (dns_sptodn(srch, ctx->dnsc_srch[ctx->dnsc_nsrch], DNS_MAXDN) <= 0)
|
|
return errno = EINVAL, -1;
|
|
else
|
|
return ++ctx->dnsc_nsrch;
|
|
}
|
|
|
|
int dns_add_srch(struct dns_ctx *ctx, const char *srch) {
|
|
SETCTXINACTIVE(ctx);
|
|
return dns_add_srch_internal(ctx, srch);
|
|
}
|
|
|
|
static void dns_set_srch_internal(struct dns_ctx *ctx, char *srch) {
|
|
ctx->dnsc_nsrch = 0;
|
|
for(srch = strtok(srch, space); srch; srch = strtok(NULL, space))
|
|
dns_add_srch_internal(ctx, srch);
|
|
}
|
|
|
|
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;
|
|
if (!ctx->dnsc_utmfn)
|
|
return;
|
|
q = QLIST_FIRST(&ctx->dnsc_qactive, next);
|
|
if (QLIST_ISLAST(&ctx->dnsc_qactive, 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
|
|
typedef DWORD (WINAPI *GetAdaptersAddressesFunc)(
|
|
ULONG Family, DWORD Flags, PVOID Reserved,
|
|
PIP_ADAPTER_ADDRESSES pAdapterAddresses,
|
|
PULONG pOutBufLen);
|
|
|
|
static int dns_initns_iphlpapi(struct dns_ctx *ctx) {
|
|
HANDLE h_iphlpapi;
|
|
GetAdaptersAddressesFunc pfnGetAdAddrs;
|
|
PIP_ADAPTER_ADDRESSES pAddr, pAddrBuf;
|
|
PIP_ADAPTER_DNS_SERVER_ADDRESS pDnsAddr;
|
|
ULONG ulOutBufLen;
|
|
DWORD dwRetVal;
|
|
int ret = -1;
|
|
|
|
h_iphlpapi = LoadLibrary("iphlpapi.dll");
|
|
if (!h_iphlpapi)
|
|
return -1;
|
|
pfnGetAdAddrs = (GetAdaptersAddressesFunc)
|
|
GetProcAddress(h_iphlpapi, "GetAdaptersAddresses");
|
|
if (!pfnGetAdAddrs) goto freelib;
|
|
ulOutBufLen = 0;
|
|
dwRetVal = pfnGetAdAddrs(AF_UNSPEC, 0, NULL, NULL, &ulOutBufLen);
|
|
if (dwRetVal != ERROR_BUFFER_OVERFLOW) goto freelib;
|
|
pAddrBuf = malloc(ulOutBufLen);
|
|
if (!pAddrBuf) goto freelib;
|
|
dwRetVal = pfnGetAdAddrs(AF_UNSPEC, 0, NULL, pAddrBuf, &ulOutBufLen);
|
|
if (dwRetVal != ERROR_SUCCESS) goto freemem;
|
|
for (pAddr = pAddrBuf;
|
|
pAddr && ctx->dnsc_nserv <= DNS_MAXSERV;
|
|
pAddr = pAddr->Next)
|
|
for (pDnsAddr = pAddr->FirstDnsServerAddress;
|
|
pDnsAddr && ctx->dnsc_nserv <= DNS_MAXSERV;
|
|
pDnsAddr = pDnsAddr->Next)
|
|
dns_add_serv_s_internal(ctx, pDnsAddr->Address.lpSockaddr);
|
|
ret = 0;
|
|
freemem:
|
|
free(pAddrBuf);
|
|
freelib:
|
|
FreeLibrary(h_iphlpapi);
|
|
return ret;
|
|
}
|
|
|
|
static int dns_initns_registry(struct dns_ctx *ctx) {
|
|
LONG res;
|
|
HKEY hk;
|
|
DWORD type = REG_EXPAND_SZ | REG_SZ;
|
|
DWORD len;
|
|
char valBuf[1024];
|
|
|
|
#define REGKEY_WINNT "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"
|
|
#define REGKEY_WIN9x "SYSTEM\\CurrentControlSet\\Services\\VxD\\MSTCP"
|
|
res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGKEY_WINNT, 0, KEY_QUERY_VALUE, &hk);
|
|
if (res != ERROR_SUCCESS)
|
|
res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGKEY_WIN9x,
|
|
0, KEY_QUERY_VALUE, &hk);
|
|
if (res != ERROR_SUCCESS)
|
|
return -1;
|
|
len = sizeof(valBuf) - 1;
|
|
res = RegQueryValueEx(hk, "NameServer", NULL, &type, valBuf, &len);
|
|
if (res != ERROR_SUCCESS || !len || !valBuf[0]) {
|
|
len = sizeof(valBuf) - 1;
|
|
res = RegQueryValueEx(hk, "DhcpNameServer", NULL, &type, valBuf, &len);
|
|
}
|
|
RegCloseKey(hk);
|
|
if (res != ERROR_SUCCESS || !len || !valBuf[0])
|
|
return -1;
|
|
valBuf[len] = '\0';
|
|
/* nameservers are stored as a whitespace-seperate list:
|
|
* "192.168.1.1 123.21.32.12" */
|
|
dns_set_serv_internal(ctx, valBuf);
|
|
return 0;
|
|
}
|
|
|
|
static int dns_init_internal(struct dns_ctx *ctx) {
|
|
if (dns_initns_iphlpapi(ctx) != 0)
|
|
dns_initns_registry(ctx);
|
|
/*XXX WIN32: probably good to get default domain and search list too...
|
|
* And options. Something is in registry. */
|
|
/*XXX WIN32: maybe environment variables are also useful? */
|
|
return 0;
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(disable:4100)
|
|
|
|
#include "windows.h"
|
|
|
|
void gettimeofday(struct timeval *tv, void *tz)
|
|
{
|
|
long int l = GetTickCount();
|
|
|
|
tv->tv_sec = l / 1000;
|
|
tv->tv_usec = (l % 1000) * 1000;
|
|
return;
|
|
}
|
|
#endif
|
|
#else /* !WIN32 */
|
|
|
|
static int dns_init_internal(struct dns_ctx *ctx) {
|
|
char *v;
|
|
char buf[2049]; /* this buffer is used to hold /etc/resolv.conf */
|
|
|
|
/* read resolv.conf... */
|
|
{ int fd = open("/etc/resolv.conf", O_RDONLY);
|
|
if (fd >= 0) {
|
|
int l = read(fd, buf, sizeof(buf) - 1);
|
|
close(fd);
|
|
buf[l < 0 ? 0 : l] = '\0';
|
|
}
|
|
else
|
|
buf[0] = '\0';
|
|
}
|
|
if (buf[0]) { /* ...and parse it */
|
|
char *line, *nextline;
|
|
line = buf;
|
|
do {
|
|
nextline = strchr(line, '\n');
|
|
if (nextline) *nextline++ = '\0';
|
|
v = line;
|
|
while(*v && !ISSPACE(*v)) ++v;
|
|
if (!*v) continue;
|
|
*v++ = '\0';
|
|
while(ISSPACE(*v)) ++v;
|
|
if (!*v) continue;
|
|
if (strcmp(line, "domain") == 0)
|
|
dns_set_srch_internal(ctx, strtok(v, space));
|
|
else if (strcmp(line, "search") == 0)
|
|
dns_set_srch_internal(ctx, v);
|
|
else if (strcmp(line, "nameserver") == 0)
|
|
dns_add_serv_internal(ctx, strtok(v, space));
|
|
else if (strcmp(line, "options") == 0)
|
|
dns_set_opts_internal(ctx, v);
|
|
} while((line = nextline) != NULL);
|
|
}
|
|
|
|
buf[sizeof(buf)-1] = '\0';
|
|
|
|
/* get list of nameservers from env. vars. */
|
|
if ((v = getenv("NSCACHEIP")) != NULL ||
|
|
(v = getenv("NAMESERVERS")) != NULL) {
|
|
strncpy(buf, v, sizeof(buf) - 1);
|
|
dns_set_serv_internal(ctx, buf);
|
|
}
|
|
/* if $LOCALDOMAIN is set, use it for search list */
|
|
if ((v = getenv("LOCALDOMAIN")) != NULL) {
|
|
strncpy(buf, v, sizeof(buf) - 1);
|
|
dns_set_srch_internal(ctx, buf);
|
|
}
|
|
if ((v = getenv("RES_OPTIONS")) != NULL)
|
|
dns_set_opts_internal(ctx, v);
|
|
|
|
/* if still no search list, use local domain name */
|
|
if (!ctx->dnsc_nsrch &&
|
|
gethostname(buf, sizeof(buf) - 1) == 0 &&
|
|
(v = strchr(buf, '.')) != NULL &&
|
|
*++v != '\0')
|
|
dns_add_srch_internal(ctx, v);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* dns_init_internal() for !WIN32 */
|
|
|
|
static void dns_firstid(struct dns_ctx *ctx) {
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
ctx->dnsc_nextid = (unsigned short)((tv.tv_usec ^ getpid()) & 0xffff);
|
|
}
|
|
|
|
dns_socket dns_init(int do_open) {
|
|
struct dns_ctx *ctx = &dns_defctx;
|
|
assert(!CTXINITED(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 = INVALID_SOCKET;
|
|
qlist_init(&ctx->dnsc_qactive);
|
|
if (dns_init_internal(ctx) != 0)
|
|
return -1;
|
|
dns_firstid(ctx);
|
|
ctx->dnsc_flags |= DNS_INITED;
|
|
return do_open ? dns_open(ctx) : 0;
|
|
}
|
|
|
|
struct dns_ctx *dns_new(const struct dns_ctx *ctx) {
|
|
struct dns_ctx *n;
|
|
SETCTXINITED(ctx);
|
|
dns_assert_ctx(ctx);
|
|
n = malloc(sizeof(*n));
|
|
if (!n)
|
|
return NULL;
|
|
*n = *ctx;
|
|
n->dnsc_udpsock = INVALID_SOCKET;
|
|
qlist_init(&n->dnsc_qactive);
|
|
n->dnsc_nactive = 0;
|
|
n->dnsc_pbuf = NULL;
|
|
n->dnsc_qstatus = 0;
|
|
n->dnsc_utmfn = NULL;
|
|
n->dnsc_utmctx = NULL;
|
|
dns_firstid(n);
|
|
return n;
|
|
}
|
|
|
|
void dns_free(struct dns_ctx *ctx) {
|
|
struct dns_query *q;
|
|
SETCTXINITED(ctx);
|
|
dns_assert_ctx(ctx);
|
|
dns_drop_utm(ctx);
|
|
if (ctx->dnsc_udpsock >= 0)
|
|
closesocket(ctx->dnsc_udpsock);
|
|
if (ctx->dnsc_pbuf)
|
|
free(ctx->dnsc_pbuf);
|
|
while((q = qlist_pop(&ctx->dnsc_qactive)))
|
|
free(q);
|
|
if (ctx != &dns_defctx)
|
|
free(ctx);
|
|
else
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
}
|
|
|
|
dns_socket dns_open(struct dns_ctx *ctx) {
|
|
dns_socket sock;
|
|
unsigned i;
|
|
int port;
|
|
union usockaddr_ns *sns;
|
|
#if HAVE_INET6
|
|
unsigned have_inet6 = 0;
|
|
#endif
|
|
|
|
SETCTXINITED(ctx);
|
|
assert(!CTXOPEN(ctx));
|
|
|
|
port = htons(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 */
|
|
#if HAVE_INET6
|
|
if (sns->sa.sa_family == AF_INET6) {
|
|
if (!sns->sin6.sin6_port) sns->sin6.sin6_port = port;
|
|
++have_inet6;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
assert(sns->sa.sa_family == AF_INET);
|
|
if (!sns->sin.sin_port) sns->sin.sin_port = port;
|
|
}
|
|
}
|
|
|
|
#if !HAVE_INET6
|
|
ctx->dnsc_salen = sizeof(struct sockaddr_in);
|
|
#else
|
|
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;
|
|
((struct in_addr*)&sin6.sin6_addr)[3] = sns->sin.sin_addr;
|
|
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
|
|
#endif /* HAVE_INET6 */
|
|
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
|
|
if (sock < 0) {
|
|
ctx->dnsc_qstatus = DNS_E_TEMPFAIL;
|
|
return -1;
|
|
}
|
|
#ifdef WIN32
|
|
{ unsigned long on = 1;
|
|
if (ioctlsocket(sock, FIONBIO, &on) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
ctx->dnsc_qstatus = DNS_E_TEMPFAIL;
|
|
return -1;
|
|
}
|
|
}
|
|
#else /* !WIN32 */
|
|
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 /* WIN32 */
|
|
/* allocate the packet buffer */
|
|
if (!(ctx->dnsc_pbuf = malloc(ctx->dnsc_udpbuf))) {
|
|
closesocket(sock);
|
|
ctx->dnsc_qstatus = DNS_E_NOMEM;
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
ctx->dnsc_udpsock = sock;
|
|
return sock;
|
|
}
|
|
|
|
void dns_close(struct dns_ctx *ctx) {
|
|
SETCTXINITED(ctx);
|
|
if (ctx->dnsc_udpsock < 0) return;
|
|
closesocket(ctx->dnsc_udpsock);
|
|
ctx->dnsc_udpsock = INVALID_SOCKET;
|
|
free(ctx->dnsc_pbuf);
|
|
ctx->dnsc_pbuf = NULL;
|
|
}
|
|
|
|
dns_socket 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 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;
|
|
/* 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)
|
|
|
|
/* Try next search, filling in qDN in query.
|
|
* Return new qDN len or 0 if no more to search.
|
|
* Caller should fill up the rest of the query.
|
|
*/
|
|
static unsigned dns_next_srch(const struct dns_ctx *ctx, struct dns_query *q) {
|
|
unsigned ol = q->dnsq_origdnl - 1; /* origdnl is at least 1 */
|
|
dnsc_t *p = dns_payload(q->dnsq_buf) + ol;
|
|
dnscc_t *dn;
|
|
int n;
|
|
while (q->dnsq_srchi < ctx->dnsc_nsrch) {
|
|
dn = ctx->dnsc_srch[q->dnsq_srchi++];
|
|
if (!*dn) { /* root dn */
|
|
if (!(q->dnsq_flags & DNS_ASIS_DONE))
|
|
break;
|
|
}
|
|
else if ((n = dns_dntodn(dn, p, DNS_MAXDN - ol)) > 0)
|
|
return n + ol;
|
|
}
|
|
if (q->dnsq_flags & DNS_ASIS_DONE)
|
|
return 0;
|
|
q->dnsq_flags |= DNS_ASIS_DONE;
|
|
*p = '\0';
|
|
return ol + 1;
|
|
}
|
|
|
|
/* find the next server which isn't skipped starting from current.
|
|
* return 0 if ok, >0 if ok but we started next cycle, or <0 if
|
|
* number of tries exceeded or no more servers.
|
|
*/
|
|
static int dns_find_serv(const struct dns_ctx *ctx, struct dns_query *q) {
|
|
int cycle;
|
|
if (q->dnsq_try < ctx->dnsc_ntries) for(cycle = 0;;) {
|
|
if (q->dnsq_servi < ctx->dnsc_nserv) {
|
|
if (!(q->dnsq_servskip & (1 << q->dnsq_servi)))
|
|
return cycle;
|
|
++q->dnsq_servi;
|
|
}
|
|
else if (cycle || ++q->dnsq_try >= ctx->dnsc_ntries)
|
|
break;
|
|
else {
|
|
cycle = 1;
|
|
q->dnsq_servi = 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* send the query out and add it to the active list. */
|
|
static void dns_send(struct dns_ctx *ctx, struct dns_query *q, time_t now) {
|
|
int n;
|
|
struct dns_query *p;
|
|
|
|
/* 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 there's no more servers, fail the query */
|
|
n = dns_find_serv(ctx, q);
|
|
if (n < 0) {
|
|
dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0);
|
|
return;
|
|
}
|
|
|
|
/* send the query */
|
|
n = 10;
|
|
while (sendto(ctx->dnsc_udpsock, q->dnsq_buf, q->dnsq_len, 0,
|
|
&ctx->dnsc_serv[q->dnsq_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 (--n) continue;
|
|
/* if we can't send the query, fail it. */
|
|
dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0);
|
|
return;
|
|
}
|
|
DNS_DBGQ(ctx, q, 1,
|
|
&ctx->dnsc_serv[q->dnsq_servi].sa, sizeof(union usockaddr_ns),
|
|
q->dnsq_buf, q->dnsq_len);
|
|
q->dnsq_servwait |= 1 << q->dnsq_servi; /* expect reply from this ns */
|
|
|
|
/* advance to the next server, and choose a timeout.
|
|
* we will try next server in 1 secound, but start next
|
|
* cycle waiting for proper timeout. */
|
|
++q->dnsq_servi;
|
|
n = dns_find_serv(ctx, q) ? ctx->dnsc_timeout << (q->dnsq_try - 1) : 1;
|
|
|
|
q->dnsq_deadline = now = now + n;
|
|
|
|
/* insert the query to the tail of the list */
|
|
QLIST_FOR_EACH(&ctx->dnsc_qactive, p, prev)
|
|
if (p->dnsq_deadline <= now)
|
|
break;
|
|
qlist_insert_after(q, p);
|
|
|
|
}
|
|
|
|
static void dns_dummy_cb(struct dns_ctx *ctx, void *result, void *data) {
|
|
if (result) free(result);
|
|
data = ctx = 0; /* used */
|
|
}
|
|
|
|
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) {
|
|
dnsc_t *p;
|
|
unsigned dnl;
|
|
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;
|
|
|
|
flags = (flags | ctx->dnsc_flags) & ~DNS_INTERNAL;
|
|
if (!ctx->dnsc_nsrch) q->dnsq_flags |= DNS_NOSRCH;
|
|
if (!(flags & DNS_NORD)) q->dnsq_buf[DNS_H_F1] |= DNS_HF1_RD;
|
|
if (flags & DNS_AAONLY) q->dnsq_buf[DNS_H_F1] |= DNS_HF1_AA;
|
|
q->dnsq_buf[DNS_H_QDCNT2] = 1;
|
|
dns_put16(q->dnsq_buf + DNS_H_QID, ctx->dnsc_nextid++);
|
|
|
|
q->dnsq_origdnl = dns_dnlen(dn);
|
|
assert(q->dnsq_origdnl > 0 && q->dnsq_origdnl <= DNS_MAXDN);
|
|
memcpy(dns_payload(q->dnsq_buf), dn, q->dnsq_origdnl);
|
|
p = dns_payload(q->dnsq_buf) + q->dnsq_origdnl;
|
|
if (flags & DNS_NOSRCH || dns_dnlabels(dn) > ctx->dnsc_ndots)
|
|
flags |= DNS_ASIS_DONE;
|
|
else if ((dnl = dns_next_srch(ctx, q)) > 0)
|
|
p = dns_payload(q->dnsq_buf) + dnl;
|
|
else
|
|
p[-1] = '\0';
|
|
q->dnsq_flags = flags;
|
|
q->dnsq_typ = qtyp;
|
|
p = dns_put16(p, qtyp);
|
|
q->dnsq_cls = qcls;
|
|
p = dns_put16(p, qcls);
|
|
if (ctx->dnsc_udpbuf > DNS_MAXPACKET) {
|
|
p++; /* empty (root) DN */
|
|
p = dns_put16(p, DNS_T_OPT);
|
|
p = dns_put16(p, ctx->dnsc_udpbuf);
|
|
p += 2; /* EDNS0 RCODE & VERSION */
|
|
p += 2; /* rest of the TTL field */
|
|
p += 2; /* RDLEN */
|
|
q->dnsq_buf[DNS_H_ARCNT2] = 1;
|
|
}
|
|
assert(p <= q->dnsq_buf + DNS_QBUF);
|
|
q->dnsq_len = (unsigned)(p - q->dnsq_buf);
|
|
|
|
qlist_add_head(q, &ctx->dnsc_qactive);
|
|
++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.
|
|
* Current loop/goto looks just terrible... */
|
|
void dns_ioevent(struct dns_ctx *ctx, time_t now) {
|
|
int r;
|
|
unsigned servi, l;
|
|
struct dns_query *q;
|
|
dnsc_t *pbuf;
|
|
dnscc_t *pend, *pcur;
|
|
void *result;
|
|
union usockaddr_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:
|
|
|
|
for(;;) { /* receive the reply */
|
|
dnsc_t dn[DNS_MAXDN];
|
|
|
|
slen = sizeof(sns);
|
|
r = recvfrom(ctx->dnsc_udpsock, pbuf, ctx->dnsc_udpbuf, 0, &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 WIN32
|
|
if (WSAGetLastError() == WSAEWOULDBLOCK)
|
|
#else
|
|
if (errno == EAGAIN)
|
|
#endif
|
|
{
|
|
dns_request_utm(ctx, now);
|
|
return;
|
|
}
|
|
continue;
|
|
}
|
|
/* ignore replies from wrong server */
|
|
#if HAVE_INET6
|
|
if (sns.sa.sa_family == AF_INET6 && slen >= sizeof(sns.sin6)) {
|
|
for (servi = 0; servi < ctx->dnsc_nserv; ++servi)
|
|
if (ctx->dnsc_serv[servi].sin6.sin6_port == sns.sin6.sin6_port &&
|
|
memcmp(&ctx->dnsc_serv[servi].sin6.sin6_addr,
|
|
&sns.sin6.sin6_addr, sizeof(sns.sin6.sin6_addr)) == 0)
|
|
break;
|
|
}
|
|
else
|
|
#endif
|
|
if (sns.sa.sa_family == AF_INET && slen >= sizeof(sns.sin)) {
|
|
for (servi = 0; servi < ctx->dnsc_nserv; ++servi)
|
|
if (ctx->dnsc_serv[servi].sin.sin_addr.s_addr == sns.sin.sin_addr.s_addr &&
|
|
ctx->dnsc_serv[servi].sin.sin_port == sns.sin.sin_port)
|
|
break;
|
|
}
|
|
else {
|
|
DNS_DBG(ctx, -1, &sns.sa, slen, pbuf, r);
|
|
continue;
|
|
}
|
|
if (servi >= ctx->dnsc_nserv) {
|
|
DNS_DBG(ctx, -2, &sns.sa, slen, pbuf, r);
|
|
continue;
|
|
}
|
|
|
|
pend = pbuf + r;
|
|
pcur = dns_payload(pbuf);
|
|
if (pcur >= pend || dns_numqd(pbuf) != 1 || dns_opcode(pbuf) != 0 ||
|
|
dns_getdn(pbuf, &pcur, pend, dn, sizeof(dn)) < 0 ||
|
|
pcur + 4 > pend) {
|
|
/*XXX ignore non-query replies and replies with numqd!=1? */
|
|
DNS_DBG(ctx, -3, &sns.sa, slen, pbuf, r);
|
|
continue;
|
|
}
|
|
|
|
/* truncation bit (TC). Ooh, we don't handle TCP (yet?),
|
|
* but we do handle larger UDP sizes.
|
|
* Note that e.g. djbdns will only send header if resp.
|
|
* does not fit, not whatever is fit in 512 bytes. */
|
|
if (dns_tc(pbuf)) {
|
|
DNS_DBG(ctx, -4, &sns.sa, slen, pbuf, r);
|
|
continue; /* just ignore response for now.. any hope? */
|
|
}
|
|
|
|
/* find the request for this reply in active queue
|
|
* Note we pick any request, even queued for another
|
|
* server - in case first server replies a bit later
|
|
* than we expected. */
|
|
for (q = QLIST_FIRST(&ctx->dnsc_qactive, next);; q = QLIST_NEXT(q, next)) {
|
|
if (QLIST_ISLAST(&ctx->dnsc_qactive, q)) {
|
|
/* no more requests: old reply? */
|
|
DNS_DBG(ctx, -5, &sns.sa, slen, pbuf, r);
|
|
goto again;
|
|
}
|
|
/* ignore replies that has not been sent to this server.
|
|
* Note dnsq_servi is the *next* server to try. */
|
|
if (!q->dnsq_try && q->dnsq_servi <= servi)
|
|
continue;
|
|
/*XXX ignore replies from servers we're ignoring? o/
|
|
if (q->dnsq_servskip & (1 << servi))
|
|
continue; */
|
|
/* check qID */
|
|
if (q->dnsq_buf[DNS_H_QID1] != pbuf[DNS_H_QID1] ||
|
|
q->dnsq_buf[DNS_H_QID2] != pbuf[DNS_H_QID2])
|
|
continue;
|
|
/* check qDN, qCLS and qTYP */
|
|
if (!(l = dns_dnequal(dn, dns_payload(q->dnsq_buf))) ||
|
|
memcmp(pcur, dns_payload(q->dnsq_buf) + l, 4) != 0)
|
|
continue;
|
|
/* ok, this is expected reply with matching query. */
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
DNS_DBGQ(ctx, q, 0, &sns.sa, slen, pbuf, r);
|
|
|
|
/* we got a reply for our query */
|
|
q->dnsq_servwait &= ~(1 << servi); /* don't expect reply from this serv */
|
|
|
|
/* process the RCODE */
|
|
switch(dns_rcode(pbuf)) {
|
|
|
|
case DNS_R_NOERROR:
|
|
qlist_remove(q);
|
|
if (!dns_numan(pbuf)) { /* no data of requested type */
|
|
q->dnsq_flags |= DNS_SEEN_NODATA;
|
|
r = DNS_E_NODATA;
|
|
break;
|
|
}
|
|
/* the only case where we may succeed */
|
|
if (q->dnsq_parse) {
|
|
r = q->dnsq_parse(dns_payload(q->dnsq_buf), pbuf, pcur, pend, &result);
|
|
if (r < 0)
|
|
result = NULL;
|
|
}
|
|
else if ((result = malloc(r)) != NULL)
|
|
memcpy(result, pbuf, r);
|
|
else
|
|
r = DNS_E_NOMEM;
|
|
/* (maybe) successeful answer (modulo nomem and parsing probs) */
|
|
/* note we pass DNS_E_NODATA here */
|
|
dns_end_query(ctx, q, r, result);
|
|
goto again;
|
|
|
|
case DNS_R_NXDOMAIN:
|
|
qlist_remove(q);
|
|
r = DNS_E_NXDOMAIN;
|
|
break;
|
|
|
|
case DNS_R_SERVFAIL:
|
|
q->dnsq_flags |= DNS_SEEN_FAIL;
|
|
case DNS_R_NOTIMPL:
|
|
case DNS_R_REFUSED:
|
|
/* for these rcodes, advance this request
|
|
* to the next server and reschedule */
|
|
default: /* unknown rcode? hmmm... */
|
|
/* try next server */
|
|
q->dnsq_servskip |= 1 << servi; /* don't retry this server */
|
|
if (!q->dnsq_servwait) {
|
|
qlist_remove(q);
|
|
dns_send(ctx, q, now);
|
|
}
|
|
else {
|
|
/* else this is the only place where q will be left unconnected
|
|
* if we will move qlist_remove() before the switch{}. */
|
|
}
|
|
goto again;
|
|
|
|
}
|
|
|
|
/* here we have either NODATA or NXDOMAIN */
|
|
if (!(q->dnsq_flags & DNS_NOSRCH)) {
|
|
/* try next element from search list */
|
|
unsigned sl;
|
|
|
|
l = dns_dnlen(dns_payload(q->dnsq_buf)) + DNS_HSIZE; /* past qDN */
|
|
/* save qcls, qtyp and EDNS0 stuff (of len sl) in pbuf */
|
|
sl = q->dnsq_len - l;
|
|
memcpy(pbuf, q->dnsq_buf + l, sl);
|
|
/* try next search list */
|
|
l = dns_next_srch(ctx, q);
|
|
if (l) { /* something else to try, of len l */
|
|
l += DNS_HSIZE;
|
|
memcpy(q->dnsq_buf + l, pbuf, sl);
|
|
q->dnsq_len = l + sl;
|
|
q->dnsq_try = 0; q->dnsq_servi = 0;
|
|
q->dnsq_servwait = q->dnsq_servskip = 0;
|
|
dns_send(ctx, q, now);
|
|
goto again;
|
|
}
|
|
/* else we have nothing more to search, end the query. */
|
|
if (q->dnsq_flags & DNS_SEEN_FAIL)
|
|
/* at least one server/query failed, fail the query */
|
|
r = DNS_E_TEMPFAIL;
|
|
else if (q->dnsq_flags & DNS_SEEN_NODATA)
|
|
/* for one domain we have seen NODATA, return it */
|
|
r = DNS_E_NODATA;
|
|
else /* else all should be NXDOMAINs */
|
|
r = DNS_E_NXDOMAIN;
|
|
}
|
|
|
|
dns_end_query(ctx, q, r, 0);
|
|
goto again;
|
|
}
|
|
|
|
/* handle all timeouts */
|
|
int dns_timeouts(struct dns_ctx *ctx, int maxwait, time_t now) {
|
|
struct dns_query *q;
|
|
int w;
|
|
SETCTX(ctx);
|
|
dns_assert_ctx(ctx);
|
|
if (!now) now = time(NULL);
|
|
while((q = qlist_first(&ctx->dnsc_qactive)) && q->dnsq_deadline <= now) {
|
|
qlist_remove(q);
|
|
dns_send(ctx, q, now);
|
|
}
|
|
dns_request_utm(ctx, now);
|
|
if (!q)
|
|
return maxwait;
|
|
w = (int)(q->dnsq_deadline - now);
|
|
return maxwait < 0 || maxwait > w ? w : 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;
|
|
#ifdef HAVE_POLL
|
|
struct pollfd pfd;
|
|
#else
|
|
fd_set rfd;
|
|
struct timeval tv;
|
|
#endif
|
|
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;
|
|
|
|
#ifdef HAVE_POLL
|
|
pfd.fd = ctx->dnsc_udpsock;
|
|
pfd.events = POLLIN;
|
|
#else
|
|
FD_ZERO(&rfd);
|
|
#endif
|
|
|
|
now = time(NULL);
|
|
while(!d.dnsrd_done && (n = dns_timeouts(ctx, -1, now)) >= 0) {
|
|
#ifdef HAVE_POLL
|
|
n = poll(&pfd, 1, n * 1000);
|
|
#else
|
|
tv.tv_sec = n;
|
|
tv.tv_usec = 0;
|
|
FD_SET(ctx->dnsc_udpsock, &rfd);
|
|
n = select((int)(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(q);
|
|
--ctx->dnsc_nactive;
|
|
dns_request_utm(ctx, 0);
|
|
return 0;
|
|
}
|
|
|