aboutsummaryrefslogtreecommitdiff
path: root/src/dns.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dns.c')
-rw-r--r--src/dns.c535
1 files changed, 535 insertions, 0 deletions
diff --git a/src/dns.c b/src/dns.c
new file mode 100644
index 00000000..2813f54f
--- /dev/null
+++ b/src/dns.c
@@ -0,0 +1,535 @@
+/*
+ * File: dns.c
+ *
+ * Copyright (C) 1999-2006 Jorge Arellano Cid <jcid@dillo.org>
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ */
+
+/*
+ * Non blocking pthread-handled Dns scheme
+ */
+
+#include <pthread.h>
+
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <string.h>
+
+#include "msg.h"
+#include "dns.h"
+#include "list.h"
+#include "timeout.hh"
+
+#define DEBUG_LEVEL 5
+#include "debug.h"
+
+
+/*
+ * Uncomment the following line for debugging or gprof profiling.
+ */
+/* #undef D_DNS_THREADED */
+
+/*
+ * Uncomment the following line for libc5 optimization
+ */
+/* #define LIBC5 */
+
+
+/* Maximum dns resolving threads */
+#ifdef D_DNS_THREADED
+# define D_DNS_MAX_SERVERS 4
+#else
+# define D_DNS_MAX_SERVERS 1
+#endif
+
+
+typedef struct {
+ int channel; /* Index of this channel [0 based] */
+ bool_t in_use; /* boolean to tell if server is doing a lookup */
+ bool_t ip_ready; /* boolean: is IP lookup done? */
+ Dlist *addr_list; /* IP address */
+ char *hostname; /* Adress to resolve */
+ int status; /* errno code for resolving function */
+#ifdef D_DNS_THREADED
+ pthread_t th1; /* Thread id */
+#endif
+} DnsServer;
+
+typedef struct {
+ char *hostname; /* host name for cache */
+ Dlist *addr_list; /* addresses of host */
+} GDnsCache;
+
+typedef struct {
+ int channel; /* -2 if waiting, otherwise index to dns_server[] */
+ char *hostname; /* The one we're resolving */
+ DnsCallback_t cb_func; /* callback function */
+ void *cb_data; /* extra data for the callback function */
+} GDnsQueue;
+
+
+/*
+ * Forward declarations
+ */
+static void Dns_timeout_client(void *data);
+
+/*
+ * Local Data
+ */
+static DnsServer dns_server[D_DNS_MAX_SERVERS];
+static int num_servers;
+static GDnsCache *dns_cache;
+static int dns_cache_size, dns_cache_size_max;
+static GDnsQueue *dns_queue;
+static int dns_queue_size, dns_queue_size_max;
+static bool_t ipv6_enabled;
+
+
+/* ----------------------------------------------------------------------
+ * Dns queue functions
+ */
+static void Dns_queue_add(int channel, const char *hostname,
+ DnsCallback_t cb_func, void *cb_data)
+{
+ a_List_add(dns_queue, dns_queue_size, dns_queue_size_max);
+ dns_queue[dns_queue_size].channel = channel;
+ dns_queue[dns_queue_size].hostname = dStrdup(hostname);
+ dns_queue[dns_queue_size].cb_func = cb_func;
+ dns_queue[dns_queue_size].cb_data = cb_data;
+ dns_queue_size++;
+}
+
+/*
+ * Find hostname index in dns_queue
+ * (if found, returns queue index; -1 if not)
+ */
+static int Dns_queue_find(const char *hostname)
+{
+ int i;
+
+ for (i = 0; i < dns_queue_size; i++)
+ if (!strcmp(hostname, dns_queue[i].hostname))
+ return i;
+
+ return -1;
+}
+
+/*
+ * Given an index, remove an entry from the dns_queue
+ */
+static void Dns_queue_remove(int index)
+{
+ int i;
+
+ DEBUG_MSG(2, "Dns_queue_remove: deleting client [%d] [queue_size=%d]\n",
+ index, dns_queue_size);
+
+ if (index < dns_queue_size) {
+ dFree(dns_queue[index].hostname);
+ --dns_queue_size; /* you'll find out why ;-) */
+ for (i = index; i < dns_queue_size; i++)
+ dns_queue[i] = dns_queue[i + 1];
+ }
+}
+
+/*
+ * Debug function
+ *
+void Dns_queue_print()
+{
+ int i;
+
+ MSG("Queue: [");
+ for (i = 0; i < dns_queue_size; i++)
+ MSG("%d:%s ", dns_queue[i].channel, dns_queue[i].hostname);
+ MSG("]\n");
+}
+ */
+
+/*
+ * Add an IP/hostname pair to Dns-cache
+ */
+static void Dns_cache_add(char *hostname, Dlist *addr_list)
+{
+ a_List_add(dns_cache, dns_cache_size, dns_cache_size_max);
+ dns_cache[dns_cache_size].hostname = dStrdup(hostname);
+ dns_cache[dns_cache_size].addr_list = addr_list;
+ ++dns_cache_size;
+ DEBUG_MSG(1, "Cache objects: %d\n", dns_cache_size);
+}
+
+
+/*
+ * Initializer function
+ */
+void a_Dns_init(void)
+{
+ int i;
+
+#ifdef D_DNS_THREADED
+ DEBUG_MSG(5, "dillo_dns_init: Here we go! (threaded)\n");
+#else
+ DEBUG_MSG(5, "dillo_dns_init: Here we go! (not threaded)\n");
+#endif
+
+ dns_queue_size = 0;
+ dns_queue_size_max = 16;
+ dns_queue = dNew(GDnsQueue, dns_queue_size_max);
+
+ dns_cache_size = 0;
+ dns_cache_size_max = 16;
+ dns_cache = dNew(GDnsCache, dns_cache_size_max);
+
+ num_servers = D_DNS_MAX_SERVERS;
+
+ /* Initialize servers data */
+ for (i = 0; i < num_servers; ++i) {
+ dns_server[i].channel = i;
+ dns_server[i].in_use = FALSE;
+ dns_server[i].ip_ready = FALSE;
+ dns_server[i].addr_list = NULL;
+ dns_server[i].hostname = NULL;
+ dns_server[i].status = 0;
+#ifdef D_DNS_THREADED
+ dns_server[i].th1 = (pthread_t) -1;
+#endif
+ }
+
+ /* IPv6 test */
+ ipv6_enabled = FALSE;
+#ifdef ENABLE_IPV6
+ {
+ /* If the IPv6 address family is not available there is no point
+ wasting time trying to connect to v6 addresses. */
+ int fd = socket(AF_INET6, SOCK_STREAM, 0);
+ if (fd >= 0) {
+ close(fd);
+ ipv6_enabled = TRUE;
+ }
+ }
+#endif
+}
+
+/*
+ * Allocate a host structure and add it to the list
+ */
+static void Dns_note_hosts(Dlist *list, int af, struct hostent *host)
+{
+ int i;
+
+ if (host->h_length > DILLO_ADDR_MAX)
+ return;
+ for (i = 0; host->h_addr_list[i]; i++) {
+ DilloHost *dh = dNew0(DilloHost, 1);
+ dh->af = af;
+ dh->alen = host->h_length;
+ memcpy(&dh->data[0], host->h_addr_list[i], (size_t)host->h_length);
+ dList_append(list, dh);
+ }
+}
+
+#ifdef D_DNS_THREADED
+/*
+ * Server function (runs on its own thread)
+ */
+static void *Dns_server(void *data)
+{
+ struct hostent *host;
+ int channel = VOIDP2INT(data);
+#ifdef LIBC5
+ int h_err;
+ char buff[1024];
+ struct hostent sh;
+#endif
+ Dlist *hosts = dList_new(2);
+
+ DEBUG_MSG(3, "Dns_server: starting...\n ch: %d host: %s\n",
+ channel, dns_server[channel].hostname);
+
+#ifdef ENABLE_IPV6
+ if (ipv6_enabled) {
+ host = gethostbyname2(dns_server[channel].hostname, AF_INET6);
+ if (host) {
+ Dns_note_hosts(hosts, AF_INET6, host);
+ }
+ }
+#endif
+
+#ifdef LIBC5
+ host = gethostbyname_r(dns_server[channel].hostname, &sh, buff,
+ sizeof(buff), &h_err);
+#else
+ host = gethostbyname(dns_server[channel].hostname);
+#endif
+
+ if (!host) {
+#ifdef LIBC5
+ dns_server[channel].status = h_err;
+#else
+ dns_server[channel].status = h_errno;
+ if (h_errno == HOST_NOT_FOUND)
+ MSG("DNS error: HOST_NOT_FOUND\n");
+ else if (h_errno == TRY_AGAIN)
+ MSG("DNS error: TRY_AGAIN\n");
+ else if (h_errno == NO_RECOVERY)
+ MSG("DNS error: NO_RECOVERY\n");
+ else if (h_errno == NO_ADDRESS)
+ MSG("DNS error: NO_ADDRESS\n");
+#endif
+ } else {
+ dns_server[channel].status = 0;
+ Dns_note_hosts(hosts, AF_INET, host);
+ }
+ if (dList_length(hosts) > 0) {
+ dns_server[channel].status = 0;
+ } else {
+ dList_free(hosts);
+ hosts = NULL;
+ }
+
+ /* tell our findings */
+ DEBUG_MSG(5, "Dns_server [%d]: %s is %p\n", channel,
+ dns_server[channel].hostname, hosts);
+ dns_server[channel].addr_list = hosts;
+ dns_server[channel].ip_ready = TRUE;
+
+ return NULL; /* (avoids a compiler warning) */
+}
+#endif
+
+#ifndef D_DNS_THREADED
+/*
+ * Blocking server-function (it doesn't use threads)
+ */
+static void Dns_blocking_server(void)
+{
+ int channel = 0;
+ struct hostent *host = NULL;
+ dList *hosts = dList_new(2);
+#ifdef LIBC5
+ int h_err;
+#endif
+
+ DEBUG_MSG(3, "Dns_blocking_server: starting...\n");
+ DEBUG_MSG(3, "Dns_blocking_server: dns_server[%d].hostname = %s\n",
+ channel, dns_server[channel].hostname);
+
+#ifdef ENABLE_IPV6
+ if (ipv6_enabled) {
+ host = gethostbyname2(dns_server[channel].hostname, AF_INET6);
+ if (host) {
+ Dns_note_hosts(hosts, AF_INET6, host);
+ }
+ }
+#endif
+
+#ifdef LIBC5
+ host = gethostbyname_r(dns_server[channel].hostname, &sh, buff,
+ sizeof(buff), &h_err);
+#else
+ host = gethostbyname(dns_server[channel].hostname);
+#endif
+
+ if (!host) {
+#ifdef LIBC5
+ dns_server[channel].status = h_err;
+#else
+ dns_server[channel].status = h_errno;
+#endif
+ } else {
+ Dns_note_hosts(hosts, AF_INET, host);
+ }
+ if (dList_length(hosts) > 0) {
+ /* at least one entry on the list is ok */
+ dns_server[channel].status = 0;
+ } else {
+ dList_free(hosts);
+ hosts = NULL;
+ }
+
+ /* write IP to server data channel */
+ DEBUG_MSG(3, "Dns_blocking_server: IP of %s is %p\n",
+ dns_server[channel].hostname, hosts);
+ dns_server[channel].addr_list = hosts;
+ dns_server[channel].ip_ready = TRUE;
+
+ DEBUG_MSG(3, "Dns_blocking_server: leaving...\n");
+}
+#endif
+
+/*
+ * Request function (spawn a server and let it handle the request)
+ */
+static void Dns_server_req(int channel, const char *hostname)
+{
+#ifdef D_DNS_THREADED
+ static pthread_attr_t thrATTR;
+ static int thrATTRInitialized = 0;
+#endif
+
+ dns_server[channel].in_use = TRUE;
+ dns_server[channel].ip_ready = FALSE;
+
+ dFree(dns_server[channel].hostname);
+ dns_server[channel].hostname = dStrdup(hostname);
+
+ /* Let's set a timeout client to poll the server channel (5 times/sec) */
+ a_Timeout_add(0.2,Dns_timeout_client,(void*)(dns_server[channel].channel));
+
+#ifdef D_DNS_THREADED
+ /* set the thread attribute to the detached state */
+ if (!thrATTRInitialized) {
+ pthread_attr_init(&thrATTR);
+ pthread_attr_setdetachstate(&thrATTR, PTHREAD_CREATE_DETACHED);
+ thrATTRInitialized = 1;
+ }
+ /* Spawn thread */
+ pthread_create(&dns_server[channel].th1, &thrATTR, Dns_server,
+ INT2VOIDP(dns_server[channel].channel));
+#else
+ Dns_blocking_server();
+#endif
+}
+
+/*
+ * Return the IP for the given hostname using a callback.
+ * Side effect: a thread is spawned when hostname is not cached.
+ */
+void a_Dns_resolve(const char *hostname, DnsCallback_t cb_func, void *cb_data)
+{
+ int i, channel;
+
+ if (!hostname)
+ return;
+
+ /* check for cache hit. */
+ for (i = 0; i < dns_cache_size; i++)
+ if (!strcmp(hostname, dns_cache[i].hostname))
+ break;
+
+ if (i < dns_cache_size) {
+ /* already resolved, call the Callback inmediately. */
+ cb_func(0, dns_cache[i].addr_list, cb_data);
+
+ } else if ((i = Dns_queue_find(hostname)) != -1) {
+ /* hit in queue, but answer hasn't come back yet. */
+ Dns_queue_add(dns_queue[i].channel, hostname, cb_func, cb_data);
+
+ } else {
+ /* Never requested before -- we must resolve it! */
+
+ /* Find a channel we can send the request to */
+ for (channel = 0; channel < num_servers; channel++)
+ if (!dns_server[channel].in_use)
+ break;
+ if (channel < num_servers) {
+ /* Found a free channel! */
+ Dns_queue_add(channel, hostname, cb_func, cb_data);
+ Dns_server_req(channel, hostname);
+ } else {
+ /* We'll have to wait for a thread to finish... */
+ Dns_queue_add(-2, hostname, cb_func, cb_data);
+ }
+ }
+}
+
+/*
+ * Give answer to all queued callbacks on this channel
+ */
+static void Dns_serve_channel(int channel)
+{
+ int i;
+ DnsServer *srv = &dns_server[channel];
+
+ for (i = 0; i < dns_queue_size; i++) {
+ if (dns_queue[i].channel == channel) {
+ dns_queue[i].cb_func(srv->status, srv->addr_list,
+ dns_queue[i].cb_data);
+ Dns_queue_remove(i);
+ --i;
+ }
+ }
+ /* set current channel free */
+ srv->in_use = FALSE;
+}
+
+/*
+ * Assign free channels to waiting clients (-2)
+ */
+static void Dns_assign_channels(void)
+{
+ int ch, i, j;
+
+ for (ch = 0; ch < num_servers; ++ch) {
+ if (dns_server[ch].in_use == FALSE) {
+ /* Find the next query in the queue (we're a FIFO) */
+ for (i = 0; i < dns_queue_size; i++)
+ if (dns_queue[i].channel == -2)
+ break;
+
+ if (i < dns_queue_size) {
+ /* assign this channel to every queued request
+ * with the same hostname*/
+ for (j = i; j < dns_queue_size; j++)
+ if (dns_queue[j].channel == -2 &&
+ !strcmp(dns_queue[j].hostname, dns_queue[i].hostname))
+ dns_queue[j].channel = ch;
+ Dns_server_req(ch, dns_queue[i].hostname);
+ } else
+ return;
+ }
+ }
+}
+
+/*
+ * This is a timeout function that
+ * reads the DNS results and resumes the stopped jobs.
+ */
+static void Dns_timeout_client(void *data)
+{
+ int channel = (int)data;
+ DnsServer *srv = &dns_server[channel];
+
+ if (srv->ip_ready) {
+ if (srv->addr_list != NULL) {
+ /* DNS succeeded, let's cache it */
+ Dns_cache_add(srv->hostname, srv->addr_list);
+ }
+ Dns_serve_channel(channel);
+ Dns_assign_channels();
+ a_Timeout_remove(); /* Done! */
+
+ } else {
+ /* IP not already resolved, keep on trying... */
+ a_Timeout_repeat(0.2, Dns_timeout_client, data);
+ }
+}
+
+
+/*
+ * Dns memory-deallocation
+ * (Call this one at exit time)
+ * The Dns_queue is deallocated at execution time (no need to do that here)
+ * 'dns_cache' is the only one that grows dinamically
+ */
+void a_Dns_freeall(void)
+{
+ int i;
+
+ for ( i = 0; i < dns_cache_size; ++i ){
+ dFree(dns_cache[i].hostname);
+ }
+ dFree(dns_cache);
+}
+