886 lines
20 KiB
C
886 lines
20 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/*! \file */
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <isc/util.h>
|
|
|
|
#include <isc/mem.h>
|
|
#include <isc/once.h>
|
|
#include <isc/string.h>
|
|
|
|
#include <dns/acl.h>
|
|
#include <dns/geoip.h>
|
|
|
|
#include <isc/thread.h>
|
|
#include <math.h>
|
|
#ifndef WIN32
|
|
#include <netinet/in.h>
|
|
#else
|
|
#ifndef _WINSOCKAPI_
|
|
#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */
|
|
#endif
|
|
#include <winsock2.h>
|
|
#endif /* WIN32 */
|
|
#include <dns/log.h>
|
|
|
|
#ifdef HAVE_GEOIP
|
|
#include <GeoIP.h>
|
|
#include <GeoIPCity.h>
|
|
|
|
/*
|
|
* This structure preserves state from the previous GeoIP lookup,
|
|
* so that successive lookups for the same data from the same IP
|
|
* address will not require repeated calls into the GeoIP library
|
|
* to look up data in the database. This should improve performance
|
|
* somewhat.
|
|
*
|
|
* For lookups in the City and Region databases, we preserve pointers
|
|
* to the GeoIPRecord and GeoIPregion structures; these will need to be
|
|
* freed by GeoIPRecord_delete() and GeoIPRegion_delete().
|
|
*
|
|
* for lookups in ISP, AS, Org and Domain we prserve a pointer to
|
|
* the returned name; these must be freed by free().
|
|
*
|
|
* For lookups in Country we preserve a pointer to the text of
|
|
* the country code, name, etc (we use a different pointer for this
|
|
* than for the names returned by Org, ISP, etc, because those need
|
|
* to be freed but country lookups do not).
|
|
*
|
|
* For lookups in Netspeed we preserve the returned ID.
|
|
*
|
|
* XXX: Currently this mechanism is only used for IPv4 lookups; the
|
|
* family and addr6 fields are to be used IPv6 is added.
|
|
*/
|
|
typedef struct geoip_state {
|
|
uint16_t subtype;
|
|
unsigned int family;
|
|
uint32_t ipnum;
|
|
geoipv6_t ipnum6;
|
|
uint8_t scope;
|
|
GeoIPRecord *record;
|
|
GeoIPRegion *region;
|
|
const char *text;
|
|
char *name;
|
|
int id;
|
|
isc_mem_t *mctx;
|
|
} geoip_state_t;
|
|
|
|
#ifdef ISC_PLATFORM_USETHREADS
|
|
static isc_mutex_t key_mutex;
|
|
static bool state_key_initialized = false;
|
|
static isc_thread_key_t state_key;
|
|
static isc_once_t mutex_once = ISC_ONCE_INIT;
|
|
static isc_mem_t *state_mctx = NULL;
|
|
|
|
static void
|
|
key_mutex_init(void) {
|
|
RUNTIME_CHECK(isc_mutex_init(&key_mutex) == ISC_R_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
free_state(void *arg) {
|
|
geoip_state_t *state = arg;
|
|
if (state != NULL && state->record != NULL)
|
|
GeoIPRecord_delete(state->record);
|
|
if (state != NULL)
|
|
isc_mem_putanddetach(&state->mctx,
|
|
state, sizeof(geoip_state_t));
|
|
isc_thread_key_setspecific(state_key, NULL);
|
|
}
|
|
|
|
static isc_result_t
|
|
state_key_init(void) {
|
|
isc_result_t result;
|
|
|
|
result = isc_once_do(&mutex_once, key_mutex_init);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
if (!state_key_initialized) {
|
|
LOCK(&key_mutex);
|
|
if (!state_key_initialized) {
|
|
int ret;
|
|
|
|
if (state_mctx == NULL)
|
|
result = isc_mem_create2(0, 0, &state_mctx, 0);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto unlock;
|
|
isc_mem_setname(state_mctx, "geoip_state", NULL);
|
|
isc_mem_setdestroycheck(state_mctx, false);
|
|
|
|
ret = isc_thread_key_create(&state_key, free_state);
|
|
if (ret == 0)
|
|
state_key_initialized = true;
|
|
else
|
|
result = ISC_R_FAILURE;
|
|
}
|
|
unlock:
|
|
UNLOCK(&key_mutex);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
#else
|
|
static geoip_state_t saved_state;
|
|
#endif
|
|
|
|
static void
|
|
clean_state(geoip_state_t *state) {
|
|
if (state == NULL)
|
|
return;
|
|
|
|
if (state->record != NULL) {
|
|
GeoIPRecord_delete(state->record);
|
|
state->record = NULL;
|
|
}
|
|
if (state->region != NULL) {
|
|
GeoIPRegion_delete(state->region);
|
|
state->region = NULL;
|
|
}
|
|
if (state->name != NULL) {
|
|
free (state->name);
|
|
state->name = NULL;
|
|
}
|
|
state->ipnum = 0;
|
|
state->text = NULL;
|
|
state->id = 0;
|
|
}
|
|
|
|
static isc_result_t
|
|
set_state(unsigned int family, uint32_t ipnum, const geoipv6_t *ipnum6,
|
|
uint8_t scope, dns_geoip_subtype_t subtype, GeoIPRecord *record,
|
|
GeoIPRegion *region, char *name, const char *text, int id)
|
|
{
|
|
geoip_state_t *state = NULL;
|
|
#ifdef ISC_PLATFORM_USETHREADS
|
|
isc_result_t result;
|
|
|
|
result = state_key_init();
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
state = (geoip_state_t *) isc_thread_key_getspecific(state_key);
|
|
if (state == NULL) {
|
|
state = (geoip_state_t *) isc_mem_get(state_mctx,
|
|
sizeof(geoip_state_t));
|
|
if (state == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
memset(state, 0, sizeof(*state));
|
|
|
|
result = isc_thread_key_setspecific(state_key, state);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_mem_put(state_mctx, state, sizeof(geoip_state_t));
|
|
return (result);
|
|
}
|
|
|
|
isc_mem_attach(state_mctx, &state->mctx);
|
|
} else
|
|
clean_state(state);
|
|
#else
|
|
state = &saved_state;
|
|
clean_state(state);
|
|
#endif
|
|
|
|
if (family == AF_INET) {
|
|
state->ipnum = ipnum;
|
|
} else {
|
|
INSIST(ipnum6 != NULL);
|
|
state->ipnum6 = *ipnum6;
|
|
}
|
|
|
|
state->family = family;
|
|
state->subtype = subtype;
|
|
state->scope = scope;
|
|
state->record = record;
|
|
state->region = region;
|
|
state->name = name;
|
|
state->text = text;
|
|
state->id = id;
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static geoip_state_t *
|
|
get_state_for(unsigned int family, uint32_t ipnum,
|
|
const geoipv6_t *ipnum6)
|
|
{
|
|
geoip_state_t *state;
|
|
|
|
#ifdef ISC_PLATFORM_USETHREADS
|
|
isc_result_t result;
|
|
|
|
result = state_key_init();
|
|
if (result != ISC_R_SUCCESS)
|
|
return (NULL);
|
|
|
|
state = (geoip_state_t *) isc_thread_key_getspecific(state_key);
|
|
if (state == NULL)
|
|
return (NULL);
|
|
#else
|
|
state = &saved_state;
|
|
#endif
|
|
|
|
if (state->family == family &&
|
|
((state->family == AF_INET && state->ipnum == ipnum) ||
|
|
(state->family == AF_INET6 && ipnum6 != NULL &&
|
|
memcmp(state->ipnum6.s6_addr, ipnum6->s6_addr, 16) == 0)))
|
|
return (state);
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Country lookups are performed if the previous lookup was from a
|
|
* different IP address than the current, or was for a search of a
|
|
* different subtype.
|
|
*/
|
|
static const char *
|
|
country_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
|
|
unsigned int family,
|
|
uint32_t ipnum, const geoipv6_t *ipnum6,
|
|
uint8_t *scope)
|
|
{
|
|
geoip_state_t *prev_state = NULL;
|
|
const char *text = NULL;
|
|
GeoIPLookup gl;
|
|
|
|
REQUIRE(db != NULL);
|
|
|
|
#ifndef HAVE_GEOIP_V6
|
|
/* no IPv6 support? give up now */
|
|
if (family == AF_INET6)
|
|
return (NULL);
|
|
#endif
|
|
|
|
prev_state = get_state_for(family, ipnum, ipnum6);
|
|
if (prev_state != NULL && prev_state->subtype == subtype) {
|
|
text = prev_state->text;
|
|
if (scope != NULL)
|
|
*scope = prev_state->scope;
|
|
}
|
|
|
|
if (text == NULL) {
|
|
switch (subtype) {
|
|
case dns_geoip_country_code:
|
|
if (family == AF_INET)
|
|
text = GeoIP_country_code_by_ipnum_gl(db,
|
|
ipnum, &gl);
|
|
#ifdef HAVE_GEOIP_V6
|
|
else
|
|
text = GeoIP_country_code_by_ipnum_v6_gl(db,
|
|
*ipnum6, &gl);
|
|
#endif
|
|
break;
|
|
case dns_geoip_country_code3:
|
|
if (family == AF_INET)
|
|
text = GeoIP_country_code3_by_ipnum_gl(db,
|
|
ipnum, &gl);
|
|
#ifdef HAVE_GEOIP_V6
|
|
else
|
|
text = GeoIP_country_code3_by_ipnum_v6_gl(db,
|
|
*ipnum6, &gl);
|
|
#endif
|
|
break;
|
|
case dns_geoip_country_name:
|
|
if (family == AF_INET)
|
|
text = GeoIP_country_name_by_ipnum_gl(db,
|
|
ipnum, &gl);
|
|
#ifdef HAVE_GEOIP_V6
|
|
else
|
|
text = GeoIP_country_name_by_ipnum_v6_gl(db,
|
|
*ipnum6, &gl);
|
|
#endif
|
|
break;
|
|
default:
|
|
INSIST(0);
|
|
ISC_UNREACHABLE();
|
|
}
|
|
|
|
if (text == NULL)
|
|
return (NULL);
|
|
|
|
if (scope != NULL)
|
|
*scope = gl.netmask;
|
|
|
|
set_state(family, ipnum, ipnum6, gl.netmask, subtype,
|
|
NULL, NULL, NULL, text, 0);
|
|
}
|
|
|
|
return (text);
|
|
}
|
|
|
|
static char *
|
|
city_string(GeoIPRecord *record, dns_geoip_subtype_t subtype, int *maxlen) {
|
|
const char *s;
|
|
char *deconst;
|
|
|
|
REQUIRE(record != NULL);
|
|
REQUIRE(maxlen != NULL);
|
|
|
|
/* Set '*maxlen' to the maximum length of this subtype, if any */
|
|
switch (subtype) {
|
|
case dns_geoip_city_countrycode:
|
|
case dns_geoip_city_region:
|
|
case dns_geoip_city_continentcode:
|
|
*maxlen = 2;
|
|
break;
|
|
|
|
case dns_geoip_city_countrycode3:
|
|
*maxlen = 3;
|
|
break;
|
|
|
|
default:
|
|
/* No fixed length; just use strcasecmp() for comparison */
|
|
*maxlen = 255;
|
|
}
|
|
|
|
switch (subtype) {
|
|
case dns_geoip_city_countrycode:
|
|
return (record->country_code);
|
|
case dns_geoip_city_countrycode3:
|
|
return (record->country_code3);
|
|
case dns_geoip_city_countryname:
|
|
return (record->country_name);
|
|
case dns_geoip_city_region:
|
|
return (record->region);
|
|
case dns_geoip_city_regionname:
|
|
s = GeoIP_region_name_by_code(record->country_code,
|
|
record->region);
|
|
DE_CONST(s, deconst);
|
|
return (deconst);
|
|
case dns_geoip_city_name:
|
|
return (record->city);
|
|
case dns_geoip_city_postalcode:
|
|
return (record->postal_code);
|
|
case dns_geoip_city_continentcode:
|
|
return (record->continent_code);
|
|
case dns_geoip_city_timezonecode:
|
|
s = GeoIP_time_zone_by_country_and_region(record->country_code,
|
|
record->region);
|
|
DE_CONST(s, deconst);
|
|
return (deconst);
|
|
default:
|
|
INSIST(0);
|
|
ISC_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
static bool
|
|
is_city(dns_geoip_subtype_t subtype) {
|
|
switch (subtype) {
|
|
case dns_geoip_city_countrycode:
|
|
case dns_geoip_city_countrycode3:
|
|
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_continentcode:
|
|
case dns_geoip_city_timezonecode:
|
|
case dns_geoip_city_metrocode:
|
|
case dns_geoip_city_areacode:
|
|
return (true);
|
|
default:
|
|
return (false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* GeoIPRecord lookups are performed if the previous lookup was
|
|
* from a different IP address than the current, or was for a search
|
|
* outside the City database.
|
|
*/
|
|
static GeoIPRecord *
|
|
city_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
|
|
unsigned int family, uint32_t ipnum,
|
|
const geoipv6_t *ipnum6,
|
|
uint8_t *scope)
|
|
{
|
|
GeoIPRecord *record = NULL;
|
|
geoip_state_t *prev_state = NULL;
|
|
|
|
REQUIRE(db != NULL);
|
|
|
|
#ifndef HAVE_GEOIP_V6
|
|
/* no IPv6 support? give up now */
|
|
if (family == AF_INET6)
|
|
return (NULL);
|
|
#endif
|
|
|
|
prev_state = get_state_for(family, ipnum, ipnum6);
|
|
if (prev_state != NULL && is_city(prev_state->subtype)) {
|
|
record = prev_state->record;
|
|
if (scope != NULL)
|
|
*scope = record->netmask;
|
|
}
|
|
|
|
if (record == NULL) {
|
|
if (family == AF_INET)
|
|
record = GeoIP_record_by_ipnum(db, ipnum);
|
|
#ifdef HAVE_GEOIP_V6
|
|
else
|
|
record = GeoIP_record_by_ipnum_v6(db, *ipnum6);
|
|
#endif
|
|
if (record == NULL)
|
|
return (NULL);
|
|
|
|
if (scope != NULL)
|
|
*scope = record->netmask;
|
|
|
|
set_state(family, ipnum, ipnum6, record->netmask, subtype,
|
|
record, NULL, NULL, NULL, 0);
|
|
}
|
|
|
|
return (record);
|
|
}
|
|
|
|
static char * region_string(GeoIPRegion *region, dns_geoip_subtype_t subtype, int *maxlen) {
|
|
const char *s;
|
|
char *deconst;
|
|
|
|
REQUIRE(region != NULL);
|
|
REQUIRE(maxlen != NULL);
|
|
|
|
switch (subtype) {
|
|
case dns_geoip_region_countrycode:
|
|
*maxlen = 2;
|
|
return (region->country_code);
|
|
case dns_geoip_region_code:
|
|
*maxlen = 2;
|
|
return (region->region);
|
|
case dns_geoip_region_name:
|
|
*maxlen = 255;
|
|
s = GeoIP_region_name_by_code(region->country_code,
|
|
region->region);
|
|
DE_CONST(s, deconst);
|
|
return (deconst);
|
|
default:
|
|
INSIST(0);
|
|
ISC_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
static bool
|
|
is_region(dns_geoip_subtype_t subtype) {
|
|
switch (subtype) {
|
|
case dns_geoip_region_countrycode:
|
|
case dns_geoip_region_code:
|
|
return (true);
|
|
default:
|
|
return (false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* GeoIPRegion lookups are performed if the previous lookup was
|
|
* from a different IP address than the current, or was for a search
|
|
* outside the Region database.
|
|
*/
|
|
static GeoIPRegion *
|
|
region_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
|
|
uint32_t ipnum, uint8_t *scope)
|
|
{
|
|
GeoIPRegion *region = NULL;
|
|
geoip_state_t *prev_state = NULL;
|
|
GeoIPLookup gl;
|
|
|
|
REQUIRE(db != NULL);
|
|
|
|
prev_state = get_state_for(AF_INET, ipnum, NULL);
|
|
if (prev_state != NULL && is_region(prev_state->subtype)) {
|
|
region = prev_state->region;
|
|
if (scope != NULL)
|
|
*scope = prev_state->scope;
|
|
}
|
|
|
|
if (region == NULL) {
|
|
region = GeoIP_region_by_ipnum_gl(db, ipnum, &gl);
|
|
if (region == NULL)
|
|
return (NULL);
|
|
|
|
if (scope != NULL)
|
|
*scope = gl.netmask;
|
|
|
|
set_state(AF_INET, ipnum, NULL, gl.netmask,
|
|
subtype, NULL, region, NULL, NULL, 0);
|
|
}
|
|
|
|
return (region);
|
|
}
|
|
|
|
/*
|
|
* ISP, Organization, AS Number and Domain lookups are performed if
|
|
* the previous lookup was from a different IP address than the current,
|
|
* or was for a search of a different subtype.
|
|
*/
|
|
static char *
|
|
name_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
|
|
uint32_t ipnum, uint8_t *scope)
|
|
{
|
|
char *name = NULL;
|
|
geoip_state_t *prev_state = NULL;
|
|
GeoIPLookup gl;
|
|
|
|
REQUIRE(db != NULL);
|
|
|
|
prev_state = get_state_for(AF_INET, ipnum, NULL);
|
|
if (prev_state != NULL && prev_state->subtype == subtype) {
|
|
name = prev_state->name;
|
|
if (scope != NULL)
|
|
*scope = prev_state->scope;
|
|
}
|
|
|
|
if (name == NULL) {
|
|
name = GeoIP_name_by_ipnum_gl(db, ipnum, &gl);
|
|
if (name == NULL)
|
|
return (NULL);
|
|
|
|
if (scope != NULL)
|
|
*scope = gl.netmask;
|
|
|
|
set_state(AF_INET, ipnum, NULL, gl.netmask,
|
|
subtype, NULL, NULL, name, NULL, 0);
|
|
}
|
|
|
|
return (name);
|
|
}
|
|
|
|
/*
|
|
* Netspeed lookups are performed if the previous lookup was from a
|
|
* different IP address than the current, or was for a search of a
|
|
* different subtype.
|
|
*/
|
|
static int
|
|
netspeed_lookup(GeoIP *db, dns_geoip_subtype_t subtype,
|
|
uint32_t ipnum, uint8_t *scope)
|
|
{
|
|
geoip_state_t *prev_state = NULL;
|
|
bool found = false;
|
|
GeoIPLookup gl;
|
|
int id = -1;
|
|
|
|
REQUIRE(db != NULL);
|
|
|
|
prev_state = get_state_for(AF_INET, ipnum, NULL);
|
|
if (prev_state != NULL && prev_state->subtype == subtype) {
|
|
id = prev_state->id;
|
|
if (scope != NULL)
|
|
*scope = prev_state->scope;
|
|
found = true;
|
|
}
|
|
|
|
if (!found) {
|
|
id = GeoIP_id_by_ipnum_gl(db, ipnum, &gl);
|
|
if (id == 0)
|
|
return (0);
|
|
|
|
if (scope != NULL)
|
|
*scope = gl.netmask;
|
|
|
|
set_state(AF_INET, ipnum, NULL, gl.netmask,
|
|
subtype, NULL, NULL, NULL, NULL, id);
|
|
}
|
|
|
|
return (id);
|
|
}
|
|
#endif /* HAVE_GEOIP */
|
|
|
|
#define DB46(addr, geoip, name) \
|
|
((addr->family == AF_INET) ? (geoip->name##_v4) : (geoip->name##_v6))
|
|
|
|
#ifdef HAVE_GEOIP
|
|
/*
|
|
* Find the best database to answer a generic subtype
|
|
*/
|
|
static dns_geoip_subtype_t
|
|
fix_subtype(const isc_netaddr_t *reqaddr, const dns_geoip_databases_t *geoip,
|
|
dns_geoip_subtype_t subtype)
|
|
{
|
|
dns_geoip_subtype_t ret = subtype;
|
|
|
|
switch (subtype) {
|
|
case dns_geoip_countrycode:
|
|
if (DB46(reqaddr, geoip, city) != NULL)
|
|
ret = dns_geoip_city_countrycode;
|
|
else if (reqaddr->family == AF_INET && geoip->region != NULL)
|
|
ret = dns_geoip_region_countrycode;
|
|
else if (DB46(reqaddr, geoip, country) != NULL)
|
|
ret = dns_geoip_country_code;
|
|
break;
|
|
case dns_geoip_countrycode3:
|
|
if (DB46(reqaddr, geoip, city) != NULL)
|
|
ret = dns_geoip_city_countrycode3;
|
|
else if (DB46(reqaddr, geoip, country) != NULL)
|
|
ret = dns_geoip_country_code3;
|
|
break;
|
|
case dns_geoip_countryname:
|
|
if (DB46(reqaddr, geoip, city) != NULL)
|
|
ret = dns_geoip_city_countryname;
|
|
else if (DB46(reqaddr, geoip, country) != NULL)
|
|
ret = dns_geoip_country_name;
|
|
break;
|
|
case dns_geoip_region:
|
|
if (DB46(reqaddr, geoip, city) != NULL)
|
|
ret = dns_geoip_city_region;
|
|
else if (reqaddr->family == AF_INET && geoip->region != NULL)
|
|
ret = dns_geoip_region_code;
|
|
break;
|
|
case dns_geoip_regionname:
|
|
if (DB46(reqaddr, geoip, city) != NULL)
|
|
ret = dns_geoip_city_regionname;
|
|
else if (reqaddr->family == AF_INET && geoip->region != NULL)
|
|
ret = dns_geoip_region_name;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
#endif /* HAVE_GEOIP */
|
|
|
|
bool
|
|
dns_geoip_match(const isc_netaddr_t *reqaddr, uint8_t *scope,
|
|
const dns_geoip_databases_t *geoip,
|
|
const dns_geoip_elem_t *elt)
|
|
{
|
|
#ifndef HAVE_GEOIP
|
|
UNUSED(reqaddr);
|
|
UNUSED(geoip);
|
|
UNUSED(elt);
|
|
|
|
return (false);
|
|
#else
|
|
GeoIP *db;
|
|
GeoIPRecord *record;
|
|
GeoIPRegion *region;
|
|
dns_geoip_subtype_t subtype;
|
|
uint32_t ipnum = 0;
|
|
int maxlen = 0, id, family;
|
|
const char *cs;
|
|
char *s;
|
|
#ifdef HAVE_GEOIP_V6
|
|
const geoipv6_t *ipnum6 = NULL;
|
|
#else
|
|
const void *ipnum6 = NULL;
|
|
#endif
|
|
|
|
INSIST(geoip != NULL);
|
|
|
|
family = reqaddr->family;
|
|
switch (family) {
|
|
case AF_INET:
|
|
ipnum = ntohl(reqaddr->type.in.s_addr);
|
|
break;
|
|
case AF_INET6:
|
|
#ifdef HAVE_GEOIP_V6
|
|
ipnum6 = &reqaddr->type.in6;
|
|
break;
|
|
#else
|
|
return (false);
|
|
#endif
|
|
default:
|
|
return (false);
|
|
}
|
|
|
|
subtype = fix_subtype(reqaddr, geoip, elt->subtype);
|
|
|
|
switch (subtype) {
|
|
case dns_geoip_country_code:
|
|
maxlen = 2;
|
|
goto getcountry;
|
|
|
|
case dns_geoip_country_code3:
|
|
maxlen = 3;
|
|
goto getcountry;
|
|
|
|
case dns_geoip_country_name:
|
|
maxlen = 255;
|
|
getcountry:
|
|
db = DB46(reqaddr, geoip, country);
|
|
if (db == NULL)
|
|
return (false);
|
|
|
|
INSIST(elt->as_string != NULL);
|
|
|
|
cs = country_lookup(db, subtype, family, ipnum, ipnum6, scope);
|
|
if (cs != NULL && strncasecmp(elt->as_string, cs, maxlen) == 0)
|
|
return (true);
|
|
break;
|
|
|
|
case dns_geoip_city_countrycode:
|
|
case dns_geoip_city_countrycode3:
|
|
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_continentcode:
|
|
case dns_geoip_city_timezonecode:
|
|
INSIST(elt->as_string != NULL);
|
|
|
|
db = DB46(reqaddr, geoip, city);
|
|
if (db == NULL)
|
|
return (false);
|
|
|
|
record = city_lookup(db, subtype, family,
|
|
ipnum, ipnum6, scope);
|
|
if (record == NULL)
|
|
break;
|
|
|
|
s = city_string(record, subtype, &maxlen);
|
|
INSIST(maxlen != 0);
|
|
if (s != NULL && strncasecmp(elt->as_string, s, maxlen) == 0)
|
|
return (true);
|
|
break;
|
|
|
|
case dns_geoip_city_metrocode:
|
|
db = DB46(reqaddr, geoip, city);
|
|
if (db == NULL)
|
|
return (false);
|
|
|
|
record = city_lookup(db, subtype, family,
|
|
ipnum, ipnum6, scope);
|
|
if (record == NULL)
|
|
break;
|
|
|
|
if (elt->as_int == record->metro_code)
|
|
return (true);
|
|
break;
|
|
|
|
case dns_geoip_city_areacode:
|
|
db = DB46(reqaddr, geoip, city);
|
|
if (db == NULL)
|
|
return (false);
|
|
|
|
record = city_lookup(db, subtype, family,
|
|
ipnum, ipnum6, scope);
|
|
if (record == NULL)
|
|
break;
|
|
|
|
if (elt->as_int == record->area_code)
|
|
return (true);
|
|
break;
|
|
|
|
case dns_geoip_region_countrycode:
|
|
case dns_geoip_region_code:
|
|
case dns_geoip_region_name:
|
|
case dns_geoip_region:
|
|
if (geoip->region == NULL)
|
|
return (false);
|
|
|
|
INSIST(elt->as_string != NULL);
|
|
|
|
/* Region DB is not supported for IPv6 */
|
|
if (family == AF_INET6)
|
|
return (false);
|
|
|
|
region = region_lookup(geoip->region, subtype, ipnum, scope);
|
|
if (region == NULL)
|
|
break;
|
|
|
|
s = region_string(region, subtype, &maxlen);
|
|
INSIST(maxlen != 0);
|
|
if (s != NULL && strncasecmp(elt->as_string, s, maxlen) == 0)
|
|
return (true);
|
|
break;
|
|
|
|
case dns_geoip_isp_name:
|
|
db = geoip->isp;
|
|
goto getname;
|
|
|
|
case dns_geoip_org_name:
|
|
db = geoip->org;
|
|
goto getname;
|
|
|
|
case dns_geoip_as_asnum:
|
|
db = geoip->as;
|
|
goto getname;
|
|
|
|
case dns_geoip_domain_name:
|
|
db = geoip->domain;
|
|
|
|
getname:
|
|
if (db == NULL)
|
|
return (false);
|
|
|
|
INSIST(elt->as_string != NULL);
|
|
/* ISP, Org, AS, and Domain are not supported for IPv6 */
|
|
if (family == AF_INET6)
|
|
return (false);
|
|
|
|
s = name_lookup(db, subtype, ipnum, scope);
|
|
if (s != NULL) {
|
|
size_t l;
|
|
if (strcasecmp(elt->as_string, s) == 0)
|
|
return (true);
|
|
if (subtype != dns_geoip_as_asnum)
|
|
break;
|
|
/*
|
|
* Just check if the ASNNNN value matches.
|
|
*/
|
|
l = strlen(elt->as_string);
|
|
if (l > 0U && strchr(elt->as_string, ' ') == NULL &&
|
|
strncasecmp(elt->as_string, s, l) == 0 &&
|
|
s[l] == ' ')
|
|
return (true);
|
|
}
|
|
break;
|
|
|
|
case dns_geoip_netspeed_id:
|
|
INSIST(geoip->netspeed != NULL);
|
|
|
|
/* Netspeed DB is not supported for IPv6 */
|
|
if (family == AF_INET6)
|
|
return (false);
|
|
|
|
id = netspeed_lookup(geoip->netspeed, subtype, ipnum, scope);
|
|
if (id == elt->as_int)
|
|
return (true);
|
|
break;
|
|
|
|
case dns_geoip_countrycode:
|
|
case dns_geoip_countrycode3:
|
|
case dns_geoip_countryname:
|
|
case dns_geoip_regionname:
|
|
/*
|
|
* If these were not remapped by fix_subtype(),
|
|
* the database was unavailable. Always return false.
|
|
*/
|
|
break;
|
|
|
|
default:
|
|
INSIST(0);
|
|
ISC_UNREACHABLE();
|
|
}
|
|
|
|
return (false);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
dns_geoip_shutdown(void) {
|
|
#ifdef HAVE_GEOIP
|
|
GeoIP_cleanup();
|
|
#ifdef ISC_PLATFORM_USETHREADS
|
|
if (state_mctx != NULL)
|
|
isc_mem_detach(&state_mctx);
|
|
#endif
|
|
#else
|
|
return;
|
|
#endif
|
|
}
|