/*
* Copyright (C) 2012 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program 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. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* 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.
*/
#include <sys/types.h>
#include "host_resolver.h"
#include <library.h>
#include <utils/debug.h>
#include <threading/condvar.h>
#include <threading/mutex.h>
#include <threading/thread.h>
#include <collections/hashtable.h>
#include <collections/linked_list.h>
/**
* Default minimum and maximum number of threads
*/
#define MIN_THREADS_DEFAULT 0
#define MAX_THREADS_DEFAULT 3
/**
* Timeout in seconds to wait for new queries until a thread may be stopped
*/
#define NEW_QUERY_WAIT_TIMEOUT 30
typedef struct private_host_resolver_t private_host_resolver_t;
/**
* Private data of host_resolver_t
*/
struct private_host_resolver_t {
/**
* Public interface
*/
host_resolver_t public;
/**
* Hashtable to check for queued queries, query_t*
*/
hashtable_t *queries;
/**
* Queue for queries, query_t*
*/
linked_list_t *queue;
/**
* Mutex to safely access private data
*/
mutex_t *mutex;
/**
* Condvar to signal arrival of new queries
*/
condvar_t *new_query;
/**
* Minimum number of resolver threads
*/
u_int min_threads;
/**
* Maximum number of resolver threads
*/
u_int max_threads;
/**
* Current number of threads
*/
u_int threads;
/**
* Current number of busy threads
*/
u_int busy_threads;
/**
* Pool of threads, thread_t*
*/
linked_list_t *pool;
/**
* TRUE if no new queries are accepted
*/
bool disabled;
};
typedef struct {
/** DNS name we are looking for */
char *name;
/** address family we request */
int family;
/** Condvar to signal completion of a query */
condvar_t *done;
/** refcount */
refcount_t refcount;
/** the result if successful */
host_t *result;
} query_t;
/**
* Destroy the given query_t object if refcount is zero
*/
static void query_destroy(query_t *this)
{
if (ref_put(&this->refcount))
{
DESTROY_IF(this->result);
this->done->destroy(this->done);
free(this->name);
free(this);
}
}
/**
* Signals all waiting threads and destroys the query
*/
static void query_signal_and_destroy(query_t *this)
{
this->done->broadcast(this->done);
query_destroy(this);
}
/**
* Hash a queued query
*/
static u_int query_hash(query_t *this)
{
return chunk_hash_inc(chunk_create(this->name, strlen(this->name)),
chunk_hash(chunk_from_thing(this->family)));
}
/**
* Compare two queued queries
*/
static bool query_equals(query_t *this, query_t *other)
{
return this->family == other->family && streq(this->name, other->name);
}
/**
* Main function of resolver threads
*/
static void *resolve_hosts(private_host_resolver_t *this)
{
struct addrinfo hints, *result;
query_t *query;
int error;
bool old, timed_out;
/* default resolver threads to non-cancellable */
thread_cancelability(FALSE);
while (TRUE)
{
this->mutex->lock(this->mutex);
while (this->queue->remove_first(this->queue,
(void**)&query) != SUCCESS)
{
if (this->disabled)
{
this->mutex->unlock(this->mutex);
return NULL;
}
timed_out = this->new_query->timed_wait(this->new_query,
this->mutex, NEW_QUERY_WAIT_TIMEOUT * 1000);
if (this->disabled)
{
this->mutex->unlock(this->mutex);
return NULL;
}
else if (timed_out && (this->threads > this->min_threads))
{ /* terminate this thread by detaching it */
thread_t *thread = thread_current();
this->threads--;
this->pool->remove(this->pool, thread, NULL);
this->mutex->unlock(this->mutex);
thread->detach(thread);
return NULL;
}
}
this->busy_threads++;
this->mutex->unlock(this->mutex);
memset(&hints, 0, sizeof(hints));
hints.ai_family = query->family;
hints.ai_socktype = SOCK_DGRAM;
thread_cleanup_push((thread_cleanup_t)query_signal_and_destroy, query);
old = thread_cancelability(TRUE);
error = getaddrinfo(query->name, NULL, &hints, &result);
thread_cancelability(old);
thread_cleanup_pop(FALSE);
this->mutex->lock(this->mutex);
this->busy_threads--;
if (error != 0)
{
DBG1(DBG_LIB, "resolving '%s' failed: %s", query->name,
gai_strerror(error));
}
else
{ /* result is a linked list, but we use only the first address */
query->result = host_create_from_sockaddr(result->ai_addr);
freeaddrinfo(result);
}
this->queries->remove(this->queries, query);
query->done->broadcast(query->done);
this->mutex->unlock(this->mutex);
query_destroy(query);
}
return NULL;
}
METHOD(host_resolver_t, resolve, host_t*,
private_host_resolver_t *this, char *name, int family)
{
query_t *query, lookup = {
.name = name,
.family = family,
};
host_t *result;
struct in_addr addr;
switch (family)
{
case AF_INET:
/* do not try to convert v6 addresses for v4 family */
if (strchr(name, ':'))
{
return NULL;
}
break;
case AF_INET6:
/* do not try to convert v4 addresses for v6 family */
if (inet_pton(AF_INET, name, &addr) == 1)
{
return NULL;
}
break;
}
this->mutex->lock(this->mutex);
if (this->disabled)
{
this->mutex->unlock(this->mutex);
return NULL;
}
query = this->queries->get(this->queries, &lookup);
if (!query)
{
INIT(query,
.name = strdup(name),
.family = family,
.done = condvar_create(CONDVAR_TYPE_DEFAULT),
.refcount = 1,
);
this->queries->put(this->queries, query, query);
this->queue->insert_last(this->queue, query);
this->new_query->signal(this->new_query);
}
ref_get(&query->refcount);
if (this->busy_threads == this->threads &&
this->threads < this->max_threads)
{
thread_t *thread;
thread = thread_create((thread_main_t)resolve_hosts, this);
if (thread)
{
this->threads++;
this->pool->insert_last(this->pool, thread);
}
}
if (this->threads)
{
query->done->wait(query->done, this->mutex);
}
else
{
DBG1(DBG_LIB, "resolving '%s' failed: no resolver threads", query->name);
/* this should always be the case if we end up here, but make sure */
if (query->refcount == 1)
{
this->queries->remove(this->queries, query);
this->queue->remove_last(this->queue, (void**)&query);
}
}
this->mutex->unlock(this->mutex);
result = query->result ? query->result->clone(query->result) : NULL;
query_destroy(query);
return result;
}
METHOD(host_resolver_t, flush, void,
private_host_resolver_t *this)
{
enumerator_t *enumerator;
query_t *query;
this->mutex->lock(this->mutex);
enumerator = this->queries->create_enumerator(this->queries);
while (enumerator->enumerate(enumerator, &query, NULL))
{ /* use the hashtable here as we also want to signal dequeued queries */
this->queries->remove_at(this->queries, enumerator);
query->done->broadcast(query->done);
}
enumerator->destroy(enumerator);
this->queue->destroy_function(this->queue, (void*)query_destroy);
this->queue = linked_list_create();
this->disabled = TRUE;
/* this will already terminate most idle threads */
this->new_query->broadcast(this->new_query);
this->mutex->unlock(this->mutex);
}
METHOD(host_resolver_t, destroy, void,
private_host_resolver_t *this)
{
thread_t *thread;
flush(this);
this->pool->invoke_offset(this->pool, offsetof(thread_t, cancel));
while (this->pool->remove_first(this->pool, (void**)&thread) == SUCCESS)
{
thread->join(thread);
}
this->pool->destroy(this->pool);
this->queue->destroy(this->queue);
this->queries->destroy(this->queries);
this->new_query->destroy(this->new_query);
this->mutex->destroy(this->mutex);
free(this);
}
/*
* Described in header
*/
host_resolver_t *host_resolver_create()
{
private_host_resolver_t *this;
INIT(this,
.public = {
.resolve = _resolve,
.flush = _flush,
.destroy = _destroy,
},
.queries = hashtable_create((hashtable_hash_t)query_hash,
(hashtable_equals_t)query_equals, 8),
.queue = linked_list_create(),
.pool = linked_list_create(),
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.new_query = condvar_create(CONDVAR_TYPE_DEFAULT),
);
this->min_threads = max(0, lib->settings->get_int(lib->settings,
"%s.host_resolver.min_threads",
MIN_THREADS_DEFAULT, lib->ns));
this->max_threads = max(this->min_threads ?: 1,
lib->settings->get_int(lib->settings,
"%s.host_resolver.max_threads",
MAX_THREADS_DEFAULT, lib->ns));
return &this->public;
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>