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