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