2 * Copyright (C) 2012 Tobias Brunner
4 * Copyright (C) secunet Security Networks AG
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 #include <sys/types.h>
19 #include "host_resolver.h"
22 #include <utils/debug.h>
23 #include <threading/condvar.h>
24 #include <threading/mutex.h>
25 #include <threading/thread.h>
26 #include <collections/hashtable.h>
27 #include <collections/linked_list.h>
30 * Default minimum and maximum number of threads
32 #define MIN_THREADS_DEFAULT 0
33 #define MAX_THREADS_DEFAULT 3
36 * Timeout in seconds to wait for new queries until a thread may be stopped
38 #define NEW_QUERY_WAIT_TIMEOUT 30
40 typedef struct private_host_resolver_t private_host_resolver_t
;
43 * Private data of host_resolver_t
45 struct private_host_resolver_t
{
50 host_resolver_t
public;
53 * Hashtable to check for queued queries, query_t*
58 * Queue for queries, query_t*
63 * Mutex to safely access private data
68 * Condvar to signal arrival of new queries
73 * Minimum number of resolver threads
78 * Maximum number of resolver threads
83 * Current number of threads
88 * Current number of busy threads
93 * Pool of threads, thread_t*
98 * TRUE if no new queries are accepted
105 /** DNS name we are looking for */
107 /** address family we request */
109 /** Condvar to signal completion of a query */
113 /** the result if successful */
118 * Destroy the given query_t object if refcount is zero
120 static void query_destroy(query_t
*this)
122 if (ref_put(&this->refcount
))
124 DESTROY_IF(this->result
);
125 this->done
->destroy(this->done
);
132 * Signals all waiting threads and destroys the query
134 static void query_signal_and_destroy(query_t
*this)
136 this->done
->broadcast(this->done
);
141 * Hash a queued query
143 static u_int
query_hash(query_t
*this)
145 return chunk_hash_inc(chunk_create(this->name
, strlen(this->name
)),
146 chunk_hash(chunk_from_thing(this->family
)));
150 * Compare two queued queries
152 static bool query_equals(query_t
*this, query_t
*other
)
154 return this->family
== other
->family
&& streq(this->name
, other
->name
);
158 * Main function of resolver threads
160 static void *resolve_hosts(private_host_resolver_t
*this)
162 struct addrinfo hints
, *result
;
167 /* default resolver threads to non-cancellable */
168 thread_cancelability(FALSE
);
172 this->mutex
->lock(this->mutex
);
173 while (this->queue
->remove_first(this->queue
,
174 (void**)&query
) != SUCCESS
)
178 this->mutex
->unlock(this->mutex
);
181 timed_out
= this->new_query
->timed_wait(this->new_query
,
182 this->mutex
, NEW_QUERY_WAIT_TIMEOUT
* 1000);
185 this->mutex
->unlock(this->mutex
);
188 else if (timed_out
&& (this->threads
> this->min_threads
))
189 { /* terminate this thread by detaching it */
190 thread_t
*thread
= thread_current();
193 this->pool
->remove(this->pool
, thread
, NULL
);
194 this->mutex
->unlock(this->mutex
);
195 thread
->detach(thread
);
199 this->busy_threads
++;
200 this->mutex
->unlock(this->mutex
);
202 memset(&hints
, 0, sizeof(hints
));
203 hints
.ai_family
= query
->family
;
204 hints
.ai_socktype
= SOCK_DGRAM
;
206 thread_cleanup_push((thread_cleanup_t
)query_signal_and_destroy
, query
);
207 old
= thread_cancelability(TRUE
);
208 error
= getaddrinfo(query
->name
, NULL
, &hints
, &result
);
209 thread_cancelability(old
);
210 thread_cleanup_pop(FALSE
);
212 this->mutex
->lock(this->mutex
);
213 this->busy_threads
--;
216 DBG1(DBG_LIB
, "resolving '%s' failed: %s", query
->name
,
217 gai_strerror(error
));
220 { /* result is a linked list, but we use only the first address */
221 query
->result
= host_create_from_sockaddr(result
->ai_addr
);
222 freeaddrinfo(result
);
224 this->queries
->remove(this->queries
, query
);
225 query
->done
->broadcast(query
->done
);
226 this->mutex
->unlock(this->mutex
);
227 query_destroy(query
);
232 METHOD(host_resolver_t
, resolve
, host_t
*,
233 private_host_resolver_t
*this, char *name
, int family
)
235 query_t
*query
, lookup
= {
245 /* do not try to convert v6 addresses for v4 family */
246 if (strchr(name
, ':'))
252 /* do not try to convert v4 addresses for v6 family */
253 if (inet_pton(AF_INET
, name
, &addr
) == 1)
259 this->mutex
->lock(this->mutex
);
262 this->mutex
->unlock(this->mutex
);
265 query
= this->queries
->get(this->queries
, &lookup
);
269 .name
= strdup(name
),
271 .done
= condvar_create(CONDVAR_TYPE_DEFAULT
),
274 this->queries
->put(this->queries
, query
, query
);
275 this->queue
->insert_last(this->queue
, query
);
276 this->new_query
->signal(this->new_query
);
278 ref_get(&query
->refcount
);
279 if (this->busy_threads
== this->threads
&&
280 this->threads
< this->max_threads
)
284 thread
= thread_create((thread_main_t
)resolve_hosts
, this);
288 this->pool
->insert_last(this->pool
, thread
);
293 query
->done
->wait(query
->done
, this->mutex
);
297 DBG1(DBG_LIB
, "resolving '%s' failed: no resolver threads", query
->name
);
298 /* this should always be the case if we end up here, but make sure */
299 if (query
->refcount
== 1)
301 this->queries
->remove(this->queries
, query
);
302 this->queue
->remove_last(this->queue
, (void**)&query
);
305 this->mutex
->unlock(this->mutex
);
307 result
= query
->result
? query
->result
->clone(query
->result
) : NULL
;
308 query_destroy(query
);
312 METHOD(host_resolver_t
, flush
, void,
313 private_host_resolver_t
*this)
315 enumerator_t
*enumerator
;
318 this->mutex
->lock(this->mutex
);
319 enumerator
= this->queries
->create_enumerator(this->queries
);
320 while (enumerator
->enumerate(enumerator
, &query
, NULL
))
321 { /* use the hashtable here as we also want to signal dequeued queries */
322 this->queries
->remove_at(this->queries
, enumerator
);
323 query
->done
->broadcast(query
->done
);
325 enumerator
->destroy(enumerator
);
326 this->queue
->destroy_function(this->queue
, (void*)query_destroy
);
327 this->queue
= linked_list_create();
328 this->disabled
= TRUE
;
329 /* this will already terminate most idle threads */
330 this->new_query
->broadcast(this->new_query
);
331 this->mutex
->unlock(this->mutex
);
334 METHOD(host_resolver_t
, destroy
, void,
335 private_host_resolver_t
*this)
340 this->pool
->invoke_offset(this->pool
, offsetof(thread_t
, cancel
));
341 while (this->pool
->remove_first(this->pool
, (void**)&thread
) == SUCCESS
)
343 thread
->join(thread
);
345 this->pool
->destroy(this->pool
);
346 this->queue
->destroy(this->queue
);
347 this->queries
->destroy(this->queries
);
348 this->new_query
->destroy(this->new_query
);
349 this->mutex
->destroy(this->mutex
);
354 * Described in header
356 host_resolver_t
*host_resolver_create()
358 private_host_resolver_t
*this;
366 .queries
= hashtable_create((hashtable_hash_t
)query_hash
,
367 (hashtable_equals_t
)query_equals
, 8),
368 .queue
= linked_list_create(),
369 .pool
= linked_list_create(),
370 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
371 .new_query
= condvar_create(CONDVAR_TYPE_DEFAULT
),
374 this->min_threads
= max(0, lib
->settings
->get_int(lib
->settings
,
375 "%s.host_resolver.min_threads",
376 MIN_THREADS_DEFAULT
, lib
->ns
));
377 this->max_threads
= max(this->min_threads
?: 1,
378 lib
->settings
->get_int(lib
->settings
,
379 "%s.host_resolver.max_threads",
380 MAX_THREADS_DEFAULT
, lib
->ns
));
381 return &this->public;