/* * Worldvisions Weaver Software: * Copyright (C) 1997-2002 Net Integration Technologies, Inc. * * DNS name resolver with support for background lookups. */ #include "wvresolver.h" #include "wvloopback.h" #include "wvaddr.h" #include "wvtcp.h" #include #include #include #ifdef _WIN32 #define WVRESOLVER_SKIP_FORK typedef int pid_t; #define kill(a,b) #define waitpid(a,b,c) (0) #define alarm(a) #include "streams.h" #else #include "wvautoconf.h" #include "wvfork.h" #include #include #endif class WvResolverHost { public: WvString name; WvIPAddr *addr; WvIPAddrList addrlist; bool done, negative; pid_t pid; WvLoopback *loop; time_t last_tried; WvResolverHost(WvStringParm _name) : name(_name) { init(); addr = NULL; } ~WvResolverHost() { WVRELEASE(loop); #ifndef WVRESOLVER_SKIP_FORK if (pid && pid != -1) { kill(pid, SIGKILL); pid_t rv; // In case a signal is in the process of being delivered... while ((rv = waitpid(pid, NULL, 0)) != pid) if (rv == -1 && errno != EINTR) break; } #endif } protected: WvResolverHost() { init(); } void init() { done = negative = false; pid = 0; loop = NULL; last_tried = time(NULL); } }; class WvResolverAddr : public WvResolverHost { public: WvResolverAddr(WvIPAddr *_addr) { addr = _addr; } }; // static members of WvResolver int WvResolver::numresolvers = 0; WvResolverHostDict *WvResolver::hostmap = NULL; WvResolverAddrDict *WvResolver::addrmap = NULL; // function that runs in a child task static void namelookup(const char *name, WvLoopback *loop) { struct hostent *he; // wait up to one minute... alarm(60); for (int count = 0; count < 10; count++) { he = gethostbyname(name); if (he) { char **addr = he->h_addr_list; while (*addr != NULL) { loop->print("%s ", WvIPAddr((unsigned char *)(*addr))); addr++; } loop->print("\n"); alarm(0); return; } // not found (yet?) if (h_errno != TRY_AGAIN) { alarm(0); return; // not found; blank output } // avoid spinning in a tight loop. // // sleep() is documented to possibly mess with the alarm(), so we // have to make sure to reset the alarm here. That's a shame, // because otherwise it would timeout nicely after 60 seconds // overall, not 60 seconds per request. sleep(1); alarm(60); } } WvResolver::WvResolver() { numresolvers++; if (!hostmap) hostmap = new WvResolverHostDict(10); if (!addrmap) addrmap = new WvResolverAddrDict(10); } WvResolver::~WvResolver() { numresolvers--; if (numresolvers <= 0 && hostmap && addrmap) { delete hostmap; delete addrmap; hostmap = NULL; addrmap = NULL; } } // returns >0 on success, 0 on not found, -1 on timeout // If addr==NULL, this just tests to see if the name exists. int WvResolver::findaddr(int msec_timeout, WvStringParm name, WvIPAddr const **addr, WvIPAddrList *addrlist) { WvResolverHost *host; time_t now = time(NULL); int res = 0; host = (*hostmap)[name]; if (host) { // refresh successes after 5 minutes, retry failures every 1 minute if ((host->done && host->last_tried + 60*5 < now) || (!host->done && host->last_tried + 60 < now)) { // expired from the cache. Force a repeat lookup below... hostmap->remove(host); host = NULL; } else if (host->done) { // entry exists, is marked done, and hasn't expired yet. Return // the cached value. if (addr) *addr = host->addr; if (addrlist) { WvIPAddrList::Iter i(host->addrlist); for (i.rewind(); i.next(); ) { addrlist->append(i.ptr(), false); res++; } } else res = 1; return res; } else if (host->negative) { // the entry is in the cache, but the response was negative: // the name doesn't exist. return 0; } // if we get here, 'host' either exists (still in progress) // or is NULL (need to start again). } if (!host) { // nothing matches this hostname in the cache. Create a new entry, // and start a new lookup. host = new WvResolverHost(name); hostmap->add(host, true); host->loop = new WvLoopback(); #ifdef WVRESOLVER_SKIP_FORK // background name resolution doesn't work when debugging with gdb! namelookup(name, host->loop); #else // fork a subprocess so we don't block while doing the DNS lookup. // close everything but host->loop in the subprocess. host->pid = wvfork(host->loop->getrfd(), host->loop->getwfd()); if (!host->pid) { // child process host->loop->noread(); namelookup(name, host->loop); _exit(1); } #endif // parent process host->loop->nowrite(); } #ifndef WVRESOLVER_SKIP_FORK // if we get here, we are the parent task waiting for the child. do { if (waitpid(host->pid, NULL, WNOHANG) == host->pid) host->pid = 0; if (!host->loop->select(msec_timeout < 0 ? 100 : msec_timeout, true, false)) { if (host->pid) { if (msec_timeout >= 0) return -1; // timeout, but still trying } else { // the child is dead. Clean up our stream, too. WVRELEASE(host->loop); host->loop = NULL; host->negative = true; return 0; // exited while doing search } } else break; } while (host->pid && msec_timeout < 0); // repeat if unlimited timeout! #endif // data coming in! char *line; do { line = host->loop->blocking_getline(-1); } while (!line && host->loop->isok()); if (line && line[0] != 0) { res = 1; WvIPAddr *resolvedaddr; char *p; p = strtok(line, " \n"); resolvedaddr = new WvIPAddr(p); host->addr = resolvedaddr; host->addrlist.append(resolvedaddr, true); if (addr) *addr = host->addr; if (addrlist) addrlist->append(host->addr, false); do { p = strtok(NULL, " \n"); if (p) { res++; resolvedaddr = new WvIPAddr(p); host->addrlist.append(resolvedaddr, true); if (addrlist) addrlist->append(resolvedaddr, false); } } while (p); host->done = true; } else host->negative = true; if (host->pid && waitpid(host->pid, NULL, 0) == host->pid) host->pid = 0; WVRELEASE(host->loop); host->loop = NULL; // Return as many addresses as we find. return host->negative ? 0 : res; } void WvResolver::clearhost(WvStringParm hostname) { WvResolverHost *host = (*hostmap)[hostname]; if (host) hostmap->remove(host); } void WvResolver::pre_select(WvStringParm hostname, WvStream::SelectInfo &si) { WvResolverHost *host = (*hostmap)[hostname]; if (host) { if (host->loop) host->loop->xpre_select(si, WvStream::SelectRequest(true, false, false)); else si.msec_timeout = 0; // already ready } } bool WvResolver::post_select(WvStringParm hostname, WvStream::SelectInfo &si) { WvResolverHost *host = (*hostmap)[hostname]; if (host) { if (host->loop) return host->loop->xpost_select(si, WvStream::SelectRequest(true, false, false)); else return true; // already ready } return false; }