freeswitch/libs/udns/udns_resolver.c
2007-02-08 19:23:36 +00:00

1368 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
#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+
#include "inet_pton.h"
#include "process.h"
#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
# 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 */
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 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) {
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 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 = 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 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 = (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 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 = (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;
}