mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-02-05 10:34:54 +00:00
60434decf5
mod_enum can be used as a dialplan app, an api call from the console or as a dialplan interface. Dialplan Interface: put enum as the dialplan parameter in an endpoint module i.e. instead of "XML" set it to "enum" or "enum,XML" for fall through. Dialplan App: This example will do a lookup and set the a variable that is the proper dialstring to call all of the possible routes in order of preference according to the lookup and the order of the routes in the enum.conf section. <extension name="tollfree"> <condition field="destination_number" expression="^(18(0{2}|8{2}|7{2}|6{2})\d{7})$"> <action application="enum" data="$1"/> <action application="bridge" data="${enum_auto_route}"/> </condition> </extension> You can also pick an alrernate root: <action application="enum" data="$1 myroot.org"/> API command: at the console you can say: enum <number> [<root>] The root always defaults to the one in the enum.conf section. git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@3494 d0543943-73ff-0310-b7d9-9358b9ac24b2
1335 lines
38 KiB
C
1335 lines
38 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
|
|
# 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
|
|
# 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_CONFIG_H
|
|
# include <config.h>
|
|
# endif
|
|
# 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 sockaddr_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 sockaddr_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 */
|
|
int 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))
|
|
#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;
|
|
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 sockaddr_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 (inet_pton(AF_INET, serv, &addr) > 0) {
|
|
sns->sin.sin_family = AF_INET;
|
|
sns->sin.sin_addr = addr;
|
|
return ++ctx->dnsc_nserv;
|
|
}
|
|
if (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) {
|
|
unsigned 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) = 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 = 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;
|
|
}
|
|
|
|
static void dns_firstid(struct dns_ctx *ctx) {
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
ctx->dnsc_nextid = (tv.tv_usec ^ getpid()) & 0xffff;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
#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 */
|
|
|
|
int 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 = -1;
|
|
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 = -1;
|
|
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));
|
|
}
|
|
|
|
int dns_open(struct dns_ctx *ctx) {
|
|
int sock;
|
|
unsigned i;
|
|
int port;
|
|
union sockaddr_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 = -1;
|
|
free(ctx->dnsc_pbuf);
|
|
ctx->dnsc_pbuf = NULL;
|
|
}
|
|
|
|
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 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 sockaddr_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 = 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 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:
|
|
|
|
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 = 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(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;
|
|
}
|
|
|