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...
int getrfd() const
Returns the Unix file descriptor for reading from this stream.
virtual bool isok() const
return true if the stream is actually usable right now
int getwfd() const
Returns the Unix file descriptor for writing to this stream.
An IP address is made up of a "dotted quad" – four decimal numbers in the form www....
Implementation of a WvLoopback stream.
bool post_select(WvStringParm hostname, WvStream::SelectInfo &si)
determines whether the resolving process is complete.
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.
void pre_select(WvStringParm hostname, WvStream::SelectInfo &si)
add all of our waiting fds to an fd_set for use with select().
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.
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.
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.
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.
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
the data structure used by pre_select()/post_select() and internally by select().
A SelectRequest is a convenient way to remember what we want to do to a particular stream: read from ...