view src/dns.c @ 0:6ee11bf9e3ea

Initial revision
author jcid
date Sun, 07 Oct 2007 00:36:34 +0200
parents
children 9a9338f78888
line wrap: on
line source
/*
 * 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);
}