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