Did some fixes to the opposite hosts table and added some nice features
[darkstat] / dns.c
1 /* darkstat 3
2 * copyright (c) 2001-2011 Emil Mikulic.
3 *
4 * dns.c: synchronous DNS in a child process.
5 *
6 * You may use, modify and redistribute this file under the terms of the
7 * GNU General Public License version 2. (see COPYING.GPL)
8 */
9
10 #include "cdefs.h"
11 #include "conv.h"
12 #include "config.h"
13 #include "decode.h"
14 #include "dns.h"
15 #include "err.h"
16 #include "hosts_db.h"
17 #include "queue.h"
18 #include "str.h"
19 #include "tree.h"
20
21 #include <sys/param.h>
22 #include <sys/socket.h>
23 #include <sys/wait.h>
24 #include <assert.h>
25 #include <errno.h>
26 #include <netdb.h>
27 #include <signal.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 static void dns_main(void) _noreturn_; /* the child process runs this */
33
34 #define CHILD 0 /* child process uses this socket */
35 #define PARENT 1
36 static int sock[2];
37 static pid_t pid = -1;
38
39 struct dns_reply {
40 struct dns_request request;
41 int error; /* for gai_strerror(), or 0 if no error */
42 char name[MAXHOSTNAMELEN];
43 };
44
45 void
46 dns_init(const char *privdrop_user)
47 {
48 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == -1)
49 err(1, "socketpair");
50
51 pid = fork();
52 if (pid == -1)
53 err(1, "fork");
54
55 if (pid == 0) {
56 /* We are the child. */
57 privdrop(NULL /* don't chroot */, privdrop_user);
58 close(sock[PARENT]);
59 sock[PARENT] = -1;
60 daemonize_finish(); /* drop our copy of the lifeline! */
61 if (signal(SIGUSR1, SIG_IGN) == SIG_ERR)
62 errx(1, "signal(SIGUSR1, ignore) failed");
63 dns_main();
64 verbosef("fell out of dns_main()");
65 exit(0);
66 } else {
67 /* We are the parent. */
68 close(sock[CHILD]);
69 sock[CHILD] = -1;
70 fd_set_nonblock(sock[PARENT]);
71 verbosef("DNS child has PID %d", pid);
72 }
73 }
74
75 void
76 dns_stop(void)
77 {
78 if (pid == -1)
79 return; /* no child was started */
80 close(sock[PARENT]);
81 if (kill(pid, SIGINT) == -1)
82 err(1, "kill");
83 verbosef("dns_stop() waiting for child");
84 if (waitpid(pid, NULL, 0) == -1)
85 err(1, "waitpid");
86 verbosef("dns_stop() done waiting for child");
87 }
88
89 struct tree_rec {
90 RB_ENTRY(tree_rec) ptree;
91 struct dns_request request;
92 };
93
94 static int
95 tree_cmp(struct tree_rec *a, struct tree_rec *b)
96 {
97 if (a->request.addr.family != b->request.addr.family)
98 /* Sort IPv4 to the left of IPv6. */
99 return ((a->request.addr.family == IPv4) ? -1 : +1);
100
101 if (a->request.addr.family == IPv4)
102 return (memcmp(&a->request.addr.ip.v4, &b->request.addr.ip.v4, sizeof(a->request.addr.ip.v4)));
103 else {
104 assert(a->request.addr.family == IPv6);
105 return (memcmp(&a->request.addr.ip.v6, &b->request.addr.ip.v6, sizeof(a->request.addr.ip.v6)));
106 }
107 }
108
109 static RB_HEAD(tree_t, tree_rec) ip_tree = RB_INITIALIZER(&tree_rec);
110 /* Quiet warnings. */
111 static struct tree_rec * tree_t_RB_NEXT(struct tree_rec *elm)
112 _unused_;
113 static struct tree_rec * tree_t_RB_MINMAX(struct tree_t *head, int val)
114 _unused_;
115 RB_GENERATE(tree_t, tree_rec, ptree, tree_cmp)
116
117 void
118 dns_queue(const struct dns_request *const request)
119 {
120 struct tree_rec *rec;
121 ssize_t num_w;
122
123 if (pid == -1)
124 return; /* no child was started - we're not doing any DNS */
125
126 if ((request->addr.family != IPv4) && (request->addr.family != IPv6)) {
127 verbosef("dns_queue() for unknown family %d", request->addr.family);
128 return;
129 }
130
131 rec = xmalloc(sizeof(*rec));
132 memcpy(&rec->request, request, sizeof(rec->request));
133
134 if (RB_INSERT(tree_t, &ip_tree, rec) != NULL) {
135 /* Already queued - this happens seldom enough that we don't care about
136 * the performance hit of needlessly malloc()ing. */
137 verbosef("already queued %s", addr_to_str(&request->addr));
138 free(rec);
139 return;
140 }
141
142 num_w = write(sock[PARENT], request, sizeof(*request)); /* won't block */
143 if (num_w == 0)
144 warnx("dns_queue: write: ignoring end of file");
145 else if (num_w == -1)
146 warn("dns_queue: ignoring write error");
147 else if (num_w != sizeof(*request))
148 err(1, "dns_queue: wrote %zu instead of %zu", num_w, sizeof(*request));
149 }
150
151 static void
152 dns_unqueue(const struct dns_request *const request)
153 {
154 struct tree_rec tmp, *rec;
155
156 memcpy(&tmp.request, request, sizeof(tmp.request));
157 if ((rec = RB_FIND(tree_t, &ip_tree, &tmp)) != NULL) {
158 RB_REMOVE(tree_t, &ip_tree, rec);
159 free(rec);
160 }
161 else
162 verbosef("couldn't unqueue %s - not in queue!", addr_to_str(&request->addr));
163 }
164
165 /*
166 * Returns non-zero if result waiting, stores IP and name into given pointers
167 * (name buffer is allocated by dns_poll)
168 */
169 static int
170 dns_get_result(struct dns_request *request, char **name)
171 {
172 struct dns_reply reply;
173 ssize_t numread;
174
175 numread = read(sock[PARENT], &reply, sizeof(reply));
176 if (numread == -1) {
177 if (errno == EAGAIN)
178 return (0); /* no input waiting */
179 else
180 goto error;
181 }
182 if (numread == 0)
183 goto error; /* EOF */
184 if (numread != sizeof(reply))
185 errx(1, "dns_get_result read got %zu, expected %zu",
186 numread, sizeof(reply));
187
188 /* Return successful reply. */
189 memcpy(request, &reply.request, sizeof(*request));
190 if (reply.error != 0) {
191 /* Identify common special cases. */
192 const char *type = "none";
193
194 if (reply.request.addr.family == IPv6) {
195 if (IN6_IS_ADDR_LINKLOCAL(&reply.request.addr.ip.v6))
196 type = "link-local";
197 else if (IN6_IS_ADDR_SITELOCAL(&reply.request.addr.ip.v6))
198 type = "site-local";
199 else if (IN6_IS_ADDR_MULTICAST(&reply.request.addr.ip.v6))
200 type = "multicast";
201 } else { /* AF_INET */
202 if (IN_MULTICAST(reply.request.addr.ip.v4))
203 type = "multicast";
204 }
205 xasprintf(name, "(%s)", type);
206 }
207 else /* Correctly resolved name. */
208 *name = xstrdup(reply.name);
209
210 dns_unqueue(&reply.request);
211 return (1);
212
213 error:
214 warn("dns_get_result: ignoring read error");
215 /* FIXME: re-align to stream? restart dns child? */
216 return (0);
217 }
218
219 void
220 dns_poll(void)
221 {
222 struct dns_request request;
223 char *name;
224 struct bucket *b;
225 struct bucket *o;
226
227
228 if (pid == -1)
229 return; /* no child was started - we're not doing any DNS */
230
231 while (dns_get_result(&request, &name)) {
232 /* push into hosts_db */
233 if (request.has_parent) {
234 verbosef("Find parent %s for opp. host %s",
235 addr_to_str(&request.parent_addr),
236 addr_to_str(&request.addr));
237 b = host_find(&request.parent_addr);
238 if (b == NULL) {
239 verbosef("resolved opp. host %s to %s but it's parent is not in the DB!",
240 addr_to_str(&request.addr), name);
241 return;
242 }
243 o = host_find_host_opp(b,&request.addr);
244 if (o == NULL) {
245 verbosef("resolved opp. host %s to %s but it's not in the DB!",
246 addr_to_str(&request.addr), name);
247 return;
248 }
249 if (o->u.host_opp.dns != NULL) {
250 verbosef("resolved %s to %s but it's already in the DB!",
251 addr_to_str(&request.addr), name);
252 return;
253 }
254 o->u.host_opp.dns=name;
255 }
256 else {
257 b = host_find(&request.addr);
258 if (b == NULL) {
259 verbosef("resolved %s to %s but it's not in the DB!",
260 addr_to_str(&request.addr), name);
261 return;
262 }
263 if (b->u.host.dns != NULL) {
264 verbosef("resolved %s to %s but it's already in the DB!",
265 addr_to_str(&request.addr), name);
266 return;
267 }
268 b->u.host.dns=name;
269 }
270 }
271 }
272
273 /* ------------------------------------------------------------------------ */
274
275 struct qitem {
276 STAILQ_ENTRY(qitem) entries;
277 struct dns_request request;
278 };
279
280 STAILQ_HEAD(qhead, qitem) queue = STAILQ_HEAD_INITIALIZER(queue);
281
282 static void
283 enqueue(const struct dns_request *const request)
284 {
285 struct qitem *i;
286
287 i = xmalloc(sizeof(*i));
288 memcpy(&i->request, request, sizeof(i->request));
289 STAILQ_INSERT_TAIL(&queue, i, entries);
290 verbosef("DNS: enqueued %s", addr_to_str(&request->addr));
291 }
292
293 /* Return non-zero and populate <request> pointer if queue isn't empty. */
294 static int
295 dequeue(struct dns_request *request)
296 {
297 struct qitem *i;
298
299 i = STAILQ_FIRST(&queue);
300 if (i == NULL)
301 return (0);
302 STAILQ_REMOVE_HEAD(&queue, entries);
303 memcpy(request, &i->request, sizeof(*request));
304 free(i);
305 verbosef("DNS: dequeued %s", addr_to_str(&request->addr));
306 return 1;
307 }
308
309 static void
310 xwrite(const int d, const void *buf, const size_t nbytes)
311 {
312 ssize_t ret = write(d, buf, nbytes);
313
314 if (ret == -1)
315 err(1, "write");
316 if (ret != (ssize_t)nbytes)
317 err(1, "wrote %d bytes instead of all %d bytes", (int)ret, (int)nbytes);
318 }
319
320 static void
321 dns_main(void)
322 {
323 struct dns_request request;
324
325 #ifdef HAVE_SETPROCTITLE
326 setproctitle("DNS child");
327 #endif
328 fd_set_nonblock(sock[CHILD]);
329 verbosef("DNS child entering main DNS loop");
330 for (;;) {
331 int blocking;
332
333 if (STAILQ_EMPTY(&queue)) {
334 blocking = 1;
335 fd_set_block(sock[CHILD]);
336 verbosef("entering blocking read loop");
337 } else {
338 blocking = 0;
339 fd_set_nonblock(sock[CHILD]);
340 verbosef("non-blocking poll");
341 }
342 for (;;) {
343 /* While we have input to process... */
344 ssize_t numread = read(sock[CHILD], &request, sizeof(request));
345 if (numread == 0)
346 exit(0); /* end of file, nothing more to do here. */
347 if (numread == -1) {
348 if (!blocking && (errno == EAGAIN))
349 break; /* ran out of input */
350 /* else error */
351 err(1, "DNS: read failed");
352 }
353 if (numread != sizeof(request))
354 err(1, "DNS: read got %zu bytes, expecting %zu",
355 numread, sizeof(request));
356 enqueue(&request);
357 if (blocking) {
358 /* After one blocking read, become non-blocking so that when we
359 * run out of input we fall through to queue processing.
360 */
361 blocking = 0;
362 fd_set_nonblock(sock[CHILD]);
363 }
364 }
365
366 /* Process queue. */
367 if (dequeue(&request)) {
368 struct dns_reply reply;
369 struct sockaddr_in sin;
370 struct sockaddr_in6 sin6;
371 struct hostent *he;
372 char host[NI_MAXHOST];
373 int ret, flags;
374
375 memcpy(&reply.request, &request, sizeof(reply.request));
376 flags = NI_NAMEREQD;
377 # ifdef NI_IDN
378 flags |= NI_IDN;
379 # endif
380 switch (request.addr.family) {
381 case IPv4:
382 verbosef("Get IPv4 DNS");
383 sin.sin_family = AF_INET;
384 sin.sin_addr.s_addr = request.addr.ip.v4;
385 ret = getnameinfo((struct sockaddr *) &sin, sizeof(sin),
386 host, sizeof(host), NULL, 0, flags);
387
388 verbosef("Return code: %i",ret);
389 if (ret == EAI_FAMILY) {
390 verbosef("Unknown family, trying gethostbyname instead.");
391 he = gethostbyaddr((char *)&(sin.sin_addr.s_addr), sizeof(sin.sin_addr.s_addr), sin.sin_family);
392 /* On some platforms (for example Linux with GLIBC 2.3.3), h_errno
393 * will be non-zero here even though the lookup succeeded.
394 */
395 verbosef("Return code: %i",h_errno);
396 if (he == NULL) {
397 ret = h_errno;
398 } else {
399 ret=0;
400 assert(sizeof(host) > sizeof(char *)); /* not just a ptr */
401 strlcpy(host, he->h_name, sizeof(host));
402 verbosef("Host from gethostbyname: %s",host);
403 }
404 }
405 verbosef("Retrieved IPv4 DNS");
406
407 break;
408 case IPv6:
409 verbosef("Get IPv6 DNS");
410 sin6.sin6_family = AF_INET6;
411 memcpy(&sin6.sin6_addr, &request.addr.ip.v6, sizeof(sin6.sin6_addr));
412 ret = getnameinfo((struct sockaddr *) &sin6, sizeof(sin6),
413 host, sizeof(host), NULL, 0, flags);
414 break;
415 default:
416 verbosef("PROBLEM!!! Unknown IPv? family or family not set");
417 ret = EAI_FAMILY;
418 }
419
420 if (ret != 0) {
421 reply.name[0] = '\0';
422 reply.error = ret;
423 } else {
424 assert(sizeof(reply.name) > sizeof(char *)); /* not just a ptr */
425 strlcpy(reply.name, host, sizeof(reply.name));
426 reply.error = 0;
427 }
428 fd_set_block(sock[CHILD]);
429 xwrite(sock[CHILD], &reply, sizeof(reply));
430 verbosef("DNS: %s is \"%s\".", addr_to_str(&reply.request.addr),
431 (ret == 0) ? reply.name : gai_strerror(ret));
432 }
433 }
434 }
435
436 /* vim:set ts=3 sw=3 tw=78 expandtab: */