summaryrefslogtreecommitdiff
path: root/src/libstrongswan/networking/host_resolver.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstrongswan/networking/host_resolver.c')
-rw-r--r--src/libstrongswan/networking/host_resolver.c351
1 files changed, 351 insertions, 0 deletions
diff --git a/src/libstrongswan/networking/host_resolver.c b/src/libstrongswan/networking/host_resolver.c
new file mode 100644
index 000000000..5e244f114
--- /dev/null
+++ b/src/libstrongswan/networking/host_resolver.c
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2012 Tobias Brunner
+ * 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 <sys/socket.h>
+#include <netdb.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;
+
+ while (TRUE)
+ {
+ this->mutex->lock(this->mutex);
+ thread_cleanup_push((thread_cleanup_t)this->mutex->unlock, this->mutex);
+ while (this->queue->remove_first(this->queue,
+ (void**)&query) != SUCCESS)
+ {
+ old = thread_cancelability(TRUE);
+ timed_out = this->new_query->timed_wait(this->new_query,
+ this->mutex, NEW_QUERY_WAIT_TIMEOUT * 1000);
+ thread_cancelability(old);
+ if (this->disabled)
+ {
+ thread_cleanup_pop(TRUE);
+ 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);
+ thread_cleanup_pop(TRUE);
+ thread->detach(thread);
+ return NULL;
+ }
+ }
+ this->busy_threads++;
+ thread_cleanup_pop(TRUE);
+
+ 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;
+
+ if (family == AF_INET && strchr(name, ':'))
+ { /* do not try to convert v6 addresses for v4 family */
+ return NULL;
+ }
+ 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);
+ }
+ }
+ query->done->wait(query->done, this->mutex);
+ 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,
+ "libstrongswan.host_resolver.min_threads",
+ MIN_THREADS_DEFAULT));
+ this->max_threads = max(this->min_threads ?: 1,
+ lib->settings->get_int(lib->settings,
+ "libstrongswan.host_resolver.max_threads",
+ MAX_THREADS_DEFAULT));
+ return &this->public;
+}