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
17 typedef 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 {
31 public:
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  }
57 protected:
59  { init(); }
60  void init()
61  { done = negative = false;
62  pid = 0; loop = NULL; last_tried = time(NULL); }
63 };
64 
66 {
67 public:
68  WvResolverAddr(WvIPAddr *_addr)
69  { addr = _addr; }
70 };
71 
72 // static members of WvResolver
73 int WvResolver::numresolvers = 0;
74 WvResolverHostDict *WvResolver::hostmap = NULL;
75 WvResolverAddrDict *WvResolver::addrmap = NULL;
76 
77 
78 // function that runs in a child task
79 
80 static 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 
124 WvResolver::WvResolver()
125 {
126  numresolvers++;
127  if (!hostmap)
128  hostmap = new WvResolverHostDict(10);
129  if (!addrmap)
130  addrmap = new WvResolverAddrDict(10);
131 }
132 
133 
134 WvResolver::~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.
149 int 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 
308 void 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 }
WvResolver::pre_select
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
WvFdStream::isok
virtual bool isok() const
return true if the stream is actually usable right now
Definition: wvfdstream.cc:134
WvStream::blocking_getline
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
WvStream::select
bool select(time_t msec_timeout)
Return true if any of the requested features are true on the stream.
Definition: wvstream.h:376
wvfork
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
WvFdStream::getrfd
int getrfd() const
Returns the Unix file descriptor for reading from this stream.
Definition: wvfdstream.h:63
WvResolver::post_select
bool post_select(WvStringParm hostname, WvStream::SelectInfo &si)
determines whether the resolving process is complete.
Definition: wvresolver.cc:331
WvString
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
WvResolverAddr
Definition: wvresolver.cc:65
IWvStream::SelectInfo
the data structure used by pre_select()/post_select() and internally by select().
Definition: iwvstream.h:50
WvStream::xpre_select
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
wvfork.h
WvResolver::findaddr
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
IWvStream::SelectRequest
A SelectRequest is a convenient way to remember what we want to do to a particular stream: read from ...
Definition: iwvstream.h:34
WvFdStream::getwfd
int getwfd() const
Returns the Unix file descriptor for writing to this stream.
Definition: wvfdstream.h:70
WvIPAddr
An IP address is made up of a "dotted quad" – four decimal numbers in the form www....
Definition: wvaddr.h:249
WvStream::nowrite
virtual void nowrite()
Shuts down the writing side of the stream.
Definition: wvstream.cc:576
hostname
WvString hostname()
Do gethostname() without a fixed-length buffer.
Definition: strutils.cc:870
WvLoopback
Implementation of a WvLoopback stream.
Definition: wvloopback.h:16
WvStream::xpost_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.
Definition: wvstream.h:339
WvResolverHost
Definition: wvresolver.cc:29
WvStream::noread
virtual void noread()
Shuts down the reading side of the stream.
Definition: wvstream.cc:569