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