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