xref: /netbsd/external/mpl/bind/dist/lib/dns/geoip2.c (revision c0b5d9fb)
1 /*	$NetBSD: geoip2.c,v 1.6 2022/09/23 12:15:29 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 
69 typedef 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 
77 ISC_THREAD_LOCAL geoip_state_t geoip_state = { 0 };
78 
79 static void
set_state(const MMDB_s * db,const isc_netaddr_t * addr,MMDB_lookup_result_s mmresult,MMDB_entry_s entry)80 set_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 
88 static geoip_state_t *
get_entry_for(MMDB_s * const db,const isc_netaddr_t * addr)89 get_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 
110 static dns_geoip_subtype_t
fix_subtype(const dns_geoip_databases_t * geoip,dns_geoip_subtype_t subtype)111 fix_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 
159 static MMDB_s *
geoip2_database(const dns_geoip_databases_t * geoip,dns_geoip_subtype_t subtype)160 geoip2_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 
200 static bool
match_string(MMDB_entry_data_s * value,const char * str)201 match_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 
214 static bool
match_int(MMDB_entry_data_s * value,const uint32_t ui32)215 match_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 
226 bool
dns_geoip_match(const isc_netaddr_t * reqaddr,const dns_geoip_databases_t * geoip,const dns_geoip_elem_t * elt)227 dns_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