1 /*
2  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3  *
4  * SPDX-License-Identifier: MPL-2.0
5  *
6  * This Source Code Form is subject to the terms of the Mozilla Public
7  * License, v. 2.0. If a copy of the MPL was not distributed with this
8  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9  *
10  * See the COPYRIGHT file distributed with this work for additional
11  * information regarding copyright ownership.
12  */
13 
14 #if HAVE_CMOCKA
15 
16 #include <sched.h> /* IWYU pragma: keep */
17 #include <setjmp.h>
18 #include <stdarg.h>
19 #include <stdbool.h>
20 #include <stddef.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 
25 #define UNIT_TESTING
26 #include <cmocka.h>
27 
28 #include <isc/print.h>
29 #include <isc/string.h>
30 #include <isc/types.h>
31 #include <isc/util.h>
32 
33 #include <dns/geoip.h>
34 
35 #include "dnstest.h"
36 
37 #if defined(HAVE_GEOIP2)
38 #include <maxminddb.h>
39 
40 #include "../geoip2.c"
41 
42 /* Use GeoIP2 databases from the 'geoip2' system test */
43 #define TEST_GEOIP_DATA "../../../bin/tests/system/geoip2/data"
44 
45 static dns_geoip_databases_t geoip;
46 
47 static MMDB_s geoip_country, geoip_city, geoip_as, geoip_isp, geoip_domain;
48 
49 static void
50 load_geoip(const char *dir);
51 static void
52 close_geoip(void);
53 
54 static int
_setup(void ** state)55 _setup(void **state) {
56 	isc_result_t result;
57 
58 	UNUSED(state);
59 
60 	result = dns_test_begin(NULL, false);
61 	assert_int_equal(result, ISC_R_SUCCESS);
62 
63 	/* Use databases from the geoip system test */
64 	load_geoip(TEST_GEOIP_DATA);
65 
66 	return (0);
67 }
68 
69 static int
_teardown(void ** state)70 _teardown(void **state) {
71 	UNUSED(state);
72 
73 	close_geoip();
74 
75 	dns_test_end();
76 
77 	return (0);
78 }
79 
80 static MMDB_s *
open_geoip2(const char * dir,const char * dbfile,MMDB_s * mmdb)81 open_geoip2(const char *dir, const char *dbfile, MMDB_s *mmdb) {
82 	char pathbuf[PATH_MAX];
83 	int ret;
84 
85 	snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, dbfile);
86 	ret = MMDB_open(pathbuf, MMDB_MODE_MMAP, mmdb);
87 	if (ret == MMDB_SUCCESS) {
88 		return (mmdb);
89 	}
90 
91 	return (NULL);
92 }
93 
94 static void
load_geoip(const char * dir)95 load_geoip(const char *dir) {
96 	geoip.country = open_geoip2(dir, "GeoIP2-Country.mmdb", &geoip_country);
97 	geoip.city = open_geoip2(dir, "GeoIP2-City.mmdb", &geoip_city);
98 	geoip.as = open_geoip2(dir, "GeoLite2-ASN.mmdb", &geoip_as);
99 	geoip.isp = open_geoip2(dir, "GeoIP2-ISP.mmdb", &geoip_isp);
100 	geoip.domain = open_geoip2(dir, "GeoIP2-Domain.mmdb", &geoip_domain);
101 }
102 
103 static void
close_geoip(void)104 close_geoip(void) {
105 	MMDB_close(&geoip_country);
106 	MMDB_close(&geoip_city);
107 	MMDB_close(&geoip_as);
108 	MMDB_close(&geoip_isp);
109 	MMDB_close(&geoip_domain);
110 }
111 
112 static bool
113 /* Check if an MMDB entry of a given subtype exists for the given IP */
entry_exists(dns_geoip_subtype_t subtype,const char * addr)114 entry_exists(dns_geoip_subtype_t subtype, const char *addr) {
115 	struct in6_addr in6;
116 	struct in_addr in4;
117 	isc_netaddr_t na;
118 	MMDB_s *db;
119 
120 	if (inet_pton(AF_INET6, addr, &in6) == 1) {
121 		isc_netaddr_fromin6(&na, &in6);
122 	} else if (inet_pton(AF_INET, addr, &in4) == 1) {
123 		isc_netaddr_fromin(&na, &in4);
124 	} else {
125 		INSIST(0);
126 		ISC_UNREACHABLE();
127 	}
128 
129 	db = geoip2_database(&geoip, fix_subtype(&geoip, subtype));
130 
131 	return (db != NULL && get_entry_for(db, &na) != NULL);
132 }
133 
134 /*
135  * Baseline test - check if get_entry_for() works as expected, i.e. that its
136  * return values are consistent with the contents of the test MMDBs found in
137  * bin/tests/system/geoip2/data/ (10.53.0.1 and fd92:7065:b8e:ffff::1 should be
138  * present in all databases, 192.0.2.128 should only be present in the country
139  * database, ::1 should be absent from all databases).
140  */
141 static void
baseline(void ** state)142 baseline(void **state) {
143 	dns_geoip_subtype_t subtype;
144 
145 	UNUSED(state);
146 
147 	subtype = dns_geoip_city_name;
148 
149 	assert_true(entry_exists(subtype, "10.53.0.1"));
150 	assert_false(entry_exists(subtype, "192.0.2.128"));
151 	assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1"));
152 	assert_false(entry_exists(subtype, "::1"));
153 
154 	subtype = dns_geoip_country_name;
155 
156 	assert_true(entry_exists(subtype, "10.53.0.1"));
157 	assert_true(entry_exists(subtype, "192.0.2.128"));
158 	assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1"));
159 	assert_false(entry_exists(subtype, "::1"));
160 
161 	subtype = dns_geoip_domain_name;
162 
163 	assert_true(entry_exists(subtype, "10.53.0.1"));
164 	assert_false(entry_exists(subtype, "192.0.2.128"));
165 	assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1"));
166 	assert_false(entry_exists(subtype, "::1"));
167 
168 	subtype = dns_geoip_isp_name;
169 
170 	assert_true(entry_exists(subtype, "10.53.0.1"));
171 	assert_false(entry_exists(subtype, "192.0.2.128"));
172 	assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1"));
173 	assert_false(entry_exists(subtype, "::1"));
174 
175 	subtype = dns_geoip_as_asnum;
176 
177 	assert_true(entry_exists(subtype, "10.53.0.1"));
178 	assert_false(entry_exists(subtype, "192.0.2.128"));
179 	assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1"));
180 	assert_false(entry_exists(subtype, "::1"));
181 }
182 
183 static bool
do_lookup_string(const char * addr,dns_geoip_subtype_t subtype,const char * string)184 do_lookup_string(const char *addr, dns_geoip_subtype_t subtype,
185 		 const char *string) {
186 	dns_geoip_elem_t elt;
187 	struct in_addr in4;
188 	isc_netaddr_t na;
189 	int n;
190 
191 	n = inet_pton(AF_INET, addr, &in4);
192 	assert_int_equal(n, 1);
193 	isc_netaddr_fromin(&na, &in4);
194 
195 	elt.subtype = subtype;
196 	strlcpy(elt.as_string, string, sizeof(elt.as_string));
197 
198 	return (dns_geoip_match(&na, &geoip, &elt));
199 }
200 
201 static bool
do_lookup_string_v6(const char * addr,dns_geoip_subtype_t subtype,const char * string)202 do_lookup_string_v6(const char *addr, dns_geoip_subtype_t subtype,
203 		    const char *string) {
204 	dns_geoip_elem_t elt;
205 	struct in6_addr in6;
206 	isc_netaddr_t na;
207 	int n;
208 
209 	n = inet_pton(AF_INET6, addr, &in6);
210 	assert_int_equal(n, 1);
211 	isc_netaddr_fromin6(&na, &in6);
212 
213 	elt.subtype = subtype;
214 	strlcpy(elt.as_string, string, sizeof(elt.as_string));
215 
216 	return (dns_geoip_match(&na, &geoip, &elt));
217 }
218 
219 /* GeoIP country matching */
220 static void
country(void ** state)221 country(void **state) {
222 	bool match;
223 
224 	UNUSED(state);
225 
226 	if (geoip.country == NULL) {
227 		skip();
228 	}
229 
230 	match = do_lookup_string("10.53.0.1", dns_geoip_country_code, "AU");
231 	assert_true(match);
232 
233 	match = do_lookup_string("10.53.0.1", dns_geoip_country_name,
234 				 "Australia");
235 	assert_true(match);
236 
237 	match = do_lookup_string("192.0.2.128", dns_geoip_country_code, "O1");
238 	assert_true(match);
239 
240 	match = do_lookup_string("192.0.2.128", dns_geoip_country_name,
241 				 "Other");
242 	assert_true(match);
243 }
244 
245 /* GeoIP country (ipv6) matching */
246 static void
country_v6(void ** state)247 country_v6(void **state) {
248 	bool match;
249 
250 	UNUSED(state);
251 
252 	if (geoip.country == NULL) {
253 		skip();
254 	}
255 
256 	match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
257 				    dns_geoip_country_code, "AU");
258 	assert_true(match);
259 
260 	match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
261 				    dns_geoip_country_name, "Australia");
262 	assert_true(match);
263 }
264 
265 /* GeoIP city (ipv4) matching */
266 static void
city(void ** state)267 city(void **state) {
268 	bool match;
269 
270 	UNUSED(state);
271 
272 	if (geoip.city == NULL) {
273 		skip();
274 	}
275 
276 	match = do_lookup_string("10.53.0.1", dns_geoip_city_continentcode,
277 				 "NA");
278 	assert_true(match);
279 
280 	match = do_lookup_string("10.53.0.1", dns_geoip_city_countrycode, "US");
281 	assert_true(match);
282 
283 	match = do_lookup_string("10.53.0.1", dns_geoip_city_countryname,
284 				 "United States");
285 	assert_true(match);
286 
287 	match = do_lookup_string("10.53.0.1", dns_geoip_city_region, "CA");
288 	assert_true(match);
289 
290 	match = do_lookup_string("10.53.0.1", dns_geoip_city_regionname,
291 				 "California");
292 	assert_true(match);
293 
294 	match = do_lookup_string("10.53.0.1", dns_geoip_city_name,
295 				 "Redwood City");
296 	assert_true(match);
297 
298 	match = do_lookup_string("10.53.0.1", dns_geoip_city_postalcode,
299 				 "94063");
300 	assert_true(match);
301 }
302 
303 /* GeoIP city (ipv6) matching */
304 static void
city_v6(void ** state)305 city_v6(void **state) {
306 	bool match;
307 
308 	UNUSED(state);
309 
310 	if (geoip.city == NULL) {
311 		skip();
312 	}
313 
314 	match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
315 				    dns_geoip_city_continentcode, "NA");
316 	assert_true(match);
317 
318 	match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
319 				    dns_geoip_city_countrycode, "US");
320 	assert_true(match);
321 
322 	match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
323 				    dns_geoip_city_countryname,
324 				    "United States");
325 	assert_true(match);
326 
327 	match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
328 				    dns_geoip_city_region, "CA");
329 	assert_true(match);
330 
331 	match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
332 				    dns_geoip_city_regionname, "California");
333 	assert_true(match);
334 
335 	match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
336 				    dns_geoip_city_name, "Redwood City");
337 	assert_true(match);
338 
339 	match = do_lookup_string_v6("fd92:7065:b8e:ffff::1",
340 				    dns_geoip_city_postalcode, "94063");
341 	assert_true(match);
342 }
343 
344 /* GeoIP asnum matching */
345 static void
asnum(void ** state)346 asnum(void **state) {
347 	bool match;
348 
349 	UNUSED(state);
350 
351 	if (geoip.as == NULL) {
352 		skip();
353 	}
354 
355 	match = do_lookup_string("10.53.0.3", dns_geoip_as_asnum, "AS100003");
356 	assert_true(match);
357 }
358 
359 /* GeoIP isp matching */
360 static void
isp(void ** state)361 isp(void **state) {
362 	bool match;
363 
364 	UNUSED(state);
365 
366 	if (geoip.isp == NULL) {
367 		skip();
368 	}
369 
370 	match = do_lookup_string("10.53.0.1", dns_geoip_isp_name,
371 				 "One Systems, Inc.");
372 	assert_true(match);
373 }
374 
375 /* GeoIP org matching */
376 static void
org(void ** state)377 org(void **state) {
378 	bool match;
379 
380 	UNUSED(state);
381 
382 	if (geoip.as == NULL) {
383 		skip();
384 	}
385 
386 	match = do_lookup_string("10.53.0.2", dns_geoip_org_name,
387 				 "Two Technology Ltd.");
388 	assert_true(match);
389 }
390 
391 /* GeoIP domain matching */
392 static void
domain(void ** state)393 domain(void **state) {
394 	bool match;
395 
396 	UNUSED(state);
397 
398 	if (geoip.domain == NULL) {
399 		skip();
400 	}
401 
402 	match = do_lookup_string("10.53.0.5", dns_geoip_domain_name, "five.es");
403 	assert_true(match);
404 }
405 #endif /* HAVE_GEOIP2 */
406 
407 int
main(void)408 main(void) {
409 #if defined(HAVE_GEOIP2)
410 	const struct CMUnitTest tests[] = {
411 		cmocka_unit_test(baseline),   cmocka_unit_test(country),
412 		cmocka_unit_test(country_v6), cmocka_unit_test(city),
413 		cmocka_unit_test(city_v6),    cmocka_unit_test(asnum),
414 		cmocka_unit_test(isp),	      cmocka_unit_test(org),
415 		cmocka_unit_test(domain),
416 	};
417 
418 	return (cmocka_run_group_tests(tests, _setup, _teardown));
419 #else  /* if defined(HAVE_GEOIP2) */
420 	print_message("1..0 # Skip GeoIP not enabled\n");
421 #endif /* if defined(HAVE_GEOIP2) */
422 }
423 
424 #else /* HAVE_CMOCKA */
425 
426 #include <stdio.h>
427 
428 int
main(void)429 main(void) {
430 	printf("1..0 # Skipped: cmocka not available\n");
431 	return (SKIPPED_TEST_EXIT_CODE);
432 }
433 
434 #endif /* HAVE_CMOCKA */
435