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