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