1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-dns.c: Async DNS code
5 * Copyright (C) 2000-2003, Ximian, Inc.
18 #include <sys/types.h>
21 #include "soup-misc.h"
22 #include "soup-status.h"
24 #ifndef INET_ADDRSTRLEN
25 # define INET_ADDRSTRLEN 16
26 # define INET6_ADDRSTRLEN 46
30 #define INADDR_NONE -1
34 #define SOUP_DNS_SOCKADDR_LEN(sa) \
35 (sa->sa_family == AF_INET ? sizeof (struct sockaddr_in) : \
36 sizeof (struct sockaddr_in6))
38 #define SOUP_DNS_SOCKADDR_LEN(sa) sizeof (struct sockaddr_in)
44 inet_pton(int af, const char* src, void* dst)
47 struct sockaddr_storage sa;
48 struct sockaddr_in *sin = (struct sockaddr_in *)&sa;
49 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sa;
53 address_length = sizeof (struct sockaddr_in);
57 address_length = sizeof (struct sockaddr_in6);
64 if (WSAStringToAddress ((LPTSTR) src, af, NULL, (LPSOCKADDR) &sa, &address_length) == 0) {
67 memcpy (dst, &sin->sin_addr, sizeof (struct in_addr));
71 memcpy (dst, &sin6->sin6_addr, sizeof (struct in6_addr));
81 inet_ntop(int af, const void* src, char* dst, size_t size)
84 DWORD string_length = size;
85 struct sockaddr_storage sa;
86 struct sockaddr_in *sin = (struct sockaddr_in *)&sa;
87 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sa;
89 memset (&sa, 0, sizeof (sa));
92 address_length = sizeof (struct sockaddr_in);
94 memcpy (&sin->sin_addr, src, sizeof (struct in_addr));
98 address_length = sizeof (struct sockaddr_in6);
99 sin6->sin6_family = af;
100 memcpy (&sin6->sin6_addr, src, sizeof (struct in6_addr));
107 if (WSAAddressToString ((LPSOCKADDR) &sa, address_length, NULL,
108 dst, &string_length) == 0)
122 struct sockaddr *sockaddr;
125 GThread *resolver_thread;
126 GSList *async_lookups;
129 static GHashTable *soup_dns_cache;
130 #define SOUP_DNS_CACHE_MAX 20
132 struct SoupDNSLookup {
133 SoupDNSCacheEntry *entry;
135 GMainContext *async_context;
136 GCancellable *cancellable;
137 SoupDNSCallback callback;
141 static GMutex *soup_dns_lock;
142 static GCond *soup_dns_cond;
144 #if !defined (HAVE_GETADDRINFO) || !defined (HAVE_GETNAMEINFO)
145 static GMutex *soup_gethost_lock;
151 * Initializes the libsoup DNS system. Must be called before any other
152 * soup_dns method is called. Normally this gets called automatically
153 * by #SoupAddress's class_init function.
158 static volatile gsize inited_dns = 0;
160 if (g_once_init_enter (&inited_dns)) {
161 soup_dns_cache = g_hash_table_new (soup_str_case_hash, soup_str_case_equal);
162 soup_dns_lock = g_mutex_new ();
163 soup_dns_cond = g_cond_new ();
164 #if !defined (HAVE_GETADDRINFO) || !defined (HAVE_GETNAMEINFO)
165 soup_gethost_lock = g_mutex_new ();
168 g_once_init_leave (&inited_dns, TRUE);
173 prune_cache_cb (gpointer key, gpointer value, gpointer data)
175 SoupDNSCacheEntry *entry = value, **prune_entry = data;
177 if (!*prune_entry || (*prune_entry)->expires > entry->expires)
178 *prune_entry = entry;
182 soup_dns_cache_entry_set_from_phys (SoupDNSCacheEntry *entry)
184 struct sockaddr_in sin;
186 struct sockaddr_in6 sin6;
190 memset (&sin6, 0, sizeof (struct sockaddr_in6));
191 if (inet_pton (AF_INET6, entry->entry_name, &sin6.sin6_addr) != 0) {
192 entry->sockaddr = g_memdup (&sin6, sizeof (struct sockaddr_in6));
193 entry->sockaddr->sa_family = AF_INET6;
196 #endif /* HAVE_IPV6 */
198 memset (&sin, 0, sizeof (struct sockaddr_in));
200 #if defined(HAVE_INET_PTON)
201 inet_pton (AF_INET, entry->entry_name, &sin.sin_addr) != 0
202 #elif defined(HAVE_INET_ATON)
203 inet_aton (entry->entry_name, &sin.sin_addr) != 0
205 (sin.sin_addr.s_addr = inet_addr (entry->entry_name)) &&
206 (sin.sin_addr.s_addr != INADDR_NONE)
209 entry->sockaddr = g_memdup (&sin, sizeof (struct sockaddr_in));
210 entry->sockaddr->sa_family = AF_INET;
216 soup_dns_cache_entry_ref (SoupDNSCacheEntry *entry)
222 soup_dns_cache_entry_unref (SoupDNSCacheEntry *entry)
224 if (--entry->ref_count == 0) {
225 g_free (entry->entry_name);
226 g_free (entry->hostname);
227 g_free (entry->sockaddr);
229 /* If there were lookups pending, ref_count couldn't
230 * have reached zero. So no cleanup needed there.
233 g_slice_free (SoupDNSCacheEntry, entry);
237 static SoupDNSCacheEntry *
238 soup_dns_cache_entry_new (const char *name)
240 SoupDNSCacheEntry *entry;
242 entry = g_slice_new0 (SoupDNSCacheEntry);
243 entry->entry_name = g_strdup (name);
244 entry->ref_count = 2; /* One for the caller, one for the cache */
245 soup_dns_cache_entry_set_from_phys (entry);
247 if (g_hash_table_size (soup_dns_cache) == SOUP_DNS_CACHE_MAX) {
248 SoupDNSCacheEntry *prune_entry = NULL;
250 g_hash_table_foreach (soup_dns_cache, prune_cache_cb, &prune_entry);
252 g_hash_table_remove (soup_dns_cache, prune_entry->entry_name);
253 soup_dns_cache_entry_unref (prune_entry);
257 entry->expires = time (0) + 60 * 60;
258 g_hash_table_insert (soup_dns_cache, entry->entry_name, entry);
265 * @sa: pointer to a #sockaddr
267 * Converts @sa's address into textual form (eg, "141.213.8.59"), like
268 * the standard library function inet_ntop(), except that the returned
269 * string must be freed.
271 * Return value: the text form or @sa, which must be freed.
274 soup_dns_ntop (struct sockaddr *sa)
276 switch (sa->sa_family) {
279 struct sockaddr_in *sin = (struct sockaddr_in *)sa;
280 #ifdef HAVE_INET_NTOP
281 char buffer[INET_ADDRSTRLEN];
283 inet_ntop (AF_INET, &sin->sin_addr, buffer, sizeof (buffer));
284 return g_strdup (buffer);
286 return g_strdup (inet_ntoa (sin->sin_addr));
293 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
294 char buffer[INET6_ADDRSTRLEN];
296 inet_ntop (AF_INET6, &sin6->sin6_addr, buffer, sizeof (buffer));
297 return g_strdup (buffer);
307 soup_dns_is_ip_address (const char *name)
309 struct sockaddr_in sin;
311 struct sockaddr_in6 sin6;
313 if (inet_pton (AF_INET, name, &sin.sin_addr) > 0 ||
314 inet_pton (AF_INET6, name, &sin6.sin6_addr) > 0)
316 #else /* !HAVE_IPV6 */
317 #if defined(HAVE_INET_PTON)
318 if (inet_pton (AF_INET, name, &sin.sin_addr) > 0)
320 #elif defined(HAVE_INET_ATON)
321 if (inet_aton (name, &sin.sin_addr) != 0)
324 if (inet_addr (name) != INADDR_NONE)
327 #endif /* HAVE_IPV6 */
332 resolve_address (SoupDNSCacheEntry *entry)
334 #if defined (HAVE_GETADDRINFO)
336 struct addrinfo hints, *res;
339 memset (&hints, 0, sizeof (struct addrinfo));
340 # ifdef AI_ADDRCONFIG
341 hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
343 hints.ai_flags = AI_CANONNAME;
345 hints.ai_family = PF_UNSPEC;
346 hints.ai_socktype = SOCK_STREAM;
347 hints.ai_protocol = IPPROTO_TCP;
349 retval = getaddrinfo (entry->hostname, NULL, &hints, &res);
351 entry->sockaddr = g_memdup (res->ai_addr, res->ai_addrlen);
352 entry->resolved = TRUE;
356 #else /* !HAVE_GETADDRINFO */
360 g_mutex_lock (soup_gethost_lock);
362 h = gethostbyname (entry->hostname);
363 if (h && h->h_addrtype == AF_INET) {
364 struct sockaddr_in sin;
365 memset (&sin, 0, sizeof (struct sockaddr_in));
366 sin.sin_family = AF_INET;
367 memcpy (&sin.sin_addr, h->h_addr_list[0], sizeof (struct in_addr));
368 entry->sockaddr = g_memdup (&sin, sizeof (struct sockaddr_in));
369 entry->resolved = TRUE;
372 g_mutex_unlock (soup_gethost_lock);
378 resolve_name (SoupDNSCacheEntry *entry)
380 #ifdef HAVE_GETNAMEINFO
386 name = g_realloc (name, len);
387 retval = getnameinfo (entry->sockaddr, SOUP_DNS_SOCKADDR_LEN (entry->sockaddr),
388 name, len, NULL, 0, NI_NAMEREQD);
391 retval == EAI_OVERFLOW
393 strlen (name) == len - 1
398 entry->hostname = name;
399 entry->resolved = TRUE;
403 #else /* !HAVE_GETNAMEINFO */
405 struct sockaddr_in *sin = (struct sockaddr_in *)entry->sockaddr;
408 g_mutex_lock (soup_gethost_lock);
410 if (sin->sin_family == AF_INET) {
411 h = gethostbyaddr (&sin->sin_addr, sizeof (sin->sin_addr), AF_INET);
413 entry->hostname = g_strdup (h->h_name);
414 entry->resolved = TRUE;
418 g_mutex_unlock (soup_gethost_lock);
420 #endif /* HAVE_GETNAMEINFO */
423 /* Assumes soup_dns_lock is held */
424 static SoupDNSCacheEntry *
425 soup_dns_cache_entry_lookup (const char *name)
427 SoupDNSCacheEntry *entry;
429 entry = g_hash_table_lookup (soup_dns_cache, name);
431 soup_dns_cache_entry_ref (entry);
436 * soup_dns_lookup_name:
437 * @name: a hostname (eg, "www.gnome.org") or physical address
438 * (eg, "12.107.209.247").
440 * Creates a #SoupDNSLookup for @name. This should be passed to
441 * soup_dns_lookup_resolve() or soup_dns_lookup_resolve_async().
443 * Returns: a #SoupDNSLookup, which should eventually be freed with
444 * soup_dns_lookup_free().
447 soup_dns_lookup_name (const char *name)
449 SoupDNSCacheEntry *entry;
450 SoupDNSLookup *lookup;
452 g_mutex_lock (soup_dns_lock);
454 entry = soup_dns_cache_entry_lookup (name);
456 entry = soup_dns_cache_entry_new (name);
457 entry->hostname = g_strdup (name);
459 entry->resolved = TRUE;
462 lookup = g_slice_new0 (SoupDNSLookup);
463 lookup->entry = entry;
464 g_mutex_unlock (soup_dns_lock);
470 * soup_dns_lookup_address:
471 * @sockaddr: pointer to a #sockaddr
473 * Creates a #SoupDNSLookup for @sockaddr. This should be passed to
474 * soup_dns_lookup_resolve() or soup_dns_lookup_resolve_async().
476 * Returns: a #SoupDNSLookup, which should eventually be freed with
477 * soup_dns_lookup_free()
480 soup_dns_lookup_address (struct sockaddr *sockaddr)
482 SoupDNSCacheEntry *entry;
483 SoupDNSLookup *lookup;
486 name = soup_dns_ntop (sockaddr);
487 g_return_val_if_fail (name != NULL, NULL);
489 g_mutex_lock (soup_dns_lock);
491 entry = soup_dns_cache_entry_lookup (name);
493 entry = soup_dns_cache_entry_new (name); /* FIXME */
496 lookup = g_slice_new0 (SoupDNSLookup);
497 lookup->entry = entry;
498 g_mutex_unlock (soup_dns_lock);
504 resolve_status (SoupDNSCacheEntry *entry, GCancellable *cancellable)
506 if (entry->hostname && entry->sockaddr)
507 return SOUP_STATUS_OK;
508 else if (g_cancellable_is_cancelled (cancellable))
509 return SOUP_STATUS_CANCELLED;
511 return SOUP_STATUS_CANT_RESOLVE;
514 static void async_cancel (GCancellable *cancellable, gpointer user_data);
517 do_async_callback (gpointer user_data)
519 SoupDNSLookup *lookup = user_data;
520 SoupDNSCacheEntry *entry = lookup->entry;
521 GCancellable *cancellable = lookup->cancellable;
524 g_signal_handlers_disconnect_by_func (cancellable, async_cancel, lookup);
525 lookup->callback (lookup, resolve_status (entry, cancellable),
532 resolver_thread (gpointer user_data)
534 SoupDNSCacheEntry *entry = user_data;
535 GSList *async_lookups;
536 SoupDNSLookup *lookup;
538 if (entry->hostname == NULL)
539 resolve_name (entry);
540 else if (entry->sockaddr == NULL)
541 resolve_address (entry);
543 g_mutex_lock (soup_dns_lock);
544 entry->resolver_thread = NULL;
546 async_lookups = entry->async_lookups;
547 entry->async_lookups = NULL;
548 g_mutex_unlock (soup_dns_lock);
550 g_cond_broadcast (soup_dns_cond);
552 while (async_lookups) {
553 lookup = async_lookups->data;
554 async_lookups = g_slist_remove (async_lookups, lookup);
556 soup_add_completion (lookup->async_context, do_async_callback, lookup);
559 soup_dns_cache_entry_unref (entry);
564 sync_cancel (GCancellable *cancellable, gpointer user_data)
566 /* We can't actually cancel the resolver thread. So we just
567 * wake up the blocking thread, which will see that
568 * @cancellable has been cancelled and then stop waiting for
569 * the result. If the resolver thread eventually finishes,
570 * its result will make it to the cache.
572 g_cond_broadcast (soup_dns_cond);
576 * soup_dns_lookup_resolve:
577 * @lookup: a #SoupDNSLookup
578 * @cancellable: a #GCancellable, or %NULL
580 * Synchronously resolves @lookup.
582 * Return value: %SOUP_STATUS_OK, %SOUP_STATUS_CANT_RESOLVE, or
583 * %SOUP_STATUS_CANCELLED
586 soup_dns_lookup_resolve (SoupDNSLookup *lookup, GCancellable *cancellable)
588 SoupDNSCacheEntry *entry = lookup->entry;
591 g_mutex_lock (soup_dns_lock);
593 if (!entry->resolved) {
594 if (!entry->resolver_thread) {
595 soup_dns_cache_entry_ref (entry);
596 entry->resolver_thread =
597 g_thread_create (resolver_thread, entry,
602 cancel_id = g_signal_connect (cancellable, "cancelled",
603 G_CALLBACK (sync_cancel),
608 while (entry->resolver_thread &&
609 !g_cancellable_is_cancelled (cancellable))
610 g_cond_wait (soup_dns_cond, soup_dns_lock);
613 g_signal_handler_disconnect (cancellable, cancel_id);
615 g_mutex_unlock (soup_dns_lock);
617 return resolve_status (entry, cancellable);
621 async_cancel (GCancellable *cancellable, gpointer user_data)
623 SoupDNSLookup *lookup = user_data;
624 SoupDNSCacheEntry *entry = lookup->entry;
626 /* We can't actually cancel the resolver thread. So we just
627 * remove @lookup from the list of pending async lookups and
628 * invoke its callback now. If the resolver thread eventually
629 * finishes, its result will make it to the cache.
631 g_mutex_lock (soup_dns_lock);
633 if (g_slist_find (entry->async_lookups, lookup)) {
634 entry->async_lookups = g_slist_remove (entry->async_lookups,
636 soup_add_completion (lookup->async_context, do_async_callback, lookup);
639 g_mutex_unlock (soup_dns_lock);
643 * soup_dns_lookup_resolve_async:
644 * @lookup: a #SoupDNSLookup
645 * @async_context: #GMainContext to call @callback in
646 * @cancellable: a #GCancellable, or %NULL
647 * @callback: callback to call when @lookup is resolved
648 * @user_data: data to pass to @callback;
650 * Tries to asynchronously resolve @lookup. Invokes @callback when it
651 * has succeeded or failed.
654 soup_dns_lookup_resolve_async (SoupDNSLookup *lookup,
655 GMainContext *async_context,
656 GCancellable *cancellable,
657 SoupDNSCallback callback, gpointer user_data)
659 SoupDNSCacheEntry *entry = lookup->entry;
661 g_mutex_lock (soup_dns_lock);
663 lookup->async_context = async_context;
664 lookup->cancellable = cancellable;
665 lookup->callback = callback;
666 lookup->user_data = user_data;
668 if (!entry->resolved) {
669 entry->async_lookups = g_slist_prepend (entry->async_lookups,
672 g_signal_connect (cancellable, "cancelled",
673 G_CALLBACK (async_cancel), lookup);
676 if (!entry->resolver_thread) {
677 soup_dns_cache_entry_ref (entry);
678 entry->resolver_thread =
679 g_thread_create (resolver_thread, entry,
683 soup_add_completion (lookup->async_context, do_async_callback, lookup);
685 g_mutex_unlock (soup_dns_lock);
689 * soup_dns_lookup_get_hostname:
690 * @lookup: a #SoupDNSLookup
692 * Gets the hostname of @lookup.
694 * Return value: the hostname, which the caller owns and must free, or
695 * %NULL if @lookup has not been completely resolved.
698 soup_dns_lookup_get_hostname (SoupDNSLookup *lookup)
700 return g_strdup (lookup->entry->hostname);
704 * soup_dns_lookup_get_address:
705 * @lookup: a #SoupDNSLookup
707 * Gets the address of @lookup.
709 * Return value: the address, which the caller owns and must free, or
710 * %NULL if @lookup has not been completely resolved.
713 soup_dns_lookup_get_address (SoupDNSLookup *lookup)
715 return g_memdup (lookup->entry->sockaddr,
716 SOUP_DNS_SOCKADDR_LEN (lookup->entry->sockaddr));
720 * soup_dns_lookup_free:
721 * @lookup: a #SoupDNSLookup
723 * Frees @lookup. It is an error to cancel a lookup while it is
727 soup_dns_lookup_free (SoupDNSLookup *lookup)
729 soup_dns_cache_entry_unref (lookup->entry);
730 g_slice_free (SoupDNSLookup, lookup);