1/* $NetBSD: geoip2.c,v 1.7 2024/02/21 22:52:06 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 62typedef 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 70static thread_local geoip_state_t geoip_state = { 0 }; 71 72static void 73set_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 81static geoip_state_t * 82get_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 103static dns_geoip_subtype_t 104fix_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 152static MMDB_s * 153geoip2_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 193static bool 194match_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 207static bool 208match_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 219bool 220dns_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