WvStreams
wvresolver.cc
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  * 
00005  * DNS name resolver with support for background lookups.
00006  */
00007 #include "wvresolver.h"
00008 #include "wvloopback.h"
00009 #include "wvaddr.h"
00010 #include "wvtcp.h"
00011 #include <sys/types.h>
00012 #include <signal.h>
00013 #include <time.h>
00014 
00015 #ifdef _WIN32
00016 #define WVRESOLVER_SKIP_FORK
00017 typedef int pid_t;
00018 #define kill(a,b)
00019 #define waitpid(a,b,c) (0)
00020 #define alarm(a)
00021 #include "streams.h"
00022 #else
00023 #include "wvautoconf.h"
00024 #include "wvfork.h"
00025 #include <netdb.h>
00026 #include <sys/wait.h>
00027 #endif
00028 
00029 class WvResolverHost
00030 {
00031 public:
00032     WvString name;
00033     WvIPAddr *addr;
00034     WvIPAddrList addrlist;
00035     bool done, negative;
00036     pid_t pid;
00037     WvLoopback *loop;
00038     time_t last_tried;
00039 
00040     WvResolverHost(WvStringParm _name) : name(_name)
00041         { init(); addr = NULL; }
00042     ~WvResolverHost()
00043         {
00044             WVRELEASE(loop);
00045 #ifndef WVRESOLVER_SKIP_FORK
00046             if (pid && pid != -1)
00047             {
00048                 kill(pid, SIGKILL);
00049                 pid_t rv;
00050                 // In case a signal is in the process of being delivered...
00051                 while ((rv = waitpid(pid, NULL, 0)) != pid)
00052                     if (rv == -1 && errno != EINTR)
00053                         break;
00054             }
00055 #endif
00056         }
00057 protected:
00058     WvResolverHost()
00059         { init(); }
00060     void init()
00061         { done = negative = false;
00062           pid = 0; loop = NULL; last_tried = time(NULL); }
00063 };
00064 
00065 class WvResolverAddr : public WvResolverHost
00066 {
00067 public:
00068     WvResolverAddr(WvIPAddr *_addr)
00069         { addr = _addr; }
00070 };
00071 
00072 // static members of WvResolver
00073 int WvResolver::numresolvers = 0;
00074 WvResolverHostDict *WvResolver::hostmap = NULL;
00075 WvResolverAddrDict *WvResolver::addrmap = NULL;
00076 
00077 
00078 // function that runs in a child task
00079 
00080 static void namelookup(const char *name, WvLoopback *loop)
00081 {
00082     struct hostent *he;
00083     
00084     // wait up to one minute...
00085     alarm(60);
00086     
00087     for (int count = 0; count < 10; count++)
00088     {
00089         he = gethostbyname(name);
00090         if (he)
00091         {
00092             char **addr = he->h_addr_list;
00093             while (*addr != NULL)
00094             {
00095                 loop->print("%s ", WvIPAddr((unsigned char *)(*addr)));
00096                 addr++;
00097             }
00098             loop->print("\n");
00099             alarm(0);
00100             return;
00101         }
00102         
00103         // not found (yet?)
00104         
00105         if (h_errno != TRY_AGAIN)
00106         {
00107             alarm(0);
00108             return; // not found; blank output
00109         }
00110         
00111         // avoid spinning in a tight loop.
00112         //
00113         // sleep() is documented to possibly mess with the alarm(), so we
00114         // have to make sure to reset the alarm here.  That's a shame,
00115         // because otherwise it would timeout nicely after 60 seconds
00116         // overall, not 60 seconds per request.
00117         sleep(1);
00118 
00119         alarm(60);
00120     }
00121 }
00122 
00123 
00124 WvResolver::WvResolver()
00125 {
00126     numresolvers++;
00127     if (!hostmap)
00128         hostmap = new WvResolverHostDict(10);
00129     if (!addrmap)
00130         addrmap = new WvResolverAddrDict(10);
00131 }
00132 
00133 
00134 WvResolver::~WvResolver()
00135 {
00136     numresolvers--;
00137     if (numresolvers <= 0 && hostmap && addrmap)
00138     {
00139         delete hostmap;
00140         delete addrmap;
00141         hostmap = NULL;
00142         addrmap = NULL;
00143     }
00144 }
00145 
00146 
00147 // returns >0 on success, 0 on not found, -1 on timeout
00148 // If addr==NULL, this just tests to see if the name exists.
00149 int WvResolver::findaddr(int msec_timeout, WvStringParm name,
00150                          WvIPAddr const **addr,
00151                          WvIPAddrList *addrlist)
00152 {
00153     WvResolverHost *host;
00154     time_t now = time(NULL);
00155     int res = 0;
00156     
00157     host = (*hostmap)[name];
00158 
00159     if (host)
00160     {
00161         // refresh successes after 5 minutes, retry failures every 1 minute
00162         if ((host->done && host->last_tried + 60*5 < now)
00163             || (!host->done && host->last_tried + 60 < now))
00164         {
00165             // expired from the cache.  Force a repeat lookup below...
00166             hostmap->remove(host);
00167             host = NULL;
00168         }
00169         else if (host->done)
00170         {
00171             // entry exists, is marked done, and hasn't expired yet.  Return
00172             // the cached value.
00173             if (addr)
00174                 *addr = host->addr;
00175             if (addrlist)
00176             {
00177                 WvIPAddrList::Iter i(host->addrlist);
00178                 for (i.rewind(); i.next(); )
00179                 {
00180                     addrlist->append(i.ptr(), false);
00181                     res++;
00182                 }
00183             }
00184             else
00185                 res = 1;
00186             return res;
00187         }
00188         else if (host->negative)
00189         {
00190             // the entry is in the cache, but the response was negative:
00191             // the name doesn't exist.
00192             return 0;
00193         }
00194         
00195         // if we get here, 'host' either exists (still in progress)
00196         // or is NULL (need to start again).
00197     }
00198 
00199     if (!host)
00200     {
00201         // nothing matches this hostname in the cache.  Create a new entry,
00202         // and start a new lookup.
00203         host = new WvResolverHost(name);
00204         hostmap->add(host, true);
00205         
00206         host->loop = new WvLoopback();
00207         
00208 #ifdef WVRESOLVER_SKIP_FORK
00209         // background name resolution doesn't work when debugging with gdb!
00210         namelookup(name, host->loop);
00211 #else
00212         // fork a subprocess so we don't block while doing the DNS lookup.
00213 
00214         // close everything but host->loop in the subprocess.
00215         host->pid = wvfork(host->loop->getrfd(), host->loop->getwfd());
00216         
00217         if (!host->pid)
00218         {
00219             // child process
00220             host->loop->noread();
00221             namelookup(name, host->loop);
00222             _exit(1);
00223         }
00224 #endif
00225         
00226         // parent process
00227         host->loop->nowrite();
00228     }
00229 
00230 #ifndef WVRESOLVER_SKIP_FORK
00231     
00232     // if we get here, we are the parent task waiting for the child.
00233     
00234     do
00235     {
00236         if (waitpid(host->pid, NULL, WNOHANG) == host->pid)
00237             host->pid = 0;
00238         
00239         if (!host->loop->select(msec_timeout < 0 ? 100 : msec_timeout,
00240                                 true, false))
00241         {
00242             if (host->pid)
00243             {
00244                 if (msec_timeout >= 0)
00245                     return -1; // timeout, but still trying
00246             }
00247             else
00248             {
00249                 // the child is dead.  Clean up our stream, too.
00250                 WVRELEASE(host->loop);
00251                 host->loop = NULL;
00252                 host->negative = true;
00253                 return 0; // exited while doing search
00254             }
00255         }
00256         else
00257             break;
00258     } while (host->pid && msec_timeout < 0); // repeat if unlimited timeout!
00259 #endif
00260 
00261     // data coming in!
00262     char *line;
00263     
00264     do
00265     {
00266         line = host->loop->blocking_getline(-1);
00267     } while (!line && host->loop->isok());
00268     
00269     if (line && line[0] != 0)
00270     {
00271         res = 1;
00272         WvIPAddr *resolvedaddr;
00273         char *p;
00274         p = strtok(line, " \n");
00275         resolvedaddr = new WvIPAddr(p);
00276         host->addr = resolvedaddr;
00277         host->addrlist.append(resolvedaddr, true);
00278         if (addr)
00279             *addr = host->addr;
00280         if (addrlist)
00281             addrlist->append(host->addr, false);
00282         do
00283         {
00284             p = strtok(NULL, " \n");
00285             if (p)
00286             {
00287                 res++;
00288                 resolvedaddr = new WvIPAddr(p);
00289                 host->addrlist.append(resolvedaddr, true);
00290                 if (addrlist)
00291                     addrlist->append(resolvedaddr, false);
00292             }
00293         } while (p);
00294         host->done = true;
00295     }
00296     else
00297         host->negative = true;
00298 
00299     if (host->pid && waitpid(host->pid, NULL, 0) == host->pid)
00300         host->pid = 0;
00301     WVRELEASE(host->loop);
00302     host->loop = NULL;
00303     
00304     // Return as many addresses as we find.
00305     return host->negative ? 0 : res;
00306 }
00307 
00308 void WvResolver::clearhost(WvStringParm hostname)
00309 {
00310     WvResolverHost *host = (*hostmap)[hostname];
00311     if (host)
00312         hostmap->remove(host);
00313 }
00314 
00315 
00316 void WvResolver::pre_select(WvStringParm hostname, WvStream::SelectInfo &si)
00317 {
00318     WvResolverHost *host = (*hostmap)[hostname];
00319     
00320     if (host)
00321     {
00322         if (host->loop)
00323             host->loop->xpre_select(si,
00324                             WvStream::SelectRequest(true, false, false));
00325         else
00326             si.msec_timeout = 0; // already ready
00327     }
00328 }
00329 
00330 
00331 bool WvResolver::post_select(WvStringParm hostname, WvStream::SelectInfo &si)
00332 {
00333     WvResolverHost *host = (*hostmap)[hostname];
00334     
00335     if (host)
00336     {
00337         if (host->loop)
00338             return host->loop->xpost_select(si,
00339                     WvStream::SelectRequest(true, false, false));
00340         else
00341             return true; // already ready
00342     }
00343     return false;
00344 }