async_resolv.c revision 3136a82b9d76283b10244a9768e1f325503995d5
/*
SSSD
Async resolver
Authors:
Martin Nagy <mnagy@redhat.com>
Jakub Hrozek <jhrozek@redhat.com>
Copyright (C) Red Hat, Inc 2009
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ares.h>
#include <talloc.h>
#include <tevent.h>
#include <errno.h>
#include <netdb.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include "config.h"
#include "resolv/async_resolv.h"
#include "util/dlinklist.h"
#ifndef HAVE_ARES_DATA
#define ares_free_data(dataptr) \
#define ares_malloc_data(data) \
#endif /* HAVE_ARES_DATA */
#ifndef HAVE_STRUCT_ARES_ADDRTTL
#define ares_addrttl addrttl
#endif
#ifndef HAVE_STRUCT_ARES_ADDR6TTL
#define ares_addr6ttl addr6ttl
#endif
struct fd_watch {
int fd;
struct resolv_ctx *ctx;
};
struct resolv_ctx {
/* Contexts are linked so we can keep track of them and re-create
* the ares channels in all of them at once if we need to. */
struct resolv_ctx *prev;
struct resolv_ctx *next;
struct tevent_context *ev_ctx;
/* List of file descriptors that are watched by tevent. */
/* Time in milliseconds before canceling a DNS request */
int timeout;
/* The timeout watcher periodically calls ares_process_fd() to check
* if our pending requests didn't timeout. */
int pending_requests;
struct tevent_timer *timeout_watcher;
};
struct resolv_ctx *context_list;
enum restrict_family *family_order)
{
char *str_opt;
"ipv4_first", &str_opt);
goto done;
}
} else {
goto done;
}
done:
return ret;
}
static int
return_code(int ares_code)
{
switch (ares_code) {
case ARES_SUCCESS:
return EOK;
case ARES_ENOMEM:
return ENOMEM;
case ARES_EFILE:
default:
return EIO;
}
}
const char *
resolv_strerror(int ares_code)
{
return ares_strerror(ares_code);
}
static int
fd_watch_destructor(struct fd_watch *f)
{
f->fd = -1;
return 0;
}
static void
{
return;
}
if (flags & TEVENT_FD_READ) {
}
if (flags & TEVENT_FD_WRITE) {
}
}
static void
static void
{
}
/* Enforce a minimum of 1 second. */
} else {
}
ctx);
}
}
static void
{
/* NULLify the timeout_watcher so we don't
* free it in the _done() function if it
* gets called. Now that we're already in
* the handler, tevent will take care of
* freeing it when it returns.
*/
if (ctx->pending_requests > 0) {
}
}
static void
{
ctx->pending_requests++;
if (ctx->timeout_watcher) {
return;
}
}
static void
{
if (ctx->pending_requests <= 0) {
return;
}
ctx->pending_requests--;
if (ctx->pending_requests == 0) {
}
}
/*
* When ares is ready to read or write to a file descriptor, it will
* call this callback. If both read and write are 0, it means that ares
* will soon close the socket. We are mainly using this function to register
* new file descriptors with tevent.
*/
static void
{
int flags;
/* The socket is about to get closed. */
fd_event_close(ctx, s);
return;
}
/* Are we already watching this file descriptor? */
while (watch) {
return;
}
}
}
static void
{
/* The file descriptor is new, register it with tevent. */
return;
}
return;
}
}
static void
{
/* Remove the socket from list */
while (watch) {
return;
}
}
}
static int
{
return -1;
}
/* Set ctx->channel to NULL first, so that callbacks that get
* ARES_EDESTRUCTION won't retry. */
return 0;
}
static int
{
int ret;
struct ares_options options;
/* FIXME: the options would contain
* the nameservers to contact, the domains
* to search... => get from confdb
*/
/* Only affects ares_gethostbyname */
if (ret != ARES_SUCCESS) {
resolv_strerror(ret)));
return return_code(ret);
}
if (old_channel != NULL) {
}
return EOK;
}
int
{
int ret;
struct resolv_ctx *ctx;
if (timeout < 1) {
return EINVAL;
}
return ENOMEM;
goto done;
}
return EOK;
done:
return ret;
}
void
{
struct resolv_ctx *ctx;
}
}
static errno_t
struct ares_addrttl *attl)
{
return EOK;
}
static errno_t
struct ares_addr6ttl *a6ttl)
{
return EOK;
}
static struct resolv_hostent *
{
struct resolv_hostent *ret;
int len;
int i;
return NULL;
}
goto fail;
}
}
goto fail;
}
for (i = 0; i < len; i++) {
goto fail;
}
}
}
return ret;
fail:
return NULL;
}
struct resolv_hostent *
{
struct resolv_hostent *ret;
int len;
int i;
return NULL;
}
goto fail;
}
for (i = 0; i < len; i++) {
struct resolv_addr);
goto fail;
}
src->h_addr_list[i],
goto fail;
}
}
}
return ret;
fail:
return NULL;
}
struct resolv_hostent *
int family, void *ares_ttl_data,
int num_ares_ttl_data)
{
struct resolv_hostent *ret;
int i;
return NULL;
}
if (num_ares_ttl_data > 0) {
num_ares_ttl_data + 1);
goto fail;
}
for (i = 0; i < num_ares_ttl_data; i++) {
struct resolv_addr);
goto fail;
}
switch (family) {
case AF_INET:
&((struct ares_addrttl *) ares_ttl_data)[i]);
break;
case AF_INET6:
&((struct ares_addr6ttl *) ares_ttl_data)[i]);
break;
default:
goto fail;
}
goto fail;
}
}
}
return ret;
fail:
return NULL;
}
/* =================== Resolve host name in files =========================*/
struct gethostbyname_files_state {
struct resolv_ctx *resolv_ctx;
/* Part of the query. */
const char *name;
int family;
/* query result */
struct resolv_hostent *rhostent;
/* returned by ares. */
int status;
};
/* Fake up an async interface even though files would
* always be blocking */
static struct tevent_req *
struct tevent_context *ev,
struct resolv_ctx *ctx,
const char *name,
int family)
{
struct tevent_req *req;
struct gethostbyname_files_state *state;
struct gethostbyname_files_state);
goto done;
}
&hostent);
goto done;
}
/* Just say we didn't find anything and let the caller decide
* about retrying */
goto done;
} else {
goto done;
}
done:
return req;
}
static errno_t
{
struct gethostbyname_files_state);
/* Fill in even in case of error as status contains the
* c-ares return code */
if (status) {
}
if (rhostent) {
}
return EOK;
}
/* ==================== Resolve host name in DNS =========================*/
struct gethostbyname_dns_state {
struct resolv_ctx *resolv_ctx;
struct tevent_context *ev;
/* Part of the query. */
const char *name;
int family;
/* query result */
struct resolv_hostent *rhostent;
/* These are returned by ares. */
int status;
int timeouts;
int retrying;
};
static void
static void
struct gethostbyname_dns_state *state);
static void
static int
static struct tevent_req *
int family)
{
struct gethostbyname_dns_state *state;
return NULL;
}
return NULL;
}
/* We need to have a wrapper around ares async calls, because
* they can in some cases call it's callback immediately.
* This would not let our caller to set a callback for req. */
return NULL;
}
return req;
}
static void
{
struct tevent_req);
struct gethostbyname_dns_state);
if (!tevent_wakeup_recv(subreq)) {
return;
}
return;
}
}
static void
struct gethostbyname_dns_state *state)
{
}
static void
{
struct gethostbyname_dns_state);
/* If resolv.conf changed during processing of a request we might
* destroy the old channel before the request has a chance to finish.
* We must resend the request in this case */
return;
}
/* Just say we didn't find anything and let the caller decide
* about retrying */
return;
}
if (status != ARES_SUCCESS) {
/* Any other error indicates a server error,
* so don't bother trying again
*/
return;
}
return;
}
}
static int
{
int naddrttls;
void *addr;
case AF_INET:
if (!addr) {
goto fail;
}
(struct ares_addrttl *) addr,
&naddrttls);
break;
case AF_INET6:
if (!addr) {
goto fail;
}
(struct ares_addr6ttl *) addr,
&naddrttls);
break;
default:
ret = EAFNOSUPPORT;
goto fail;
}
goto fail;
}
}
return return_code(status);
fail:
return ret;
}
static int
struct resolv_hostent **rhostent)
{
struct gethostbyname_dns_state);
/* Fill in even in case of error as status contains the
* c-ares return code */
if (status) {
}
if (timeouts) {
}
if (rhostent) {
}
return EOK;
}
/*******************************************************************
* Get host by name. *
*******************************************************************/
struct gethostbyname_state {
struct resolv_ctx *resolv_ctx;
struct tevent_context *ev;
/* Part of the query. */
const char *name;
int family;
/* In which order to use IPv4, or v6 */
enum restrict_family family_order;
/* Known hosts databases and index to the current one */
enum host_database *db;
int dbi;
/* These are returned by ares. The hostent struct will be freed
* when the user callback returns. */
struct resolv_hostent *rhostent;
int status;
int timeouts;
int retrying;
};
static errno_t
struct resolv_hostent **_rhostent);
static inline int
static bool
resolv_is_address(const char *name);
static errno_t
struct tevent_req *
enum restrict_family family_order,
enum host_database *db)
{
struct tevent_req *req;
struct gethostbyname_state *state;
return NULL;
}
return NULL;
}
/* Do not attempt to resolve IP addresses */
return NULL;
}
return req;
}
return NULL;
}
return req;
}
static bool
resolv_is_address(const char *name)
{
int ret;
if (ret != 0) {
if (ret == -2) {
} else {
}
}
return ret == 0;
}
static errno_t
struct resolv_hostent **_rhostent)
{
struct resolv_hostent *rhostent;
int family;
if (!rhostent) {
goto done;
}
goto done;
}
struct resolv_addr);
goto done;
}
sizeof(struct in6_addr));
goto done;
}
if (ret != 1) {
if (ret != 1) {
goto done;
}
}
done:
return ret;
}
static inline int
{
switch(family_order) {
case IPV4_ONLY:
case IPV4_FIRST:
return AF_INET;
case IPV6_ONLY:
case IPV6_FIRST:
return AF_INET6;
}
return -1;
}
static int
{
return EOK;
return EOK;
} else {
/* No more address families for this DB, check if
* there is another DB to try */
return EOK;
}
}
return ENOENT;
}
static void
static errno_t
{
struct gethostbyname_state);
struct tevent_req *subreq;
case DB_FILES:
break;
case DB_DNS:
break;
default:
return EINVAL;
}
return ENOMEM;
}
return EOK;
}
static void
{
struct tevent_req);
struct gethostbyname_state);
case DB_FILES:
/* files is synchronous, there can be no timeouts */
break;
case DB_DNS:
break;
default:
return;
}
}
return;
}
return;
}
return;
}
}
int
struct resolv_hostent **rhostent)
{
/* Fill in even in case of error as status contains the
* c-ares return code */
if (status) {
}
if (timeouts) {
}
if (rhostent) {
}
return EOK;
}
char *
{
char *address;
return NULL;
}
errno = 0;
return NULL;
}
return address;
}
struct sockaddr_storage *
int port)
{
struct sockaddr_storage *sockaddr;
return NULL;
}
case AF_INET:
break;
case AF_INET6:
break;
default:
return NULL;
}
return sockaddr;
}
/*
* A simple helper function that will take an array of struct ares_srv_reply that
* was allocated by malloc() in c-ares and copies it using talloc. The old one
* is freed and the talloc one is put into 'reply_list' instead.
*/
static int
{
/* Nothing to do, but not an error */
if (!old_list) {
return EOK;
}
/* Copy the linked list */
while (old_list) {
/* Special case for the first node */
if (!new_list) {
return ENOMEM;
}
} else {
return ENOMEM;
}
}
return ENOMEM;
}
}
/* Free the old one (uses malloc). */
/* And now put our own new_list in place. */
*reply_list = new_list;
return EOK;
}
/*******************************************************************
* Get SRV record *
*******************************************************************/
struct getsrv_state {
struct resolv_ctx *resolv_ctx;
/* the SRV query - for example _ldap._tcp.example.com */
const char *query;
/* parsed data returned by ares */
struct ares_srv_reply *reply_list;
int status;
int timeouts;
int retrying;
};
static void
struct tevent_req *
{
struct getsrv_state *state;
return NULL;
}
return NULL;
return NULL;
}
return req;
}
static void
{
int ret;
struct ares_srv_reply *reply_list;
return;
}
if (status != ARES_SUCCESS) {
goto fail;
}
if (status != ARES_SUCCESS) {
goto fail;
}
goto fail;
}
return;
fail:
}
int
{
if (status)
if (timeouts)
if (reply_list)
return EOK;
}
static void
{
struct tevent_req);
struct getsrv_state);
if (!tevent_wakeup_recv(subreq)) {
return;
}
return;
}
}
/* TXT parsing is not used anywhere in the code yet, so we disable it
* for now
*/
#ifdef BUILD_TXT
/*
* A simple helper function that will take an array of struct txt_reply that
* was allocated by malloc() in c-ares and copies it using talloc. The old one
* is freed and the talloc one is put into 'reply_list' instead.
*/
static int
{
/* Nothing to do, but not an error */
if (!old_list) {
return EOK;
}
/* Copy the linked list */
while (old_list) {
/* Special case for the first node */
if (!new_list) {
return ENOMEM;
}
} else {
return ENOMEM;
}
}
return ENOMEM;
}
}
/* And now put our own new_list in place. */
*reply_list = new_list;
return EOK;
}
/*******************************************************************
* Get TXT record *
*******************************************************************/
struct gettxt_state {
struct resolv_ctx *resolv_ctx;
/* the TXT query */
const char *query;
/* parsed data returned by ares */
struct ares_txt_reply *reply_list;
int status;
int timeouts;
int retrying;
};
static void
struct tevent_req *
{
struct gettxt_state *state;
return NULL;
}
return NULL;
return NULL;
}
return req;
}
static void
{
int ret;
struct ares_txt_reply *reply_list;
return;
}
if (status != ARES_SUCCESS) {
goto fail;
}
if (status != ARES_SUCCESS) {
goto fail;
}
goto fail;
}
return;
fail:
}
int
{
if (status)
if (timeouts)
if (reply_list)
return EOK;
}
static void
{
struct tevent_req);
struct gettxt_state);
if (!tevent_wakeup_recv(subreq)) {
return;
}
return;
}
}
#endif
{
if (!list) {
return NULL;
}
prev = single_step;
}
return single_step;
}
struct ares_srv_reply *right)
{
struct ares_srv_reply *l, *r;
if (!left)
return right;
if (!right)
return left;
r = right;
} else {
l = left;
}
while(l && r) {
res = l;
l = l->next;
} else {
res = r;
r = r->next;
}
}
return res_start;
}
/**
* sort linked list of struct ares_srv_reply by priority using merge sort.
*
* Merge sort is ideal for sorting linked lists as there is no problem
* with absence of random access into the list. The complexity is O(n log n)
*
* For reference, see Robert Sedgewick's "Algorithms in C", Addison-Wesley,
* ISBN 0-201-51425
*/
{
struct ares_srv_reply *half;
return list;
return list;
}
int len,
struct ares_srv_reply **start,
struct ares_srv_reply **end)
{
int i;
int *totals;
if (len <= 1) {
return EOK;
}
if (!totals) {
return ENOMEM;
}
/* promote all servers with weight==0 to the top */
r = *(start);
while (r != NULL) {
if (r->weight == 0) {
/* remove from the old list */
if (prev) {
} else {
}
/* add to the head of the new list */
tmp = r;
r = r->next;
} else {
prev = r;
r = r->next;
}
}
/* Commpute the sum of the weights of those RRs, and with each RR
* associate the running sum in the selected order.
*/
total = 0;
}
/* choose a uniform random number between 0 and the sum computed
* (inclusive), and select the RR whose running sum value is the
* first in the selected order which is greater than or equal to
* the random number selected.
*/
break;
prev = r;
}
return EIO;
}
/* remove r from the old list */
if (prev) {
} else {
}
/* add r to the end of the new list */
if (!new_start) {
new_start = r;
new_end = r;
} else {
new_end = r;
}
}
/* return the rearranged list */
return EOK;
}
int
{
int ret;
int len;
/* RFC 2782 says: If there is precisely one SRV RR, and its Target is "."
* (the root domain), abort.
*/
return EIO;
}
/* sort the list by priority */
while (pri_start) {
/* Find nodes with the same priority */
len = 1;
len++;
}
/* rearrange each priority level according to the weight field */
if (ret) {
return ret;
}
/* Hook the level back into the list */
if (prev_end) {
} else {
}
/* Move on to the next level */
}
return EOK;
}