1 /* Copyright © 2012 Brandon L Black <blblack@gmail.com>
2  *
3  * This file is part of gdnsd.
4  *
5  * gdnsd-plugin-geoip is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * gdnsd-plugin-geoip is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with gdnsd.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 // gdmaps = GeoIP -> Datacenter Mapping library code
21 
22 #include <config.h>
23 #include <gdmaps.h>
24 
25 #include "fips104.h"
26 #include "dcinfo.h"
27 #include "dclists.h"
28 #include "dcmap.h"
29 #include "nlist.h"
30 #include "ntree.h"
31 #include "nets.h"
32 #include "gdgeoip.h"
33 #include "gdgeoip2.h"
34 
35 #include <gdnsd/alloc.h>
36 #include <gdnsd/dmn.h>
37 #include <gdnsd/log.h>
38 #include <gdnsd/vscf.h>
39 #include <gdnsd/paths.h>
40 #include <gdnsd/misc.h>
41 #include <gdnsd/prcu.h>
42 
43 #include <inttypes.h>
44 #include <stdbool.h>
45 #include <stddef.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <fcntl.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <unistd.h>
52 #include <sys/mman.h>
53 #include <pthread.h>
54 
55 #include <ev.h>
56 
57 // When an input file change is detected, we wait this long
58 //  for a followup change notification before processing.  Every time we get
59 //  another notification within the window, we restart the timer again.  This
60 //  coalesces rapid-fire updates.
61 #define STAT_RELOAD_WAIT 5.0
62 
63 // *after* reloading an individual input file, this timer is kicked similarly
64 //   to the above, to wait for all rapid-fire updates to all input files to
65 //   quiesce a while.  When it finally expires, the parsed new data from each
66 //   are merged into a single runtime lookup database, and we do a locked
67 //   swap of the data for the runtime lookup threads.
68 #define ALL_RELOAD_WAIT 7.0
69 
70 typedef struct {
71     char* name;
72     char* geoip_path;
73     char* geoip_v4o_path;
74     char* nets_path;
75     const fips_t* fips;
76     dcinfo_t* dcinfo; // basic datacenter list/info
77     dcmap_t* dcmap; // map of locinfo -> dclist
78     dclists_t* dclists; // corresponds to ->tree
79     dclists_t* dclists_pend; // Pending modified dclist for latest update(s)
80                              //   to ->foo_list, eventually promoted to
81                              //   ->dclists when ->tree is updated, NULL
82                              //   when no pending update(s) are outstanding
83     nlist_t* geoip_list; // optional main geoip db
84     nlist_t* geoip_v4o_list; // optional v4 overlay
85     nlist_t* nets_list; // net overrides, optional
86     ntree_t* tree; // merged->translated from the lists above
87     ev_stat* geoip_stat_watcher;
88     ev_stat* geoip_v4o_stat_watcher;
89     ev_stat* nets_stat_watcher;
90     ev_timer* geoip_reload_timer;
91     ev_timer* geoip_v4o_reload_timer;
92     ev_timer* nets_reload_timer;
93     ev_timer* tree_update_timer;
94     bool geoip_is_v2;
95     bool city_no_region;
96     bool city_auto_mode;
97 } gdmap_t;
98 
99 F_NONNULL
_gdmap_badkey(const char * key,unsigned klen V_UNUSED,vscf_data_t * val V_UNUSED,const void * mapname_asvoid)100 static bool _gdmap_badkey(const char* key, unsigned klen V_UNUSED, vscf_data_t* val V_UNUSED, const void* mapname_asvoid) {
101     const char* mapname = mapname_asvoid;
102     log_fatal("plugin_geoip: map '%s': invalid config key '%s'", mapname, key);
103     return false;
104 }
105 
106 F_NONNULLX(1,2)
gdmap_new(const char * name,vscf_data_t * map_cfg,const fips_t * fips)107 static gdmap_t* gdmap_new(const char* name, vscf_data_t* map_cfg, const fips_t* fips) {
108     // basics
109     gdmap_t* gdmap = xcalloc(1, sizeof(gdmap_t));
110     gdmap->name = strdup(name);
111     gdmap->fips = fips;
112     if(!vscf_is_hash(map_cfg))
113         log_fatal("plugin_geoip: value for map '%s' must be a hash", name);
114 
115     // datacenters config
116     vscf_data_t* dc_cfg = vscf_hash_get_data_byconstkey(map_cfg, "datacenters", true);
117     if(!dc_cfg)
118         log_fatal("plugin_geoip: map '%s': missing required 'datacenters' array", name);
119     vscf_data_t* dc_auto_cfg = vscf_hash_get_data_byconstkey(map_cfg, "auto_dc_coords", true);
120     vscf_data_t* dc_auto_limit_cfg = vscf_hash_get_data_byconstkey(map_cfg, "auto_dc_limit", true);
121     gdmap->city_auto_mode = dc_auto_cfg ? true : false;
122     gdmap->dcinfo = dcinfo_new(dc_cfg, dc_auto_cfg, dc_auto_limit_cfg, name);
123     gdmap->dclists_pend = dclists_new(gdmap->dcinfo);
124 
125     // geoip_db config
126     vscf_data_t* gdb_cfg = vscf_hash_get_data_byconstkey(map_cfg, "geoip_db", true);
127     if(gdb_cfg) {
128         if(!vscf_is_simple(gdb_cfg) || !vscf_simple_get_len(gdb_cfg))
129             log_fatal("plugin_geoip: map '%s': 'geoip_db' must have a non-empty string value", name);
130         gdmap->geoip_path = gdnsd_resolve_path_cfg(vscf_simple_get_data(gdb_cfg), "geoip");
131     }
132 
133     // geoip_db_v4_overlay config
134     vscf_data_t* gdb_v4o_cfg = vscf_hash_get_data_byconstkey(map_cfg, "geoip_db_v4_overlay", true);
135     if(gdb_v4o_cfg) {
136         if(!gdb_cfg)
137             log_fatal("plugin_geoip: map '%s': 'geoip_db_v4_overlay' requires an IPv6 'geoip_db'", name);
138         if(!vscf_is_simple(gdb_v4o_cfg) || !vscf_simple_get_len(gdb_v4o_cfg))
139             log_fatal("plugin_geoip: map '%s': 'geoip_db_v4_overlay' must have a non-empty string value", name);
140         gdmap->geoip_v4o_path = gdnsd_resolve_path_cfg(vscf_simple_get_data(gdb_v4o_cfg), "geoip");
141     }
142 
143     // geoip2 config
144     vscf_data_t* gdb2_cfg = vscf_hash_get_data_byconstkey(map_cfg, "geoip2_db", true);
145     if(gdb2_cfg) {
146         if(!vscf_is_simple(gdb2_cfg) || !vscf_simple_get_len(gdb2_cfg))
147             log_fatal("plugin_geoip: map '%s': 'geoip2_db' must have a non-empty string value", name);
148         if(gdmap->geoip_path)
149             log_fatal("plugin_geoip: map '%s': Can only one have one of 'geoip_db' or 'geoip2_db'", name);
150         gdmap->geoip_path = gdnsd_resolve_path_cfg(vscf_simple_get_data(gdb2_cfg), "geoip");
151         gdmap->geoip_is_v2 = true;
152     }
153 
154     // map config
155     vscf_data_t* map_map = vscf_hash_get_data_byconstkey(map_cfg, "map", true);
156     if(map_map) {
157         if(!vscf_is_hash(map_map))
158             log_fatal("plugin_geoip: map '%s': 'map' stanza must be a hash", name);
159         if(!gdmap->geoip_path)
160             log_fatal("plugin_geoip: map '%s': 'map' stanza requires 'geoip_db'", name);
161         gdmap->dcmap = dcmap_new(map_map, gdmap->dclists_pend, 0, 0, name, gdmap->city_auto_mode);
162     }
163 
164     // nets config
165     vscf_data_t* nets_cfg = vscf_hash_get_data_byconstkey(map_cfg, "nets", true);
166     if(!nets_cfg || vscf_is_hash(nets_cfg)) {
167         // statically-defined hash or empty, load now, leave path undefined
168         gdmap->nets_list = nets_make_list(nets_cfg, gdmap->dclists_pend, name);
169         if(!gdmap->nets_list)
170             log_fatal("plugin_geoip: map '%s': error in 'nets' data, cannot continue", name);
171     }
172     else if(vscf_is_simple(nets_cfg) && vscf_simple_get_len(nets_cfg)) {
173         // external file, define path for later loading and stat-watching
174         gdmap->nets_path = gdnsd_resolve_path_cfg(vscf_simple_get_data(nets_cfg), "geoip");
175     }
176     else {
177         log_fatal("plugin_geoip: map '%s': 'nets' stanza must be a hash of direct entries or a filename", name);
178     }
179 
180     // optional GeoIP1 City behavior flag
181     gdmap->city_no_region = false;
182     vscf_data_t* cnr_cfg = vscf_hash_get_data_byconstkey(map_cfg, "city_no_region", true);
183     if(cnr_cfg) {
184         if(!vscf_is_simple(cnr_cfg) || !vscf_simple_get_as_bool(cnr_cfg, &gdmap->city_no_region))
185             log_fatal("plugin_geoip: map '%s': 'city_no_region' must be a boolean value ('true' or 'false')", name);
186     }
187 
188     // check for invalid keys
189     vscf_hash_iterate_const(map_cfg, true, _gdmap_badkey, name);
190 
191     return gdmap;
192 }
193 
194 F_NONNULL
gdmap_tree_update(gdmap_t * gdmap)195 static void gdmap_tree_update(gdmap_t* gdmap) {
196     dmn_assert(gdmap->dclists_pend);
197 
198     ntree_t* merged;
199 
200     if(gdmap->geoip_list) {
201         if(gdmap->geoip_v4o_list) {
202             merged = nlist_merge3_tree(gdmap->geoip_list, gdmap->geoip_v4o_list, gdmap->nets_list);
203         }
204         else {
205             merged = nlist_merge2_tree(gdmap->geoip_list, gdmap->nets_list);
206         }
207     }
208     else {
209         merged = nlist_xlate_tree(gdmap->nets_list);
210     }
211 
212     ntree_t* old_tree = gdmap->tree;
213     dclists_t* old_lists = gdmap->dclists;
214 
215     gdnsd_prcu_upd_lock();
216     gdnsd_prcu_upd_assign(gdmap->dclists, gdmap->dclists_pend);
217     gdnsd_prcu_upd_assign(gdmap->tree, merged);
218     gdnsd_prcu_upd_unlock();
219 
220     gdmap->dclists_pend = NULL;
221     if(old_tree)
222         ntree_destroy(old_tree);
223     if(old_lists)
224         dclists_destroy(old_lists, KILL_NO_LISTS);
225 
226     log_info("plugin_geoip: map '%s' runtime db updated. nets: %u dclists: %u", gdmap->name, gdmap->tree->count + 1, dclists_get_count(gdmap->dclists));
227 }
228 
229 F_NONNULL
gdmap_update_geoip(gdmap_t * gdmap,const char * path,nlist_t ** out_list_ptr,gdgeoip_v4o_t v4o_flag)230 static bool gdmap_update_geoip(gdmap_t* gdmap, const char* path, nlist_t** out_list_ptr, gdgeoip_v4o_t v4o_flag) {
231     dclists_t* update_dclists;
232 
233     if(!gdmap->dclists_pend) {
234         dmn_assert(gdmap->dclists);
235         update_dclists = dclists_clone(gdmap->dclists);
236     }
237     else {
238         update_dclists = gdmap->dclists_pend;
239     }
240 
241     nlist_t* new_list;
242 
243     if(gdmap->geoip_is_v2) {
244         dmn_assert(!gdmap->geoip_v4o_path);
245         dmn_assert(v4o_flag == V4O_NONE);
246         new_list = gdgeoip2_make_list(
247             path,
248             gdmap->name,
249             update_dclists,
250             gdmap->dcmap,
251             gdmap->city_auto_mode,
252             gdmap->city_no_region
253         );
254     }
255     else {
256         new_list = gdgeoip_make_list(
257             path,
258             gdmap->name,
259             update_dclists,
260             gdmap->dcmap,
261             gdmap->fips,
262             v4o_flag,
263             gdmap->city_auto_mode,
264             gdmap->city_no_region
265         );
266     }
267 
268     bool rv = false;
269 
270     if(!new_list) {
271         log_err("plugin_geoip: map '%s': (Re-)loading geoip database '%s' failed!", gdmap->name, path);
272         if(!gdmap->dclists_pend)
273             dclists_destroy(update_dclists, KILL_NEW_LISTS);
274         rv = true;
275     }
276     else {
277         if(!gdmap->dclists_pend)
278             gdmap->dclists_pend = update_dclists;
279         if(*out_list_ptr)
280             nlist_destroy(*out_list_ptr);
281         *out_list_ptr = new_list;
282     }
283 
284     return rv;
285 }
286 
287 F_NONNULL
gdmap_update_nets(gdmap_t * gdmap)288 static bool gdmap_update_nets(gdmap_t* gdmap) {
289     dmn_assert(gdmap->nets_path);
290 
291     dclists_t* update_dclists;
292 
293     if(!gdmap->dclists_pend) {
294         dmn_assert(gdmap->dclists);
295         update_dclists = dclists_clone(gdmap->dclists);
296     }
297     else {
298         update_dclists = gdmap->dclists_pend;
299     }
300 
301     vscf_data_t* nets_cfg = vscf_scan_filename(gdmap->nets_path);
302     nlist_t* new_list = NULL;
303     if(nets_cfg) {
304         if(vscf_is_hash(nets_cfg)) {
305             new_list = nets_make_list(nets_cfg, update_dclists, gdmap->name);
306             if(!new_list)
307                 log_err("plugin_geoip: map '%s': (Re-)loading nets file '%s' failed!", gdmap->name, gdmap->nets_path);
308         }
309         else {
310             dmn_assert(vscf_is_array(nets_cfg));
311             log_err("plugin_geoip: map '%s': (Re-)loading nets file '%s' failed: file cannot be an array of values", gdmap->name, gdmap->nets_path);
312         }
313         vscf_destroy(nets_cfg);
314     }
315     else {
316         log_err("plugin_geoip: map '%s': parsing nets file '%s' failed", gdmap->name, gdmap->nets_path);
317     }
318 
319     bool rv = false;
320 
321     if(!new_list) {
322         if(!gdmap->dclists_pend)
323             dclists_destroy(update_dclists, KILL_NEW_LISTS);
324         rv = true;
325     }
326     else {
327         if(!gdmap->dclists_pend)
328             gdmap->dclists_pend = update_dclists;
329         if(gdmap->nets_list)
330             nlist_destroy(gdmap->nets_list);
331         gdmap->nets_list = new_list;
332     }
333 
334     return rv;
335 }
336 
337 F_NONNULL
gdmap_initial_load_all(gdmap_t * gdmap)338 static void gdmap_initial_load_all(gdmap_t* gdmap) {
339     dmn_assert(gdmap->dclists_pend);
340     dmn_assert(!gdmap->geoip_list);
341 
342     if(gdmap->geoip_path) {
343         const bool v4o = !!gdmap->geoip_v4o_path;
344 
345         if(gdmap_update_geoip(gdmap, gdmap->geoip_path, &gdmap->geoip_list, v4o ? V4O_PRIMARY : V4O_NONE))
346             log_fatal("plugin_geoip: map '%s': cannot continue initial load", gdmap->name);
347 
348         if(gdmap->geoip_v4o_path)
349             if(gdmap_update_geoip(gdmap, gdmap->geoip_v4o_path, &gdmap->geoip_v4o_list, V4O_SECONDARY))
350                 log_fatal("plugin_geoip: map '%s': cannot continue initial load", gdmap->name);
351     }
352 
353     if(!gdmap->nets_list) {
354         dmn_assert(gdmap->nets_path);
355         if(gdmap_update_nets(gdmap))
356             log_fatal("plugin_geoip: map '%s': cannot continue initial load", gdmap->name);
357     }
358 
359     gdmap_tree_update(gdmap);
360 }
361 
362 F_NONNULL
gdmap_kick_tree_update(gdmap_t * gdmap,struct ev_loop * loop)363 static void gdmap_kick_tree_update(gdmap_t* gdmap, struct ev_loop* loop) {
364     if(!ev_is_active(gdmap->tree_update_timer) && !ev_is_pending(gdmap->tree_update_timer))
365         log_info("plugin_geoip: map '%s': runtime data changes are pending, waiting for %gs of change quiescence...", gdmap->name, ALL_RELOAD_WAIT);
366     else
367         log_debug("plugin_geoip: map '%s': Timer for all runtime data re-kicked for %gs due to rapid change...", gdmap->name, ALL_RELOAD_WAIT);
368     ev_timer_again(loop, gdmap->tree_update_timer);
369 }
370 
371 F_NONNULL
gdmap_geoip_reload_timer_cb(struct ev_loop * loop,ev_timer * w V_UNUSED,int revents V_UNUSED)372 static void gdmap_geoip_reload_timer_cb(struct ev_loop* loop, ev_timer* w V_UNUSED, int revents V_UNUSED) {
373     dmn_assert(revents == EV_TIMER);
374 
375     gdmap_t* gdmap = w->data;
376     dmn_assert(gdmap);
377     dmn_assert(gdmap->geoip_path);
378     const bool v4o = !!gdmap->geoip_v4o_path;
379 
380     ev_timer_stop(loop, gdmap->geoip_reload_timer);
381 
382     if(!gdmap_update_geoip(gdmap, gdmap->geoip_path, &gdmap->geoip_list, v4o ? V4O_PRIMARY : V4O_NONE)) {
383         dmn_assert(gdmap->dclists_pend);
384         gdmap_kick_tree_update(gdmap, loop);
385     }
386 }
387 
388 F_NONNULL
gdmap_geoip_v4o_reload_timer_cb(struct ev_loop * loop,ev_timer * w V_UNUSED,int revents V_UNUSED)389 static void gdmap_geoip_v4o_reload_timer_cb(struct ev_loop* loop, ev_timer* w V_UNUSED, int revents V_UNUSED) {
390     dmn_assert(revents == EV_TIMER);
391 
392     gdmap_t* gdmap = w->data;
393     dmn_assert(gdmap);
394     dmn_assert(gdmap->geoip_v4o_path);
395 
396     ev_timer_stop(loop, gdmap->geoip_v4o_reload_timer);
397 
398     if(!gdmap_update_geoip(gdmap, gdmap->geoip_v4o_path, &gdmap->geoip_v4o_list, V4O_SECONDARY)) {
399         dmn_assert(gdmap->dclists_pend);
400         gdmap_kick_tree_update(gdmap, loop);
401     }
402 }
403 
404 F_NONNULL
gdmap_nets_reload_timer_cb(struct ev_loop * loop,ev_timer * w V_UNUSED,int revents V_UNUSED)405 static void gdmap_nets_reload_timer_cb(struct ev_loop* loop, ev_timer* w V_UNUSED, int revents V_UNUSED) {
406     dmn_assert(revents == EV_TIMER);
407 
408     gdmap_t* gdmap = w->data;
409     dmn_assert(gdmap);
410     dmn_assert(gdmap->nets_path);
411 
412     ev_timer_stop(loop, gdmap->nets_reload_timer);
413 
414     if(!gdmap_update_nets(gdmap)) {
415         dmn_assert(gdmap->dclists_pend);
416         gdmap_kick_tree_update(gdmap, loop);
417     }
418 }
419 
420 F_NONNULL
gdmap_geoip_reload_stat_cb(struct ev_loop * loop,ev_stat * w,int revents V_UNUSED)421 static void gdmap_geoip_reload_stat_cb(struct ev_loop* loop, ev_stat* w, int revents V_UNUSED) {
422     dmn_assert(revents == EV_STAT);
423 
424     gdmap_t* gdmap = w->data;
425     dmn_assert(gdmap);
426 
427     const bool v4o = gdmap->geoip_v4o_path == w->path;
428     dmn_assert(v4o || gdmap->geoip_path == w->path);
429 
430     if(w->attr.st_nlink) { // file exists
431         if(w->attr.st_mtime != w->prev.st_mtime || !w->prev.st_nlink) {
432             // Start (or restart) a timer to geoip_reload_timer_cb, so that we
433             //  wait for multiple changes to "settle" before re-reading the file
434             ev_timer* which_timer = v4o ? gdmap->geoip_v4o_reload_timer : gdmap->geoip_reload_timer;
435             if(!ev_is_active(which_timer) && !ev_is_pending(which_timer))
436                 log_info("plugin_geoip: map '%s': Change detected in GeoIP database '%s', waiting for %gs of change quiescence...", gdmap->name, w->path, STAT_RELOAD_WAIT);
437             else
438                 log_debug("plugin_geoip: map '%s': Timer for GeoIP database '%s' re-kicked for %gs due to rapid change...", gdmap->name, w->path, STAT_RELOAD_WAIT);
439             ev_timer_again(loop, which_timer);
440         }
441     }
442     else {
443         log_warn("plugin_geoip: map '%s': GeoIP database '%s' disappeared! Internal DB remains unchanged, waiting for it to re-appear...", gdmap->name, w->path);
444     }
445 }
446 
447 F_NONNULL
gdmap_nets_reload_stat_cb(struct ev_loop * loop,ev_stat * w,int revents V_UNUSED)448 static void gdmap_nets_reload_stat_cb(struct ev_loop* loop, ev_stat* w, int revents V_UNUSED) {
449     dmn_assert(revents == EV_STAT);
450 
451     gdmap_t* gdmap = w->data;
452     dmn_assert(gdmap);
453     dmn_assert(gdmap->nets_path);
454     dmn_assert(gdmap->nets_path == w->path);
455 
456     if(w->attr.st_nlink) { // file exists
457         if(w->attr.st_mtime != w->prev.st_mtime || !w->prev.st_nlink) {
458             if(!ev_is_active(gdmap->nets_reload_timer) && !ev_is_pending(gdmap->nets_reload_timer))
459                 log_info("plugin_geoip: map '%s': Change detected in nets file '%s', waiting for %gs of change quiescence...", gdmap->name, w->path, STAT_RELOAD_WAIT);
460             else
461                 log_debug("plugin_geoip: map '%s': Timer for nets file '%s' re-kicked for %gs due to rapid change...", gdmap->name, w->path, STAT_RELOAD_WAIT);
462             ev_timer_again(loop, gdmap->nets_reload_timer);
463         }
464     }
465     else {
466         log_warn("plugin_geoip: map '%s': nets file '%s' disappeared! Internal DB remains unchanged, waiting for it to re-appear...", gdmap->name, w->path);
467     }
468 }
469 
470 F_NONNULL
gdmap_tree_update_cb(struct ev_loop * loop,ev_timer * w,int revents V_UNUSED)471 static void gdmap_tree_update_cb(struct ev_loop* loop, ev_timer* w, int revents V_UNUSED) {
472     dmn_assert(revents == EV_TIMER);
473 
474     gdmap_t* gdmap = w->data;
475     dmn_assert(gdmap);
476     ev_timer_stop(loop, gdmap->tree_update_timer);
477     gdmap_tree_update(gdmap);
478 }
479 
480 F_NONNULL
gdmap_setup_nets_watcher(gdmap_t * gdmap,struct ev_loop * loop)481 static void gdmap_setup_nets_watcher(gdmap_t* gdmap, struct ev_loop* loop) {
482     dmn_assert(gdmap->nets_path);
483 
484     gdmap->nets_reload_timer = xmalloc(sizeof(ev_timer));
485     ev_init(gdmap->nets_reload_timer, gdmap_nets_reload_timer_cb);
486     ev_set_priority(gdmap->nets_reload_timer, -1);
487     gdmap->nets_reload_timer->repeat = STAT_RELOAD_WAIT;
488     gdmap->nets_reload_timer->data = gdmap;
489 
490     gdmap->nets_stat_watcher = xmalloc(sizeof(ev_stat));
491     memset(&gdmap->nets_stat_watcher->attr, 0, sizeof(gdmap->nets_stat_watcher->attr));
492     ev_stat_init(gdmap->nets_stat_watcher, gdmap_nets_reload_stat_cb, gdmap->nets_path, 0);
493     ev_set_priority(gdmap->nets_stat_watcher, 0);
494     gdmap->nets_stat_watcher->data = gdmap;
495     ev_stat_start(loop, gdmap->nets_stat_watcher);
496 }
497 
498 F_NONNULL
gdmap_setup_geoip_watcher(gdmap_t * gdmap,struct ev_loop * loop)499 static void gdmap_setup_geoip_watcher(gdmap_t* gdmap, struct ev_loop* loop) {
500     dmn_assert(gdmap->geoip_path);
501 
502     const bool v4o = !!gdmap->geoip_v4o_path;
503 
504     // the reload stat-quiesce timers
505     gdmap->geoip_reload_timer = xmalloc(sizeof(ev_timer));
506     ev_init(gdmap->geoip_reload_timer, gdmap_geoip_reload_timer_cb);
507     ev_set_priority(gdmap->geoip_reload_timer, -1);
508     gdmap->geoip_reload_timer->repeat = STAT_RELOAD_WAIT;
509     gdmap->geoip_reload_timer->data = gdmap;
510 
511     if(v4o) {
512         dmn_assert(!gdmap->geoip_is_v2);
513         gdmap->geoip_v4o_reload_timer = xmalloc(sizeof(ev_timer));
514         ev_init(gdmap->geoip_v4o_reload_timer, gdmap_geoip_v4o_reload_timer_cb);
515         ev_set_priority(gdmap->geoip_v4o_reload_timer, -1);
516         gdmap->geoip_v4o_reload_timer->repeat = STAT_RELOAD_WAIT;
517         gdmap->geoip_v4o_reload_timer->data = gdmap;
518     }
519 
520     // the reload stat() watchers (they share a callback differentiated on w->path)
521     gdmap->geoip_stat_watcher = xmalloc(sizeof(ev_stat));
522     memset(&gdmap->geoip_stat_watcher->attr, 0, sizeof(gdmap->geoip_stat_watcher->attr));
523     ev_stat_init(gdmap->geoip_stat_watcher, gdmap_geoip_reload_stat_cb, gdmap->geoip_path, 0);
524     ev_set_priority(gdmap->geoip_stat_watcher, 0);
525     gdmap->geoip_stat_watcher->data = gdmap;
526     ev_stat_start(loop, gdmap->geoip_stat_watcher);
527 
528     if(v4o) {
529         gdmap->geoip_v4o_stat_watcher = xmalloc(sizeof(ev_stat));
530         memset(&gdmap->geoip_v4o_stat_watcher->attr, 0, sizeof(gdmap->geoip_v4o_stat_watcher->attr));
531         ev_stat_init(gdmap->geoip_v4o_stat_watcher, gdmap_geoip_reload_stat_cb, gdmap->geoip_v4o_path, 0);
532         ev_set_priority(gdmap->geoip_v4o_stat_watcher, 0);
533         gdmap->geoip_v4o_stat_watcher->data = gdmap;
534         ev_stat_start(loop, gdmap->geoip_v4o_stat_watcher);
535     }
536 }
537 
538 F_NONNULL
gdmap_setup_watchers(gdmap_t * gdmap,struct ev_loop * loop)539 static void gdmap_setup_watchers(gdmap_t* gdmap, struct ev_loop* loop) {
540     if(gdmap->geoip_path)
541         gdmap_setup_geoip_watcher(gdmap, loop);
542     if(gdmap->nets_path)
543         gdmap_setup_nets_watcher(gdmap, loop);
544 
545     gdmap->tree_update_timer = xmalloc(sizeof(ev_timer));
546     ev_init(gdmap->tree_update_timer, gdmap_tree_update_cb);
547     ev_set_priority(gdmap->tree_update_timer, -2);
548     gdmap->tree_update_timer->repeat = ALL_RELOAD_WAIT;
549     gdmap->tree_update_timer->data = gdmap;
550 }
551 
552 F_NONNULL F_PURE
gdmap_get_name(const gdmap_t * gdmap)553 static const char* gdmap_get_name(const gdmap_t* gdmap) {
554     return gdmap->name;
555 }
556 
557 F_NONNULL
gdmap_lookup(gdmap_t * gdmap,const client_info_t * client,unsigned * scope_mask)558 static const uint8_t* gdmap_lookup(gdmap_t* gdmap, const client_info_t* client, unsigned* scope_mask) {
559     // gdnsd_prcu_rdr_online() + gdnsd_prcu_rdr_lock()
560     //   is handled by the iothread and dns lookup code
561     //   in the main daemon, in a far outer scope from
562     //   this code in runtime terms.
563 
564     const unsigned dclist_u = ntree_lookup(
565         gdnsd_prcu_rdr_deref(gdmap->tree),
566         client,
567         scope_mask
568     );
569     const uint8_t* dclist_u8 = dclists_get_list(
570         gdnsd_prcu_rdr_deref(gdmap->dclists),
571         dclist_u
572     );
573 
574     dmn_assert(dclist_u8);
575     return dclist_u8;
576 }
577 
578 /***************************************
579  * gdmaps_t and related methods
580  **************************************/
581 
582 struct _gdmaps_t {
583     pthread_t reload_tid;
584     bool reload_thread_spawned;
585     unsigned count;
586     struct ev_loop* reload_loop;
587     fips_t* fips;
588     gdmap_t** maps;
589 };
590 
591 F_NONNULL
_gdmaps_new_iter(const char * key,unsigned klen V_UNUSED,vscf_data_t * val,void * data)592 static bool _gdmaps_new_iter(const char* key, unsigned klen V_UNUSED, vscf_data_t* val, void* data) {
593     gdmaps_t* gdmaps = data;
594     gdmaps->maps = xrealloc(gdmaps->maps, sizeof(gdmap_t*) * (gdmaps->count + 1));
595     gdmaps->maps[gdmaps->count++] = gdmap_new(key, val, gdmaps->fips);
596     return true;
597 }
598 
gdmaps_new(vscf_data_t * maps_cfg)599 gdmaps_t* gdmaps_new(vscf_data_t* maps_cfg) {
600     dmn_assert(vscf_is_hash(maps_cfg));
601 
602     gdgeoip2_init();
603 
604     gdmaps_t* gdmaps = xcalloc(1, sizeof(gdmaps_t));
605 
606     vscf_data_t* crn_cfg = vscf_hash_get_data_byconstkey(maps_cfg, "city_region_names", true);
607     if(crn_cfg) {
608         if(!vscf_is_simple(crn_cfg))
609             log_fatal("plugin_geoip: 'city_region_names' must be a filename as a simple string value");
610         char* fips_path = gdnsd_resolve_path_cfg(vscf_simple_get_data(crn_cfg), "geoip");
611         gdmaps->fips = fips_init(fips_path);
612         free(fips_path);
613     }
614 
615     vscf_hash_iterate(maps_cfg, true, _gdmaps_new_iter, gdmaps);
616     return gdmaps;
617 }
618 
gdmaps_name2idx(const gdmaps_t * gdmaps,const char * map_name)619 int gdmaps_name2idx(const gdmaps_t* gdmaps, const char* map_name) {
620     for(unsigned i = 0; i < gdmaps->count; i++)
621         if(!strcmp(map_name, gdmap_get_name(gdmaps->maps[i])))
622             return (int)i;
623     return -1;
624 }
625 
gdmaps_idx2name(const gdmaps_t * gdmaps,const unsigned gdmap_idx)626 const char* gdmaps_idx2name(const gdmaps_t* gdmaps, const unsigned gdmap_idx) {
627     if(gdmap_idx >= gdmaps->count)
628         return NULL;
629     return gdmap_get_name(gdmaps->maps[gdmap_idx]);
630 }
631 
gdmaps_get_dc_count(const gdmaps_t * gdmaps,const unsigned gdmap_idx)632 unsigned gdmaps_get_dc_count(const gdmaps_t* gdmaps, const unsigned gdmap_idx) {
633     dmn_assert(gdmap_idx < gdmaps->count);
634     return dcinfo_get_count(gdmaps->maps[gdmap_idx]->dcinfo);
635 }
636 
gdmaps_dcname2num(const gdmaps_t * gdmaps,const unsigned gdmap_idx,const char * dcname)637 unsigned gdmaps_dcname2num(const gdmaps_t* gdmaps, const unsigned gdmap_idx, const char* dcname) {
638     dmn_assert(gdmap_idx < gdmaps->count);
639     return dcinfo_name2num(gdmaps->maps[gdmap_idx]->dcinfo, dcname);
640 }
641 
gdmaps_dcnum2name(const gdmaps_t * gdmaps,const unsigned gdmap_idx,const unsigned dcnum)642 const char* gdmaps_dcnum2name(const gdmaps_t* gdmaps, const unsigned gdmap_idx, const unsigned dcnum) {
643     dmn_assert(gdmap_idx < gdmaps->count);
644     return dcinfo_num2name(gdmaps->maps[gdmap_idx]->dcinfo, dcnum);
645 }
646 
gdmaps_map_mon_idx(const gdmaps_t * gdmaps,const unsigned gdmap_idx,const unsigned dcnum)647 unsigned gdmaps_map_mon_idx(const gdmaps_t* gdmaps, const unsigned gdmap_idx, const unsigned dcnum) {
648     dmn_assert(gdmap_idx < gdmaps->count);
649     return dcinfo_map_mon_idx(gdmaps->maps[gdmap_idx]->dcinfo, dcnum);
650 }
651 
652 // mostly for debugging / error output
653 // Note that this doesn't participate in liburcu stuff, and therefore could crash if it were
654 //   running concurrently with an update swap.  It's only used from the testsuite and gdmaps_geoip_test
655 //   stuff, though, so that's not important.
656 static const char dclist_nodc[] = "<INVALID>";
gdmaps_logf_dclist(const gdmaps_t * gdmaps,const unsigned gdmap_idx,const uint8_t * dclist)657 const char* gdmaps_logf_dclist(const gdmaps_t* gdmaps, const unsigned gdmap_idx, const uint8_t* dclist) {
658     dmn_assert(gdmap_idx < gdmaps->count);
659 
660     // Save original...
661     const uint8_t* dclist_orig = dclist;
662 
663     // Size the output
664     unsigned output_len = 0;
665     unsigned dcnum;
666     bool first = true;
667     while((dcnum = *dclist++)) {
668         const char* dcname = gdmaps_dcnum2name(gdmaps, gdmap_idx, dcnum);
669         output_len += strlen(dcname ? dcname : dclist_nodc);
670         if(!first) output_len += 2;
671         first = false;
672     }
673 
674     // Allocate buffer
675     char* buf = dmn_fmtbuf_alloc(output_len + 1);
676     buf[0] = '\0';
677 
678     // Actually write the output
679     first = true;
680     dclist = dclist_orig;
681     while((dcnum = *dclist++)) {
682         const char* dcname = gdmaps_dcnum2name(gdmaps, gdmap_idx, dcnum);
683         if(!first)
684             strcat(buf, ", ");
685         strcat(buf, dcname ? dcname : dclist_nodc);
686         first = false;
687     }
688 
689     return buf;
690 }
691 
gdmaps_lookup(const gdmaps_t * gdmaps,const unsigned gdmap_idx,const client_info_t * client,unsigned * scope_mask)692 const uint8_t* gdmaps_lookup(const gdmaps_t* gdmaps, const unsigned gdmap_idx, const client_info_t* client, unsigned* scope_mask) {
693     dmn_assert(gdmap_idx < gdmaps->count);
694     return gdmap_lookup(gdmaps->maps[gdmap_idx], client, scope_mask);
695 }
696 
gdmaps_load_databases(gdmaps_t * gdmaps)697 void gdmaps_load_databases(gdmaps_t* gdmaps) {
698     for(unsigned i = 0; i < gdmaps->count; i++)
699         gdmap_initial_load_all(gdmaps->maps[i]);
700 }
701 
702 F_NONNULL
gdmaps_reload_thread(void * arg)703 static void* gdmaps_reload_thread(void* arg) {
704     pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
705     gdnsd_thread_setname("gdnsd-geoip-db");
706 
707     gdmaps_t* gdmaps = arg;
708     dmn_assert(gdmaps);
709 
710     gdmaps->reload_loop = ev_loop_new(EVFLAG_AUTO);
711     for(unsigned i = 0; i < gdmaps->count; i++)
712         gdmap_setup_watchers(gdmaps->maps[i], gdmaps->reload_loop);
713 
714     ev_run(gdmaps->reload_loop, 0);
715 
716     return NULL;
717 }
718 
gdmaps_setup_watchers(gdmaps_t * gdmaps)719 void gdmaps_setup_watchers(gdmaps_t* gdmaps) {
720     pthread_attr_t attribs;
721     pthread_attr_init(&attribs);
722     pthread_attr_setdetachstate(&attribs, PTHREAD_CREATE_DETACHED);
723     pthread_attr_setscope(&attribs, PTHREAD_SCOPE_SYSTEM);
724 
725     sigset_t sigmask_all;
726     sigfillset(&sigmask_all);
727     sigset_t sigmask_prev;
728     sigemptyset(&sigmask_prev);
729     if(pthread_sigmask(SIG_SETMASK, &sigmask_all, &sigmask_prev))
730         log_fatal("pthread_sigmask() failed");
731 
732     int pthread_err;
733     if((pthread_err = pthread_create(&gdmaps->reload_tid, &attribs, gdmaps_reload_thread, gdmaps)))
734         log_fatal("plugin_geoip: failed to create GeoIP reload thread: %s", dmn_logf_strerror(pthread_err));
735 
736     gdmaps->reload_thread_spawned = true;
737 
738     if(pthread_sigmask(SIG_SETMASK, &sigmask_prev, NULL))
739         log_fatal("pthread_sigmask() failed");
740     pthread_attr_destroy(&attribs);
741 }
742