root/src/noit_check_resolver.c

Revision b553f9a4d73487ee85e5cfedf2cd67352b4d5e6b, 15.1 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 3 years ago)

refs #340

This is a rather major change. Targets can now be hostnames in addition
to IP addresses so long as //checks/@resolve_targets is not false.

If a target is entered that does not look like an IP (inet_pton fails)
then the check is marked as needing resolution NP_RESOLVE.

A passive look-aside dns cache has been implemented in noit_check_resolver.c
that is used to power the whole system and some reasonably simply console
command have been provided:

show dns_cache [fqdn1 [fqdn2]] -- shows the state
dns_cache <fqdn> [fqdn2 [fqdn3]] -- submits for lookup
no dns_cache <fqdn> [fqdn2 [fqdn3]] -- purges from cache

The big change is that modules that relied on check->target to be an IP
address are going to explode when names are provided. Instead, modules
should now use target for the provided target (possibly a FQDN) and use
target_ip (check->target_ip or check.target_ip) for a resolved IP address
and also check for the case of empty string: (check->target_ip[0] == '\0')
for the case that resolution has failed. In lua, the target_ip will be
presented as nil in the case of failed name resolution.

I believe I've updated all necessary components of the system for this to
"just work" but people that have implemented their own check should update
them before they elect to use non-IP addresses as targets.

