1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
/* Copyright © 2012 Brandon L Black <blblack@gmail.com>
*
* This file is part of gdnsd.
*
* gdnsd-plugin-geoip is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* gdnsd-plugin-geoip is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gdnsd. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <config.h>
#include "nets.h"
#include <gdnsd/log.h>
#include <gdnsd/net.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
// Check whether the passed network is a subnet
// of (or the entirety of) any of the "undefined"
// v4-like subspaces in our ntree databases...
F_NONNULL F_PURE
static bool v6_subnet_of(const uint8_t* check, const unsigned check_mask, const uint8_t* v4, const unsigned v4_mask)
{
gdnsd_assert(!(v4_mask & 7)); // all v4_mask are whole byte masks
bool rv = false;
if (check_mask >= v4_mask)
rv = !memcmp(check, v4, (v4_mask >> 3));
return rv;
}
F_NONNULL F_PURE
static bool check_v4_issues(const uint8_t* ipv6, const unsigned mask)
{
gdnsd_assert(mask < 129);
return (
v6_subnet_of(ipv6, mask, start_v4mapped, 96)
|| v6_subnet_of(ipv6, mask, start_siit, 96)
|| v6_subnet_of(ipv6, mask, start_wkp, 96)
|| v6_subnet_of(ipv6, mask, start_teredo, 32)
|| v6_subnet_of(ipv6, mask, start_6to4, 16)
);
}
// 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(const vscf_data_t* nets_cfg, dclists_t* dclists, const char* map_name, nlist_t* nl)
{
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);
if (net_str_len >= GDNSD_ANYSIN_MAXSTR) {
log_err("plugin_geoip: map '%s': nets entry '%s' is too long", map_name, net_str_cfg);
return true;
}
char net_str[GDNSD_ANYSIN_MAXSTR];
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);
return true;
}
*mask_str++ = '\0';
gdnsd_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));
return true;
}
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);
return true;
}
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);
return true;
}
} else {
gdnsd_assert(tempsin.sa.sa_family == AF_INET);
mask = ntohs(tempsin.sin4.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);
return true;
}
memset(ipv6, 0, 16);
memcpy(&ipv6[12], &tempsin.sin4.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);
gdnsd_assert(dclist <= DCLIST_MAX); // auto not allowed here
nlist_append(nl, ipv6, mask, dclist);
}
return false;
}
nlist_t* nets_make_list(const vscf_data_t* nets_cfg, dclists_t* dclists, const char* map_name)
{
nlist_t* nl = nlist_new(map_name, false);
if (nets_cfg) {
gdnsd_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;
}
|