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