Home | History | Annotate | Line # | Download | only in dns
      1 /*	$NetBSD: geoip2.c,v 1.8 2025/01/26 16:25:22 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5  *
      6  * SPDX-License-Identifier: MPL-2.0
      7  *
      8  * This Source Code Form is subject to the terms of the Mozilla Public
      9  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11  *
     12  * See the COPYRIGHT file distributed with this work for additional
     13  * information regarding copyright ownership.
     14  */
     15 
     16 /*! \file */
     17 
     18 #include <inttypes.h>
     19 #include <stdbool.h>
     20 #include <stdlib.h>
     21 
     22 /*
     23  * This file is only built and linked if GeoIP2 has been configured.
     24  */
     25 #include <math.h>
     26 #include <maxminddb.h>
     27 #include <netinet/in.h>
     28 
     29 #include <isc/mem.h>
     30 #include <isc/once.h>
     31 #include <isc/sockaddr.h>
     32 #include <isc/string.h>
     33 #include <isc/thread.h>
     34 #include <isc/util.h>
     35 
     36 #include <dns/acl.h>
     37 #include <dns/geoip.h>
     38 #include <dns/log.h>
     39 
     40 /*
     41  * This structure preserves state from the previous GeoIP lookup,
     42  * so that successive lookups for the same data from the same IP
     43  * address will not require repeated database lookups.
     44  * This should improve performance somewhat.
     45  *
     46  * For all lookups we preserve pointers to the MMDB_lookup_result_s
     47  * and MMDB_entry_s structures, a pointer to the database from which
     48  * the lookup was answered, and a copy of the request address.
     49  *
     50  * If the next geoip ACL lookup is for the same database and from the
     51  * same address, we can reuse the MMDB entry without repeating the lookup.
     52  * This is for the case when a single query has to process multiple
     53  * geoip ACLs: for example, when there are multiple views with
     54  * match-clients statements that search for different countries.
     55  *
     56  * (XXX: Currently the persistent state is stored in thread specific
     57  * memory, but it could more simply be stored in the client object.
     58  * Also multiple entries could be stored in case the ACLs require
     59  * searching in more than one GeoIP database.)
     60  */
     61 
     62 typedef struct geoip_state {
     63 	uint16_t subtype;
     64 	const MMDB_s *db;
     65 	isc_netaddr_t addr;
     66 	MMDB_lookup_result_s mmresult;
     67 	MMDB_entry_s entry;
     68 } geoip_state_t;
     69 
     70 static thread_local geoip_state_t geoip_state = { 0 };
     71 
     72 static void
     73 set_state(const MMDB_s *db, const isc_netaddr_t *addr,
     74 	  MMDB_lookup_result_s mmresult, MMDB_entry_s entry) {
     75 	geoip_state.db = db;
     76 	geoip_state.addr = *addr;
     77 	geoip_state.mmresult = mmresult;
     78 	geoip_state.entry = entry;
     79 }
     80 
     81 static geoip_state_t *
     82 get_entry_for(MMDB_s *const db, const isc_netaddr_t *addr) {
     83 	isc_sockaddr_t sa;
     84 	MMDB_lookup_result_s match;
     85 	int err;
     86 
     87 	if (db == geoip_state.db && isc_netaddr_equal(addr, &geoip_state.addr))
     88 	{
     89 		return &geoip_state;
     90 	}
     91 
     92 	isc_sockaddr_fromnetaddr(&sa, addr, 0);
     93 	match = MMDB_lookup_sockaddr(db, &sa.type.sa, &err);
     94 	if (err != MMDB_SUCCESS || !match.found_entry) {
     95 		return NULL;
     96 	}
     97 
     98 	set_state(db, addr, match, match.entry);
     99 
    100 	return &geoip_state;
    101 }
    102 
    103 static dns_geoip_subtype_t
    104 fix_subtype(const dns_geoip_databases_t *geoip, dns_geoip_subtype_t subtype) {
    105 	dns_geoip_subtype_t ret = subtype;
    106 
    107 	switch (subtype) {
    108 	case dns_geoip_countrycode:
    109 		if (geoip->city != NULL) {
    110 			ret = dns_geoip_city_countrycode;
    111 		} else if (geoip->country != NULL) {
    112 			ret = dns_geoip_country_code;
    113 		}
    114 		break;
    115 	case dns_geoip_countryname:
    116 		if (geoip->city != NULL) {
    117 			ret = dns_geoip_city_countryname;
    118 		} else if (geoip->country != NULL) {
    119 			ret = dns_geoip_country_name;
    120 		}
    121 		break;
    122 	case dns_geoip_continentcode:
    123 		if (geoip->city != NULL) {
    124 			ret = dns_geoip_city_continentcode;
    125 		} else if (geoip->country != NULL) {
    126 			ret = dns_geoip_country_continentcode;
    127 		}
    128 		break;
    129 	case dns_geoip_continent:
    130 		if (geoip->city != NULL) {
    131 			ret = dns_geoip_city_continent;
    132 		} else if (geoip->country != NULL) {
    133 			ret = dns_geoip_country_continent;
    134 		}
    135 		break;
    136 	case dns_geoip_region:
    137 		if (geoip->city != NULL) {
    138 			ret = dns_geoip_city_region;
    139 		}
    140 		break;
    141 	case dns_geoip_regionname:
    142 		if (geoip->city != NULL) {
    143 			ret = dns_geoip_city_regionname;
    144 		}
    145 	default:
    146 		break;
    147 	}
    148 
    149 	return ret;
    150 }
    151 
    152 static MMDB_s *
    153 geoip2_database(const dns_geoip_databases_t *geoip,
    154 		dns_geoip_subtype_t subtype) {
    155 	switch (subtype) {
    156 	case dns_geoip_country_code:
    157 	case dns_geoip_country_name:
    158 	case dns_geoip_country_continentcode:
    159 	case dns_geoip_country_continent:
    160 		return geoip->country;
    161 
    162 	case dns_geoip_city_countrycode:
    163 	case dns_geoip_city_countryname:
    164 	case dns_geoip_city_continentcode:
    165 	case dns_geoip_city_continent:
    166 	case dns_geoip_city_region:
    167 	case dns_geoip_city_regionname:
    168 	case dns_geoip_city_name:
    169 	case dns_geoip_city_postalcode:
    170 	case dns_geoip_city_timezonecode:
    171 	case dns_geoip_city_metrocode:
    172 	case dns_geoip_city_areacode:
    173 		return geoip->city;
    174 
    175 	case dns_geoip_isp_name:
    176 		return geoip->isp;
    177 
    178 	case dns_geoip_as_asnum:
    179 	case dns_geoip_org_name:
    180 		return geoip->as;
    181 
    182 	case dns_geoip_domain_name:
    183 		return geoip->domain;
    184 
    185 	default:
    186 		/*
    187 		 * All other subtypes are unavailable in GeoIP2.
    188 		 */
    189 		return NULL;
    190 	}
    191 }
    192 
    193 static bool
    194 match_string(MMDB_entry_data_s *value, const char *str) {
    195 	REQUIRE(str != NULL);
    196 
    197 	if (value == NULL || !value->has_data ||
    198 	    value->type != MMDB_DATA_TYPE_UTF8_STRING ||
    199 	    value->utf8_string == NULL)
    200 	{
    201 		return false;
    202 	}
    203 
    204 	return strncasecmp(value->utf8_string, str, value->data_size) == 0;
    205 }
    206 
    207 static bool
    208 match_int(MMDB_entry_data_s *value, const uint32_t ui32) {
    209 	if (value == NULL || !value->has_data ||
    210 	    (value->type != MMDB_DATA_TYPE_UINT32 &&
    211 	     value->type != MMDB_DATA_TYPE_UINT16))
    212 	{
    213 		return false;
    214 	}
    215 
    216 	return value->uint32 == ui32;
    217 }
    218 
    219 bool
    220 dns_geoip_match(const isc_netaddr_t *reqaddr,
    221 		const dns_geoip_databases_t *geoip,
    222 		const dns_geoip_elem_t *elt) {
    223 	MMDB_s *db = NULL;
    224 	MMDB_entry_data_s value;
    225 	geoip_state_t *state = NULL;
    226 	dns_geoip_subtype_t subtype;
    227 	const char *s = NULL;
    228 	int ret;
    229 
    230 	REQUIRE(reqaddr != NULL);
    231 	REQUIRE(elt != NULL);
    232 	REQUIRE(geoip != NULL);
    233 
    234 	subtype = fix_subtype(geoip, elt->subtype);
    235 	db = geoip2_database(geoip, subtype);
    236 	if (db == NULL) {
    237 		return false;
    238 	}
    239 
    240 	state = get_entry_for(db, reqaddr);
    241 	if (state == NULL) {
    242 		return false;
    243 	}
    244 
    245 	switch (subtype) {
    246 	case dns_geoip_country_code:
    247 	case dns_geoip_city_countrycode:
    248 		ret = MMDB_get_value(&state->entry, &value, "country",
    249 				     "iso_code", (char *)0);
    250 		if (ret == MMDB_SUCCESS) {
    251 			return match_string(&value, elt->as_string);
    252 		}
    253 		break;
    254 
    255 	case dns_geoip_country_name:
    256 	case dns_geoip_city_countryname:
    257 		ret = MMDB_get_value(&state->entry, &value, "country", "names",
    258 				     "en", (char *)0);
    259 		if (ret == MMDB_SUCCESS) {
    260 			return match_string(&value, elt->as_string);
    261 		}
    262 		break;
    263 
    264 	case dns_geoip_country_continentcode:
    265 	case dns_geoip_city_continentcode:
    266 		ret = MMDB_get_value(&state->entry, &value, "continent", "code",
    267 				     (char *)0);
    268 		if (ret == MMDB_SUCCESS) {
    269 			return match_string(&value, elt->as_string);
    270 		}
    271 		break;
    272 
    273 	case dns_geoip_country_continent:
    274 	case dns_geoip_city_continent:
    275 		ret = MMDB_get_value(&state->entry, &value, "continent",
    276 				     "names", "en", (char *)0);
    277 		if (ret == MMDB_SUCCESS) {
    278 			return match_string(&value, elt->as_string);
    279 		}
    280 		break;
    281 
    282 	case dns_geoip_region:
    283 	case dns_geoip_city_region:
    284 		ret = MMDB_get_value(&state->entry, &value, "subdivisions", "0",
    285 				     "iso_code", (char *)0);
    286 		if (ret == MMDB_SUCCESS) {
    287 			return match_string(&value, elt->as_string);
    288 		}
    289 		break;
    290 
    291 	case dns_geoip_regionname:
    292 	case dns_geoip_city_regionname:
    293 		ret = MMDB_get_value(&state->entry, &value, "subdivisions", "0",
    294 				     "names", "en", (char *)0);
    295 		if (ret == MMDB_SUCCESS) {
    296 			return match_string(&value, elt->as_string);
    297 		}
    298 		break;
    299 
    300 	case dns_geoip_city_name:
    301 		ret = MMDB_get_value(&state->entry, &value, "city", "names",
    302 				     "en", (char *)0);
    303 		if (ret == MMDB_SUCCESS) {
    304 			return match_string(&value, elt->as_string);
    305 		}
    306 		break;
    307 
    308 	case dns_geoip_city_postalcode:
    309 		ret = MMDB_get_value(&state->entry, &value, "postal", "code",
    310 				     (char *)0);
    311 		if (ret == MMDB_SUCCESS) {
    312 			return match_string(&value, elt->as_string);
    313 		}
    314 		break;
    315 
    316 	case dns_geoip_city_timezonecode:
    317 		ret = MMDB_get_value(&state->entry, &value, "location",
    318 				     "time_zone", (char *)0);
    319 		if (ret == MMDB_SUCCESS) {
    320 			return match_string(&value, elt->as_string);
    321 		}
    322 		break;
    323 
    324 	case dns_geoip_city_metrocode:
    325 		ret = MMDB_get_value(&state->entry, &value, "location",
    326 				     "metro_code", (char *)0);
    327 		if (ret == MMDB_SUCCESS) {
    328 			return match_string(&value, elt->as_string);
    329 		}
    330 		break;
    331 
    332 	case dns_geoip_isp_name:
    333 		ret = MMDB_get_value(&state->entry, &value, "isp", (char *)0);
    334 		if (ret == MMDB_SUCCESS) {
    335 			return match_string(&value, elt->as_string);
    336 		}
    337 		break;
    338 
    339 	case dns_geoip_as_asnum:
    340 		INSIST(elt->as_string != NULL);
    341 
    342 		ret = MMDB_get_value(&state->entry, &value,
    343 				     "autonomous_system_number", (char *)0);
    344 		if (ret == MMDB_SUCCESS) {
    345 			int i;
    346 			s = elt->as_string;
    347 			if (strncasecmp(s, "AS", 2) == 0) {
    348 				s += 2;
    349 			}
    350 			i = strtol(s, NULL, 10);
    351 			return match_int(&value, i);
    352 		}
    353 		break;
    354 
    355 	case dns_geoip_org_name:
    356 		ret = MMDB_get_value(&state->entry, &value,
    357 				     "autonomous_system_organization",
    358 				     (char *)0);
    359 		if (ret == MMDB_SUCCESS) {
    360 			return match_string(&value, elt->as_string);
    361 		}
    362 		break;
    363 
    364 	case dns_geoip_domain_name:
    365 		ret = MMDB_get_value(&state->entry, &value, "domain",
    366 				     (char *)0);
    367 		if (ret == MMDB_SUCCESS) {
    368 			return match_string(&value, elt->as_string);
    369 		}
    370 		break;
    371 
    372 	default:
    373 		/*
    374 		 * For any other subtype, we assume the database was
    375 		 * unavailable and return false.
    376 		 */
    377 		return false;
    378 	}
    379 
    380 	/*
    381 	 * No database matched: return false.
    382 	 */
    383 	return false;
    384 }
    385