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