The dns subsystem supports both IPv4 and IPv6, but currently prefers IPv4
addresses if any are present.

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2011, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38 #include <time.h>
39 #include <ctype.h>
40 #include <assert.h>
41 #include <netinet/in.h>
42 #include <arpa/inet.h>
43
44 #include "eventer/eventer.h"
45 #include "utils/noit_log.h"
46 #include "utils/noit_skiplist.h"
47 #include "udns/udns.h"
48 #include "noit_console.h"
49
50 #define MAX_RR 256
51 #define DEFAULT_FAILED_TTL 300
52 #define DEFAULT_PURGE_AGE  1200 /* 20 minutes */
53
54 static struct dns_ctx *dns_ctx;
55 static noit_skiplist nc_dns_cache;
56 static eventer_t dns_cache_timeout = NULL;
57
58 typedef struct {
59   time_t last_needed;
60   time_t last_updated;
61   noit_boolean lookup_inflight_v4;
62   noit_boolean lookup_inflight_v6;
63   char *target;
64   unsigned char dn[DNS_MAXDN];
65   time_t ttl;
66   int ip4_cnt;
67   char **ip4;
68   int ip6_cnt;
69   char **ip6;
70 } dns_cache_node;
71
72 void dns_cache_node_free(void *vn) {
73   dns_cache_node *n = vn;
74   int i;
75   if(!n) return;
76   if(n->target) free(n->target);
77   for(i=0;i<n->ip4_cnt;i++) if(n->ip4[i]) free(n->ip4[i]);
78   for(i=0;i<n->ip6_cnt;i++) if(n->ip6[i]) free(n->ip6[i]);
79   if(n->ip4) free(n->ip4);
80   if(n->ip6) free(n->ip6);
81   free(n);
82 }
83
84 static int name_lookup(const void *av, const void *bv) {
85   const dns_cache_node *a = av;
86   const dns_cache_node *b = bv;
87   return strcmp(a->target, b->target);
88 }
89 static int name_lookup_k(const void *akv, const void *bv) {
90   const char *ak = akv;
91   const dns_cache_node *b = bv;
92   return strcmp(ak, b->target);
93 }
94 static int refresh_idx(const void *av, const void *bv) {
95   const dns_cache_node *a = av;
96   const dns_cache_node *b = bv;
97   if((a->last_updated + a->ttl) < (b->last_updated + b->ttl)) return -1;
98   return 1;
99 }
100 static int refresh_idx_k(const void *akv, const void *bv) {
101   time_t f = (time_t) akv;
102   const dns_cache_node *b = bv;
103   if(f < (b->last_updated + b->ttl)) return -1;
104   return 1;
105 }
106
107 void noit_check_resolver_remind(const char *target) {
108   dns_cache_node *n;
109   if(!target) return;
110   n = noit_skiplist_find(&nc_dns_cache, target, NULL);
111   if(n != NULL) {
112     n->last_needed = time(NULL);
113     return;
114   }
115   n = calloc(1, sizeof(*n));
116   n->target = strdup(target);
117   n->last_needed = time(NULL);
118   noit_skiplist_insert(&nc_dns_cache, n);
119 }
120
121
122 int noit_check_resolver_fetch(const char *target, char *buff, int len,
123                               uint8_t prefer_family) {
124   int i;
125   uint8_t progression[2];
126   dns_cache_node *n;
127
128   buff[0] = '\0';
129   if(!target) return -1;
130   progression[0] = prefer_family;
131   progression[1] = (prefer_family == AF_INET) ? AF_INET6 : AF_INET;
132
133   n = noit_skiplist_find(&nc_dns_cache, target, NULL);
134   if(n != NULL) {
135     int rv;
136     if(n->last_updated == 0) return -1; /* not resolved yet */
137     rv = n->ip4_cnt + n->ip6_cnt;
138     for(i=0; i<2; i++) {
139       switch(progression[i]) {
140         case AF_INET:
141           if(n->ip4_cnt > 0) {
142             strlcpy(buff, n->ip4[0], len);
143             return rv;
144           }
145         case AF_INET6:
146           if(n->ip6_cnt > 0) {
147             strlcpy(buff, n->ip6[0], len);
148             return rv;
149           }
150       }
151     }
152     return rv;
153   }
154   return -1;
155 }
156
157 static void blank_update_v4(dns_cache_node *n) {
158   int i;
159   for(i=0;i<n->ip4_cnt;i++) if(n->ip4[i]) free(n->ip4[i]);
160   if(n->ip4) free(n->ip4);
161   n->ip4 = NULL;
162   n->ip4_cnt = 0;
163   noit_skiplist_remove(&nc_dns_cache, n->target, NULL);
164   n->last_updated = time(NULL);
165   if(n->ttl == 0) n->ttl = DEFAULT_FAILED_TTL;
166   n->lookup_inflight_v4 = noit_false;
167   noit_skiplist_insert(&nc_dns_cache, n);
168 }
169 static void blank_update_v6(dns_cache_node *n) {
170   int i;
171   for(i=0;i<n->ip6_cnt;i++) if(n->ip6[i]) free(n->ip6[i]);
172   if(n->ip6) free(n->ip6);
173   n->ip6 = NULL;
174   n->ip6_cnt = 0;
175   noit_skiplist_remove(&nc_dns_cache, n->target, NULL);
176   n->last_updated = time(NULL);
177   if(n->ttl == 0) n->ttl = DEFAULT_FAILED_TTL;
178   n->lookup_inflight_v6 = noit_false;
179   noit_skiplist_insert(&nc_dns_cache, n);
180 }
181 static void blank_update(dns_cache_node *n) {
182   blank_update_v4(n);
183   blank_update_v6(n);
184 }
185
186 static int dns_cache_callback(eventer_t e, int mask, void *closure,
187                               struct timeval *now) {
188   struct dns_ctx *ctx = closure;
189   dns_ioevent(ctx, now->tv_sec);
190   return EVENTER_READ | EVENTER_EXCEPTION;
191 }
192
193 static int dns_invoke_timeouts(eventer_t e, int mask, void *closure,
194                                struct timeval *now) {
195   struct dns_ctx *ctx = closure;
196   dns_timeouts(ctx, 0, now->tv_sec);
197   return 0;
198 }
199
200 static void dns_cache_utm_fn(struct dns_ctx *ctx, int timeout, void *data) {
201   eventer_t e = NULL, newe = NULL;
202   if(ctx == NULL) e = eventer_remove(dns_cache_timeout);
203   else {
204     if(timeout < 0) e = eventer_remove(dns_cache_timeout);
205     else {
206       newe = eventer_alloc();
207       newe->mask = EVENTER_TIMER;
208       newe->callback = dns_invoke_timeouts;
209       newe->closure = dns_ctx;
210       gettimeofday(&newe->whence, NULL);
211       newe->whence.tv_sec += timeout;
212     }
213   }
214   if(e) eventer_free(e);
215   if(newe) eventer_add(newe);
216   dns_cache_timeout = newe;
217 }
218
219 static void dns_cache_resolve(struct dns_ctx *ctx, void *result, void *data,
220                               enum dns_type rtype) {
221   int i, ttl, acnt, r = dns_status(ctx);
222   dns_cache_node *n = data;
223   unsigned char idn[DNS_MAXDN], dn[DNS_MAXDN];
224   struct dns_parse p;
225   struct dns_rr rr;
226   unsigned nrr;
227   char **answers;
228   const unsigned char *pkt, *cur, *end;
229
230   if(!result) goto blank;
231
232   dns_dntodn(n->dn, idn, sizeof(idn));
233
234   pkt = result; end = pkt + r; cur = dns_payload(pkt);
235   dns_getdn(pkt, &cur, end, dn, sizeof(dn));
236   dns_initparse(&p, NULL, pkt, cur, end);
237   p.dnsp_qcls = 0;
238   p.dnsp_qtyp = 0;
239   nrr = 0;
240   ttl = 0;
241
242   while((r = dns_nextrr(&p, &rr)) > 0) {
243     if (!dns_dnequal(dn, rr.dnsrr_dn)) continue;
244     if (DNS_C_IN == rr.dnsrr_cls && rtype == rr.dnsrr_typ) ++nrr;
245     else if (rr.dnsrr_typ == DNS_T_CNAME && !nrr) {
246       if (dns_getdn(pkt, &rr.dnsrr_dptr, end,
247                     p.dnsp_dnbuf, sizeof(p.dnsp_dnbuf)) <= 0 ||
248           rr.dnsrr_dptr != rr.dnsrr_dend) {
249         break;
250       }
251       else {
252         if(ttl == 0) ttl = rr.dnsrr_ttl;
253         dns_dntodn(p.dnsp_dnbuf, idn, sizeof(idn));
254       }
255     }
256   }
257   if(!r && !nrr) goto blank;
258
259   dns_rewind(&p, NULL);
260   p.dnsp_qcls = DNS_C_IN;
261   p.dnsp_qtyp = rtype;
262   answers = calloc(nrr, sizeof(*answers));
263   acnt = 0;
264   while(dns_nextrr(&p, &rr) && nrr < MAX_RR) {
265     char buff[INET6_ADDRSTRLEN];
266     if (!dns_dnequal(idn, rr.dnsrr_dn)) continue;
267     if (p.dnsp_rrl && !rr.dnsrr_dn[0] && rr.dnsrr_typ == DNS_T_OPT) continue;
268     if (rtype == rr.dnsrr_typ) {
269       if (ttl == 0) ttl = rr.dnsrr_ttl;
270       switch(rr.dnsrr_typ) {
271         case DNS_T_A:
272           if(rr.dnsrr_dsz != 4) continue;
273           inet_ntop(AF_INET, rr.dnsrr_dptr, buff, sizeof(buff));
274           answers[acnt++] = strdup(buff);
275           break;
276         case DNS_T_AAAA:
277           if(rr.dnsrr_dsz != 16) continue;
278           inet_ntop(AF_INET6, rr.dnsrr_dptr, buff, sizeof(buff));
279           answers[acnt++] = strdup(buff);
280           break;
281         default:
282           break;
283       }
284     }
285   }
286
287   n->ttl = ttl;
288   if(rtype == DNS_T_A) {
289     for(i=0;i<n->ip4_cnt;i++) if(n->ip4[i]) free(n->ip4[i]);
290     if(n->ip4) free(n->ip4);
291     n->ip4_cnt = acnt;
292     n->ip4 = answers;
293     n->lookup_inflight_v4 = noit_false;
294   }
295   else if(rtype == DNS_T_AAAA) {
296     for(i=0;i<n->ip6_cnt;i++) if(n->ip6[i]) free(n->ip6[i]);
297     if(n->ip6) free(n->ip6);
298     n->ip6_cnt = acnt;
299     n->ip6 = answers;
300     n->lookup_inflight_v6 = noit_false;
301   }
302   noit_skiplist_remove(&nc_dns_cache, n->target, NULL);
303   n->last_updated = time(NULL);
304   noit_skiplist_insert(&nc_dns_cache, n);
305   return;
306
307  blank:
308   if(rtype == DNS_T_A) blank_update_v4(n);
309   if(rtype == DNS_T_AAAA) blank_update_v6(n);
310   return;
311 }
312 static void dns_cache_resolve_v4(struct dns_ctx *ctx, void *result, void *data) {
313   dns_cache_resolve(ctx, result, data, DNS_T_A);
314 }
315 static void dns_cache_resolve_v6(struct dns_ctx *ctx, void *result, void *data) {
316   dns_cache_resolve(ctx, result, data, DNS_T_AAAA);
317 }
318
319 void noit_check_resolver_maintain() {
320   time_t now;
321   noit_skiplist *tlist;
322   noit_skiplist_node *sn;
323
324   now = time(NULL);
325   sn = noit_skiplist_getlist(nc_dns_cache.index);
326   tlist = sn->data;
327   sn = noit_skiplist_getlist(tlist);
328   while(sn) {
329     dns_cache_node *n = sn->data;
330     noit_skiplist_next(tlist, &sn); /* move forward */
331     /* remove if needed */
332     if(n->last_updated + n->ttl > now) break;
333     if(n->last_needed + DEFAULT_PURGE_AGE < now &&
334        !(n->lookup_inflight_v4 || n->lookup_inflight_v6))
335       noit_skiplist_remove(&nc_dns_cache, n->target, dns_cache_node_free);
336     else {
337       int abs;
338       if(!dns_ptodn(n->target, strlen(n->target),
339                     n->dn, sizeof(n->dn), &abs)) {
340         blank_update(n);
341       }
342       else {
343         if(!n->lookup_inflight_v4) {
344           n->lookup_inflight_v4 = noit_true;
345           if(!dns_submit_dn(dns_ctx, n->dn, DNS_C_IN, DNS_T_A,
346                             abs | DNS_NOSRCH, NULL, dns_cache_resolve_v4, n))
347             blank_update_v4(n);
348           else
349             dns_timeouts(dns_ctx, -1, now);
350         }
351         if(!n->lookup_inflight_v6) {
352           n->lookup_inflight_v6 = noit_true;
353           if(!dns_submit_dn(dns_ctx, n->dn, DNS_C_IN, DNS_T_AAAA,
354                             abs | DNS_NOSRCH, NULL, dns_cache_resolve_v6, n))
355             blank_update_v6(n);
356           else
357             dns_timeouts(dns_ctx, -1, now);
358         }
359       }
360       noitL(noit_debug, "Firing lookup for '%s'\n", n->target);
361       continue;
362     }
363   }
364 }
365
366 int noit_check_resolver_loop(eventer_t e, int mask, void *c,
367                              struct timeval *now) {
368   noit_check_resolver_maintain();
369   eventer_add_in_s_us(noit_check_resolver_loop, NULL, 1, 0);
370   return 0;
371 }
372
373 static int
374 nc_print_dns_cache_node(noit_console_closure_t ncct,
375                         const char *target, dns_cache_node *n) {
376   nc_printf(ncct, "==== %s ====\n", target);
377   if(!n) nc_printf(ncct, "NOT FOUND\n");
378   else {
379     int i;
380     time_t now = time(NULL);
381     nc_printf(ncct, "%16s: %ds ago\n", "last needed", now - n->last_needed);
382     nc_printf(ncct, "%16s: %ds ago\n", "resolved", now - n->last_updated);
383     nc_printf(ncct, "%16s: %ds\n", "ttl", n->ttl);
384     if(n->lookup_inflight_v4) nc_printf(ncct, "actively resolving A RRs\n");
385     if(n->lookup_inflight_v6) nc_printf(ncct, "actively resolving AAAA RRs\n");
386     for(i=0;i<n->ip4_cnt;i++)
387       nc_printf(ncct, "%17s %s\n", i?"":"IPv4:", n->ip4[i]);
388     for(i=0;i<n->ip6_cnt;i++)
389       nc_printf(ncct, "%17s %s\n", i?"":"IPv6:", n->ip6[i]);
390   }
391   return 0;
392 }
393 static int
394 noit_console_show_dns_cache(noit_console_closure_t ncct,
395                             int argc, char **argv,
396                             noit_console_state_t *dstate,
397                             void *closure) {
398   int i;
399
400   if(argc == 0) {
401     noit_skiplist_node *sn;
402     for(sn = noit_skiplist_getlist(&nc_dns_cache); sn;
403         noit_skiplist_next(&nc_dns_cache, &sn)) {
404       dns_cache_node *n = (dns_cache_node *)sn->data;
405       nc_print_dns_cache_node(ncct, n->target, n);
406     }
407   }
408   for(i=0;i<argc;i++) {
409     dns_cache_node *n;
410     n = noit_skiplist_find(&nc_dns_cache, argv[i], NULL);
411     nc_print_dns_cache_node(ncct, argv[i], n);
412   }
413   return 0;
414 }
415 static int
416 noit_console_manip_dns_cache(noit_console_closure_t ncct,
417                              int argc, char **argv,
418                              noit_console_state_t *dstate,
419                              void *closure) {
420   int i;
421   if(argc == 0) {
422     nc_printf(ncct, "dns_cache what?\n");
423     return 0;
424   }
425   if(closure == NULL) {
426     /* adding */
427     for(i=0;i<argc;i++) {
428       dns_cache_node *n;
429       if(NULL != (n = noit_skiplist_find(&nc_dns_cache, argv[i], NULL))) {
430         nc_printf(ncct, " == Already in system ==\n");
431         nc_print_dns_cache_node(ncct, argv[i], n);
432       }
433       else {
434         nc_printf(ncct, "%s submitted.\n", argv[i]);
435         noit_check_resolver_remind(argv[i]);
436       }
437     }
438   }
439   else {
440     for(i=0;i<argc;i++) {
441       dns_cache_node *n;
442       if(NULL != (n = noit_skiplist_find(&nc_dns_cache, argv[i], NULL))) {
443         if(n->lookup_inflight_v4 || n->lookup_inflight_v6)
444           nc_printf(ncct, "%s is currently resolving and cannot be removed.\n");
445         else {
446           noit_skiplist_remove(&nc_dns_cache, argv[i], dns_cache_node_free);
447           nc_printf(ncct, "%s removed.\n", argv[i]);
448         }
449       }
450       else nc_printf(ncct, "%s not in system.\n", argv[i]);
451     }
452   }
453   return 0;
454 }
455
456 static void
457 register_console_dns_cache_commands() {
458   noit_console_state_t *tl;
459   cmd_info_t *showcmd, *nocmd;
460
461   tl = noit_console_state_initial();
462   showcmd = noit_console_state_get_cmd(tl, "show");
463   assert(showcmd && showcmd->dstate);
464
465   nocmd = noit_console_state_get_cmd(tl, "no");
466   assert(nocmd && nocmd->dstate);
467
468   noit_console_state_add_cmd(showcmd->dstate,
469     NCSCMD("dns_cache", noit_console_show_dns_cache, NULL, NULL, NULL));
470
471   noit_console_state_add_cmd(tl,
472     NCSCMD("dns_cache", noit_console_manip_dns_cache, NULL, NULL, NULL));
473
474   noit_console_state_add_cmd(nocmd->dstate,
475     NCSCMD("dns_cache", noit_console_manip_dns_cache, NULL, NULL, (void *)0x1));
476 }
477
478
479 void noit_check_resolver_init() {
480   eventer_t e;
481   dns_ctx = dns_new(NULL);
482   if(dns_init(dns_ctx, 0) != 0 ||
483      dns_open(dns_ctx) < 0) {
484     noitL(noit_error, "dns initialization failed.\n");
485   }
486   dns_set_tmcbck(dns_ctx, dns_cache_utm_fn, dns_ctx);
487   e = eventer_alloc();
488   e->mask = EVENTER_READ | EVENTER_EXCEPTION;
489   e->closure = dns_ctx;
490   e->callback = dns_cache_callback;
491   e->fd = dns_sock(dns_ctx);
492   eventer_add(e);
493
494   noit_skiplist_init(&nc_dns_cache);
495   noit_skiplist_set_compare(&nc_dns_cache, name_lookup, name_lookup_k);
496   noit_skiplist_add_index(&nc_dns_cache, refresh_idx, refresh_idx_k);
497   noit_check_resolver_loop(NULL, 0, NULL, NULL);
498   register_console_dns_cache_commands();
499 }
Note: See TracBrowser for help on using the browser.