Resolve hosts by DNS name in separate threads so we can cancel them
authorTobias Brunner <tobias@strongswan.org>
Tue, 16 Oct 2012 08:57:02 +0000 (10:57 +0200)
committerTobias Brunner <tobias@strongswan.org>
Thu, 18 Oct 2012 08:57:55 +0000 (10:57 +0200)
getaddrinfo(3) may block a long time so proper termination of the daemon may
block if DNS servers are not reachable.

getaddrinfo(3) is an optional cancellation point in posix threads so it
might still block a shutdown but at least on Android (with the signal based
pthread_cancel implementation) it works, on Linux starter will kill charon
anyway after a while.

src/libcharon/daemon.c
src/libstrongswan/Android.mk
src/libstrongswan/Makefile.am
src/libstrongswan/host_resolver.c [new file with mode: 0644]
src/libstrongswan/host_resolver.h [new file with mode: 0644]
src/libstrongswan/library.c
src/libstrongswan/library.h
src/libstrongswan/utils/host.c

index 6e977ef..9ae56a9 100644 (file)
@@ -84,7 +84,8 @@ static void destroy(private_daemon_t *this)
 {
        /* terminate all idle threads */
        lib->processor->set_threads(lib->processor, 0);
-
+       /* make sure nobody waits for a DNS query */
+       lib->hosts->flush(lib->hosts);
        /* close all IKE_SAs */
        if (this->public.ike_sa_manager)
        {
index 4912576..9c7ef1d 100644 (file)
@@ -3,8 +3,8 @@ include $(CLEAR_VARS)
 
 # copy-n-paste from Makefile.am
 LOCAL_SRC_FILES := \
-library.c chunk.c debug.c enum.c settings.c printf_hook.c asn1/asn1.c \
-asn1/asn1_parser.c asn1/oid.c bio/bio_reader.c bio/bio_writer.c \
+library.c chunk.c debug.c enum.c host_resolver.c settings.c printf_hook.c \
+asn1/asn1.c asn1/asn1_parser.c asn1/oid.c bio/bio_reader.c bio/bio_writer.c \
 crypto/crypters/crypter.c crypto/hashers/hasher.c crypto/pkcs7.c crypto/pkcs9.c \
 crypto/proposal/proposal_keywords.c crypto/proposal/proposal_keywords_static.c \
 crypto/prfs/prf.c crypto/prfs/mac_prf.c \
index 463d57d..4017bfc 100644 (file)
@@ -1,8 +1,8 @@
 ipseclib_LTLIBRARIES = libstrongswan.la
 
 libstrongswan_la_SOURCES = \
-library.c chunk.c debug.c enum.c settings.c printf_hook.c asn1/asn1.c \
-asn1/asn1_parser.c asn1/oid.c bio/bio_reader.c bio/bio_writer.c \
+library.c chunk.c debug.c enum.c host_resolver.c settings.c printf_hook.c \
+asn1/asn1.c asn1/asn1_parser.c asn1/oid.c bio/bio_reader.c bio/bio_writer.c \
 crypto/crypters/crypter.c crypto/hashers/hasher.c crypto/pkcs7.c crypto/pkcs9.c \
 crypto/proposal/proposal_keywords.c crypto/proposal/proposal_keywords_static.c \
 crypto/prfs/prf.c crypto/prfs/mac_prf.c \
@@ -31,7 +31,7 @@ utils/optionsfrom.c utils/capabilities.c utils/backtrace.c utils/tun_device.c
 if USE_DEV_HEADERS
 strongswan_includedir = ${dev_headers}
 nobase_strongswan_include_HEADERS = \
-library.h chunk.h debug.h enum.h settings.h printf_hook.h \
+library.h chunk.h debug.h enum.h host_resolver.h settings.h printf_hook.h \
 asn1/asn1.h asn1/asn1_parser.h asn1/oid.h bio/bio_reader.h bio/bio_writer.h \
 crypto/crypters/crypter.h crypto/hashers/hasher.h crypto/mac.h \
 crypto/pkcs7.h crypto/pkcs9.h crypto/proposal/proposal_keywords.h \
diff --git a/src/libstrongswan/host_resolver.c b/src/libstrongswan/host_resolver.c
new file mode 100644 (file)
index 0000000..8a97118
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * 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 <debug.h>
+#include <processing/jobs/callback_job.h>
+#include <threading/condvar.h>
+#include <threading/mutex.h>
+#include <threading/thread.h>
+#include <utils/hashtable.h>
+#include <utils/linked_list.h>
+
+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;
+
+       /**
+        * Maximum number of resolver threads
+        */
+       u_int max_threads;
+
+       /**
+        * Current number of threads
+        */
+       u_int threads;
+
+       /**
+        * 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 job_requeue_t resolve_hosts(private_host_resolver_t *this)
+{
+       struct addrinfo hints, *result;
+       query_t *query;
+       int error;
+       bool old;
+
+       this->mutex->lock(this->mutex);
+       while (this->queue->remove_first(this->queue, (void**)&query) != SUCCESS)
+       {
+               thread_cleanup_push((thread_cleanup_t)this->mutex->unlock, this->mutex);
+               old = thread_cancelability(TRUE);
+               this->new_query->wait(this->new_query, this->mutex);
+               thread_cancelability(old);
+               thread_cleanup_pop(FALSE);
+       }
+       this->mutex->unlock(this->mutex);
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = query->family;
+
+       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);
+       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 JOB_REQUEUE_DIRECT;
+}
+
+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 (streq(name, "%any") || streq(name, "0.0.0.0"))
+       {
+               return host_create_any(family ? family : AF_INET);
+       }
+       if (streq(name, "%any6") || streq(name, "::"))
+       {
+               return host_create_any(family ? family : AF_INET6);
+       }
+       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->threads < this->max_threads)
+       {
+               this->threads++;
+               lib->processor->queue_job(lib->processor,
+                       (job_t*)callback_job_create_with_prio(
+                               (callback_job_cb_t)resolve_hosts, this, NULL,
+                               (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
+       }
+       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->mutex->unlock(this->mutex);
+}
+
+METHOD(host_resolver_t, destroy, void,
+       private_host_resolver_t *this)
+{
+       this->queue->destroy_function(this->queue, (void*)query_signal_and_destroy);
+       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(u_int max_threads)
+{
+       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(),
+               .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
+               .new_query = condvar_create(CONDVAR_TYPE_DEFAULT),
+               .max_threads = max_threads,
+       );
+
+       return &this->public;
+}
diff --git a/src/libstrongswan/host_resolver.h b/src/libstrongswan/host_resolver.h
new file mode 100644 (file)
index 0000000..855e3fe
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+/**
+ * @defgroup host_resolver host_resolver
+ * @{ @ingroup libstrongswan
+ */
+
+#ifndef HOST_RESOLVER_H_
+#define HOST_RESOLVER_H_
+
+#include "utils/host.h"
+
+typedef struct host_resolver_t host_resolver_t;
+
+/**
+ * Resolve hosts by DNS name but do so in a separate thread (calling
+ * getaddrinfo(3) directly might block indefinitely, or at least a very long
+ * time if no DNS servers are reachable).
+ */
+struct host_resolver_t {
+
+       /**
+        * Resolve host from the given DNS name.
+        *
+        * @param name          name to lookup
+        * @param family        requested address family
+        * @return                      resolved host or NULL if failed or canceled
+        */
+       host_t *(*resolve)(host_resolver_t *this, char *name, int family);
+
+       /**
+        * Flush the queue of queries. No new queries will be accepted afterwards.
+        */
+       void (*flush)(host_resolver_t *this);
+
+       /**
+        * Destroy a host_resolver_t.
+        */
+       void (*destroy)(host_resolver_t *this);
+};
+
+/**
+ * Create a host_resolver_t instance.
+ *
+ * @param max_threads  maximum number of resolver threads to use
+ */
+host_resolver_t *host_resolver_create(u_int max_threads);
+
+#endif /** HOST_RESOLVER_H_ @}*/
index 1179b46..b85ebea 100644 (file)
@@ -27,6 +27,7 @@
 #include <selectors/traffic_selector.h>
 
 #define CHECKSUM_LIBRARY IPSEC_LIB_DIR"/libchecksum.so"
+#define HOST_RESOLVER_MAX_THREADS 2
 
 typedef struct private_library_t private_library_t;
 
@@ -68,6 +69,7 @@ void library_deinit()
        this->public.scheduler->destroy(this->public.scheduler);
        this->public.processor->destroy(this->public.processor);
        this->public.plugins->destroy(this->public.plugins);
+       this->public.hosts->destroy(this->public.hosts);
        this->public.settings->destroy(this->public.settings);
        this->public.credmgr->destroy(this->public.credmgr);
        this->public.creds->destroy(this->public.creds);
@@ -182,6 +184,7 @@ bool library_init(char *settings)
 
        this->objects = hashtable_create((hashtable_hash_t)hash,
                                                                         (hashtable_equals_t)equals, 4);
+       this->public.hosts = host_resolver_create(HOST_RESOLVER_MAX_THREADS);
        this->public.settings = settings_create(settings);
        this->public.proposal = proposal_keywords_create();
        this->public.crypto = crypto_factory_create();
index b79bd91..5bd0d67 100644 (file)
@@ -77,6 +77,7 @@
 #include "printf_hook.h"
 #include "utils.h"
 #include "chunk.h"
+#include "host_resolver.h"
 #include "settings.h"
 #include "integrity_checker.h"
 #include "processing/processor.h"
@@ -171,6 +172,11 @@ struct library_t {
        scheduler_t *scheduler;
 
        /**
+        * resolve hosts by DNS name
+        */
+       host_resolver_t *hosts;
+
+       /**
         * various settings loaded from settings file
         */
        settings_t *settings;
index e17b6ad..1d06140 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2009 Tobias Brunner
+ * Copyright (C) 2006-2012 Tobias Brunner
  * Copyright (C) 2006 Daniel Roethlisberger
  * Copyright (C) 2005-2006 Martin Willi
  * Copyright (C) 2005 Jan Hutter
  * for more details.
  */
 
-#define _GNU_SOURCE
-#include <sys/socket.h>
-#include <netdb.h>
-#include <string.h>
-
 #include "host.h"
 
 #include <debug.h>
+#include <library.h>
 
 #define IPV4_LEN        4
 #define IPV6_LEN       16
@@ -450,48 +446,14 @@ host_t *host_create_from_sockaddr(sockaddr_t *sockaddr)
  */
 host_t *host_create_from_dns(char *string, int af, u_int16_t port)
 {
-       private_host_t *this;
-       struct addrinfo hints, *result;
-       int error;
-
-       if (streq(string, "%any"))
-       {
-               return host_create_any_port(af ? af : AF_INET, port);
-       }
-       if (streq(string, "%any6"))
-       {
-               return host_create_any_port(af ? af : AF_INET6, port);
-       }
-       if (af == AF_INET && strchr(string, ':'))
-       {       /* do not try to convert v6 addresses for v4 family */
-               return NULL;
-       }
+       host_t *this;
 
-       memset(&hints, 0, sizeof(hints));
-       hints.ai_family = af;
-       error = getaddrinfo(string, NULL, &hints, &result);
-       if (error != 0)
-       {
-               DBG1(DBG_LIB, "resolving '%s' failed: %s", string, gai_strerror(error));
-               return NULL;
-       }
-       /* result is a linked list, but we use only the first address */
-       this = (private_host_t*)host_create_from_sockaddr(result->ai_addr);
-       freeaddrinfo(result);
+       this = lib->hosts->resolve(lib->hosts, string, af);
        if (this)
        {
-               switch (this->address.sa_family)
-               {
-                       case AF_INET:
-                               this->address4.sin_port = htons(port);
-                               break;
-                       case AF_INET6:
-                               this->address6.sin6_port = htons(port);
-                               break;
-               }
-               return &this->public;
+               this->set_port(this, port);
        }
-       return NULL;
+       return this;
 }
 
 /*