WvStreams
wvresolver.cc
1/*
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
4 *
5 * DNS name resolver with support for background lookups.
6 */
7#include "wvresolver.h"
8#include "wvloopback.h"
9#include "wvaddr.h"
10#include "wvtcp.h"
11#include <sys/types.h>
12#include <signal.h>
13#include <time.h>
14
15#ifdef _WIN32
16#define WVRESOLVER_SKIP_FORK
17typedef int pid_t;
18#define kill(a,b)
19#define waitpid(a,b,c) (0)
20#define alarm(a)
21#include "streams.h"
22#else
23#include "wvautoconf.h"
24#include "wvfork.h"
25#include <netdb.h>
26#include <sys/wait.h>
27#endif
28
30{
31public:
32 WvString name;
33 WvIPAddr *addr;
34 WvIPAddrList addrlist;
35 bool done, negative;
36 pid_t pid;
37 WvLoopback *loop;
38 time_t last_tried;
39
40 WvResolverHost(WvStringParm _name) : name(_name)
41 { init(); addr = NULL; }
43 {
44 WVRELEASE(loop);
45#ifndef WVRESOLVER_SKIP_FORK
46 if (pid && pid != -1)
47 {
48 kill(pid, SIGKILL);
49 pid_t rv;
50 // In case a signal is in the process of being delivered...
51 while ((rv = waitpid(pid, NULL, 0)) != pid)
52 if (rv == -1 && errno != EINTR)
53 break;
54 }
55#endif
56 }
57protected:
59 { init(); }
60 void init()
61 { done = negative = false;
62 pid = 0; loop = NULL; last_tried = time(NULL); }
63};
64
66{
67public:
69 { addr = _addr; }
70};
71
72// static members of WvResolver
73int WvResolver::numresolvers = 0;
74WvResolverHostDict *WvResolver::hostmap = NULL;
75WvResolverAddrDict *WvResolver::addrmap = NULL;
76
77
78// function that runs in a child task
79
80static void namelookup(const char *name, WvLoopback *loop)
81{
82 struct hostent *he;
83
84 // wait up to one minute...
85 alarm(60);
86
87 for (int count = 0; count < 10; count++)
88 {
89 he = gethostbyname(name);
90 if (he)
91 {
92 char **addr = he->h_addr_list;
93 while (*addr != NULL)
94 {
95 loop->print("%s ", WvIPAddr((unsigned char *)(*addr)));
96 addr++;
97 }
98 loop->print("\n");
99 alarm(0);
100 return;
101 }
102
103 // not found (yet?)
104
105 if (h_errno != TRY_AGAIN)
106 {
107 alarm(0);
108 return; // not found; blank output
109 }
110
111 // avoid spinning in a tight loop.
112 //
113 // sleep() is documented to possibly mess with the alarm(), so we
114 // have to make sure to reset the alarm here. That's a shame,
115 // because otherwise it would timeout nicely after 60 seconds
116 // overall, not 60 seconds per request.
117 sleep(1);
118
119 alarm(60);
120 }
121}
122
123
124WvResolver::WvResolver()
125{
126 numresolvers++;
127 if (!hostmap)
128 hostmap = new WvResolverHostDict(10);
129 if (!addrmap)
130 addrmap = new WvResolverAddrDict(10);
131}
132
133
134WvResolver::~WvResolver()
135{
136 numresolvers--;
137 if (numresolvers <= 0 && hostmap && addrmap)
138 {
139 delete hostmap;
140 delete addrmap;
141 hostmap = NULL;
142 addrmap = NULL;
143 }
144}
145
146
147// returns >0 on success, 0 on not found, -1 on timeout
148// If addr==NULL, this just tests to see if the name exists.
149int WvResolver::findaddr(int msec_timeout, WvStringParm name,
150 WvIPAddr const **addr,
151 WvIPAddrList *addrlist)
152{
153 WvResolverHost *host;
154 time_t now = time(NULL);
155 int res = 0;
156
157 host = (*hostmap)[name];
158
159 if (host)
160 {
161 // refresh successes after 5 minutes, retry failures every 1 minute
162 if ((host->done && host->last_tried + 60*5 < now)
163 || (!host->done && host->last_tried + 60 < now))
164 {
165 // expired from the cache. Force a repeat lookup below...
166 hostmap->remove(host);
167 host = NULL;
168 }
169 else if (host->done)
170 {
171 // entry exists, is marked done, and hasn't expired yet. Return
172 // the cached value.
173 if (addr)
174 *addr = host->addr;
175 if (addrlist)
176 {
177 WvIPAddrList::Iter i(host->addrlist);
178 for (i.rewind(); i.next(); )
179 {
180 addrlist->append(i.ptr(), false);
181 res++;
182 }
183 }
184 else
185 res = 1;
186 return res;
187 }
188 else if (host->negative)
189 {
190 // the entry is in the cache, but the response was negative:
191 // the name doesn't exist.
192 return 0;
193 }
194
195 // if we get here, 'host' either exists (still in progress)
196 // or is NULL (need to start again).
197 }
198
199 if (!host)
200 {
201 // nothing matches this hostname in the cache. Create a new entry,
202 // and start a new lookup.
203 host = new WvResolverHost(name);
204 hostmap->add(host, true);
205
206 host->loop = new WvLoopback();
207
208#ifdef WVRESOLVER_SKIP_FORK
209 // background name resolution doesn't work when debugging with gdb!
210 namelookup(name, host->loop);
211#else
212 // fork a subprocess so we don't block while doing the DNS lookup.
213
214 // close everything but host->loop in the subprocess.
215 host->pid = wvfork(host->loop->getrfd(), host->loop->getwfd());
216
217 if (!host->pid)
218 {
219 // child process
220 host->loop->noread();
221 namelookup(name, host->loop);
222 _exit(1);
223 }
224#endif
225
226 // parent process
227 host->loop->nowrite();
228 }
229
230#ifndef WVRESOLVER_SKIP_FORK
231
232 // if we get here, we are the parent task waiting for the child.
233
234 do
235 {
236 if (waitpid(host->pid, NULL, WNOHANG) == host->pid)
237 host->pid = 0;
238
239 if (!host->loop->select(msec_timeout < 0 ? 100 : msec_timeout,
240 true, false))
241 {
242 if (host->pid)
243 {
244 if (msec_timeout >= 0)
245 return -1; // timeout, but still trying
246 }
247 else
248 {
249 // the child is dead. Clean up our stream, too.
250 WVRELEASE(host->loop);
251 host->loop = NULL;
252 host->negative = true;
253 return 0; // exited while doing search
254 }
255 }
256 else
257 break;
258 } while (host->pid && msec_timeout < 0); // repeat if unlimited timeout!
259#endif
260
261 // data coming in!
262 char *line;
263
264 do
265 {
266 line = host->loop->blocking_getline(-1);
267 } while (!line && host->loop->isok());
268
269 if (line && line[0] != 0)
270 {
271 res = 1;
272 WvIPAddr *resolvedaddr;
273 char *p;
274 p = strtok(line, " \n");
275 resolvedaddr = new WvIPAddr(p);
276 host->addr = resolvedaddr;
277 host->addrlist.append(resolvedaddr, true);
278 if (addr)
279 *addr = host->addr;
280 if (addrlist)
281 addrlist->append(host->addr, false);
282 do
283 {
284 p = strtok(NULL, " \n");
285 if (p)
286 {
287 res++;
288 resolvedaddr = new WvIPAddr(p);
289 host->addrlist.append(resolvedaddr, true);
290 if (addrlist)
291 addrlist->append(resolvedaddr, false);
292 }
293 } while (p);
294 host->done = true;
295 }
296 else
297 host->negative = true;
298
299 if (host->pid && waitpid(host->pid, NULL, 0) == host->pid)
300 host->pid = 0;
301 WVRELEASE(host->loop);
302 host->loop = NULL;
303
304 // Return as many addresses as we find.
305 return host->negative ? 0 : res;
306}
307
308void WvResolver::clearhost(WvStringParm hostname)
309{
310 WvResolverHost *host = (*hostmap)[hostname];
311 if (host)
312 hostmap->remove(host);
313}
314
315
317{
318 WvResolverHost *host = (*hostmap)[hostname];
319
320 if (host)
321 {
322 if (host->loop)
323 host->loop->xpre_select(si,
324 WvStream::SelectRequest(true, false, false));
325 else
326 si.msec_timeout = 0; // already ready
327 }
328}
329
330
332{
333 WvResolverHost *host = (*hostmap)[hostname];
334
335 if (host)
336 {
337 if (host->loop)
338 return host->loop->xpost_select(si,
339 WvStream::SelectRequest(true, false, false));
340 else
341 return true; // already ready
342 }
343 return false;
344}
A WvFastString acts exactly like a WvString, but can take (const char *) strings without needing to a...
Definition: wvstring.h:94
int getrfd() const
Returns the Unix file descriptor for reading from this stream.
Definition: wvfdstream.h:63
virtual bool isok() const
return true if the stream is actually usable right now
Definition: wvfdstream.cc:134
int getwfd() const
Returns the Unix file descriptor for writing to this stream.
Definition: wvfdstream.h:70
An IP address is made up of a "dotted quad" – four decimal numbers in the form www....
Definition: wvaddr.h:250
Implementation of a WvLoopback stream.
Definition: wvloopback.h:17
bool post_select(WvStringParm hostname, WvStream::SelectInfo &si)
determines whether the resolving process is complete.
Definition: wvresolver.cc:331
int findaddr(int msec_timeout, WvStringParm name, WvIPAddr const **addr, WvIPAddrList *addrlist=NULL)
Return -1 on timeout, or the number of addresses found, which may be 0 if the address does not exist.
Definition: wvresolver.cc:149
void pre_select(WvStringParm hostname, WvStream::SelectInfo &si)
add all of our waiting fds to an fd_set for use with select().
Definition: wvresolver.cc:316
bool xpost_select(SelectInfo &si, const SelectRequest &r)
Like post_select(), but still exists even if you override the other post_select() in a subclass.
Definition: wvstream.h:339
char * blocking_getline(time_t wait_msec, int separator='\n', int readahead=1024)
This is a version of getline() that allows you to block for more data to arrive.
Definition: wvstream.cc:602
virtual void nowrite()
Shuts down the writing side of the stream.
Definition: wvstream.cc:576
bool select(time_t msec_timeout)
Return true if any of the requested features are true on the stream.
Definition: wvstream.h:376
void xpre_select(SelectInfo &si, const SelectRequest &r)
Like pre_select(), but still exists even if you override the other pre_select() in a subclass.
Definition: wvstream.h:318
virtual void noread()
Shuts down the reading side of the stream.
Definition: wvstream.cc:569
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:330
the data structure used by pre_select()/post_select() and internally by select().
Definition: iwvstream.h:50
A SelectRequest is a convenient way to remember what we want to do to a particular stream: read from ...
Definition: iwvstream.h:34
Provides support for forking processes.
pid_t wvfork(int dontclose1=-1, int dontclose2=-1)
wvfork() just runs fork(), but it closes all file descriptors that are flagged close-on-exec,...
Definition: wvfork.cc:71
WvString hostname()
Do gethostname() without a fixed-length buffer.
Definition: strutils.cc:870