Show adns.c syntax highlighted
/*
* $Id: adns.c 13982 2007-06-28 18:43:47Z cbiere $
*
* Copyright (c) 2004, Christian Biere
*
*----------------------------------------------------------------------
* This file is part of gtk-gnutella.
*
* gtk-gnutella is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gtk-gnutella 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 gtk-gnutella; if not, write to the Free Software
* Foundation, Inc.:
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*----------------------------------------------------------------------
*/
/**
* @ingroup lib
* @file
*
* Asynchronous DNS lookup.
*
* @author Christian Biere
* @date 2004
*/
#include "common.h"
RCSID("$Id: adns.c 13982 2007-06-28 18:43:47Z cbiere $")
#include "adns.h"
#include "atoms.h"
#include "inputevt.h"
#include "misc.h"
#include "glib-missing.h"
#include "tm.h"
#include "walloc.h"
#include "socket.h"
#include "override.h" /* Must be the last header included */
static guint32 common_dbg = 0; /**< @bug XXX need to init lib's props --RAM */
/* private data types */
struct adns_common {
void (*user_callback)(void);
gpointer user_data;
gboolean reverse;
};
struct adns_reverse_query {
host_addr_t addr;
};
struct adns_query {
enum net_type net;
gchar hostname[MAX_HOSTLEN + 1];
};
struct adns_reply {
gchar hostname[MAX_HOSTLEN + 1];
host_addr_t addrs[10];
};
struct adns_reverse_reply {
gchar hostname[MAX_HOSTLEN + 1];
host_addr_t addr;
};
struct adns_request {
struct adns_common common;
union {
struct adns_query by_addr;
struct adns_reverse_query reverse;
} query;
};
struct adns_response {
struct adns_common common;
union {
struct adns_reply by_addr;
struct adns_reverse_reply reverse;
} reply;
};
typedef struct adns_async_write {
struct adns_request req; /**< The original ADNS request */
gchar *buf; /**< Remaining data to write; walloc()ed */
size_t pos; /**< Read position */
size_t size; /**< Size of the buffer */
} adns_async_write_t;
typedef struct adns_cache_entry {
const gchar *hostname; /**< atom */
time_t timestamp;
size_t n; /**< Number of addr items */
guint id;
host_addr_t addrs[1 /* pseudo-size */];
} adns_cache_entry_t;
static inline size_t
adns_cache_entry_size(size_t n)
{
struct adns_cache_entry *entry;
g_assert(n > 0);
g_assert(((size_t) -1 - sizeof *entry) / sizeof entry->addrs[0] > n);
return sizeof *entry + n * sizeof entry->addrs[0];
}
static inline size_t
count_addrs(const host_addr_t *addrs, size_t m)
{
size_t n;
for (n = 0; n < m; n++) {
if (!is_host_addr(addrs[n]))
break;
}
return n;
}
/**
* Cache entries will expire after ADNS_CACHE_TIMEOUT seconds.
*/
#define ADNS_CACHE_TIMEOUT (60)
/**
* Cache max. ADNS_CACHE_SIZE of adns_cache_entry_t entries.
*/
#define ADNS_CACHE_MAX_SIZE (1024)
static const gchar adns_process_title[] = "DNS helper for gtk-gnutella";
typedef struct adns_cache_struct {
GHashTable *ht;
guint pos;
gint timeout;
adns_cache_entry_t *entries[ADNS_CACHE_MAX_SIZE];
} adns_cache_t;
static adns_cache_t *adns_cache = NULL;
/* private variables */
static gint adns_query_fd = -1;
static guint adns_query_event_id = 0;
static guint adns_reply_event_id = 0;
static gboolean is_helper = FALSE; /**< Are we the DNS helper process? */
/**
* Private macros.
*/
#define CLOSE_IF_VALID(fd) \
do { \
if (-1 != (fd)) { \
close(fd); \
fd = -1; \
} \
} while(0)
/**
* Private functions.
*/
static adns_cache_t *
adns_cache_init(void)
{
adns_cache_t *cache;
size_t i;
cache = g_malloc(sizeof *cache);
cache->timeout = ADNS_CACHE_TIMEOUT;
cache->ht = g_hash_table_new(g_str_hash, g_str_equal);
cache->pos = 0;
for (i = 0; i < G_N_ELEMENTS(cache->entries); i++) {
cache->entries[i] = NULL;
}
return cache;
}
/* these are not needed anywhere else so undefine them */
#undef ADNS_CACHE_MAX_SIZE
#undef ADNS_CACHE_TIMEOUT
static inline adns_cache_entry_t *
adns_cache_get_entry(adns_cache_t *cache, guint i)
{
adns_cache_entry_t *entry;
g_assert(cache);
g_assert(i < G_N_ELEMENTS(cache->entries));
entry = cache->entries[i];
if (entry) {
g_assert(i == entry->id);
g_assert(entry->hostname);
g_assert(entry->n > 0);
}
return entry;
}
static void
adns_cache_free_entry(adns_cache_t *cache, guint i)
{
adns_cache_entry_t *entry;
g_assert(cache);
g_assert(i < G_N_ELEMENTS(cache->entries));
entry = cache->entries[i];
if (entry) {
g_assert(i == entry->id);
g_assert(entry->hostname);
g_assert(entry->n > 0);
atom_str_free_null(&entry->hostname);
wfree(entry, adns_cache_entry_size(entry->n));
cache->entries[i] = NULL;
}
}
/**
* Frees all memory allocated by the cache and returns NULL.
*/
void
adns_cache_free(adns_cache_t *cache)
{
guint i;
g_assert(cache);
g_assert(cache->ht);
for (i = 0; i < G_N_ELEMENTS(cache->entries); i++) {
adns_cache_free_entry(cache, i);
}
g_hash_table_destroy(cache->ht);
cache->ht = NULL;
G_FREE_NULL(cache);
}
/**
* Adds ``hostname'' and ``addr'' to the cache. The cache is implemented
* as a wrap-around FIFO. In case it's full, the oldest entry will be
* overwritten.
*/
static void
adns_cache_add(adns_cache_t *cache, time_t now,
const gchar *hostname, const host_addr_t *addrs, size_t n)
{
adns_cache_entry_t *entry;
size_t i;
g_assert(NULL != addrs);
g_assert(NULL != cache);
g_assert(NULL != hostname);
g_assert(n > 0);
g_assert(NULL == g_hash_table_lookup(cache->ht, hostname));
g_assert(cache->pos < G_N_ELEMENTS(cache->entries));
entry = adns_cache_get_entry(cache, cache->pos);
if (entry) {
g_assert(entry->hostname);
g_assert(g_hash_table_lookup(cache->ht, entry->hostname) == entry);
g_hash_table_remove(cache->ht, entry->hostname);
adns_cache_free_entry(cache, cache->pos);
entry = NULL;
}
entry = walloc(adns_cache_entry_size(n));
entry->n = n;
entry->hostname = atom_str_get(hostname);
entry->timestamp = now;
entry->id = cache->pos;
for (i = 0; i < entry->n; i++) {
entry->addrs[i] = addrs[i];
}
g_hash_table_insert(cache->ht, deconstify_gchar(entry->hostname), entry);
cache->entries[cache->pos++] = entry;
cache->pos %= G_N_ELEMENTS(cache->entries);
}
/**
* Looks for ``hostname'' in ``cache'' wrt to cache->timeout. If
* ``hostname'' is not found or the entry is expired, FALSE will be
* returned. Expired entries will be removed! ``addr'' is allowed to
* be NULL, otherwise the cached IP will be stored into the variable
* ``addr'' points to.
*
* @param addrs An array of host_addr_t items. If not NULL, up to
* ``n'' items will be copied from the cache.
* @param n The number of items "addrs" can hold.
* @return The number of cached addresses for the given hostname.
*/
static size_t
adns_cache_lookup(adns_cache_t *cache, time_t now,
const gchar *hostname, host_addr_t *addrs, size_t n)
{
adns_cache_entry_t *entry;
g_assert(NULL != cache);
g_assert(NULL != hostname);
g_assert(0 == n || NULL != addrs);
entry = g_hash_table_lookup(cache->ht, hostname);
if (entry) {
if (delta_time(now, entry->timestamp) < cache->timeout) {
size_t i;
for (i = 0; i < n; i++) {
if (i < entry->n) {
addrs[i] = entry->addrs[i];
if (common_dbg > 0)
g_message("adns_cache_lookup: \"%s\" cached (addr=%s)",
entry->hostname, host_addr_to_string(addrs[i]));
} else {
addrs[i] = zero_host_addr;
}
}
} else {
if (common_dbg > 0)
g_message("adns_cache_lookup: removing \"%s\" from cache",
entry->hostname);
g_hash_table_remove(cache->ht, hostname);
adns_cache_free_entry(cache, entry->id);
entry = NULL;
}
}
return entry ? entry->n : 0;
}
/**
* Transfers the data in `buf' of size `len' through `fd'. If `do_write' is
* FALSE the buffer will be filled from `fd'. Otherwise, the data from the
* buffer will be written to `fd'. The function returns only if all data
* has been transferred or if an unrecoverable error occurs. This function
* should only be used with a blocking `fd'.
*/
static gboolean
adns_do_transfer(gint fd, gpointer buf, size_t len, gboolean do_write)
{
ssize_t ret;
size_t n = len;
while (n > 0) {
if (common_dbg > 2)
g_message("adns_do_transfer (%s): n=%lu",
do_write ? "write" : "read", (gulong) n);
if (do_write)
ret = write(fd, buf, n);
else
ret = read(fd, buf, n);
if ((ssize_t) -1 == ret && !is_temporary_error(errno)) {
/* Ignore the failure, if the parent process is gone.
This prevents an unnecessary warning when quitting. */
if (!is_helper || getppid() != 1)
g_warning("adns_do_transfer (%s): %s (errno=%d)",
do_write ? "write" : "read", g_strerror(errno), errno);
return FALSE;
} else if (0 == ret) {
/*
* Don't warn on EOF if this is the child process and the
* parent is gone.
*/
if (!do_write && !(is_helper && getppid() == 1))
g_warning("adns_do_transfer (%s): EOF",
do_write ? "write" : "read");
return FALSE;
} else if (ret > 0) {
n -= ret;
buf = (gchar *) buf + ret;
}
}
return TRUE;
}
/**
* Read the complete buffer ``buf'' of size ``len'' from file descriptor ``fd''
*
* @return TRUE on success, FALSE if the operation failed
*/
static gboolean
adns_do_read(gint fd, gpointer buf, size_t len)
{
return adns_do_transfer(fd, buf, len, FALSE);
}
/**
* Write the complete buffer ``buf'' of size ``len'' to file descriptor ``fd''
*
* @return TRUE on success, FALSE if the operation failed
*/
static gboolean
adns_do_write(gint fd, gpointer buf, size_t len)
{
return adns_do_transfer(fd, buf, len, TRUE);
}
/**
* Copies user_callback and user_data from the query buffer to the
* reply buffer. This function won't fail. However, if gethostbyname()
* fails ``reply->addr'' will be set to zero.
*/
static void
adns_gethostbyname(const struct adns_request *req, struct adns_response *ans)
{
g_assert(NULL != req);
g_assert(NULL != ans);
ans->common = req->common;
if (req->common.reverse) {
const struct adns_reverse_query *query = &req->query.reverse;
struct adns_reverse_reply *reply = &ans->reply.reverse;
const gchar *host;
if (common_dbg > 1) {
g_message("adns_gethostbyname: Resolving \"%s\" ...",
host_addr_to_string(query->addr));
}
reply->addr = query->addr;
host = host_addr_to_name(query->addr);
g_strlcpy(reply->hostname, host ? host : "", sizeof reply->hostname);
} else {
const struct adns_query *query = &req->query.by_addr;
struct adns_reply *reply = &ans->reply.by_addr;
GSList *sl_addr, *sl;
size_t i = 0;
if (common_dbg > 1) {
g_message("adns_gethostbyname: Resolving \"%s\" ...",
query->hostname);
}
g_strlcpy(reply->hostname, query->hostname, sizeof reply->hostname);
sl_addr = name_to_host_addr(query->hostname, query->net);
for (sl = sl_addr; NULL != sl; sl = g_slist_next(sl)) {
host_addr_t *addr = sl->data;
g_assert(addr);
if (i >= G_N_ELEMENTS(reply->addrs)) {
break;
}
reply->addrs[i++] = *addr;
}
host_addr_free_list(&sl_addr);
if (i < G_N_ELEMENTS(reply->addrs)) {
reply->addrs[i] = zero_host_addr;
}
}
}
/**
* The ``main'' function of the adns helper process (server).
*
* Simply reads requests (queries) from fd_in, performs a DNS lookup for it
* and writes the result to fd_out. All operations should be blocking. Exits
* in case of non-recoverable error during read or write.
*/
static void
adns_helper(gint fd_in, gint fd_out)
{
g_set_prgname(adns_process_title);
gm_setproctitle(g_get_prgname());
#ifdef SIGQUIT
set_signal(SIGQUIT, SIG_IGN); /* Avoid core dumps on SIGQUIT */
#endif
is_helper = TRUE;
for (;;) {
struct adns_request req;
struct adns_response ans;
size_t size;
gpointer buf;
if (!adns_do_read(fd_in, &req.common, sizeof req.common))
break;
if (req.common.reverse) {
size = sizeof req.query.reverse;
buf = &req.query.reverse;
} else {
size = sizeof req.query.by_addr;
buf = &req.query.by_addr;
}
if (!adns_do_read(fd_in, buf, size))
break;
adns_gethostbyname(&req, &ans);
if (!adns_do_write(fd_out, &ans.common, sizeof ans.common))
break;
if (ans.common.reverse) {
size = sizeof ans.reply.reverse;
buf = &ans.reply.reverse;
} else {
size = sizeof ans.reply.by_addr;
buf = &ans.reply.by_addr;
}
if (!adns_do_write(fd_out, buf, size))
break;
}
close(fd_in);
close(fd_out);
_exit(EXIT_SUCCESS);
}
static inline void
adns_invoke_user_callback(const struct adns_response *ans)
{
if (ans->common.reverse) {
const struct adns_reverse_reply *reply = &ans->reply.reverse;
adns_reverse_callback_t func;
func = (adns_reverse_callback_t) ans->common.user_callback;
func(reply->hostname[0] != '\0' ? reply->hostname : NULL,
ans->common.user_data);
} else {
const struct adns_reply *reply = &ans->reply.by_addr;
adns_callback_t func;
size_t n;
n = count_addrs(reply->addrs, G_N_ELEMENTS(reply->addrs));
func = (adns_callback_t) ans->common.user_callback;
func(reply->addrs, n, ans->common.user_data);
}
}
/**
* Handles the query in synchronous (blocking) mode and is used if the
* dns helper is busy i.e., the pipe buffer is full or in case the dns
* helper is dead.
*/
static void
adns_fallback(const struct adns_request *req)
{
struct adns_response ans;
g_assert(req);
adns_gethostbyname(req, &ans);
g_assert(ans.common.user_callback);
adns_invoke_user_callback(&ans);
}
static void
adns_reply_ready(const struct adns_response *ans)
{
time_t now = tm_time();
g_assert(ans);
if (ans->common.reverse) {
if (common_dbg > 1) {
const struct adns_reverse_reply *reply = &ans->reply.reverse;
g_message("adns_reply_ready: Resolved \"%s\" to \"%s\".",
host_addr_to_string(reply->addr), reply->hostname);
}
} else {
const struct adns_reply *reply = &ans->reply.by_addr;
size_t num;
num = count_addrs(reply->addrs, G_N_ELEMENTS(reply->addrs));
num = MAX(1, num); /* For negative caching */
if (common_dbg > 1) {
size_t i;
for (i = 0; i < num; i++) {
g_message("adns_reply_ready: Resolved \"%s\" to \"%s\".",
reply->hostname, host_addr_to_string(reply->addrs[i]));
}
}
if (!adns_cache_lookup(adns_cache, now, reply->hostname, NULL, 0)) {
adns_cache_add(adns_cache, now, reply->hostname, reply->addrs, num);
}
}
g_assert(ans->common.user_callback);
adns_invoke_user_callback(ans);
}
/**
* Callback function for inputevt_add(). This function invokes the callback
* function given in DNS query on the client-side i.e., gtk-gnutella itself.
* It handles partial reads if necessary. In case of an unrecoverable error
* the reply pipe will be closed and the callback will be lost.
*/
static void
adns_reply_callback(gpointer data, gint source, inputevt_cond_t condition)
{
static struct adns_response ans;
static gpointer buf;
static size_t size, pos;
g_assert(NULL == data);
g_assert(condition & INPUT_EVENT_RX);
for (;;) {
ssize_t ret;
size_t n;
if (pos == size) {
pos = 0;
if (cast_to_gpointer(&ans.common) == buf) {
if (ans.common.reverse) {
buf = &ans.reply.reverse;
size = sizeof ans.reply.reverse;
} else {
buf = &ans.reply.by_addr;
size = sizeof ans.reply.by_addr;
}
} else {
if (buf) {
adns_reply_ready(&ans);
}
buf = &ans.common;
size = sizeof ans.common;
}
}
g_assert(buf);
g_assert(size > 0);
g_assert(pos < size);
n = size - pos;
ret = read(source, cast_to_gchar_ptr(buf) + pos, n);
if ((ssize_t) -1 == ret) {
if (!is_temporary_error(errno)) {
g_warning("adns_reply_callback: read() failed: %s",
g_strerror(errno));
goto error;
}
break;
} else if (0 == ret) {
g_warning("adns_reply_callback: read() failed: EOF");
goto error;
} else {
g_assert(ret > 0);
g_assert((size_t) ret <= n);
pos += (size_t) ret;
}
}
return;
error:
inputevt_remove(adns_reply_event_id);
adns_reply_event_id = 0;
g_warning("adns_reply_callback: removed myself");
close(source);
}
/**
* Allocate a "spill" buffer of size `size'.
*/
static adns_async_write_t *
adns_async_write_alloc(const struct adns_request *req,
gconstpointer buf, size_t size)
{
adns_async_write_t *remain;
g_assert(req);
g_assert(buf);
g_assert(size > 0);
remain = walloc(sizeof *remain);
remain->req = *req;
remain->size = size;
remain->buf = wcopy(buf, remain->size);
remain->pos = 0;
return remain;
}
/**
* Dispose of the "spill" buffer.
*/
static void
adns_async_write_free(adns_async_write_t *remain)
{
g_assert(remain);
g_assert(remain->buf);
g_assert(remain->size > 0);
wfree(remain->buf, remain->size);
wfree(remain, sizeof *remain);
}
/**
* Callback function for inputevt_add(). This function pipes the query to
* the server using the pipe in non-blocking mode, partial writes are handled
* appropriately. In case of an unrecoverable error the query pipe will be
* closed and the blocking adns_fallback() will be invoked.
*/
static void
adns_query_callback(gpointer data, gint dest, inputevt_cond_t condition)
{
adns_async_write_t *remain = data;
g_assert(NULL != remain);
g_assert(NULL != remain->buf);
g_assert(remain->pos < remain->size);
g_assert(dest == adns_query_fd);
g_assert(0 != adns_query_event_id);
if (condition & INPUT_EVENT_EXCEPTION) {
g_warning("adns_query_callback: write exception");
goto abort;
}
while (remain->pos < remain->size) {
ssize_t ret;
size_t n;
n = remain->size - remain->pos;
ret = write(dest, &remain->buf[remain->pos], n);
if (0 == ret) {
errno = ECONNRESET;
ret = (ssize_t) -1;
}
/* FALL THROUGH */
if ((ssize_t) -1 == ret) {
if (!is_temporary_error(errno))
goto error;
return;
}
g_assert(ret > 0);
g_assert((size_t) ret <= n);
remain->pos += (size_t) ret;
}
g_assert(remain->pos == remain->size);
inputevt_remove(adns_query_event_id);
adns_query_event_id = 0;
goto done;
error:
g_warning("adns_query_callback: write() failed: %s", g_strerror(errno));
abort:
g_warning("adns_query_callback: removed myself");
inputevt_remove(adns_query_event_id);
adns_query_event_id = 0;
CLOSE_IF_VALID(adns_query_fd);
g_warning("adns_query_callback: using fallback");
adns_fallback(&remain->req);
done:
adns_async_write_free(remain);
return;
}
/* public functions */
/**
* Initializes the adns helper i.e., fork()s a child process which will
* be used to resolve hostnames asynchronously.
*/
void
adns_init(void)
{
gint fd_query[2] = {-1, -1};
gint fd_reply[2] = {-1, -1};
pid_t pid;
if (-1 == pipe(fd_query) || -1 == pipe(fd_reply)) {
g_warning("adns_init: pipe() failed: %s", g_strerror(errno));
goto prefork_failure;
}
pid = fork();
if ((pid_t) -1 == pid) {
g_warning("adns_init: fork() failed: %s", g_strerror(errno));
goto prefork_failure;
}
if (0 == pid) {
/* child process */
/**
* Close all standard FILEs so that they don't keep a reference
* to the log files when they are reopened by the main process
* on SIGHUP. This means there will be no visible messages from
* ADNS at all.
*/
if (!freopen("/dev/null", "r", stdin))
g_error("adns_init: freopen(\"/dev/null\", \"r\", stdin) failed: "
"%s", g_strerror(errno));
if (!freopen("/dev/null", "a", stdout))
g_error("adns_init: freopen(\"/dev/null\", \"a\", stdout) failed: "
"%s", g_strerror(errno));
if (!freopen("/dev/null", "a", stderr))
g_error("adns_init: freopen(\"/dev/null\", \"a\", stderr) failed: "
"%s", g_strerror(errno));
close(fd_query[1]);
close(fd_reply[0]);
set_close_on_exec(fd_query[0]);
set_close_on_exec(fd_reply[1]);
adns_helper(fd_query[0], fd_reply[1]);
g_assert_not_reached();
_exit(EXIT_SUCCESS);
}
/* parent process */
close(fd_query[0]);
close(fd_reply[1]);
fd_query[1] = get_non_stdio_fd(fd_query[1]);
fd_reply[0] = get_non_stdio_fd(fd_reply[0]);
adns_query_fd = fd_query[1];
set_close_on_exec(adns_query_fd);
set_close_on_exec(fd_reply[0]);
socket_set_nonblocking(adns_query_fd);
socket_set_nonblocking(fd_reply[0]);
adns_reply_event_id = inputevt_add(fd_reply[0], INPUT_EVENT_RX,
adns_reply_callback, NULL);
/* FALL THROUGH */
prefork_failure:
if (!adns_reply_event_id) {
g_warning("Cannot use ADNS; DNS lookups may cause stalling");
CLOSE_IF_VALID(fd_query[0]);
CLOSE_IF_VALID(fd_query[1]);
CLOSE_IF_VALID(fd_reply[0]);
CLOSE_IF_VALID(fd_reply[1]);
}
adns_cache = adns_cache_init();
}
/**
* @return TRUE on success, FALSE on failure.
*/
static gboolean
adns_send_request(const struct adns_request *req)
{
gchar buf[sizeof *req];
size_t size;
ssize_t written;
g_assert(req);
if (!adns_reply_event_id || 0 != adns_query_event_id)
return FALSE;
g_assert(adns_query_fd >= 0);
memcpy(buf, &req->common, sizeof req->common);
size = sizeof req->common;
{
gconstpointer p;
size_t n;
if (req->common.reverse) {
n = sizeof req->query.reverse;
p = &req->query.reverse;
} else {
n = sizeof req->query.by_addr;
p = &req->query.by_addr;
}
memcpy(&buf[size], p, n);
size += n;
}
/*
* Try to write the query atomically into the pipe.
*/
written = write(adns_query_fd, buf, size);
if (written == (ssize_t) -1) {
if (!is_temporary_error(errno)) {
g_warning("adns_resolve: write() failed: %s",
g_strerror(errno));
inputevt_remove(adns_reply_event_id);
adns_reply_event_id = 0;
CLOSE_IF_VALID(adns_query_fd);
return FALSE;
}
written = 0;
}
g_assert(0 == adns_query_event_id);
/*
* If not written fully, allocate a spill buffer and record
* callback that will write the remaining data when the pipe
* can absorb new data.
*/
if ((size_t) written < size) {
adns_async_write_t *aq;
aq = adns_async_write_alloc(req, &buf[written], size - written);
adns_query_event_id = inputevt_add(adns_query_fd, INPUT_EVENT_WX,
adns_query_callback, aq);
}
return TRUE;
}
/**
* Creates a DNS resolve query for ``hostname''.
*
* The given function ``user_callback'' (which MUST NOT be NULL)
* will be invoked with the resolved IP address and ``user_data''
* as its parameters. The IP address 0.0.0.0 i.e., ``(guint32) 0''
* is used to indicate a failure. In case the hostname is given as
* an IP string, it will be directly converted and the callback
* immediately invoked. If the adns helper process is ``out of service''
* the query will be resolved synchronously.
*
* @return TRUE if the resolution is asynchronous i.e., the callback
* will be called AFTER adns_resolve() returned. If the resolution is
* synchronous i.e., the callback was called BEFORE adns_resolve()
* returned, adns_resolve() returns FALSE.
*/
gboolean
adns_resolve(const gchar *hostname, enum net_type net,
adns_callback_t user_callback, gpointer user_data)
{
struct adns_request req;
struct adns_response ans;
struct adns_query *query = &req.query.by_addr;
struct adns_reply *reply = &ans.reply.by_addr;
size_t hostname_len;
host_addr_t addr;
g_assert(NULL != hostname);
g_assert(NULL != user_callback);
req.common.user_callback = (void (*)(void)) user_callback;
req.common.user_data = user_data;
req.common.reverse = FALSE;
ans.common = req.common;
query->net = net;
reply->hostname[0] = '\0';
reply->addrs[0] = zero_host_addr;
hostname_len = g_strlcpy(query->hostname, hostname, sizeof query->hostname);
if (hostname_len >= sizeof query->hostname) {
/* truncation detected */
adns_invoke_user_callback(&ans);
return FALSE; /* synchronous */
}
if (string_to_host_addr(hostname, NULL, &addr)) {
reply->addrs[0] = addr;
reply->addrs[1] = zero_host_addr;
adns_invoke_user_callback(&ans);
return FALSE; /* synchronous */
}
ascii_strlower(query->hostname, hostname);
g_strlcpy(reply->hostname, query->hostname, sizeof reply->hostname);
if (
adns_cache_lookup(adns_cache, tm_time(), query->hostname,
reply->addrs, G_N_ELEMENTS(reply->addrs))
) {
adns_invoke_user_callback(&ans);
return FALSE; /* synchronous */
}
if (adns_send_request(&req))
return TRUE; /* asynchronous */
if (adns_reply_event_id)
g_warning("adns_resolve: using synchronous resolution for \"%s\"",
query->hostname);
adns_fallback(&req);
return FALSE; /* synchronous */
}
/**
* Creates a DNS reverse lookup query for ``addr''. The given function
* ``user_callback'' (which MUST NOT be NULL) will be invoked with
* the resolved hostname and ``user_data'' as its parameters. If the lookup
* failed, the callback will be invoked with ``hostname'' NULL. If the adns
* helper process is ``out of service'' the query will be processed
* synchronously.
*
* @return TRUE if the resolution is asynchronous i.e., the callback
* will be called AFTER adns_reverse_lookup() returned. If the resolution is
* synchronous i.e., the callback was called BEFORE adns_reverse_lookup()
* returned, adns_reverse_lookup() returns FALSE.
*/
gboolean
adns_reverse_lookup(const host_addr_t addr,
adns_reverse_callback_t user_callback, gpointer user_data)
{
struct adns_request req;
struct adns_reverse_query *query = &req.query.reverse;
g_assert(user_callback);
req.common.user_callback = (void (*)(void)) user_callback;
req.common.user_data = user_data;
req.common.reverse = TRUE;
query->addr = addr;
if (adns_send_request(&req))
return TRUE; /* asynchronous */
g_warning("adns_reverse_lookup: using synchronous resolution for \"%s\"",
host_addr_to_string(query->addr));
adns_fallback(&req);
return FALSE; /* synchronous */
}
/**
* Removes the callback and frees the cache.
*/
void
adns_close(void)
{
if (adns_reply_event_id) {
inputevt_remove(adns_reply_event_id);
adns_reply_event_id = 0;
}
if (adns_query_event_id) {
inputevt_remove(adns_query_event_id);
adns_query_event_id = 0;
}
adns_cache_free(adns_cache);
adns_cache = NULL;
}
/* vi: set ts=4 sw=4 cindent: */
See more files for this project here