F_NONNULL static const vscf_data_t* conf_get_maps(const vscf_data_t* cfg_root) { dmn_assert(cfg_root); // plugins stanza const vscf_data_t* plugins = vscf_hash_get_data_byconstkey(cfg_root, "plugins", true); if(!plugins) log_fatal("Config file has no plugins stanza"); if(!vscf_is_hash(plugins)) log_fatal("Config stanza 'plugins' must be a hash"); // plugins->geoip stanza const vscf_data_t* geoip = vscf_hash_get_data_byconstkey(plugins, "geoip", true); if(!geoip) log_fatal("Config file has no geoip plugin config"); if(!vscf_is_hash(geoip)) log_fatal("Plugin config for 'geoip' must be a hash"); // plugins->geoip->maps stanza const vscf_data_t* maps = vscf_hash_get_data_byconstkey(geoip, "maps", true); if(!maps) log_fatal("Config file has no geoip maps defined"); if(!vscf_is_hash(maps)) log_fatal("Geoip plugin config for 'maps' must be a hash"); return maps; }
F_NONNULL static unsigned res_get_mapnum(const vscf_data_t* res_cfg, const char* res_name) { dmn_assert(res_cfg); dmn_assert(res_name); // Get 'dclist' name, convert, store, return 0-based dclist index const vscf_data_t* dc_cfg = vscf_hash_get_data_byconstkey(res_cfg, "datacenters", true); if(!dc_cfg) log_fatal("plugin_metafo: resource '%s': required key 'datacenters' is missing", res_name); dclist_t* dcl = malloc(sizeof(dclist_t)); if(vscf_is_hash(dc_cfg) || !(dcl->num_dcs = vscf_array_get_len(dc_cfg))) log_fatal("plugin_metafo: resource '%s': 'datacenters' must be an array of one or more datacenter name strings", res_name); uint8_t* dclptr = dcl->dc_list = malloc(dcl->num_dcs + 1); dcl->dc_names = malloc((dcl->num_dcs + 1) * sizeof(char*)); dcl->dc_names[0] = NULL; // index zero is invalid for(unsigned i = 0; i < dcl->num_dcs; i++) { const vscf_data_t* dcname_cfg = vscf_array_get_data(dc_cfg, i); if(!dcname_cfg || !vscf_is_simple(dcname_cfg)) log_fatal("plugin_metafo: resource '%s': 'datacenters' must be an array of one or more datacenter name strings", res_name); const unsigned dcidx = i + 1; *dclptr++ = dcidx; dcl->dc_names[dcidx] = strdup(vscf_simple_get_data(dcname_cfg)); } *dclptr = 0; const unsigned rv_idx = num_dclists++; dclists = realloc(dclists, num_dclists * sizeof(dclist_t*)); dclists[rv_idx] = dcl; return rv_idx; }
F_NONNULL static void make_resource(resource_t* res, const char* res_name, const vscf_data_t* res_cfg) { dmn_assert(res); dmn_assert(res_name); dmn_assert(res_cfg); res->name = strdup(res_name); if(!vscf_is_hash(res_cfg)) log_fatal("plugin_" PNSTR ": the value of resource '%s' must be a hash", res_name); res->map = res_get_mapnum(res_cfg, res_name); unsigned dc_count = map_get_len(res->map); dmn_assert(dc_count); // empty lists not allowed! // the core item: dcmap (dc -> result map) const vscf_data_t* dcs_cfg = vscf_hash_get_data_byconstkey(res_cfg, "dcmap", true); if(!dcs_cfg) log_fatal("plugin_" PNSTR ": resource '%s': missing required stanza 'dcmap'", res_name); // Get/check datacenter count res->num_dcs = vscf_hash_get_len(dcs_cfg); if(res->num_dcs != dc_count) log_fatal("plugin_" PNSTR ": resource '%s': the dcmap does not match the datacenters list", res_name); res->dcs = config_res_perdc(res->map, dcs_cfg, res_name); }
nlist_t* nets_make_list(vscf_data_t* nets_cfg, dclists_t* dclists, const char* map_name) { nlist_t* nl = nlist_new(map_name, false); if(nets_cfg) { dmn_assert(vscf_is_hash(nets_cfg)); if(nets_parse(nets_cfg, dclists, map_name, nl)) { nlist_destroy(nl); nl = NULL; } } if(nl) { // This masks out the 5x v4-like spaces that we *never* // lookup directly. These "NN_UNDEF" dclists will // never be seen by runtime lookups. The only // reason these exist is so that supernets and // adjacent networks get proper masks. Otherwise // lookups in these nearby spaces might return // oversized edns-client-subnet masks and cause // the cache to affect lookup of these spaces... nlist_append(nl, start_v4mapped, 96, NN_UNDEF); nlist_append(nl, start_siit, 96, NN_UNDEF); nlist_append(nl, start_wkp, 96, NN_UNDEF); nlist_append(nl, start_6to4, 16, NN_UNDEF); nlist_append(nl, start_teredo, 32, NN_UNDEF); nlist_finish(nl); } return nl; }
F_NONNULL static dc_t* config_res_perdc(const unsigned mapnum, const vscf_data_t* cfg, const char* resname) { dmn_assert(cfg); dmn_assert(resname); dmn_assert(vscf_is_hash(cfg)); const unsigned num_dcs = vscf_hash_get_len(cfg); dc_t* store = calloc((num_dcs + 1), sizeof(dc_t)); for(unsigned i = 0; i < num_dcs; i++) { const char* dcname = vscf_hash_get_key_byindex(cfg, i, NULL); const unsigned dc_idx = map_get_dcidx(mapnum, dcname); if(!dc_idx) log_fatal("plugin_" PNSTR ": resource '%s': datacenter name '%s' is not valid", resname, dcname); dmn_assert(dc_idx <= num_dcs); dc_t* this_dc = &store[dc_idx]; this_dc->dc_name = strdup(dcname); const vscf_data_t* plugdata = vscf_hash_get_data_byindex(cfg, i); if(vscf_is_simple(plugdata)) { const char* textdata = vscf_simple_get_data(plugdata); if(*textdata == '%') { char* child_plugname = strdup(textdata + 1); this_dc->plugin_name = child_plugname; char* child_resname = strchr(child_plugname, '!'); if(child_resname) { *child_resname++ = '\0'; this_dc->res_name = strdup(child_resname); } if(!strcmp(this_dc->plugin_name, PNSTR) && !strcmp(this_dc->res_name, resname)) log_fatal("plugin_" PNSTR ": resource '%s': not allowed to reference itself!", resname); } else if(*textdata == '!') { this_dc->res_name = strdup(textdata + 1); const vscf_data_t* res_cfg = vscf_get_parent(cfg); this_dc->plugin_name = get_defaulted_plugname(res_cfg, resname, dcname); if(!strcmp(this_dc->plugin_name, PNSTR) && !strcmp(this_dc->res_name, resname)) log_fatal("plugin_" PNSTR ": resource '%s': not allowed to reference itself!", resname); } else { anysin_t tempsin; if(gdnsd_anysin_getaddrinfo(textdata, NULL, &tempsin)) { // failed to parse as address, so set up direct CNAME if possible uint8_t* dname = malloc(256); dname_status_t dnstat = vscf_simple_get_as_dname(plugdata, dname); if(dnstat == DNAME_INVALID) log_fatal("plugin_" PNSTR ": resource '%s': CNAME for datacenter '%s' is not a legal domainname", resname, dcname); if(dnstat == DNAME_VALID) dname = dname_trim(dname); this_dc->dname = dname; } else { inject_child_plugin_config(this_dc, resname, (vscf_data_t*)plugdata); } } } else { inject_child_plugin_config(this_dc, resname, (vscf_data_t*)plugdata); } } return store; }
vscf_data_t* gdnsd_initialize(const char* config_dir, const bool check_create_dirs) { static bool has_run = false; if(has_run) log_fatal("BUG: gdnsd_initialize() should only be called once!"); else has_run = true; // Initialize other areas of libgdnsd gdnsd_init_net(); gdnsd_rand_meta_init(); // set up config dir if(!config_dir) config_dir = GDNSD_DEFPATH_CONFIG; gdnsd_dirs[CFG] = gdnsd_realdir(config_dir, "config", false, 0); // parse config file char* cfg_file = gdnsd_resolve_path_cfg("config", NULL); vscf_data_t* cfg_root = conf_load_vscf(cfg_file); free(cfg_file); #ifndef NDEBUG // in developer debug builds, exercise clone+destroy if(cfg_root) { vscf_data_t* temp_cfg = vscf_clone(cfg_root, false); vscf_destroy(cfg_root); cfg_root = temp_cfg; } #endif // find run/state paths, possibly using config input const char* run_dir = GDNSD_DEFPATH_RUN; const char* state_dir = GDNSD_DEFPATH_STATE; if(cfg_root) { vscf_data_t* options = vscf_hash_get_data_byconstkey(cfg_root, "options", true); if(options) { if(!vscf_is_hash(options)) log_fatal("Config key 'options': wrong type (must be hash)"); CFG_DIR(options, run_dir); CFG_DIR(options, state_dir); } } // set them up if(check_create_dirs) { gdnsd_dirs[RUN] = gdnsd_realdir(run_dir, "run", true, 0750); gdnsd_dirs[STATE] = gdnsd_realdir(state_dir, "state", true, 0755); } else { gdnsd_dirs[RUN] = strdup(run_dir); gdnsd_dirs[STATE] = strdup(state_dir); } // This is just fixed at compiletime, period gdnsd_dirs[LIBEXEC] = GDNSD_DEFPATH_LIBEXEC; return cfg_root; }
F_NONNULL static const vscf_data_t* conf_load(const char* cfg_file) { dmn_assert(cfg_file); char* vscf_err; const vscf_data_t* cfg_root = vscf_scan_filename(cfg_file, &vscf_err); if(!cfg_root) log_fatal("Configuration load failed: %s", vscf_err); dmn_assert(vscf_is_hash(cfg_root)); return cfg_root; }
F_NONNULL static unsigned dcinfo_init_auto(dcinfo_t* info, vscf_data_t* dc_auto_cfg, const char* map_name) { if (!vscf_is_hash(dc_auto_cfg)) log_fatal("plugin_geoip: map '%s': auto_dc_coords must be a key-value hash", map_name); const unsigned num_auto = vscf_hash_get_len(dc_auto_cfg); const unsigned num_dcs = info->num_dcs; for (unsigned i = 0; i < num_dcs; i++) { info->dcs[i].coords.lat = (double)NAN; info->dcs[i].coords.lon = (double)NAN; info->dcs[i].coords.cos_lat = (double)NAN; } for (unsigned i = 0; i < num_auto; i++) { const char* dcname = vscf_hash_get_key_byindex(dc_auto_cfg, i, NULL); unsigned dcidx; for (dcidx = 0; dcidx < num_dcs; dcidx++) { if (!strcmp(dcname, info->dcs[dcidx].name)) break; } if (dcidx == num_dcs) log_fatal("plugin_geoip: map '%s': auto_dc_coords key '%s' not matched from 'datacenters' list", map_name, dcname); GDNSD_DIAG_PUSH_IGNORED("-Wdouble-promotion") if (!isnan(info->dcs[dcidx].coords.lat)) log_fatal("plugin_geoip: map '%s': auto_dc_coords key '%s' defined twice", map_name, dcname); GDNSD_DIAG_POP vscf_data_t* coord_cfg = vscf_hash_get_data_byindex(dc_auto_cfg, i); if (!vscf_is_array(coord_cfg) || vscf_array_get_len(coord_cfg) != 2) log_fatal("plugin_geoip: map '%s': auto_dc_coords value for datacenter '%s' must be an array of two values", map_name, dcname); vscf_data_t* lat_cfg = vscf_array_get_data(coord_cfg, 0); vscf_data_t* lon_cfg = vscf_array_get_data(coord_cfg, 1); gdnsd_assert(lat_cfg); gdnsd_assert(lon_cfg); double lat; double lon; if (!vscf_is_simple(lat_cfg) || !vscf_is_simple(lon_cfg) || !vscf_simple_get_as_double(lat_cfg, &lat) || !vscf_simple_get_as_double(lon_cfg, &lon) || lat > 90.0 || lat < -90.0 || lon > 180.0 || lon < -180.0 ) log_fatal("plugin_geoip: map '%s': auto_dc_coords value for datacenter '%s' must be a legal latitude and longitude in decimal degrees", map_name, dcname); info->dcs[dcidx].coords.lat = lat * DEG2RAD; info->dcs[dcidx].coords.lon = lon * DEG2RAD; info->dcs[dcidx].coords.cos_lat = cos(lat * DEG2RAD); } return num_auto; }
gdmaps_t* gdmaps_test_load(const char* cfg_data) { vscf_data_t* maps_cfg = vscf_scan_buf(strlen(cfg_data), cfg_data, "(test maps)", false); if(!maps_cfg) log_fatal("Test config load failed"); if(!vscf_is_hash(maps_cfg)) log_fatal("Geoip plugin config for 'maps' must be a hash"); gdmaps_t* rv = gdmaps_new(maps_cfg); vscf_destroy(maps_cfg); gdmaps_load_databases(rv); return rv; }
F_NONNULL static void conf_options(const vscf_data_t* cfg_root) { dmn_assert(cfg_root); // options stanza: set dmn_debug bool debug_tmp = false; const vscf_data_t* options = vscf_hash_get_data_byconstkey(cfg_root, "options", true); if(options) { if(!vscf_is_hash(options)) log_fatal("Config stanza 'options' must be a hash"); const vscf_data_t* debug_setting = vscf_hash_get_data_byconstkey(options, "debug", false); if(debug_setting && (!vscf_is_simple(debug_setting) || !vscf_simple_get_as_bool(debug_setting, &debug_tmp))) log_fatal("Config option 'debug': value must be 'true' or 'false'"); } dmn_set_debug(debug_tmp); }
static vscf_data_t* conf_load_vscf(const char* cfg_file) { vscf_data_t* out = NULL; struct stat cfg_stat; if(!stat(cfg_file, &cfg_stat)) { log_info("Loading configuration from '%s'", cfg_file); out = vscf_scan_filename(cfg_file); if(!out) log_fatal("Loading configuration from '%s' failed", cfg_file); if(!vscf_is_hash(out)) { dmn_assert(vscf_is_array(out)); log_fatal("Config file '%s' cannot be an '[ array ]' at the top level", cfg_file); } } else { log_info("No config file at '%s', using defaults", cfg_file); } return out; }
F_NONNULL static bool _dcmap_new_iter(const char* key, unsigned klen V_UNUSED, const vscf_data_t* val, void* data) { dmn_assert(key); dmn_assert(val); dmn_assert(data); dcmap_iter_data* did = data; unsigned true_depth = did->true_depth + (did->dcmap->skip_level ? 1 : 0); if(true_depth == 0) validate_continent_code(key, did->map_name); else if(true_depth == 1) validate_country_code(key, did->map_name); did->dcmap->child_names[did->child_num] = strdup(key); if(vscf_is_hash(val)) did->dcmap->child_dcmaps[did->child_num] = dcmap_new(val, did->dclists, did->dcmap->def_dclist, true_depth + 1, did->map_name, did->allow_auto); else did->dcmap->child_dclists[did->child_num] = dclists_find_or_add_vscf(did->dclists, val, did->map_name, did->allow_auto); did->child_num++; return true; }
static const vscf_data_t* conf_load_vscf(const char* cfg_dir) { const vscf_data_t* out = NULL; gdnsd_set_config_dir(cfg_dir); char*cfg_path = gdnsd_resolve_path_cfg("config", NULL); struct stat cfg_stat; if(!stat(cfg_path, &cfg_stat)) { log_info("Loading configuration from '%s'", cfg_path); char* vscf_err; out = vscf_scan_filename(cfg_path, &vscf_err); if(!out) log_fatal("Configuration from '%s' failed: %s", cfg_path, vscf_err); if(!vscf_is_hash(out)) log_fatal("Configuration from '%s' failed: config was an array!", cfg_path); } else { log_info("No config file at '%s', using defaults + zones auto-scan", cfg_path); } free(cfg_path); return out; }
F_NONNULL static void config_cnameset(const char* res_name, const char* stanza, cnset_t* cnset, const vscf_data_t* cfg) { dmn_assert(res_name); dmn_assert(stanza); dmn_assert(cnset); dmn_assert(cfg); if(!vscf_is_hash(cfg)) log_fatal("plugin_weighted: resource '%s' stanza '%s' value must be a hash", res_name, stanza); cnset->count = vscf_hash_get_len(cfg); if(cnset->count > MAX_ITEMS_PER_SET) log_fatal("plugin_weighted: resource '%s' (%s): number of cnames cannot be more than %u", res_name, stanza, MAX_ITEMS_PER_SET); if(!cnset->count) log_fatal("plugin_weighted: resource '%s' (%s): empty cname sets not allowed", res_name, stanza); cnset->items = calloc(cnset->count, sizeof(res_citem_t)); cname_iter_data_t cid = { .cnset = cnset, .res_name = res_name, .stanza = stanza, .item_idx = 0, }; vscf_hash_iterate(cfg, true, config_item_cname, &cid); cnset->weight = 0; for(unsigned i = 0; i < cnset->count; i++) { const unsigned cwt = cnset->items[i].weight; dmn_assert(cwt); cnset->weight += cwt; } dmn_assert(cnset->weight); } F_NONNULL static void config_auto(resource_t* res, const vscf_data_t* res_cfg) { dmn_assert(res); dmn_assert(res_cfg); dmn_assert(vscf_is_hash(res_cfg)); // mark all possible parameter-keys vscf_hash_get_data_byconstkey(res_cfg, "service_types", true); vscf_hash_get_data_byconstkey(res_cfg, "multi", true); vscf_hash_get_data_byconstkey(res_cfg, "up_thresh", true); // make a copy that contains no parameters, only item-name keys vscf_data_t* res_cfg_noparams = vscf_clone(res_cfg, true); if(!vscf_hash_get_len(res_cfg_noparams)) log_fatal("plugin_weighted: resource '%s' (direct) contains no weighted items", res->name); const char* first_name = vscf_hash_get_key_byindex(res_cfg_noparams, 0, NULL); const vscf_data_t* first_cfg = vscf_hash_get_data_byindex(res_cfg_noparams, 0); if(vscf_is_hash(first_cfg)) { // grouped address mode... if(!vscf_hash_get_len(first_cfg)) log_fatal("plugin_weighted: resource '%s' (direct): group '%s': contains no addresses", res->name, first_name); const char* lb_name = vscf_hash_get_key_byindex(first_cfg, 0, NULL); const vscf_data_t* lb_cfg = vscf_hash_get_data_byindex(first_cfg, 0); if(!vscf_is_array(lb_cfg) || !vscf_array_get_len(lb_cfg) || !vscf_is_simple(vscf_array_get_data(lb_cfg, 0))) log_fatal("plugin_weighted: resource '%s' (direct): group '%s': item '%s': value must be an array of [ IP, weight ]", res->name, first_name, lb_name); const char* first_addr_txt = vscf_simple_get_data(vscf_array_get_data(lb_cfg, 0)); anysin_t temp_sin; int addr_err = gdnsd_anysin_getaddrinfo(first_addr_txt, NULL, &temp_sin); if(addr_err) log_fatal("plugin_weighted: resource '%s' (direct): group '%s': item '%s': could not parse '%s' as an IP address: %s", res->name, first_name, lb_name, first_addr_txt, gai_strerror(addr_err)); if(temp_sin.sa.sa_family == AF_INET6) { res->addrs_v6 = calloc(1, sizeof(addrset_t)); config_addrset(res->name, "direct", true, res->addrs_v6, res_cfg); } else { dmn_assert(temp_sin.sa.sa_family == AF_INET); res->addrs_v4 = calloc(1, sizeof(addrset_t)); config_addrset(res->name, "direct", false, res->addrs_v4, res_cfg); } } else if(vscf_is_array(first_cfg)) { // ungrouped address, or cnames const vscf_data_t* first_ac = vscf_array_get_data(first_cfg, 0); if(!first_ac || !vscf_is_simple(first_ac)) log_fatal("plugin_weighted: resource '%s' (direct): item '%s': first element of array should be an IP address or CNAME string", res->name, first_name); anysin_t temp_sin; if(gdnsd_anysin_getaddrinfo(vscf_simple_get_data(first_ac), NULL, &temp_sin)) { // was not a valid address, try cnames mode res->cnames = calloc(1, sizeof(cnset_t)); config_cnameset(res->name, "direct", res->cnames, res_cfg_noparams); } else { // was a valid address, try addrset mode if(temp_sin.sa.sa_family == AF_INET6) { res->addrs_v6 = calloc(1, sizeof(addrset_t)); config_addrset(res->name, "direct", true, res->addrs_v6, res_cfg); } else { dmn_assert(temp_sin.sa.sa_family == AF_INET); res->addrs_v4 = calloc(1, sizeof(addrset_t)); config_addrset(res->name, "direct", false, res->addrs_v4, res_cfg); } } } else { log_fatal("plugin_weighted: resource '%s' (direct): item '%s': resource type not detectable (should be array of [ IP, weight ], array of [ CNAME, weight ], or hashed address group ...)", res->name, first_name); } vscf_destroy(res_cfg_noparams); }
dcmap_t* dcmap_new(const vscf_data_t* map_cfg, dclists_t* dclists, const unsigned parent_def, const unsigned true_depth, const char* map_name, const bool allow_auto) { dmn_assert(map_cfg); dmn_assert(dclists); dmn_assert(map_name); dmn_assert(vscf_is_hash(map_cfg)); dcmap_t* dcmap = calloc(1, sizeof(dcmap_t)); unsigned nchild = vscf_hash_get_len(map_cfg); const vscf_data_t* def_cfg = vscf_hash_get_data_byconstkey(map_cfg, "default", true); if(def_cfg) { if(!true_depth) { uint8_t newlist[256]; int status = dclists_xlate_vscf(dclists, def_cfg, map_name, newlist, allow_auto); if(status) { dmn_assert(status == -1 && allow_auto); dcmap->def_dclist = -1; } else { dcmap->def_dclist = 0; dclists_replace_list0(dclists, (uint8_t*)strdup((char*)newlist)); } } else { dcmap->def_dclist = dclists_find_or_add_vscf(dclists, def_cfg, map_name, allow_auto); } nchild--; // don't iterate "default" later } else { if(!true_depth) { dcmap->def_dclist = allow_auto ? -1 : 0; } else { dcmap->def_dclist = parent_def; } } const vscf_data_t* skip_cfg = vscf_hash_get_data_byconstkey(map_cfg, "skip_level", true); if(skip_cfg) { if(!vscf_is_simple(skip_cfg) || !vscf_simple_get_as_bool(skip_cfg, &dcmap->skip_level)) log_fatal("plugin_geoip: map '%s': 'skip_level' must be a boolean value ('true' or 'false')", map_name); nchild--; // don't iterate "skip_level" later } if(nchild) { dcmap->num_children = nchild; dcmap->child_names = calloc(nchild, sizeof(char*)); dcmap->child_dclists = calloc(nchild, sizeof(unsigned)); dcmap->child_dcmaps = calloc(nchild, sizeof(dcmap_t*)); dcmap_iter_data did = { .child_num = 0, .dcmap = dcmap, .dclists = dclists, .map_name = map_name, .true_depth = true_depth, .allow_auto = allow_auto }; vscf_hash_iterate(map_cfg, true, _dcmap_new_iter, &did); } return dcmap; } int dcmap_lookup_loc(const dcmap_t* dcmap, const char* locstr) { dmn_assert(dcmap); dmn_assert(locstr); if(*locstr && dcmap->skip_level) locstr += strlen(locstr) + 1; if(*locstr) { for(unsigned i = 0; i < dcmap->num_children; i++) { if(!strcasecmp(locstr, dcmap->child_names[i])) { if(dcmap->child_dcmaps[i]) return dcmap_lookup_loc(dcmap->child_dcmaps[i], locstr + strlen(locstr) + 1); return dcmap->child_dclists[i]; } } } return dcmap->def_dclist; }
F_NONNULL static void config_item_addr_groups(res_aitem_t* res_item, const char* res_name, const char* stanza, const char* item_name, const bool ipv6, const vscf_data_t* cfg_data, addrset_t* addrset) { dmn_assert(res_name); dmn_assert(stanza); dmn_assert(item_name); dmn_assert(res_item); dmn_assert(cfg_data); dmn_assert(addrset); if(!vscf_is_hash(cfg_data)) log_fatal("plugin_weighted: resource '%s' (%s), group '%s': groups values must be a hashes", res_name, stanza, item_name); unsigned num_addrs = vscf_hash_get_len(cfg_data); if(!num_addrs) log_fatal("plugin_weighted: resource '%s' (%s), group '%s': must contain one or more label => [ IPADDR, WEIGHT ] settings", res_name, stanza, item_name); if(num_addrs > MAX_ADDRS_PER_GROUP) log_fatal("plugin_weighted: resource '%s' (%s), group '%s': too many addresses (max %u)", res_name, stanza, item_name, MAX_ADDRS_PER_GROUP); // track maximum group-size actually configured if(cfg_max_addrs_per_group < num_addrs) cfg_max_addrs_per_group = num_addrs; res_item->count = num_addrs; res_item->as = calloc(num_addrs, sizeof(addrstate_t)); iaga_t iaga = { .addrset = addrset, .res_item = res_item, .res_name = res_name, .stanza = stanza, .item_name = item_name, .ipv6 = ipv6, .lb_idx = 0 }; vscf_hash_iterate(cfg_data, true, config_addr_group_addr, &iaga); // sum/max the lb weights res_item->weight = 0; res_item->max_weight = 0; for(unsigned i = 0; i < res_item->count; i++) { const unsigned awt = res_item->as[i].weight; res_item->weight += awt; if(res_item->max_weight < awt) res_item->max_weight = awt; } log_debug("plugin_weighted: resource '%s' (%s), group '%s' with %u addresses & weight %u added", res_name, stanza, item_name, num_addrs, res_item->weight); } typedef struct { unsigned item_idx; addrset_t* addrset; const char* res_name; const char* stanza; bool ipv6; } addr_iter_data_t; static bool config_addrset_item(const char* item_name, unsigned klen V_UNUSED, const vscf_data_t* cfg_data, void* aid_asvoid) { // pull a bunch of data from addr_iter_data_t... addr_iter_data_t* addr_iter_data = (addr_iter_data_t*)aid_asvoid; const unsigned item_idx = addr_iter_data->item_idx++; addrset_t* addrset = addr_iter_data->addrset; const char* res_name = addr_iter_data->res_name; const char* stanza = addr_iter_data->stanza; const bool ipv6 = addr_iter_data->ipv6; // autodetect if not set if(unlikely(addrset->gmode == RES_ASET_UNKNOWN)) { dmn_assert(!item_idx); // should get set on first iteration if(vscf_is_hash(cfg_data)) { addrset->gmode = RES_ASET_GROUPED; } else { if(!vscf_is_array(cfg_data)) log_fatal("plugin_weighted: resource '%s' (%s): item data must be hash (grouped mode) or array (ungrouped mode)", res_name, stanza); addrset->gmode = RES_ASET_UNGROUPED; } } res_aitem_t* res_item = &addrset->items[item_idx]; if(addrset->gmode == RES_ASET_UNGROUPED) { config_item_addrs(res_item, res_name, stanza, item_name, ipv6, cfg_data, addrset); } else { dmn_assert(addrset->gmode == RES_ASET_GROUPED); config_item_addr_groups(res_item, res_name, stanza, item_name, ipv6, cfg_data, addrset); } return true; }
F_NONNULL static void inject_child_plugin_config(dc_t* this_dc, const char* resname, vscf_data_t* cfg) { dmn_assert(this_dc); dmn_assert(resname); dmn_assert(cfg); char* child_resname = make_synth_resname(resname, this_dc->dc_name); this_dc->res_name = child_resname; // Move up 2 layers: dcX -> dcmap -> resX vscf_data_t* res_cfg = (vscf_data_t*)cfg; for(unsigned i = 0; i < 2; i++) { res_cfg = (vscf_data_t*)vscf_get_parent(res_cfg); dmn_assert(res_cfg); } // Move up 3 more layers: // resX -> resources -> metafo|geoip -> plugins vscf_data_t* plugins_top = res_cfg; for(unsigned i = 0; i < 3; i++) { plugins_top = (vscf_data_t*)vscf_get_parent(plugins_top); dmn_assert(plugins_top); } // synth multifo stanza for: dc1 => 192.0.2.1, or dc1 => [ 192.0.2.1, ... ] bool cfg_synthed = false; if(!vscf_is_hash(cfg)) { // synthesize a hash for multifo for single/array vscf_data_t* newhash = vscf_hash_new(); vscf_data_t* plugname_cfg = vscf_simple_new("multifo", 7); vscf_hash_add_val("plugin", 6, newhash, plugname_cfg); const unsigned alen = vscf_array_get_len(cfg); for(unsigned i = 0; i < alen; i++) { const vscf_data_t* this_addr_cfg = vscf_array_get_data(cfg, i); if(!vscf_is_simple(this_addr_cfg)) log_fatal("plugin_" PNSTR ": resource '%s': datacenter '%s': if defined as an array, array values must all be address strings", resname, this_dc->dc_name); const unsigned lnum = i + 1; char lbuf[12]; snprintf(lbuf, 12, "%u", lnum); vscf_hash_add_val(lbuf, strlen(lbuf), newhash, vscf_clone(this_addr_cfg, false)); } cfg_synthed = true; cfg = newhash; } // inherit resource-level stuff down to dc-level vscf_hash_inherit_all(res_cfg, cfg, true); this_dc->plugin_name = get_defaulted_plugname(cfg, resname, this_dc->dc_name); if(!strcmp(this_dc->plugin_name, PNSTR)) log_fatal("plugin_" PNSTR ": resource '%s': datacenter '%s': plugin_" PNSTR " cannot synthesize config for itself...", resname, this_dc->dc_name); // Create top-level plugins => { foo => {} } if necc vscf_data_t* plug_cfg = (vscf_data_t*)vscf_hash_get_data_bystringkey(plugins_top, this_dc->plugin_name, false); if(!plug_cfg) { plug_cfg = vscf_hash_new(); vscf_hash_add_val(this_dc->plugin_name, strlen(this_dc->plugin_name), plugins_top, plug_cfg); } // special-case for geoip -> metafo synthesis, use resources sub-stanza if(!strcmp(this_dc->plugin_name, "metafo")) { vscf_data_t* synth_res_cfg = (vscf_data_t*)vscf_hash_get_data_byconstkey(plug_cfg, "resources", false); if(!synth_res_cfg) { synth_res_cfg = vscf_hash_new(); vscf_hash_add_val("resources", strlen("resources"), plug_cfg, synth_res_cfg); } plug_cfg = synth_res_cfg; // for below } // Check if resource already exists if(vscf_hash_get_data_bystringkey(plug_cfg, child_resname, false)) log_fatal("plugin_" PNSTR ": resource '%s': datacenter '%s': synthesis of resource '%s' for plugin '%s' failed (resource name already exists)", resname, this_dc->dc_name, child_resname, this_dc->plugin_name); // Add it, using clone() to skip marked key "plugin" vscf_hash_add_val(child_resname, strlen(child_resname), plug_cfg, vscf_clone(cfg, true)); // destroy clone source if synthesized and disconnected from real config tree if(cfg_synthed) vscf_destroy(cfg); }
F_NONNULL static void config_addrset(const char* res_name, const char* stanza, const bool ipv6, addrset_t* addrset, const vscf_data_t* cfg) { dmn_assert(res_name); dmn_assert(stanza); dmn_assert(addrset); dmn_assert(cfg); if(!vscf_is_hash(cfg)) log_fatal("plugin_weighted: resource '%s' stanza '%s' value must be a hash", res_name, stanza); const vscf_data_t* parent = vscf_get_parent(cfg); // inherit down the applicable res-level parameters vscf_hash_inherit(parent, (vscf_data_t*)cfg, "service_types", true); vscf_hash_inherit(parent, (vscf_data_t*)cfg, "multi", true); vscf_hash_inherit(parent, (vscf_data_t*)cfg, "up_thresh", true); // Get a starting assumption of our item count addrset->count = vscf_hash_get_len(cfg); /////// Process the parameters... // service_types const vscf_data_t* res_stypes = vscf_hash_get_data_byconstkey(cfg, "service_types", true); if (res_stypes) { addrset->count--; // minus one for service_types entry addrset->num_svcs = vscf_array_get_len(res_stypes); if(!addrset->num_svcs) log_fatal("plugin_weighted: resource '%s' (%s): service_types cannot be an empty array", res_name, stanza); addrset->svc_names = malloc(addrset->num_svcs * sizeof(char*)); for(unsigned i = 0; i < addrset->num_svcs; i++) { const vscf_data_t* this_svc_cfg = vscf_array_get_data(res_stypes, i); if(!vscf_is_simple(this_svc_cfg)) log_fatal("plugin_weighted: resource '%s' (%s): service_types values must be strings", res_name, stanza); addrset->svc_names[i] = strdup(vscf_simple_get_data(this_svc_cfg)); } } else { addrset->num_svcs = 1; addrset->svc_names = malloc(sizeof(char*)); addrset->svc_names[0] = strdup("default"); } // multi option addrset->multi = false; const vscf_data_t* multi_cfg = vscf_hash_get_data_byconstkey(cfg, "multi", true); if(multi_cfg) { addrset->count--; // minus one for multi entry if(!vscf_is_simple(multi_cfg) || !vscf_simple_get_as_bool(multi_cfg, &addrset->multi)) log_fatal("plugin_weighted: resource '%s' (%s): 'multi' must be a boolean value ('true' or 'false')", res_name, stanza); } // up threshold as double double up_thresh = 0.5; const vscf_data_t* thresh_cfg = vscf_hash_get_data_byconstkey(cfg, "up_thresh", true); if(thresh_cfg) { addrset->count--; // minus one for up_thresh entry if(!vscf_is_simple(thresh_cfg) || !vscf_simple_get_as_double(thresh_cfg, &up_thresh) || up_thresh <= 0.0 || up_thresh > 1.0) log_fatal("plugin_weighted: resource '%s' (%s): 'up_thresh' must be a floating point value in the range (0.0 - 1.0]", res_name, stanza); } if(addrset->count > MAX_ITEMS_PER_SET) log_fatal("plugin_weighted: resource '%s' (%s): number of direct groups or addrs within one family cannot be more than %u", res_name, stanza, MAX_ITEMS_PER_SET); if(!addrset->count) log_fatal("plugin_weighted: resource '%s' (%s): empty address-family sets not allowed", res_name, stanza); // track maximum res-size actually configured if(cfg_max_items_per_res < addrset->count) cfg_max_items_per_res = addrset->count; addrset->items = calloc(addrset->count, sizeof(res_aitem_t)); addrset->gmode = RES_ASET_UNKNOWN; addr_iter_data_t aid = { .item_idx = 0, .addrset = addrset, .res_name = res_name, .stanza = stanza, .ipv6 = ipv6 }; vscf_hash_iterate(cfg, true, config_addrset_item, &aid); addrset->weight = 0; addrset->max_weight = 0; for(unsigned i = 0; i < addrset->count; i++) { const unsigned iwt = addrset->items[i].weight; dmn_assert(iwt); dmn_assert(addrset->items[i].max_weight); addrset->weight += iwt; if(addrset->max_weight < iwt) addrset->max_weight = iwt; } dmn_assert(addrset->weight); dmn_assert(addrset->max_weight); addrset->up_weight = ceil(up_thresh * addrset->weight); dmn_assert(addrset->up_weight); } typedef struct { cnset_t* cnset; const char* res_name; const char* stanza; unsigned item_idx; } cname_iter_data_t; F_NONNULL static bool config_item_cname(const char* item_name, unsigned klen V_UNUSED, const vscf_data_t* cfg_data, void* cid_asvoid) { dmn_assert(item_name); dmn_assert(cfg_data); dmn_assert(cid_asvoid); cname_iter_data_t* cid = (cname_iter_data_t*)cid_asvoid; cnset_t* cnset = cid->cnset; const char* res_name = cid->res_name; const char* stanza = cid->stanza; const unsigned item_idx = cid->item_idx++; res_citem_t* res_item = &cnset->items[item_idx]; long wtemp = 0; if(!vscf_is_array(cfg_data) || (2 != vscf_array_get_len(cfg_data)) || !vscf_is_simple(vscf_array_get_data(cfg_data, 0)) || !vscf_is_simple(vscf_array_get_data(cfg_data, 1)) || !vscf_simple_get_as_long(vscf_array_get_data(cfg_data, 1), &wtemp) || wtemp < 1 || wtemp > MAX_WEIGHT ) log_fatal("plugin_weighted: resource '%s' (%s), item '%s': values in cname mode must be arrays of [ CNAME, WEIGHT ], where weight must be an integer in the range 1 - " MAX_WEIGHT_STR, res_name, stanza, item_name); res_item->weight = wtemp; uint8_t* dname = malloc(256); dname_status_t dnstat = vscf_simple_get_as_dname(vscf_array_get_data(cfg_data, 0), dname); if(dnstat == DNAME_INVALID) log_fatal("plugin_weighted: resource '%s' (%s), item '%s': '%s' is not a legal domainname", res_name, stanza, item_name, vscf_simple_get_data(vscf_array_get_data(cfg_data, 0))); if(dnstat == DNAME_VALID) dname = dname_trim(dname); res_item->cname = dname; log_debug("plugin_weighted: resource '%s' (%s), item '%s', CNAME '%s' added with weight %u", res_name, stanza, item_name, logf_dname(dname), res_item->weight); return true; }
// Technically we could/should check for duplicates here. The plugin will // still fail later though: when a resource is defined, the datacenter // names go into a hash requiring uniqueness, and the count is required // to match (ditto for auto_dc_coords never succeeding with dupes in the // datacenters list). dcinfo_t* dcinfo_new(const vscf_data_t* dc_cfg, const vscf_data_t* dc_auto_cfg, const vscf_data_t* dc_auto_limit_cfg, const char* map_name) { dmn_assert(dc_cfg); dmn_assert(map_name); dcinfo_t* info = malloc(sizeof(dcinfo_t)); const unsigned num_dcs = vscf_array_get_len(dc_cfg); unsigned num_auto = num_dcs; if(!num_dcs) log_fatal("plugin_geoip: map '%s': 'datacenters' must be an array of one or more strings", map_name); if(num_dcs > 254) log_fatal("plugin_geoip: map '%s': %u datacenters is too many, this code only supports up to 254", map_name, num_dcs); info->names = malloc(sizeof(char*) * num_dcs); info->num_dcs = num_dcs; for(unsigned i = 0; i < num_dcs; i++) { const vscf_data_t* dcname_cfg = vscf_array_get_data(dc_cfg, i); if(!dcname_cfg || !vscf_is_simple(dcname_cfg)) log_fatal("plugin_geoip: map '%s': 'datacenters' must be an array of one or more strings", map_name); info->names[i] = strdup(vscf_simple_get_data(dcname_cfg)); if(!strcmp(info->names[i], "auto")) log_fatal("plugin_geoip: map '%s': datacenter name 'auto' is illegal", map_name); } if(dc_auto_cfg) { if(!vscf_is_hash(dc_auto_cfg)) log_fatal("plugin_geoip: map '%s': auto_dc_coords must be a key-value hash", map_name); num_auto = vscf_hash_get_len(dc_auto_cfg); info->coords = malloc(num_dcs * 2 * sizeof(double)); for(unsigned i = 0; i < 2*num_dcs; i++) info->coords[i] = NAN; for(unsigned i = 0; i < num_auto; i++) { const char* dcname = vscf_hash_get_key_byindex(dc_auto_cfg, i, NULL); unsigned dcidx; for(dcidx = 0; dcidx < num_dcs; dcidx++) { if(!strcmp(dcname, info->names[dcidx])) break; } if(dcidx == num_dcs) log_fatal("plugin_geoip: map '%s': auto_dc_coords key '%s' not matched from 'datacenters' list", map_name, dcname); if(!isnan(info->coords[(dcidx*2)])) log_fatal("plugin_geoip: map '%s': auto_dc_coords key '%s' defined twice", map_name, dcname); const vscf_data_t* coord_cfg = vscf_hash_get_data_byindex(dc_auto_cfg, i); const vscf_data_t* lat_cfg; const vscf_data_t* lon_cfg; double lat, lon; if( !vscf_is_array(coord_cfg) || vscf_array_get_len(coord_cfg) != 2 || !(lat_cfg = vscf_array_get_data(coord_cfg, 0)) || !(lon_cfg = vscf_array_get_data(coord_cfg, 1)) || !vscf_is_simple(lat_cfg) || !vscf_is_simple(lon_cfg) || !vscf_simple_get_as_double(lat_cfg, &lat) || !vscf_simple_get_as_double(lon_cfg, &lon) || lat > 90.0 || lat < -90.0 || lon > 180.0 || lon < -180.0 ) 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); info->coords[(dcidx * 2)] = lat * DEG2RAD; info->coords[(dcidx * 2) + 1] = lon * DEG2RAD; } } else { info->coords = NULL; } if(dc_auto_limit_cfg) { unsigned long auto_limit_ul; if(!vscf_is_simple(dc_auto_limit_cfg) || !vscf_simple_get_as_ulong(dc_auto_limit_cfg, &auto_limit_ul)) log_fatal("plugin_geoip: map '%s': auto_dc_limit must be a single unsigned integer value", map_name); if(auto_limit_ul > num_auto || !auto_limit_ul) auto_limit_ul = num_auto; info->auto_limit = auto_limit_ul; } else { info->auto_limit = (num_auto > 3) ? 3 : num_auto; } return info; }
F_NONNULL static void config_cnameset(const char* res_name, const char* stanza, cnset_t* cnset, const vscf_data_t* cfg) { dmn_assert(res_name); dmn_assert(stanza); dmn_assert(cnset); dmn_assert(cfg); if(!vscf_is_hash(cfg)) log_fatal("plugin_weighted: resource '%s' stanza '%s' value must be a hash", res_name, stanza); cnset->count = vscf_hash_get_len(cfg); // service_types cnset->num_svcs = 0; const vscf_data_t* res_stypes = vscf_hash_get_data_byconstkey(cfg, "service_types", true); if (res_stypes) { cnset->count--; // minus one for service_types entry cnset->num_svcs = vscf_array_get_len(res_stypes); if(cnset->num_svcs) { cnset->svc_names = malloc(cnset->num_svcs * sizeof(char*)); for(unsigned i = 0; i < cnset->num_svcs; i++) { const vscf_data_t* this_svc_cfg = vscf_array_get_data(res_stypes, i); if(!vscf_is_simple(this_svc_cfg)) log_fatal("plugin_weighted: resource '%s' (%s): service_types values must be strings", res_name, stanza); cnset->svc_names[i] = strdup(vscf_simple_get_data(this_svc_cfg)); } } } else { cnset->num_svcs = 1; cnset->svc_names = malloc(sizeof(char*)); cnset->svc_names[0] = strdup(DEFAULT_SVCNAME); } // up threshold as double double up_thresh = 0.5; const vscf_data_t* thresh_cfg = vscf_hash_get_data_byconstkey(cfg, "up_thresh", true); if(thresh_cfg) { cnset->count--; // minus one for up_thresh entry if(!vscf_is_simple(thresh_cfg) || !vscf_simple_get_as_double(thresh_cfg, &up_thresh) || up_thresh <= 0.0 || up_thresh > 1.0) log_fatal("plugin_weighted: resource '%s' (%s): 'up_thresh' must be a floating point value in the range (0.0 - 1.0]", res_name, stanza); } // multi option is processed for count-correctness, but ignored (it's not legal // here, but may be present due to inheritance of defaults!) if(vscf_hash_get_data_byconstkey(cfg, "multi", true)) cnset->count--; if(cnset->count > MAX_ITEMS_PER_SET) log_fatal("plugin_weighted: resource '%s' (%s): number of cnames cannot be more than %u", res_name, stanza, MAX_ITEMS_PER_SET); if(!cnset->count) log_fatal("plugin_weighted: resource '%s' (%s): empty cname sets not allowed", res_name, stanza); cnset->items = calloc(cnset->count, sizeof(res_citem_t)); cname_iter_data_t cid = { .cnset = cnset, .res_name = res_name, .stanza = stanza, .item_idx = 0, }; vscf_hash_iterate(cfg, true, config_item_cname, &cid); cnset->weight = 0; for(unsigned i = 0; i < cnset->count; i++) { const unsigned cwt = cnset->items[i].weight; dmn_assert(cwt); cnset->weight += cwt; } dmn_assert(cnset->weight); cnset->up_weight = ceil(up_thresh * cnset->weight); } F_NONNULL static void config_auto(resource_t* res, const vscf_data_t* res_cfg) { dmn_assert(res); dmn_assert(res_cfg); dmn_assert(vscf_is_hash(res_cfg)); // mark all possible parameter-keys vscf_hash_get_data_byconstkey(res_cfg, "service_types", true); vscf_hash_get_data_byconstkey(res_cfg, "multi", true); vscf_hash_get_data_byconstkey(res_cfg, "up_thresh", true); // make a copy that contains no parameters, only item-name keys vscf_data_t* res_cfg_noparams = vscf_clone(res_cfg, true); if(!vscf_hash_get_len(res_cfg_noparams)) log_fatal("plugin_weighted: resource '%s' (direct) contains no weighted items", res->name); const char* first_name = vscf_hash_get_key_byindex(res_cfg_noparams, 0, NULL); const vscf_data_t* first_cfg = vscf_hash_get_data_byindex(res_cfg_noparams, 0); if(vscf_is_hash(first_cfg)) { // grouped address mode... if(!vscf_hash_get_len(first_cfg)) log_fatal("plugin_weighted: resource '%s' (direct): group '%s': contains no addresses", res->name, first_name); const char* lb_name = vscf_hash_get_key_byindex(first_cfg, 0, NULL); const vscf_data_t* lb_cfg = vscf_hash_get_data_byindex(first_cfg, 0); if(!vscf_is_array(lb_cfg) || !vscf_array_get_len(lb_cfg) || !vscf_is_simple(vscf_array_get_data(lb_cfg, 0))) log_fatal("plugin_weighted: resource '%s' (direct): group '%s': item '%s': value must be an array of [ IP, weight ]", res->name, first_name, lb_name); const char* first_addr_txt = vscf_simple_get_data(vscf_array_get_data(lb_cfg, 0)); dmn_anysin_t temp_sin; int addr_err = gdnsd_anysin_getaddrinfo(first_addr_txt, NULL, &temp_sin); if(addr_err) log_fatal("plugin_weighted: resource '%s' (direct): group '%s': item '%s': could not parse '%s' as an IP address: %s", res->name, first_name, lb_name, first_addr_txt, gai_strerror(addr_err)); if(temp_sin.sa.sa_family == AF_INET6) { res->addrs_v6 = calloc(1, sizeof(addrset_t)); config_addrset(res->name, "direct", true, res->addrs_v6, res_cfg); } else { dmn_assert(temp_sin.sa.sa_family == AF_INET); res->addrs_v4 = calloc(1, sizeof(addrset_t)); config_addrset(res->name, "direct", false, res->addrs_v4, res_cfg); } } else if(vscf_is_array(first_cfg)) { // ungrouped address, or cnames const vscf_data_t* first_ac = vscf_array_get_data(first_cfg, 0); if(!first_ac || !vscf_is_simple(first_ac)) log_fatal("plugin_weighted: resource '%s' (direct): item '%s': first element of array should be an IP address or CNAME string", res->name, first_name); dmn_anysin_t temp_sin; if(gdnsd_anysin_getaddrinfo(vscf_simple_get_data(first_ac), NULL, &temp_sin)) { // was not a valid address, try cnames mode res->cnames = calloc(1, sizeof(cnset_t)); config_cnameset(res->name, "direct", res->cnames, res_cfg); } else { // was a valid address, try addrset mode if(temp_sin.sa.sa_family == AF_INET6) { res->addrs_v6 = calloc(1, sizeof(addrset_t)); config_addrset(res->name, "direct", true, res->addrs_v6, res_cfg); } else { dmn_assert(temp_sin.sa.sa_family == AF_INET); res->addrs_v4 = calloc(1, sizeof(addrset_t)); config_addrset(res->name, "direct", false, res->addrs_v4, res_cfg); } } } else { log_fatal("plugin_weighted: resource '%s' (direct): item '%s': resource type not detectable (should be array of [ IP, weight ], array of [ CNAME, weight ], or hashed address group ...)", res->name, first_name); } vscf_destroy(res_cfg_noparams); }