WvStreams
|
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 }