From: Dan Winship Date: Tue, 12 Apr 2005 19:18:46 +0000 (+0000) Subject: Remove the various gethostbyname_r checks and just check for X-Git-Tag: LIBSOUP_2_2_5~14 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=7ef7034cde21f3db6bbbf9218072b6d2e648d6c1;p=platform%2Fupstream%2Flibsoup.git Remove the various gethostbyname_r checks and just check for * configure.in: Remove the various gethostbyname_r checks and just check for getnameinfo/getaddrinfo. * libsoup/soup-dns.c: de-nastify. Make this use threads instead of forking. Change the API around a bunch in the process. * libsoup/soup-address.c: Update for soup-dns changes * tests/dns.c: take multiple hostnames on the command line and resolve them all at once (patch from tml) --- diff --git a/ChangeLog b/ChangeLog index 575fc58..845984c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2005-04-12 Dan Winship + + * configure.in: Remove the various gethostbyname_r checks and just + check for getnameinfo/getaddrinfo. + + * libsoup/soup-dns.c: de-nastify. Make this use threads instead of + forking. Change the API around a bunch in the process. + + * libsoup/soup-address.c: Update for soup-dns changes + + * tests/dns.c: take multiple hostnames on the command line and + resolve them all at once (patch from tml) + 2005-04-11 Dan Winship * configure.in: require glib-2.0 >= 2.4.0 diff --git a/configure.in b/configure.in index ac03ab8..ffb5198 100644 --- a/configure.in +++ b/configure.in @@ -85,88 +85,7 @@ dnl ********************************* AC_CHECK_FUNC(socket, , AC_CHECK_LIB(socket, socket)) AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, gethostbyname)) -AC_CHECK_FUNCS(inet_pton inet_aton) - -### Check if we have gethostbyname_r (if so, assume gethostbyaddr_r). -AC_CHECK_FUNC(gethostbyname_r, - [ - dnl First check for the glibc variant of gethostbyname_r - - AC_MSG_CHECKING(for glibc gethostbyname_r) - AC_TRY_LINK([ #include ],[ - struct hostent result_buf; - char buf[1024]; - struct hostent* result; - int h_errnop; - - gethostbyname_r("localhost", &result_buf, buf, sizeof(buf), - &result, &h_errnop); - ], [ - - dnl Have glibc gethostbyname_r - - AC_MSG_RESULT(yes) - AC_DEFINE(HAVE_GETHOSTBYNAME_R_GLIBC, 1, - [Define if you have a glibc-style gethostbyname_r()]) - HAVE_GETHOSTBYNAME_R=yes - - ], [ - - dnl If we don't have glibc gethostbyname_r, check - dnl for Solaris/Irix gethostbyname_r - - AC_MSG_RESULT(no) - AC_MSG_CHECKING(for Solaris/Irix gethostbyname_r) - AC_TRY_LINK([ #include ],[ - struct hostent result; - char buf[1024]; - int h_errnop; - - gethostbyname_r("localhost", &result, buf, sizeof(buf), &h_errnop); - - ], [ - - dnl Have Solaris/Irix gethostbyname_r - - AC_MSG_RESULT(yes) - AC_DEFINE(HAVE_GETHOSTBYNAME_R_SOLARIS, 1, - [Define if you have a Solaris-style gethostbyname_r()]) - HAVE_GETHOSTBYNAME_R=yes - - ], [ - dnl If don't have Solaris/Irix gethostbyname_r, check - dnl for HP-UX gethostbyname_r - - AC_MSG_RESULT(no) - AC_MSG_CHECKING(for HP-UX gethostbyname_r) - AC_TRY_LINK([ #include ],[ - struct hostent result; - char buf[1024]; - gethostbyname_r("localhost", &result, buf); - ], [ - - dnl Have HP-UX gethostbyname_r - - AC_MSG_RESULT(yes) - AC_DEFINE(HAVE_GETHOSTBYNAME_R_HPUX, 1, - [Define if you have an HP-UX-style gethostbyname_r()]) - HAVE_GETHOSTBYNAME_R=yes - - ] - )] - )] -)]) - -# If we don't have gethostbyname_r, we'll use Glib mutexes, but give a warning -if test -z "$HAVE_GETHOSTBYNAME_R"; then - AC_DEFINE(HAVE_GETHOSTBYNAME_R_GLIB_MUTEX, 1, - [Define if you have no gethostbyname_r()]) - AC_MSG_WARN([You have neither Glib threads nor the function - gethostbyname_r. This means that calls to - gethostbyname (called by the Soup address - functions) will not be thread safe so could - malfunction in programs that use threads.]) -fi +AC_CHECK_FUNCS(inet_pton inet_aton getaddrinfo getnameinfo) AC_CACHE_CHECK(IPv6 support, soup_cv_ipv6, [ AC_EGREP_HEADER(sockaddr_in6, netinet/in.h, soup_cv_ipv6=yes, soup_cv_ipv6=no) diff --git a/libsoup/soup-address.c b/libsoup/soup-address.c index 4d00678..cd61df5 100644 --- a/libsoup/soup-address.c +++ b/libsoup/soup-address.c @@ -42,7 +42,7 @@ typedef struct { char *name, *physical; guint port; - SoupDNSEntry *lookup; + SoupDNSLookup *lookup; guint timeout_id; } SoupAddressPrivate; #define SOUP_ADDRESS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_ADDRESS, SoupAddressPrivate)) @@ -132,7 +132,7 @@ finalize (GObject *object) g_free (priv->physical); if (priv->lookup) - soup_dns_entry_cancel_lookup (priv->lookup); + soup_dns_lookup_free (priv->lookup); if (priv->timeout_id) g_source_remove (priv->timeout_id); @@ -144,6 +144,8 @@ soup_address_class_init (SoupAddressClass *address_class) { GObjectClass *object_class = G_OBJECT_CLASS (address_class); + soup_dns_init (); + g_type_class_add_private (address_class, sizeof (SoupAddressPrivate)); /* virtual method override */ @@ -187,6 +189,7 @@ soup_address_new (const char *name, guint port) priv = SOUP_ADDRESS_GET_PRIVATE (addr); priv->name = g_strdup (name); priv->port = port; + priv->lookup = soup_dns_lookup_name (priv->name); return addr; } @@ -215,6 +218,8 @@ soup_address_new_from_sockaddr (struct sockaddr *sa, int len) priv = SOUP_ADDRESS_GET_PRIVATE (addr); priv->sockaddr = g_memdup (sa, len); priv->port = ntohs (SOUP_ADDRESS_GET_PORT (priv)); + priv->lookup = soup_dns_lookup_address (priv->sockaddr); + return addr; } @@ -245,6 +250,7 @@ soup_address_new_any (SoupAddressFamily family, guint port) priv->sockaddr = g_malloc0 (SOUP_ADDRESS_FAMILY_SOCKADDR_SIZE (family)); SOUP_ADDRESS_SET_FAMILY (priv, family); SOUP_ADDRESS_SET_PORT (priv, htons (port)); + priv->lookup = soup_dns_lookup_address (priv->sockaddr); return addr; } @@ -309,11 +315,8 @@ soup_address_get_physical (SoupAddress *addr) if (!priv->sockaddr) return NULL; - if (!priv->physical) { - priv->physical = - soup_dns_ntop (SOUP_ADDRESS_GET_DATA (priv), - SOUP_ADDRESS_GET_FAMILY (priv)); - } + if (!priv->physical) + priv->physical = soup_dns_ntop (priv->sockaddr); return priv->physical; } @@ -335,58 +338,23 @@ soup_address_get_port (SoupAddress *addr) } -static guint -update_address_from_entry (SoupAddress *addr, SoupDNSEntry *entry) -{ - SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr); - struct hostent *h; - - h = soup_dns_entry_get_hostent (entry); - if (!h) - return SOUP_STATUS_CANT_RESOLVE; - - if (!priv->name) - priv->name = g_strdup (h->h_name); - - if (!priv->sockaddr && - SOUP_ADDRESS_FAMILY_IS_VALID (h->h_addrtype) && - SOUP_ADDRESS_FAMILY_DATA_SIZE (h->h_addrtype) == h->h_length) { - priv->sockaddr = g_malloc0 (SOUP_ADDRESS_FAMILY_SOCKADDR_SIZE (h->h_addrtype)); - SOUP_ADDRESS_SET_FAMILY (priv, h->h_addrtype); - SOUP_ADDRESS_SET_PORT (priv, htons (priv->port)); - SOUP_ADDRESS_SET_DATA (priv, h->h_addr, h->h_length); - } - - soup_dns_free_hostent (h); - - if (priv->name && priv->sockaddr) - return SOUP_STATUS_OK; - else - return SOUP_STATUS_CANT_RESOLVE; -} - -static gboolean -timeout_check_lookup (gpointer user_data) +static void +update_address (SoupDNSLookup *lookup, gboolean success, gpointer user_data) { SoupAddress *addr = user_data; SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr); - guint status; - if (priv->name && priv->sockaddr) { - priv->timeout_id = 0; - g_signal_emit (addr, signals[DNS_RESULT], 0, SOUP_STATUS_OK); - return FALSE; - } - - if (!soup_dns_entry_check_lookup (priv->lookup)) - return TRUE; + if (success) { + if (!priv->name) + priv->name = soup_dns_lookup_get_hostname (lookup); - status = update_address_from_entry (addr, priv->lookup); - priv->lookup = NULL; - priv->timeout_id = 0; + if (!priv->sockaddr) { + priv->sockaddr = soup_dns_lookup_get_address (lookup); + SOUP_ADDRESS_SET_PORT (priv, htons (priv->port)); + } + } - g_signal_emit (addr, signals[DNS_RESULT], 0, status); - return FALSE; + g_signal_emit (addr, signals[DNS_RESULT], 0, success ? SOUP_STATUS_OK : SOUP_STATUS_CANT_RESOLVE); } /** @@ -416,17 +384,7 @@ soup_address_resolve_async (SoupAddress *addr, G_CALLBACK (callback), user_data); } - if (priv->timeout_id) - return; - - if (!priv->sockaddr) { - priv->lookup = soup_dns_entry_from_name (priv->name); - } else if (!priv->name) { - priv->lookup = soup_dns_entry_from_addr (SOUP_ADDRESS_GET_DATA (priv), - SOUP_ADDRESS_GET_FAMILY (priv)); - } - - priv->timeout_id = g_timeout_add (100, timeout_check_lookup, addr); + soup_dns_lookup_resolve_async (priv->lookup, update_address, addr); } /** @@ -441,18 +399,13 @@ soup_address_resolve_async (SoupAddress *addr, guint soup_address_resolve_sync (SoupAddress *addr) { - SoupDNSEntry *entry; SoupAddressPrivate *priv; + gboolean success; g_return_val_if_fail (SOUP_IS_ADDRESS (addr), SOUP_STATUS_MALFORMED); priv = SOUP_ADDRESS_GET_PRIVATE (addr); - if (priv->name) - entry = soup_dns_entry_from_name (priv->name); - else { - entry = soup_dns_entry_from_addr (SOUP_ADDRESS_GET_DATA (priv), - SOUP_ADDRESS_GET_FAMILY (priv)); - } - - return update_address_from_entry (addr, entry); + success = soup_dns_lookup_resolve (priv->lookup); + update_address (priv->lookup, success, addr); + return success ? SOUP_STATUS_OK : SOUP_STATUS_CANT_RESOLVE; } diff --git a/libsoup/soup-dns.c b/libsoup/soup-dns.c index 050327d..3a98c0f 100644 --- a/libsoup/soup-dns.c +++ b/libsoup/soup-dns.c @@ -35,130 +35,183 @@ #define INADDR_NONE -1 #endif -static struct hostent * -new_hostent (const char *name, int type, int length, gpointer addr) -{ - struct hostent *h; +#ifdef HAVE_IPV6 +#define SOUP_DNS_SOCKADDR_LEN(sa) \ + (sa->sa_family == AF_INET ? sizeof (struct sockaddr_in) : \ + sizeof (struct sockaddr_in6)) +#else +#define SOUP_DNS_SOCKADDR_LEN(sa) sizeof (struct sockaddr_in) +#endif - h = g_new0 (struct hostent, 1); - h->h_name = g_strdup (name); - h->h_aliases = NULL; - h->h_addrtype = type; - h->h_length = length; - h->h_addr_list = g_new (char *, 2); - h->h_addr_list[0] = g_memdup (addr, length); - h->h_addr_list[1] = NULL; +typedef struct { + char *entry_name; + guint ref_count; + time_t expires; - return h; -} + char *hostname; + struct sockaddr *sockaddr; -static struct hostent * -copy_hostent (struct hostent *h) -{ - return new_hostent (h->h_name, h->h_addrtype, - h->h_length, h->h_addr_list[0]); -} + gboolean resolved; + GThread *resolver_thread; + GSList *lookups; +} SoupDNSCacheEntry; + +static GHashTable *soup_dns_cache; +#define SOUP_DNS_CACHE_MAX 20 + +struct SoupDNSLookup { + SoupDNSCacheEntry *entry; + + SoupDNSCallback callback; + gpointer user_data; + gboolean running; +}; + +static GMutex *soup_dns_lock; +static GCond *soup_dns_cond; + +#if !defined (HAVE_GETADDRINFO) || !defined (HAVE_GETNAMEINFO) +static GMutex *soup_gethost_lock; +#endif -/** - * soup_dns_free_hostent: - * @h: a #hostent - * - * Frees @h. Use this to free the return value from - * soup_dns_entry_get_hostent(). - **/ void -soup_dns_free_hostent (struct hostent *h) +soup_dns_init (void) { - g_free (h->h_name); - g_free (h->h_addr_list[0]); - g_free (h->h_addr_list); - g_free (h); + if (soup_dns_cache == NULL) { + soup_dns_cache = g_hash_table_new (soup_str_case_hash, soup_str_case_equal); + soup_dns_lock = g_mutex_new (); + soup_dns_cond = g_cond_new (); +#if !defined (HAVE_GETADDRINFO) || !defined (HAVE_GETNAMEINFO) + soup_gethost_lock = g_mutex_new (); +#endif + } } static void -write_hostent (struct hostent *h, int fd) +prune_cache_cb (gpointer key, gpointer value, gpointer data) { - guchar namelen = strlen (h->h_name) + 1; - guchar addrlen = h->h_length; - guchar addrtype = h->h_addrtype; - struct iovec iov[5]; - - iov[0].iov_base = &namelen; - iov[0].iov_len = 1; - iov[1].iov_base = h->h_name; - iov[1].iov_len = namelen; - iov[2].iov_base = &addrtype; - iov[2].iov_len = 1; - iov[3].iov_base = &addrlen; - iov[3].iov_len = 1; - iov[4].iov_base = h->h_addr_list[0]; - iov[4].iov_len = addrlen; - - if (writev (fd, iov, 5) == -1) - g_warning ("Problem writing to pipe"); + SoupDNSCacheEntry *entry = value, **prune_entry = data; + + if (!*prune_entry || (*prune_entry)->expires > entry->expires) + *prune_entry = entry; } -static struct hostent * -new_hostent_from_phys (const char *addr) +static void +soup_dns_cache_entry_set_from_phys (SoupDNSCacheEntry *entry) { - struct in_addr inaddr; + struct sockaddr_in sin; #ifdef HAVE_IPV6 - struct in6_addr inaddr6; + struct sockaddr_in6 sin6; #endif -#if defined(HAVE_INET_PTON) #ifdef HAVE_IPV6 - if (inet_pton (AF_INET6, addr, &inaddr6) != 0) - return new_hostent (addr, AF_INET6, sizeof (inaddr6), &inaddr6); - else -#endif - if (inet_pton (AF_INET, addr, &inaddr) != 0) - return new_hostent (addr, AF_INET, sizeof (inaddr), &inaddr); + memset (&sin6, 0, sizeof (struct sockaddr_in6)); + if (inet_pton (AF_INET6, entry->entry_name, &sin6.sin6_addr) != 0) { + entry->sockaddr = g_memdup (&sin6, sizeof (struct sockaddr_in6)); + entry->sockaddr->sa_family = AF_INET6; + return; + } +#endif /* HAVE_IPV6 */ + + memset (&sin, 0, sizeof (struct sockaddr_in)); + if ( +#if defined(HAVE_INET_PTON) + inet_pton (AF_INET, entry->entry_name, &sin.sin_addr) != 0 #elif defined(HAVE_INET_ATON) - if (inet_aton (addr, &inaddr) != 0) - return new_hostent (addr, AF_INET, sizeof (inaddr), &inaddr); + inet_aton (entry->entry_name, &sin.sin_addr) != 0 #else - inaddr.s_addr = inet_addr (addr); - if (inaddr.s_addr != INADDR_NONE) - return new_hostent (addr, AF_INET, sizeof (inaddr), &inaddr); + (sin.sin_addr.s_addr = inet_addr (entry->entry_name)) && + (sin.sin_addr.s_addr != INADDR_NONE) #endif + ) { + entry->sockaddr = g_memdup (&sin, sizeof (struct sockaddr_in)); + entry->sockaddr->sa_family = AF_INET; + return; + } +} - return NULL; +static void +soup_dns_cache_entry_ref (SoupDNSCacheEntry *entry) +{ + entry->ref_count++; +} + +static void +soup_dns_cache_entry_unref (SoupDNSCacheEntry *entry) +{ + if (--entry->ref_count == 0) { + g_free (entry->entry_name); + g_free (entry->hostname); + g_free (entry->sockaddr); + + /* If there were lookups pending, ref_count couldn't + * have reached zero. So no cleanup needed there. + */ + + g_free (entry); + } +} + +static SoupDNSCacheEntry * +soup_dns_cache_entry_new (const char *name) +{ + SoupDNSCacheEntry *entry; + + entry = g_new0 (SoupDNSCacheEntry, 1); + entry->entry_name = g_strdup (name); + entry->ref_count = 2; /* One for the caller, one for the cache */ + soup_dns_cache_entry_set_from_phys (entry); + + if (g_hash_table_size (soup_dns_cache) == SOUP_DNS_CACHE_MAX) { + SoupDNSCacheEntry *prune_entry = NULL; + + g_hash_table_foreach (soup_dns_cache, prune_cache_cb, &prune_entry); + if (prune_entry) { + g_hash_table_remove (soup_dns_cache, prune_entry->entry_name); + soup_dns_cache_entry_unref (prune_entry); + } + } + + entry->expires = time (0) + 60 * 60; + g_hash_table_insert (soup_dns_cache, entry->entry_name, entry); + + return entry; } /** * soup_dns_ntop: - * @addr: pointer to address data (eg, an #in_addr_t) - * @family: address family of @addr + * @sa: pointer to a #sockaddr * - * Converts @addr into textual form (eg, "141.213.8.59"), like the - * standard library function inet_ntop(), except that the returned + * Converts @sa's address into textual form (eg, "141.213.8.59"), like + * the standard library function inet_ntop(), except that the returned * string must be freed. * - * Return value: the text form or @addr, which must be freed. + * Return value: the text form or @sa, which must be freed. **/ char * -soup_dns_ntop (gconstpointer addr, int family) +soup_dns_ntop (struct sockaddr *sa) { - switch (family) { + switch (sa->sa_family) { case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)sa; #ifdef HAVE_INET_NTOP char buffer[INET_ADDRSTRLEN]; - inet_ntop (family, addr, buffer, sizeof (buffer)); + inet_ntop (family, &sin->sin_addr, buffer, sizeof (buffer)); return g_strdup (buffer); #else - return g_strdup (inet_ntoa (*(struct in_addr *)addr)); + return g_strdup (inet_ntoa (sin->sin_addr)); #endif } #ifdef HAVE_IPV6 case AF_INET6: { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; char buffer[INET6_ADDRSTRLEN]; - inet_ntop (family, addr, buffer, sizeof (buffer)); + inet_ntop (AF_INET6, &sin6->sin6_addr, buffer, sizeof (buffer)); return g_strdup (buffer); } #endif @@ -168,569 +221,350 @@ soup_dns_ntop (gconstpointer addr, int family) } } - -static struct hostent * -soup_gethostbyname_internal (const char *hostname) +static void +resolve_address (SoupDNSCacheEntry *entry) { - struct hostent result_buf, *result = &result_buf, *out; - char *buf = NULL; - -#if defined(HAVE_GETHOSTBYNAME_R_GLIBC) - { - size_t len; - int herr, res; - - len = 1024; - buf = g_new (char, len); - - while ((res = gethostbyname_r (hostname, - &result_buf, - buf, - len, - &result, - &herr)) == ERANGE) { - len *= 2; - buf = g_renew (char, buf, len); - } - - if (res || result == NULL || result->h_addr_list [0] == NULL) - result = NULL; - } -#elif defined(HAVE_GETHOSTBYNAME_R_SOLARIS) - { - size_t len; - int herr, res; - - len = 1024; - buf = g_new (char, len); - - while ((res = gethostbyname_r (hostname, - &result_buf, - buf, - len, - &herr)) == ERANGE) { - len *= 2; - buf = g_renew (char, buf, len); - } - - if (res) - result = NULL; - } -#elif defined(HAVE_GETHOSTBYNAME_R_HPUX) - { - struct hostent_data hdbuf; - - if (!gethostbyname_r (hostname, &result_buf, &hdbuf)) - result = NULL; - } -#else - { - result = gethostbyname (hostname); +#if defined (HAVE_GETADDRINFO) + + struct addrinfo hints, *res; + int retval; + + memset (&hints, 0, sizeof (struct addrinfo)); +# ifdef HAVE_AI_ADDRCONFIG + hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG; +# else + hints.ai_flags = AI_CANONNAME; +# endif + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + retval = getaddrinfo (entry->hostname, NULL, &hints, &res); + if (retval == 0) { + entry->sockaddr = g_memdup (res->ai_addr, res->ai_addrlen); + freeaddrinfo (res); } -#endif - - if (result) - out = copy_hostent (result); - else - out = NULL; - - if (buf) - g_free (buf); - return out; -} - -static struct hostent * -soup_gethostbyaddr_internal (gconstpointer addr, int family) -{ - struct hostent result_buf, *result = &result_buf, *out; - char *buf = NULL; - int length; +#else /* !HAVE_GETADDRINFO */ - switch (family) { - case AF_INET: - length = sizeof (struct in_addr); - break; -#ifdef HAVE_IPV6 - case AF_INET6: - length = sizeof (struct in6_addr); - break; -#endif - default: - return NULL; - } + struct hostent *h; -#if defined(HAVE_GETHOSTBYNAME_R_GLIBC) - { - size_t len; - int herr, res; - - len = 1024; - buf = g_new (char, len); - - while ((res = gethostbyaddr_r (addr, - length, - family, - &result_buf, - buf, - len, - &result, - &herr)) == ERANGE) { - len *= 2; - buf = g_renew (char, buf, len); - } + g_mutex_lock (soup_gethost_lock); - if (res || result == NULL || result->h_name == NULL) - result = NULL; + h = gethostbyname (entry->hostname); + if (h && h->h_addrtype == AF_INET) { + struct sockaddr_in sin; + memset (&sin, 0, sizeof (struct sockaddr_in)); + sin.sin_family = AF_INET; + memcpy (&sin.sin_addr, h->h_addr_list[0], sizeof (struct in_addr)); + entry->sockaddr = g_memdup (&sin, sizeof (struct sockaddr_in)); } -#elif defined(HAVE_GETHOSTBYNAME_R_SOLARIS) - { - size_t len; - int herr, res; - - len = 1024; - buf = g_new (char, len); - - while ((res = gethostbyaddr_r (addr, - length, - family, - &result_buf, - buf, - len, - &herr)) == ERANGE) { - len *= 2; - buf = g_renew (char, buf, len); - } - if (res) - result = NULL; - } -#elif defined(HAVE_GETHOSTBYNAME_R_HPUX) - { - struct hostent_data hdbuf; + g_mutex_unlock (soup_gethost_lock); - if (!gethostbyaddr_r (addr, length, family, &result_buf, &hdbuf)) - result = NULL; - } -#else - { - result = gethostbyaddr (addr, length, family); - } #endif - - if (result) - out = copy_hostent (result); - else - out = NULL; - - if (buf) - g_free (buf); - - return out; -} - - -/* Cache */ - -struct SoupDNSEntry { - char *name; - struct hostent *h; - gboolean resolved; - - time_t expires; - guint ref_count; - - pid_t lookup_pid; - int fd; -}; - -static GHashTable *soup_dns_entries; - -#define SOUP_DNS_ENTRIES_MAX 20 - -static GStaticMutex soup_dns_mutex = G_STATIC_MUTEX_INIT; -#define soup_dns_lock() g_static_mutex_lock (&soup_dns_mutex) -#define soup_dns_unlock() g_static_mutex_unlock (&soup_dns_mutex) - -static void -soup_dns_entry_ref (SoupDNSEntry *entry) -{ - entry->ref_count++; -} - -static void -soup_dns_entry_unref (SoupDNSEntry *entry) -{ - if (!--entry->ref_count) { - g_free (entry->name); - - if (entry->h) - soup_dns_free_hostent (entry->h); - - if (entry->fd) - close (entry->fd); - if (entry->lookup_pid) { - kill (entry->lookup_pid, SIGKILL); - waitpid (entry->lookup_pid, NULL, 0); - } - - g_free (entry); - } -} - -static void -uncache_entry (SoupDNSEntry *entry) -{ - g_hash_table_remove (soup_dns_entries, entry->name); - soup_dns_entry_unref (entry); } static void -prune_cache_cb (gpointer key, gpointer value, gpointer data) +resolve_name (SoupDNSCacheEntry *entry) { - SoupDNSEntry *entry = value, **prune_entry = data; +#ifdef HAVE_GETNAMEINFO + int retval, len = 128; + char *name = NULL; - if (!*prune_entry || (*prune_entry)->expires > entry->expires) - *prune_entry = entry; -} + do { + name = g_realloc (name, len); + retval = getnameinfo (entry->sockaddr, SOUP_DNS_SOCKADDR_LEN (entry->sockaddr), + name, len, NULL, 0, NI_NAMEREQD); + len += 128; + } while (retval == EAI_OVERFLOW); + + if (retval == 0) + entry->hostname = name; + else + g_free (name); -static SoupDNSEntry * -soup_dns_entry_new (const char *name) -{ - SoupDNSEntry *entry; +#else /* !HAVE_GETNAMEINFO */ - entry = g_new0 (SoupDNSEntry, 1); - entry->name = g_strdup (name); - entry->ref_count = 2; /* One for the caller, one for the cache */ + struct sockaddr_in *sin = (struct sockaddr_in *)entry->sockaddr; + struct hostent *h; - if (!soup_dns_entries) { - soup_dns_entries = g_hash_table_new (soup_str_case_hash, - soup_str_case_equal); - } else if (g_hash_table_size (soup_dns_entries) == SOUP_DNS_ENTRIES_MAX) { - SoupDNSEntry *prune_entry = NULL; + g_mutex_lock (soup_gethost_lock); - g_hash_table_foreach (soup_dns_entries, prune_cache_cb, - &prune_entry); - if (prune_entry) - uncache_entry (prune_entry); + if (sin->sin_family == AF_INET) { + h = gethostbyaddr (&sin->sin_addr, sizeof (sin->sin_addr), AF_INET); + if (h) + entry->hostname = g_strdup (h->h_name); } - entry->expires = time (0) + 60 * 60; - g_hash_table_insert (soup_dns_entries, entry->name, entry); + g_mutex_unlock (soup_gethost_lock); - return entry; +#endif /* HAVE_GETNAMEINFO */ } -static SoupDNSEntry * -soup_dns_lookup_entry (const char *name) +/* Assumes soup_dns_lock is held */ +static SoupDNSCacheEntry * +soup_dns_cache_entry_lookup (const char *name) { - SoupDNSEntry *entry; - - if (!soup_dns_entries) - return NULL; + SoupDNSCacheEntry *entry; - entry = g_hash_table_lookup (soup_dns_entries, name); + entry = g_hash_table_lookup (soup_dns_cache, name); if (entry) - soup_dns_entry_ref (entry); + soup_dns_cache_entry_ref (entry); return entry; } /** - * soup_dns_entry_from_name: - * @name: a nice name (eg, mofo.eecs.umich.edu) or a dotted decimal name - * (eg, 141.213.8.59). - * - * Begins asynchronous resolution of @name. The caller should - * periodically call soup_dns_entry_check_lookup() to see if it is - * done, and call soup_dns_entry_get_hostent() when - * soup_dns_entry_check_lookup() returns %TRUE. + * soup_dns_lookup_name: + * @name: a hostname (eg, "www.gnome.org") or physical address + * (eg, "12.107.209.247"). * - * Currently, this routine forks and does the lookup, which can cause - * some problems. In general, this will work ok for most programs most - * of the time. It will be slow or even fail when using operating - * systems that copy the entire process when forking. + * Creates a #SoupDNSLookup for @name. This should be passed to + * soup_dns_lookup_resolve() or soup_dns_lookup_resolve_async(). * - * Returns: a #SoupDNSEntry, which will be freed when you call - * soup_dns_entry_get_hostent() or soup_dns_entry_cancel_lookup(). + * Returns: a #SoupDNSLookup, which should eventually be freed with + * soup_dns_lookup_free(). **/ -SoupDNSEntry * -soup_dns_entry_from_name (const char *name) +SoupDNSLookup * +soup_dns_lookup_name (const char *name) { - SoupDNSEntry *entry; - int pipes[2]; + SoupDNSCacheEntry *entry; + SoupDNSLookup *lookup; - soup_dns_lock (); + g_mutex_lock (soup_dns_lock); - /* Try the cache */ - entry = soup_dns_lookup_entry (name); - if (entry) { - soup_dns_unlock (); - return entry; + entry = soup_dns_cache_entry_lookup (name); + if (!entry) { + entry = soup_dns_cache_entry_new (name); + entry->hostname = g_strdup (name); + if (entry->sockaddr) + entry->resolved = TRUE; } - entry = soup_dns_entry_new (name); + lookup = g_new0 (SoupDNSLookup, 1); + lookup->entry = entry; + g_mutex_unlock (soup_dns_lock); - /* Try to read the name as if it were dotted decimal */ - entry->h = new_hostent_from_phys (name); - if (entry->h) { - entry->resolved = TRUE; - soup_dns_unlock (); - return entry; - } - - /* Check to see if we are doing synchronous DNS lookups */ - if (getenv ("SOUP_SYNC_DNS")) { - entry->h = soup_gethostbyname_internal (name); - entry->resolved = TRUE; - soup_dns_unlock (); - return entry; - } - - /* Ok, we need to start a new lookup */ - - if (pipe (pipes) == -1) { - entry->resolved = TRUE; - soup_dns_unlock (); - return entry; - } - - entry->lookup_pid = fork (); - switch (entry->lookup_pid) { - case -1: - g_warning ("Fork error: %s (%d)\n", g_strerror (errno), errno); - close (pipes[0]); - close (pipes[1]); - - entry->resolved = TRUE; - soup_dns_unlock (); - return entry; - - case 0: - /* Child */ - close (pipes[0]); - - entry->h = soup_gethostbyname_internal (name); - if (entry->h) - write_hostent (entry->h, pipes[1]); - - /* Close the socket */ - close (pipes[1]); - - /* Exit (we don't want atexit called, so do _exit instead) */ - _exit (EXIT_SUCCESS); - - default: - /* Parent */ - close (pipes[1]); - - entry->fd = pipes[0]; - soup_dns_unlock (); - return entry; - } + return lookup; } /** - * soup_dns_entry_from_addr: - * @addr: pointer to address data (eg, an #in_addr_t) - * @family: address family of @addr + * soup_dns_lookup_address: + * @sockaddr: pointer to a #sockaddr * - * Begins asynchronous resolution of @addr. The caller should - * periodically call soup_dns_entry_check_lookup() to see if it is - * done, and call soup_dns_entry_get_hostent() when - * soup_dns_entry_check_lookup() returns %TRUE. + * Creates a #SoupDNSLookup for @sockaddr. This should be passed to + * soup_dns_lookup_resolve() or soup_dns_lookup_resolve_async(). * - * Currently, this routine forks and does the lookup, which can cause - * some problems. In general, this will work ok for most programs most - * of the time. It will be slow or even fail when using operating - * systems that copy the entire process when forking. - * - * Returns: a #SoupDNSEntry, which will be freed when you call - * soup_dns_entry_get_hostent() or soup_dns_entry_cancel_lookup(). + * Returns: a #SoupDNSLookup, which should eventually be freed with + * soup_dns_lookup_free() **/ -SoupDNSEntry * -soup_dns_entry_from_addr (gconstpointer addr, int family) +SoupDNSLookup * +soup_dns_lookup_address (struct sockaddr *sockaddr) { - SoupDNSEntry *entry; - int pipes[2]; + SoupDNSCacheEntry *entry; + SoupDNSLookup *lookup; char *name; - name = soup_dns_ntop (addr, family); + name = soup_dns_ntop (sockaddr); g_return_val_if_fail (name != NULL, NULL); - soup_dns_lock (); + g_mutex_lock (soup_dns_lock); - /* Try the cache */ - entry = soup_dns_lookup_entry (name); - if (entry) { - g_free (name); - soup_dns_unlock (); - return entry; - } + entry = soup_dns_cache_entry_lookup (name); + if (!entry) + entry = soup_dns_cache_entry_new (name); // FIXME + g_free (name); - entry = soup_dns_entry_new (name); + lookup = g_new0 (SoupDNSLookup, 1); + lookup->entry = entry; + g_mutex_unlock (soup_dns_lock); - /* Check to see if we are doing synchronous DNS lookups */ - if (getenv ("SOUP_SYNC_DNS")) { - entry->h = soup_gethostbyaddr_internal (addr, family); - entry->resolved = TRUE; - soup_dns_unlock (); - return entry; - } + return lookup; +} - if (pipe (pipes) != 0) { - entry->resolved = TRUE; - soup_dns_unlock (); - return entry; +static gboolean +do_async_callbacks (gpointer user_data) +{ + SoupDNSCacheEntry *entry = user_data; + GSList *lookups; + SoupDNSLookup *lookup; + gboolean success = (entry->hostname != NULL && entry->sockaddr != NULL); + + g_mutex_lock (soup_dns_lock); + lookups = entry->lookups; + entry->lookups = NULL; + g_mutex_unlock (soup_dns_lock); + + while (lookups) { + lookup = lookups->data; + lookups = g_slist_remove (lookups, lookup); + if (lookup->running) { + lookup->running = FALSE; + lookup->callback (lookup, success, lookup->user_data); + } } - entry->lookup_pid = fork (); - switch (entry->lookup_pid) { - case -1: - close (pipes[0]); - close (pipes[1]); - - g_warning ("Fork error: %s (%d)\n", g_strerror(errno), errno); - entry->resolved = TRUE; - soup_dns_unlock (); - return entry; - - case 0: - /* Child */ - close (pipes[0]); + soup_dns_cache_entry_unref (entry); + return FALSE; +} - entry->h = soup_gethostbyaddr_internal (addr, family); - if (entry->h) - write_hostent (entry->h, pipes[1]); +static gpointer +resolver_thread (gpointer user_data) +{ + SoupDNSCacheEntry *entry = user_data; - /* Close the socket */ - close (pipes[1]); + if (entry->hostname == NULL) + resolve_name (entry); + if (entry->sockaddr == NULL) + resolve_address (entry); - /* Exit (we don't want atexit called, so do _exit instead) */ - _exit (EXIT_SUCCESS); + entry->resolved = TRUE; + entry->resolver_thread = NULL; + g_cond_broadcast (soup_dns_cond); - default: - /* Parent */ - close (pipes[1]); + if (entry->lookups) + g_idle_add (do_async_callbacks, entry); + else + soup_dns_cache_entry_unref (entry); - entry->fd = pipes[0]; - soup_dns_unlock (); - return entry; - } + return NULL; } -static void -check_hostent (SoupDNSEntry *entry, gboolean block) +/** + * soup_dns_lookup_resolve: + * @lookup: a #SoupDNSLookup + * + * Synchronously resolves @lookup. You can cancel a pending resolution + * using soup_dns_lookup_cancel(). + * + * Return value: success or failure. + **/ +gboolean +soup_dns_lookup_resolve (SoupDNSLookup *lookup) { - char buf[256], *namelenp, *name, *typep, *addrlenp, *addr; - int bytes_read, nread, status; - fd_set readfds; - struct timeval tv = { 0, 0 }, *tvp; + SoupDNSCacheEntry *entry = lookup->entry; - soup_dns_lock (); + g_mutex_lock (soup_dns_lock); - if (entry->resolved) { - soup_dns_unlock (); - return; + lookup->running = TRUE; + + if (!entry->resolved && !entry->resolver_thread) { + soup_dns_cache_entry_ref (entry); + entry->resolver_thread = + g_thread_create (resolver_thread, entry, FALSE, NULL); } - if (block) - tvp = NULL; - else - tvp = &tv; + while (!entry->resolved && lookup->running) + g_cond_wait (soup_dns_cond, soup_dns_lock); - do { - FD_ZERO (&readfds); - FD_SET (entry->fd, &readfds); - status = select (entry->fd + 1, &readfds, NULL, NULL, tvp); - } while (status == -1 && errno == EINTR); + lookup->running = FALSE; - if (status == 0) { - soup_dns_unlock (); - return; - } - - nread = 0; - do { - bytes_read = read (entry->fd, buf + nread, - sizeof (buf) - nread); - - if (bytes_read > 0) - nread += bytes_read; - } while (bytes_read > 0 || (bytes_read == -1 && errno == EINTR)); - - close (entry->fd); - entry->fd = -1; - kill (entry->lookup_pid, SIGKILL); - waitpid (entry->lookup_pid, NULL, 0); - entry->lookup_pid = 0; - entry->resolved = TRUE; + g_mutex_unlock (soup_dns_lock); + return entry->hostname != NULL && entry->sockaddr != NULL; +} - if (nread < 1) { - soup_dns_unlock (); - return; +/** + * soup_dns_lookup_resolve_async: + * @lookup: a #SoupDNSLookup + * @callback: callback to call when @lookup is resolved + * @user_data: data to pass to @callback; + * + * Tries to asynchronously resolve @lookup. Invokes @callback when it + * has succeeded or failed. You can cancel a pending resolution using + * soup_dns_lookup_cancel(). + **/ +void +soup_dns_lookup_resolve_async (SoupDNSLookup *lookup, + SoupDNSCallback callback, gpointer user_data) +{ + SoupDNSCacheEntry *entry = lookup->entry; + + g_mutex_lock (soup_dns_lock); + + lookup->callback = callback; + lookup->user_data = user_data; + lookup->running = TRUE; + entry->lookups = g_slist_prepend (entry->lookups, lookup); + + if (!entry->resolved) { + if (!entry->resolver_thread) { + soup_dns_cache_entry_ref (entry); + entry->resolver_thread = + g_thread_create (resolver_thread, entry, FALSE, NULL); + } + } else { + soup_dns_cache_entry_ref (entry); + g_idle_add (do_async_callbacks, entry); } - namelenp = buf; - name = namelenp + 1; - typep = name + *namelenp; - addrlenp = typep + 1; - addr = addrlenp + 1; + g_mutex_unlock (soup_dns_lock); +} - if (addrlenp < buf + nread && (addr + *addrlenp) == buf + nread) - entry->h = new_hostent (name, *typep, *addrlenp, addr); - soup_dns_unlock (); +/** + * soup_dns_lookup_cancel: + * @lookup: a #SoupDNSLookup + * + * Cancels @lookup. If @lookup was running synchronously in another + * thread, it will immediately return %FALSE. If @lookup was running + * asynchronously, its callback function will not be called. + **/ +void +soup_dns_lookup_cancel (SoupDNSLookup *lookup) +{ + /* We never really cancel the DNS lookup itself (since GThread + * doesn't have a kill function, and it might mess up + * underlying resolver data anyway). But clearing lookup->running + * and broadcasting on soup_dns_cond will immediately stop any + * blocking synchronous lookups, and clearing lookup->running + * will also make sure that its async callback is never invoked. + */ + lookup->running = FALSE; + g_cond_broadcast (soup_dns_cond); } /** - * soup_dns_entry_check_lookup: - * @entry: a #SoupDNSEntry + * soup_dns_lookup_get_hostname: + * @lookup: a #SoupDNSLookup * - * Checks if @entry has finished resolving + * Gets the hostname of @lookup. * - * Return value: %TRUE if @entry has finished resolving (either - * successfully or not) + * Return value: the hostname, which the caller owns and must free, or + * %NULL if @lookup has not been completely resolved. **/ -gboolean -soup_dns_entry_check_lookup (SoupDNSEntry *entry) +char * +soup_dns_lookup_get_hostname (SoupDNSLookup *lookup) { - check_hostent (entry, FALSE); - - if (entry->resolved && entry->h == NULL) - uncache_entry (entry); - - return entry->resolved; + return g_strdup (lookup->entry->hostname); } /** - * soup_dns_entry_get_hostent: - * @entry: a #SoupDNSEntry + * soup_dns_lookup_get_address: + * @lookup: a #SoupDNSLookup * - * Extracts a #hostent from @entry. If @entry had not already finished - * resolving, soup_dns_entry_get_hostent() will block until it does. + * Gets the address of @lookup. * - * Return value: the #hostent (which must be freed with - * soup_dns_free_hostent()), or %NULL if it couldn't be resolved. + * Return value: the address, which the caller owns and must free, or + * %NULL if @lookup has not been completely resolved. **/ -struct hostent * -soup_dns_entry_get_hostent (SoupDNSEntry *entry) +struct sockaddr * +soup_dns_lookup_get_address (SoupDNSLookup *lookup) { - struct hostent *h; - - check_hostent (entry, TRUE); - h = entry->h ? copy_hostent (entry->h) : NULL; - soup_dns_entry_unref (entry); - - return h; + return g_memdup (lookup->entry->sockaddr, + SOUP_DNS_SOCKADDR_LEN (lookup->entry->sockaddr)); } /** - * soup_dns_entry_cancel_lookup: - * @entry: a #SoupDNSEntry + * soup_dns_lookup_free: + * @lookup: a #SoupDNSLookup * - * Cancels the lookup for @entry. + * Frees @lookup. If @lookup is still running, it will be canceled + * first. **/ void -soup_dns_entry_cancel_lookup (SoupDNSEntry *entry) +soup_dns_lookup_free (SoupDNSLookup *lookup) { - soup_dns_entry_unref (entry); + if (lookup->running) + soup_dns_lookup_cancel (lookup); + soup_dns_cache_entry_unref (lookup->entry); + g_free (lookup); } diff --git a/libsoup/soup-dns.h b/libsoup/soup-dns.h index 2162e65..d3a51a1 100644 --- a/libsoup/soup-dns.h +++ b/libsoup/soup-dns.h @@ -11,19 +11,28 @@ #include #include -typedef struct SoupDNSEntry SoupDNSEntry; +void soup_dns_init (void); -SoupDNSEntry *soup_dns_entry_from_name (const char *name); -SoupDNSEntry *soup_dns_entry_from_addr (gconstpointer addr, - int family); +typedef struct SoupDNSLookup SoupDNSLookup; -gboolean soup_dns_entry_check_lookup (SoupDNSEntry *entry); -void soup_dns_entry_cancel_lookup (SoupDNSEntry *entry); +SoupDNSLookup *soup_dns_lookup_name (const char *name); +SoupDNSLookup *soup_dns_lookup_address (struct sockaddr *address); -struct hostent *soup_dns_entry_get_hostent (SoupDNSEntry *entry); -void soup_dns_free_hostent (struct hostent *h); +typedef void (*SoupDNSCallback) (SoupDNSLookup *lookup, gboolean success, gpointer user_data); + +gboolean soup_dns_lookup_resolve (SoupDNSLookup *lookup); +void soup_dns_lookup_resolve_async (SoupDNSLookup *lookup, + SoupDNSCallback callback, + gpointer user_data); +void soup_dns_lookup_cancel (SoupDNSLookup *lookup); + +char *soup_dns_lookup_get_hostname (SoupDNSLookup *lookup); +struct sockaddr *soup_dns_lookup_get_address (SoupDNSLookup *lookup); + +void soup_dns_lookup_free (SoupDNSLookup *lookup); + + +char *soup_dns_ntop (struct sockaddr *address); -char *soup_dns_ntop (gconstpointer addr, - int family); #endif /* SOUP_DNS_H */ diff --git a/tests/dns.c b/tests/dns.c index 496c543..755ff69 100644 --- a/tests/dns.c +++ b/tests/dns.c @@ -5,25 +5,30 @@ #include "libsoup/soup-address.h" -GMainLoop *loop; +static GMainLoop *loop; +static int nlookups = 0; static void resolve_callback (SoupAddress *addr, guint status, gpointer data) { - if (status != SOUP_STATUS_OK) { - fprintf (stderr, "%s\n", soup_status_get_phrase (status)); - exit (1); + if (status == SOUP_STATUS_OK) { + printf ("Name: %s\n", soup_address_get_name (addr)); + printf ("Address: %s\n", soup_address_get_physical (addr)); + } else { + printf ("Name: %s\n", soup_address_get_name (addr)); + printf ("Error: %s\n", soup_status_get_phrase (status)); } + printf ("\n"); - printf ("Name: %s\n", soup_address_get_name (addr)); - printf ("Address: %s\n", soup_address_get_physical (addr)); - g_main_loop_quit (loop); + nlookups--; + if (nlookups == 0) + g_main_loop_quit (loop); } static void usage (void) { - fprintf (stderr, "Usage: dns [hostname | -r IP]\n"); + fprintf (stderr, "Usage: dns hostname ...\n"); exit (1); } @@ -31,20 +36,24 @@ int main (int argc, char **argv) { SoupAddress *addr; + int i; - if (argc != 2) + if (argc < 2) usage (); g_type_init (); g_thread_init (NULL); - addr = soup_address_new (argv[1], 0); - if (!addr) { - fprintf (stderr, "Could not parse address %s\n", argv[1]); - exit (1); - } + for (i = 1; i < argc; i++) { + addr = soup_address_new (argv[i], 0); + if (!addr) { + fprintf (stderr, "Could not parse address %s\n", argv[1]); + exit (1); + } - soup_address_resolve_async (addr, resolve_callback, NULL); + soup_address_resolve_async (addr, resolve_callback, NULL); + nlookups++; + } loop = g_main_loop_new (NULL, TRUE); g_main_run (loop);