implement searching of geoip2 database

- revise mapping of search terms to database types to match the
  GeoIP2 schemas.
- open GeoIP2 databases when starting up; close when shutting down.
- clarify the logged error message when an unknown database type
  is configured.
- add new geoip ACL subtypes to support searching for continent in
  country databases.
- map geoip ACL subtypes to specific MMDB database queries.
- perform MMDB lookups based on subtype, saving state between
  queries so repeated lookups for the same address aren't necessary.
This commit is contained in:
Evan Hunt
2019-06-11 20:32:21 -07:00
parent fe46d5bc34
commit 6e0b93e5a0
9 changed files with 1016 additions and 108 deletions

View File

@@ -11,6 +11,7 @@
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <isc/mem.h>
#include <isc/print.h>
@@ -25,13 +26,30 @@
#include <dns/fixedname.h>
#include <dns/log.h>
#ifdef HAVE_GEOIP
#include <stdlib.h>
#include <math.h>
#endif /* HAVE_GEOIP */
#define LOOP_MAGIC ISC_MAGIC('L','O','O','P')
#if defined(HAVE_GEOIP2)
static const char *geoip_dbnames[] = {
"country",
"city",
"asnum",
"isp",
"domain",
NULL,
};
#elif defined(HAVE_GEOIP)
static const char *geoip_dbnames[] = {
"country",
"city",
"asnum",
"isp",
"domain",
"netspeed",
"org",
NULL,
};
#endif /* HAVE_GEOIP */
isc_result_t
cfg_aclconfctx_create(isc_mem_t *mctx, cfg_aclconfctx_t **ret) {
cfg_aclconfctx_t *actx;
@@ -284,7 +302,7 @@ count_acl_elements(const cfg_obj_t *caml, const cfg_obj_t *cctx,
return (ISC_R_SUCCESS);
}
#if defined(HAVE_GEOIP)
#if defined(HAVE_GEOIP2)
static dns_geoip_subtype_t
get_subtype(const cfg_obj_t *obj, isc_log_t *lctx,
dns_geoip_subtype_t subtype, const char *dbname)
@@ -297,23 +315,11 @@ get_subtype(const cfg_obj_t *obj, isc_log_t *lctx,
case dns_geoip_countrycode:
if (strcasecmp(dbname, "city") == 0) {
return (dns_geoip_city_countrycode);
} else if (strcasecmp(dbname, "region") == 0) {
return (dns_geoip_region_countrycode);
} else if (strcasecmp(dbname, "country") == 0) {
return (dns_geoip_country_code);
}
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid GeoIP DB specified for "
"country search: ignored");
return (subtype);
case dns_geoip_countrycode3:
if (strcasecmp(dbname, "city") == 0) {
return (dns_geoip_city_countrycode3);
} else if (strcasecmp(dbname, "country") == 0) {
return (dns_geoip_country_code3);
}
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid GeoIP DB specified for "
"invalid database specified for "
"country search: ignored");
return (subtype);
case dns_geoip_countryname:
@@ -323,25 +329,368 @@ get_subtype(const cfg_obj_t *obj, isc_log_t *lctx,
return (dns_geoip_country_name);
}
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid GeoIP DB specified for "
"invalid database specified for "
"country search: ignored");
return (subtype);
case dns_geoip_continentcode:
if (strcasecmp(dbname, "city") == 0) {
return (dns_geoip_city_continentcode);
} else if (strcasecmp(dbname, "country") == 0) {
return (dns_geoip_country_continentcode);
}
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid database specified for "
"continent search: ignored");
return (subtype);
case dns_geoip_continent:
if (strcasecmp(dbname, "city") == 0) {
return (dns_geoip_city_continent);
} else if (strcasecmp(dbname, "country") == 0) {
return (dns_geoip_country_continent);
}
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid database specified for "
"continent search: ignored");
return (subtype);
case dns_geoip_region:
if (strcasecmp(dbname, "city") == 0) {
return (dns_geoip_city_region);
} else if (strcasecmp(dbname, "region") == 0) {
return (dns_geoip_region_code);
}
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid database specified for "
"region/subdivision search: ignored");
return (subtype);
case dns_geoip_regionname:
if (strcasecmp(dbname, "city") == 0) {
return (dns_geoip_city_regionname);
}
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid database specified for "
"region/subdivision search: ignored");
return (subtype);
/*
* Log a warning if the wrong database was specified
* on an unambiguous query
*/
case dns_geoip_city_name:
case dns_geoip_city_postalcode:
case dns_geoip_city_metrocode:
case dns_geoip_city_areacode:
case dns_geoip_city_timezonecode:
if (strcasecmp(dbname, "city") != 0) {
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid database specified for "
"a 'city'-only search type: ignoring");
}
return (subtype);
case dns_geoip_isp_name:
if (strcasecmp(dbname, "isp") != 0) {
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid database specified for "
"an 'isp' search: ignoring");
}
return (subtype);
case dns_geoip_org_name:
if (strcasecmp(dbname, "org") != 0) {
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid database specified for "
"an 'org' search: ignoring");
}
return (subtype);
case dns_geoip_as_asnum:
if (strcasecmp(dbname, "asnum") != 0) {
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid database specified for "
"an 'asnum' search: ignoring");
}
return (subtype);
case dns_geoip_domain_name:
if (strcasecmp(dbname, "domain") != 0) {
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid database specified for "
"a 'domain' search: ignoring");
}
return (subtype);
case dns_geoip_netspeed_id:
if (strcasecmp(dbname, "netspeed") != 0) {
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid database specified for "
"a 'netspeed' search: ignoring");
}
return (subtype);
default:
INSIST(0);
ISC_UNREACHABLE();
}
}
static bool
geoip_can_answer(dns_aclelement_t *elt, cfg_aclconfctx_t *ctx) {
if (ctx->geoip == NULL) {
return (true);
}
switch (elt->geoip_elem.subtype) {
case dns_geoip_countrycode:
case dns_geoip_countryname:
case dns_geoip_continentcode:
case dns_geoip_continent:
if (ctx->geoip->country != NULL ||
ctx->geoip->city != NULL)
{
return (true);
}
break;
case dns_geoip_country_code:
case dns_geoip_country_name:
case dns_geoip_country_continentcode:
case dns_geoip_country_continent:
if (ctx->geoip->country != NULL) {
return (true);
}
/* city db can answer these too, so: */
/* FALLTHROUGH */
case dns_geoip_region:
case dns_geoip_regionname:
case dns_geoip_city_countrycode:
case dns_geoip_city_countryname:
case dns_geoip_city_region:
case dns_geoip_city_regionname:
case dns_geoip_city_name:
case dns_geoip_city_postalcode:
case dns_geoip_city_metrocode:
case dns_geoip_city_areacode:
case dns_geoip_city_continentcode:
case dns_geoip_city_continent:
case dns_geoip_city_timezonecode:
if (ctx->geoip->city != NULL) {
return (true);
}
break;
case dns_geoip_isp_name:
if (ctx->geoip->isp != NULL) {
return (true);
}
break;
case dns_geoip_as_asnum:
case dns_geoip_org_name:
if (ctx->geoip->as != NULL) {
return (true);
}
break;
case dns_geoip_domain_name:
if (ctx->geoip->domain != NULL) {
return (true);
}
break;
default:
break;
}
return (false);
}
static isc_result_t
parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx,
cfg_aclconfctx_t *ctx, dns_aclelement_t *dep)
{
const cfg_obj_t *ge;
const char *dbname = NULL;
const char *stype = NULL, *search = NULL;
dns_geoip_subtype_t subtype;
dns_aclelement_t de;
size_t len;
REQUIRE(dep != NULL);
de = *dep;
ge = cfg_tuple_get(obj, "db");
if (!cfg_obj_isvoid(ge)) {
int i;
dbname = cfg_obj_asstring(ge);
for (i = 0; geoip_dbnames[i] != NULL; i++) {
if (strcasecmp(dbname, geoip_dbnames[i]) == 0) {
break;
}
}
if (geoip_dbnames[i] == NULL) {
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"database '%s' is not defined for GeoIP",
dbname);
return (ISC_R_UNEXPECTED);
}
}
stype = cfg_obj_asstring(cfg_tuple_get(obj, "subtype"));
search = cfg_obj_asstring(cfg_tuple_get(obj, "search"));
len = strlen(search);
if (len == 0) {
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"zero-length geoip search field");
return (ISC_R_FAILURE);
}
if (strcasecmp(stype, "country") == 0 && len == 2) {
/* Two-letter country code */
subtype = dns_geoip_countrycode;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if (strcasecmp(stype, "country") == 0 && len == 3) {
/* Three-letter country code */
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"three-letter country codes are unavailable "
"in GeoIP2 databases");
return (ISC_R_FAILURE);
} else if (strcasecmp(stype, "country") == 0) {
/* Country name */
subtype = dns_geoip_countryname;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if (strcasecmp(stype, "continent") == 0 && len == 2) {
/* Two-letter continent code */
subtype = dns_geoip_continentcode;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if (strcasecmp(stype, "continent") == 0) {
subtype = dns_geoip_continent;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if ((strcasecmp(stype, "region") == 0 ||
strcasecmp(stype, "subdivision") == 0) && len == 2)
{
/* Two-letter region code */
subtype = dns_geoip_region;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if (strcasecmp(stype, "region") == 0 ||
strcasecmp(stype, "subdivision") == 0)
{
/* Region name */
subtype = dns_geoip_regionname;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if (strcasecmp(stype, "city") == 0) {
/* City name */
subtype = dns_geoip_city_name;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if (strcasecmp(stype, "postal") == 0 ||
strcasecmp(stype, "postalcode") == 0)
{
if (len < 7) {
subtype = dns_geoip_city_postalcode;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else {
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"geoiop postal code (%s) too long",
search);
return (ISC_R_FAILURE);
}
} else if (strcasecmp(stype, "metro") == 0 ||
strcasecmp(stype, "metrocode") == 0)
{
subtype = dns_geoip_city_metrocode;
de.geoip_elem.as_int = atoi(search);
} else if (strcasecmp(stype, "tz") == 0 ||
strcasecmp(stype, "timezone") == 0)
{
subtype = dns_geoip_city_timezonecode;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if (strcasecmp(stype, "isp") == 0) {
subtype = dns_geoip_isp_name;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if (strcasecmp(stype, "asnum") == 0) {
subtype = dns_geoip_as_asnum;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if (strcasecmp(stype, "org") == 0) {
subtype = dns_geoip_org_name;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else if (strcasecmp(stype, "domain") == 0) {
subtype = dns_geoip_domain_name;
strlcpy(de.geoip_elem.as_string, search,
sizeof(de.geoip_elem.as_string));
} else {
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"type '%s' is unavailable "
"in GeoIP2 databases", stype);
return (ISC_R_FAILURE);
}
de.geoip_elem.subtype = get_subtype(obj, lctx, subtype, dbname);
if (! geoip_can_answer(&de, ctx)) {
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"no GeoIP2 database installed which can answer "
"queries of type '%s'", stype);
return (ISC_R_FAILURE);
}
*dep = de;
return (ISC_R_SUCCESS);
}
#elif defined(HAVE_GEOIP)
static dns_geoip_subtype_t
get_subtype(const cfg_obj_t *obj, isc_log_t *lctx,
dns_geoip_subtype_t subtype, const char *dbname)
{
if (dbname == NULL)
return (subtype);
switch (subtype) {
case dns_geoip_countrycode:
if (strcasecmp(dbname, "city") == 0)
return (dns_geoip_city_countrycode);
else if (strcasecmp(dbname, "region") == 0)
return (dns_geoip_region_countrycode);
else if (strcasecmp(dbname, "country") == 0)
return (dns_geoip_country_code);
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid GeoIP DB specified for "
"country search: ignored");
return (subtype);
case dns_geoip_countrycode3:
if (strcasecmp(dbname, "city") == 0)
return (dns_geoip_city_countrycode3);
else if (strcasecmp(dbname, "country") == 0)
return (dns_geoip_country_code3);
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid GeoIP DB specified for "
"country search: ignored");
return (subtype);
case dns_geoip_countryname:
if (strcasecmp(dbname, "city") == 0)
return (dns_geoip_city_countryname);
else if (strcasecmp(dbname, "country") == 0)
return (dns_geoip_country_name);
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid GeoIP DB specified for "
"country search: ignored");
return (subtype);
case dns_geoip_region:
if (strcasecmp(dbname, "city") == 0)
return (dns_geoip_city_region);
else if (strcasecmp(dbname, "region") == 0)
return (dns_geoip_region_code);
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid GeoIP DB specified for "
"region search: ignored");
return (subtype);
case dns_geoip_regionname:
if (strcasecmp(dbname, "city") == 0) {
if (strcasecmp(dbname, "city") == 0)
return (dns_geoip_city_region);
} else if (strcasecmp(dbname, "region") == 0) {
else if (strcasecmp(dbname, "region") == 0)
return (dns_geoip_region_name);
}
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"invalid GeoIP DB specified for "
"region search: ignored");
@@ -357,46 +706,40 @@ get_subtype(const cfg_obj_t *obj, isc_log_t *lctx,
case dns_geoip_city_areacode:
case dns_geoip_city_continentcode:
case dns_geoip_city_timezonecode:
if (strcasecmp(dbname, "city") != 0) {
if (strcasecmp(dbname, "city") != 0)
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid GeoIP DB specified for "
"a 'city'-only search type: ignoring");
}
return (subtype);
case dns_geoip_isp_name:
if (strcasecmp(dbname, "isp") != 0) {
if (strcasecmp(dbname, "isp") != 0)
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid GeoIP DB specified for "
"an 'isp' search: ignoring");
}
return (subtype);
case dns_geoip_org_name:
if (strcasecmp(dbname, "org") != 0) {
if (strcasecmp(dbname, "org") != 0)
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid GeoIP DB specified for "
"an 'org' search: ignoring");
}
return (subtype);
case dns_geoip_as_asnum:
if (strcasecmp(dbname, "asnum") != 0) {
if (strcasecmp(dbname, "asnum") != 0)
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid GeoIP DB specified for "
"an 'asnum' search: ignoring");
}
return (subtype);
case dns_geoip_domain_name:
if (strcasecmp(dbname, "domain") != 0) {
if (strcasecmp(dbname, "domain") != 0)
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid GeoIP DB specified for "
"a 'domain' search: ignoring");
}
return (subtype);
case dns_geoip_netspeed_id:
if (strcasecmp(dbname, "netspeed") != 0) {
if (strcasecmp(dbname, "netspeed") != 0)
cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
"invalid GeoIP DB specified for "
"a 'netspeed' search: ignoring");
}
return (subtype);
default:
INSIST(0);
@@ -406,9 +749,8 @@ get_subtype(const cfg_obj_t *obj, isc_log_t *lctx,
static bool
geoip_can_answer(dns_aclelement_t *elt, cfg_aclconfctx_t *ctx) {
if (ctx->geoip == NULL) {
if (ctx->geoip == NULL)
return (true);
}
switch (elt->geoip_elem.subtype) {
case dns_geoip_countrycode:
@@ -454,39 +796,41 @@ geoip_can_answer(dns_aclelement_t *elt, cfg_aclconfctx_t *ctx) {
case dns_geoip_city_timezonecode:
if (ctx->geoip->city_v4 != NULL ||
ctx->geoip->city_v6 != NULL)
{
return (true);
}
/* FALLTHROUGH */
case dns_geoip_isp_name:
if (ctx->geoip->isp != NULL) {
if (ctx->geoip->isp != NULL)
return (true);
}
/* FALLTHROUGH */
case dns_geoip_org_name:
if (ctx->geoip->org != NULL) {
if (ctx->geoip->org != NULL)
return (true);
}
/* FALLTHROUGH */
case dns_geoip_as_asnum:
if (ctx->geoip->as != NULL) {
if (ctx->geoip->as != NULL)
return (true);
}
/* FALLTHROUGH */
case dns_geoip_domain_name:
if (ctx->geoip->domain != NULL) {
if (ctx->geoip->domain != NULL)
return (true);
}
/* FALLTHROUGH */
case dns_geoip_netspeed_id:
if (ctx->geoip->netspeed != NULL) {
if (ctx->geoip->netspeed != NULL)
return (true);
}
/*
* The following enums are only valid with GeoIP2,
* not legacy GeoIP.
*/
case dns_geoip_continentcode:
case dns_geoip_continent:
case dns_geoip_country_continentcode:
case dns_geoip_country_continent:
case dns_geoip_city_continent:
INSIST(0);
}
return (false);
}
#endif
static isc_result_t
parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx,
@@ -494,7 +838,7 @@ parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx,
{
const cfg_obj_t *ge;
const char *dbname = NULL;
const char *stype, *search;
const char *stype = NULL, *search = NULL;
dns_geoip_subtype_t subtype;
dns_aclelement_t de;
size_t len;
@@ -505,7 +849,20 @@ parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx,
ge = cfg_tuple_get(obj, "db");
if (!cfg_obj_isvoid(ge)) {
int i;
dbname = cfg_obj_asstring(ge);
for (i = 0; geoip_dbnames[i] != NULL; i++) {
if (strcasecmp(dbname, geoip_dbnames[i]) == 0) {
break;
}
}
if (geoip_dbnames[i] == NULL) {
cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
"database '%s' is not defined for GeoIP",
dbname);
}
}
stype = cfg_obj_asstring(cfg_tuple_get(obj, "subtype"));
@@ -623,6 +980,7 @@ parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx,
return (ISC_R_SUCCESS);
}
#endif /* HAVE_GEOIP */
isc_result_t
cfg_acl_fromconfig(const cfg_obj_t *caml, const cfg_obj_t *cctx,