// 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); 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); }
// arguably, with at least some of the v4-like spaces we could simply translate and hope to de-dupe, // if we upgraded nlist_normalize1 to de-dupe matching dclists instead of failing them F_NONNULL static bool nets_parse(vscf_data_t* nets_cfg, dclists_t* dclists, const char* map_name, nlist_t* nl) { bool rv = false; const unsigned input_nnets = vscf_hash_get_len(nets_cfg); for(unsigned i = 0; i < input_nnets; i++) { // convert 192.0.2.0/24 -> anysin_t w/ mask in port field unsigned net_str_len = 0; const char* net_str_cfg = vscf_hash_get_key_byindex(nets_cfg, i, &net_str_len); char net_str[net_str_len + 1]; memcpy(net_str, net_str_cfg, net_str_len + 1); char* mask_str = strchr(net_str, '/'); if(!mask_str) { log_err("plugin_geoip: map '%s': nets entry '%s' does not parse as addr/mask", map_name, net_str); rv = true; break; } *mask_str++ = '\0'; dmn_anysin_t tempsin; int addr_err = gdnsd_anysin_getaddrinfo(net_str, mask_str, &tempsin); if(addr_err) { log_err("plugin_geoip: map '%s': nets entry '%s/%s' does not parse as addr/mask: %s", map_name, net_str, mask_str, gai_strerror(addr_err)); rv = true; break; } unsigned mask; uint8_t ipv6[16]; // now store the anysin data into net_t if(tempsin.sa.sa_family == AF_INET6) { mask = ntohs(tempsin.sin6.sin6_port); if(mask > 128) { log_err("plugin_geoip: map '%s': nets entry '%s/%s': illegal IPv6 mask (>128)", map_name, net_str, mask_str); rv = true; break; } memcpy(ipv6, tempsin.sin6.sin6_addr.s6_addr, 16); if(check_v4_issues(ipv6, mask)) { log_err("plugin_geoip: map '%s': 'nets' entry '%s/%s' covers illegal IPv4-like space, see the documentation for more info", map_name, net_str, mask_str); rv = true; break; } } else { dmn_assert(tempsin.sa.sa_family == AF_INET); mask = ntohs(tempsin.sin.sin_port) + 96U; if(mask > 128) { log_err("plugin_geoip: map '%s': nets entry '%s/%s': illegal IPv4 mask (>32)", map_name, net_str, mask_str); rv = true; break; } memset(ipv6, 0, 16); memcpy(&ipv6[12], &tempsin.sin.sin_addr.s_addr, 4); } // get dclist integer from rhs vscf_data_t* dc_cfg = vscf_hash_get_data_byindex(nets_cfg, i); const uint32_t dclist = dclists_find_or_add_vscf(dclists, dc_cfg, map_name, false); dmn_assert(dclist <= DCLIST_MAX); // auto not allowed here nlist_append(nl, ipv6, mask, dclist); } return rv; }
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); }