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