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 #include <config.h>
21 #include "dcinfo.h"
22
23 #include <gdnsd/alloc.h>
24 #include <gdnsd/log.h>
25 #include <gdnsd/misc.h>
26 #include <gdnsd/mon.h>
27
28 #include <math.h>
29
30 /***************************************
31 * dcinfo_t and related methods
32 **************************************/
33
34 // The datacenter numbers are always 1-based, and only up to 254
35 // datacenters are supported. The first datacenter is always #1,
36 // and in a 3-datacenter config they're 1, 2, 3. The zero-value
37 // is used to terminate datacenter lists that are implemented
38 // as uint8_t* strings on which standard string ops work (e.g.
39 // strcmp(), strcpy()).
40 // dcinfo_t holds a list of text datacenters names in the order
41 // specified in the config, which is the default order. Therefore
42 // the default order, in dclist format, is e.g. for num_dcs == 3,
43 // \1\2\3\0.
44 // dcinfo_t also holds auto_limit, which is the lesser of the
45 // configured auto_dc_limit and the actual num_dcs, so that it's
46 // always the correct limit for direct application even if num_dcs
47 // is < auto_dc_limit.
48 // Finally, dcinfo_t also holds the list of coordinates for each
49 // datacenter in the case that auto_dc_coords was used. This
50 // array of doubles is twice as long as the names array, and stores
51 // a latitude follow by a longitude for each datacenter, in
52 // radian units.
53
54 struct _dcinfo {
55 unsigned num_dcs; // count of datacenters
56 unsigned auto_limit; // lesser of num_dcs and dc_auto_limit cfg
57 char** names; // #num_dcs, ordered map
58 double* coords; // #(num_dcs * 2, lat then lon, in radians)
59 unsigned* indices; // mon_admin indices for map-level forced state
60 };
61
62 // Technically we could/should check for duplicates here. The plugin will
63 // still fail later though: when a resource is defined, the datacenter
64 // names go into a hash requiring uniqueness, and the count is required
65 // to match (ditto for auto_dc_coords never succeeding with dupes in the
66 // datacenters list).
dcinfo_new(vscf_data_t * dc_cfg,vscf_data_t * dc_auto_cfg,vscf_data_t * dc_auto_limit_cfg,const char * map_name)67 dcinfo_t* dcinfo_new(vscf_data_t* dc_cfg, vscf_data_t* dc_auto_cfg, vscf_data_t* dc_auto_limit_cfg, const char* map_name) {
68 dcinfo_t* info = xmalloc(sizeof(dcinfo_t));
69
70 const unsigned num_dcs = vscf_array_get_len(dc_cfg);
71 unsigned num_auto = num_dcs;
72 if(!num_dcs)
73 log_fatal("plugin_geoip: map '%s': 'datacenters' must be an array of one or more strings", map_name);
74 if(num_dcs > 254)
75 log_fatal("plugin_geoip: map '%s': %u datacenters is too many, this code only supports up to 254", map_name, num_dcs);
76
77 info->names = xmalloc(sizeof(char*) * num_dcs);
78 info->indices = xmalloc(sizeof(unsigned) * num_dcs);
79 info->num_dcs = num_dcs;
80 for(unsigned i = 0; i < num_dcs; i++) {
81 vscf_data_t* dcname_cfg = vscf_array_get_data(dc_cfg, i);
82 if(!dcname_cfg || !vscf_is_simple(dcname_cfg))
83 log_fatal("plugin_geoip: map '%s': 'datacenters' must be an array of one or more strings", map_name);
84 info->names[i] = strdup(vscf_simple_get_data(dcname_cfg));
85 if(!strcmp(info->names[i], "auto"))
86 log_fatal("plugin_geoip: map '%s': datacenter name 'auto' is illegal", map_name);
87 char* map_mon_desc = gdnsd_str_combine_n(4, "geoip/", map_name, "/", info->names[i]);
88 info->indices[i] = gdnsd_mon_admin(map_mon_desc);
89 free(map_mon_desc);
90 }
91
92 if(dc_auto_cfg) {
93 if(!vscf_is_hash(dc_auto_cfg))
94 log_fatal("plugin_geoip: map '%s': auto_dc_coords must be a key-value hash", map_name);
95 num_auto = vscf_hash_get_len(dc_auto_cfg);
96 info->coords = xmalloc(num_dcs * 2 * sizeof(double));
97 for(unsigned i = 0; i < 2*num_dcs; i++)
98 info->coords[i] = (double)NAN;
99 for(unsigned i = 0; i < num_auto; i++) {
100 const char* dcname = vscf_hash_get_key_byindex(dc_auto_cfg, i, NULL);
101 unsigned dcidx;
102 for(dcidx = 0; dcidx < num_dcs; dcidx++) {
103 if(!strcmp(dcname, info->names[dcidx]))
104 break;
105 }
106 if(dcidx == num_dcs)
107 log_fatal("plugin_geoip: map '%s': auto_dc_coords key '%s' not matched from 'datacenters' list", map_name, dcname);
108 if(!isnan(info->coords[(dcidx*2)]))
109 log_fatal("plugin_geoip: map '%s': auto_dc_coords key '%s' defined twice", map_name, dcname);
110 vscf_data_t* coord_cfg = vscf_hash_get_data_byindex(dc_auto_cfg, i);
111 vscf_data_t* lat_cfg;
112 vscf_data_t* lon_cfg;
113 double lat, lon;
114 if(
115 !vscf_is_array(coord_cfg) || vscf_array_get_len(coord_cfg) != 2
116 || !(lat_cfg = vscf_array_get_data(coord_cfg, 0))
117 || !(lon_cfg = vscf_array_get_data(coord_cfg, 1))
118 || !vscf_is_simple(lat_cfg)
119 || !vscf_is_simple(lon_cfg)
120 || !vscf_simple_get_as_double(lat_cfg, &lat)
121 || !vscf_simple_get_as_double(lon_cfg, &lon)
122 || lat > 90.0 || lat < -90.0
123 || lon > 180.0 || lon < -180.0
124 )
125 log_fatal("plugin_geoip: map '%s': auto_dc_coords value for datacenter '%s' must be an array of two floating-point values representing a legal latitude and longitude in decimal degrees", map_name, dcname);
126 info->coords[(dcidx * 2)] = lat * DEG2RAD;
127 info->coords[(dcidx * 2) + 1] = lon * DEG2RAD;
128 }
129 }
130 else {
131 info->coords = NULL;
132 }
133
134 if(dc_auto_limit_cfg) {
135 unsigned long auto_limit_ul;
136 if(!vscf_is_simple(dc_auto_limit_cfg) || !vscf_simple_get_as_ulong(dc_auto_limit_cfg, &auto_limit_ul))
137 log_fatal("plugin_geoip: map '%s': auto_dc_limit must be a single unsigned integer value", map_name);
138 if(auto_limit_ul > num_auto || !auto_limit_ul)
139 auto_limit_ul = num_auto;
140 info->auto_limit = auto_limit_ul;
141 }
142 else {
143 info->auto_limit = (num_auto > 3) ? 3 : num_auto;
144 }
145
146 return info;
147 }
148
dcinfo_get_count(const dcinfo_t * info)149 unsigned dcinfo_get_count(const dcinfo_t* info) {
150 return info->num_dcs;
151 }
152
dcinfo_get_limit(const dcinfo_t * info)153 unsigned dcinfo_get_limit(const dcinfo_t* info) {
154 return info->auto_limit;
155 }
156
dcinfo_get_coords(const dcinfo_t * info,const unsigned dcnum)157 const double* dcinfo_get_coords(const dcinfo_t* info, const unsigned dcnum) {
158 dmn_assert(dcnum < info->num_dcs);
159 return &info->coords[dcnum * 2];
160 }
161
dcinfo_name2num(const dcinfo_t * info,const char * dcname)162 unsigned dcinfo_name2num(const dcinfo_t* info, const char* dcname) {
163 if(dcname)
164 for(unsigned i = 0; i < info->num_dcs; i++)
165 if(!strcmp(dcname, info->names[i]))
166 return i + 1;
167 return 0;
168 }
169
dcinfo_num2name(const dcinfo_t * info,const unsigned dcnum)170 const char* dcinfo_num2name(const dcinfo_t* info, const unsigned dcnum) {
171 if(!dcnum || dcnum > info->num_dcs)
172 return NULL;
173
174 return info->names[dcnum - 1];
175 }
176
dcinfo_map_mon_idx(const dcinfo_t * info,const unsigned dcnum)177 unsigned dcinfo_map_mon_idx(const dcinfo_t* info, const unsigned dcnum) {
178 dmn_assert(dcnum && dcnum <= info->num_dcs);
179 return info->indices[dcnum - 1];
180 }
181