more decompress
[wireshark-sm.git] / epan / maxmind_db.c
blob6031276a071a4b3b324aaf5abc09662b7e22f132
1 /* maxmind_db.c
2 * GeoIP database support
4 * Copyright 2018, Gerald Combs <gerald@wireshark.org>
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
13 #include "config.h"
15 #define WS_LOG_DOMAIN LOG_DOMAIN_MMDB
17 #include <glib.h>
19 #include <epan/maxmind_db.h>
21 static mmdb_lookup_t mmdb_not_found;
23 #ifdef HAVE_MAXMINDDB
25 #include <stdio.h>
26 #include <errno.h>
27 #include <fcntl.h>
29 #ifdef HAVE_SYS_WAIT_H
30 #include <sys/wait.h>
31 #endif
33 #include <epan/wmem_scopes.h>
35 #include <epan/addr_resolv.h>
36 #include <epan/uat.h>
37 #include <epan/prefs.h>
39 #include <wsutil/report_message.h>
40 #include <wsutil/file_util.h>
41 #include <wsutil/filesystem.h>
42 #include <wsutil/ws_pipe.h>
43 #include <wsutil/strtoi.h>
44 #include <wsutil/glib-compat.h>
46 // To do:
47 // - Add RBL lookups? Along with the "is this a spammer" information that most RBL databases
48 // provide, you can also fetch AS information: https://www.team-cymru.com/IP-ASN-mapping.html
49 // - Switch to a different format? I was going to use g_key_file_* to parse
50 // the mmdbresolve output, but it was easier to just parse it directly.
52 static GThread *write_mmdbr_stdin_thread;
53 static GAsyncQueue *mmdbr_request_q; // g_allocated char *
54 static char mmdbr_stop_sentinel[] = "\x04"; // ASCII EOT. Could be anything.
56 // The GLib documentation says that g_rw_lock_reader_lock can be called
57 // recursively:
58 // https://developer-old.gnome.org/glib/stable/glib-Threads.html#g-rw-lock-reader-lock
59 // However, g_rw_lock_reader_lock calls AcquireSRWLockShared
60 // https://gitlab.gnome.org/GNOME/glib/blob/master/glib/gthread-win32.c#L206
61 // and SRW locks "cannot be acquired recursively"
62 // https://docs.microsoft.com/en-us/windows/desktop/Sync/slim-reader-writer--srw--locks
63 // https://devblogs.microsoft.com/oldnewthing/?p=93416
64 static GRWLock mmdbr_pipe_mtx;
66 // Hashes of mmdb_lookup_t
67 typedef struct _mmdbr_response_t {
68 bool fatal_err;
69 bool is_ipv4;
70 ws_in4_addr ipv4_addr;
71 ws_in6_addr ipv6_addr;
72 mmdb_lookup_t mmdb_val;
73 } mmdb_response_t;
75 static wmem_map_t *mmdb_ipv4_map;
76 static wmem_map_t *mmdb_ipv6_map;
77 static GAsyncQueue *mmdbr_response_q; // g_allocated mmdbr_response_t *
78 static GThread *read_mmdbr_stdout_thread;
80 // Interned strings
81 static wmem_map_t *mmdb_str_chunk;
82 static wmem_map_t *mmdb_ipv6_chunk;
84 /* Child mmdbresolve process */
85 static ws_pipe_t mmdbr_pipe; // Requires mutex
87 /* UAT definitions. Copied from oids.c */
88 typedef struct _maxmind_db_path_t {
89 char* path;
90 } maxmind_db_path_t;
92 static maxmind_db_path_t *maxmind_db_paths;
93 static unsigned num_maxmind_db_paths;
94 static const maxmind_db_path_t maxmind_db_system_paths[] = {
95 #ifdef _WIN32
96 // XXX Properly expand "%ProgramData%\GeoIP".
97 { "C:\\ProgramData\\GeoIP" },
98 { "C:\\GeoIP" },
99 #else
100 { "/usr/share/GeoIP" },
101 { "/var/lib/GeoIP" },
102 #endif
103 { NULL }
105 static uat_t *maxmind_db_paths_uat;
106 UAT_DIRECTORYNAME_CB_DEF(maxmind_mod, path, maxmind_db_path_t)
108 static GPtrArray *mmdb_file_arr; // .mmdb files
110 static bool resolve_synchronously;
112 static void mmdb_resolve_stop(void);
114 // Hopefully scanning a few lines asynchronously has less overhead than
115 // reading in a child thread.
116 #define RES_INVALID_LINE "# Invalid"
117 #define RES_STATUS_ERROR "mmdbresolve.status: false"
118 #define RES_COUNTRY_ISO_CODE "country.iso_code"
119 #define RES_COUNTRY_NAMES_EN "country.names.en"
120 #define RES_CITY_NAMES_EN "city.names.en"
121 #define RES_ASN_ORG "autonomous_system_organization"
122 #define RES_ASN_NUMBER "autonomous_system_number"
123 #define RES_LOCATION_LATITUDE "location.latitude"
124 #define RES_LOCATION_LONGITUDE "location.longitude"
125 #define RES_LOCATION_ACCURACY "location.accuracy_radius"
126 #define RES_END "# End "
128 // Interned strings and v6 addresses, similar to GLib's string chunks.
129 static const char *chunkify_string(char *key) {
130 char *chunk_string = (char *) wmem_map_lookup(mmdb_str_chunk, key);
132 if (!chunk_string) {
133 chunk_string = wmem_strdup(wmem_epan_scope(), key);
134 wmem_map_insert(mmdb_str_chunk, chunk_string, chunk_string);
137 return chunk_string;
140 static const void *chunkify_v6_addr(const ws_in6_addr *addr) {
141 void *chunk_v6_bytes = (char *) wmem_map_lookup(mmdb_ipv6_chunk, addr->bytes);
143 if (!chunk_v6_bytes) {
144 chunk_v6_bytes = wmem_memdup(wmem_epan_scope(), addr->bytes, sizeof(ws_in6_addr));
145 wmem_map_insert(mmdb_ipv6_chunk, chunk_v6_bytes, chunk_v6_bytes);
148 return chunk_v6_bytes;
151 static void init_lookup(mmdb_lookup_t *lookup) {
152 mmdb_lookup_t empty_lookup = { false, NULL, NULL, NULL, 0, NULL, DBL_MAX, DBL_MAX, 0 };
153 *lookup = empty_lookup;
156 static bool mmdbr_pipe_valid(void) {
157 g_rw_lock_reader_lock(&mmdbr_pipe_mtx);
158 bool pipe_valid = ws_pipe_valid(&mmdbr_pipe);
159 g_rw_lock_reader_unlock(&mmdbr_pipe_mtx);
160 return pipe_valid;
163 // Writing to mmdbr_pipe.stdin_fd can block. Do so in a separate thread.
164 static void *
165 write_mmdbr_stdin_worker(void *data _U_) {
166 GIOStatus status;
167 GError *err = NULL;
168 size_t bytes_written;
169 ws_debug("starting write worker");
171 while (1) {
172 // On some operating systems (most notably macOS), g_async_queue_timeout_pop
173 // will return immediately if we've been built with an older version of GLib:
174 // https://bugzilla.gnome.org/show_bug.cgi?id=673607
175 // Call g_async_queue_pop instead. When we need to stop processing,
176 // mmdb_resolve_stop will close our pipe and then push an invalid address
177 // (mmdbr_stop_sentinel) onto the queue.
178 char *request = (char *) g_async_queue_pop(mmdbr_request_q);
179 if (!request) {
180 continue;
182 if (strcmp(request, mmdbr_stop_sentinel) == 0) {
183 g_free(request);
184 return NULL;
187 ws_noisy("write %s ql %d", request, g_async_queue_length(mmdbr_request_q));
188 status = g_io_channel_write_chars(mmdbr_pipe.stdin_io, request, strlen(request), &bytes_written, &err);
189 if (status != G_IO_STATUS_NORMAL) {
190 ws_debug("write error %s. exiting thread.", err->message);
191 g_clear_error(&err);
192 g_free(request);
193 mmdb_response_t *response = g_new0(mmdb_response_t, 1);
194 response->fatal_err = true;
195 g_async_queue_push(mmdbr_response_q, response); // Will be freed by maxmind_db_pop_response.
196 return NULL;
198 g_clear_error(&err);
199 g_free(request);
201 return NULL;
204 #define MAX_MMDB_LINE_LEN 2001
205 static void *
206 read_mmdbr_stdout_worker(void *data _U_) {
207 mmdb_response_t *response = g_new0(mmdb_response_t, 1);
208 char *line_buf = g_new(char, MAX_MMDB_LINE_LEN);
209 GString *country_iso = g_string_new("");
210 GString *country = g_string_new("");
211 GString *city = g_string_new("");
212 GString *as_org = g_string_new("");
213 char cur_addr[WS_INET6_ADDRSTRLEN] = { 0 };
215 size_t bytes_in_buffer, search_offset;
216 bool line_feed_found;
218 ws_debug("starting read worker");
220 bytes_in_buffer = search_offset = 0;
221 line_feed_found = false;
222 for (;;) {
223 if (line_feed_found) {
224 /* Line parsed, move all (if any) next line bytes to beginning */
225 bytes_in_buffer -= (search_offset + 1);
226 memmove(line_buf, &line_buf[search_offset + 1], bytes_in_buffer);
227 search_offset = 0;
228 line_feed_found = false;
231 while (search_offset < bytes_in_buffer) {
232 if (line_buf[search_offset] == '\n') {
233 line_buf[search_offset] = 0; /* NULL-terminate the string */
234 line_feed_found = true;
235 break;
237 search_offset++;
240 if (!line_feed_found) {
241 int space_available = (int)(MAX_MMDB_LINE_LEN - bytes_in_buffer);
242 if (space_available > 0) {
243 size_t bytes_read;
244 g_io_channel_read_chars(mmdbr_pipe.stdout_io, &line_buf[bytes_in_buffer],
245 space_available, &bytes_read, NULL);
246 if (bytes_read > 0) {
247 bytes_in_buffer += bytes_read;
248 } else {
249 ws_debug("no pipe data. exiting thread.");
250 response->fatal_err = true;
251 g_async_queue_push(mmdbr_response_q, response); // Will be freed by maxmind_db_pop_response.
252 response = NULL;
253 break;
255 } else {
256 ws_debug("long line");
257 bytes_in_buffer = g_strlcpy(line_buf, RES_INVALID_LINE, MAX_MMDB_LINE_LEN);
258 search_offset = bytes_in_buffer;
260 continue;
263 char *line = g_strstrip(line_buf);
264 size_t line_len = strlen(line);
265 ws_noisy("read %zd bytes: %s", line_len, line);
266 if (line_len < 1) continue;
268 char *val_start = strchr(line, ':');
269 if (val_start) {
270 val_start = g_strstrip(val_start + 1);
273 if (line[0] == '[' && line_len > 2) {
274 // [init] or resolved address in square brackets.
275 line[line_len - 1] = '\0';
276 (void) g_strlcpy(cur_addr, line + 1, WS_INET6_ADDRSTRLEN);
277 if (ws_inet_pton4(cur_addr, &response->ipv4_addr)) {
278 response->is_ipv4 = true;
279 } else if (ws_inet_pton6(cur_addr, &response->ipv6_addr)) {
280 response->is_ipv4 = false;
281 } else if (strcmp(cur_addr, "init") != 0) {
282 ws_debug("Invalid address: %s", cur_addr);
283 cur_addr[0] = '\0';
285 // Reset state.
286 init_lookup(&response->mmdb_val);
287 g_string_truncate(country_iso, 0);
288 g_string_truncate(country, 0);
289 g_string_truncate(city, 0);
290 g_string_truncate(as_org, 0);
291 } else if (strcmp(line, RES_STATUS_ERROR) == 0) {
292 // Error during init.
293 cur_addr[0] = '\0';
294 init_lookup(&response->mmdb_val);
295 break;
296 } else if (val_start && g_str_has_prefix(line, RES_COUNTRY_ISO_CODE)) {
297 response->mmdb_val.found = true;
298 g_string_assign(country_iso, val_start);
299 } else if (val_start && g_str_has_prefix(line, RES_COUNTRY_NAMES_EN)) {
300 response->mmdb_val.found = true;
301 g_string_assign(country, val_start);
302 } else if (val_start && g_str_has_prefix(line, RES_CITY_NAMES_EN)) {
303 response->mmdb_val.found = true;
304 g_string_assign(city, val_start);
305 } else if (val_start && g_str_has_prefix(line, RES_ASN_ORG)) {
306 response->mmdb_val.found = true;
307 g_string_assign(as_org, val_start);
308 } else if (val_start && g_str_has_prefix(line, RES_ASN_NUMBER)) {
309 if (ws_strtou32(val_start, NULL, &response->mmdb_val.as_number)) {
310 response->mmdb_val.found = true;
311 } else {
312 ws_debug("Invalid ASN: %s", val_start);
314 } else if (val_start && g_str_has_prefix(line, RES_LOCATION_LATITUDE)) {
315 response->mmdb_val.found = true;
316 response->mmdb_val.latitude = g_ascii_strtod(val_start, NULL);
317 } else if (val_start && g_str_has_prefix(line, RES_LOCATION_LONGITUDE)) {
318 response->mmdb_val.found = true;
319 response->mmdb_val.longitude = g_ascii_strtod(val_start, NULL);
320 } else if (val_start && g_str_has_prefix(line, RES_LOCATION_ACCURACY)) {
321 if (ws_strtou16(val_start, NULL, &response->mmdb_val.accuracy)) {
322 response->mmdb_val.found = true;
323 } else {
324 ws_debug("Invalid accuracy radius: %s", val_start);
326 } else if (g_str_has_prefix(line, RES_END)) {
327 if (response->mmdb_val.found && cur_addr[0]) {
328 if (country_iso->len) {
329 response->mmdb_val.country_iso = g_strdup(country_iso->str);
331 if (country->len) {
332 response->mmdb_val.country = g_strdup(country->str);
334 if (city->len) {
335 response->mmdb_val.city = g_strdup(city->str);
337 if (as_org->len) {
338 response->mmdb_val.as_org = g_strdup(as_org->str);
340 ws_debug("queued %p %s %s: city %s country %s", response, response->is_ipv4 ? "v4" : "v6", cur_addr, response->mmdb_val.city, response->mmdb_val.country);
341 g_async_queue_push(mmdbr_response_q, response); // Will be freed by maxmind_db_pop_response.
342 response = g_new0(mmdb_response_t, 1);
343 } else if (strcmp(cur_addr, "init") != 0) {
344 if (resolve_synchronously) {
345 // Synchronous lookups expect a 1-in 1-out resolution.
346 ws_debug("Pushing not-found result due to bad address");
347 g_async_queue_push(mmdbr_response_q, response); // Will be freed by maxmind_db_pop_response.
348 response = g_new0(mmdb_response_t, 1);
350 else {
351 ws_debug("Discarded previous values due to bad address");
354 cur_addr[0] = '\0';
355 init_lookup(&response->mmdb_val);
359 g_string_free(country_iso, TRUE);
360 g_string_free(country, TRUE);
361 g_string_free(city, TRUE);
362 g_string_free(as_org, TRUE);
363 g_free(line_buf);
364 g_free(response);
365 return NULL;
369 * Stop our mmdbresolve process.
370 * Main thread only.
372 static void mmdb_resolve_stop(void) {
373 char *request;
374 mmdb_response_t *response;
376 while (mmdbr_request_q && (request = (char *) g_async_queue_try_pop(mmdbr_request_q)) != NULL) {
377 g_free(request);
380 if (!mmdbr_pipe_valid()) {
381 ws_debug("not cleaning up, invalid PID %"G_PID_FORMAT, mmdbr_pipe.pid);
382 return;
385 g_rw_lock_writer_lock(&mmdbr_pipe_mtx);
387 g_async_queue_push(mmdbr_request_q, g_strdup(mmdbr_stop_sentinel));
389 g_rw_lock_writer_unlock(&mmdbr_pipe_mtx);
391 // write_mmdbr_stdin_worker should exit
392 g_thread_join(write_mmdbr_stdin_thread);
393 write_mmdbr_stdin_thread = NULL;
395 ws_debug("closing stdin IO");
396 g_io_channel_unref(mmdbr_pipe.stdin_io);
398 ws_debug("closing pid %"G_PID_FORMAT, mmdbr_pipe.pid);
399 g_spawn_close_pid(mmdbr_pipe.pid);
400 #ifndef _WIN32
401 /* Reap mmdbresolve, especially as we may not be shutting down.
402 * (E.g. when the configuration changed or it terminated unexpectedly,
403 * leading to this getting called when the worker threads exited.)
405 for (int retry_waitpid = 0; retry_waitpid <= 3; ++retry_waitpid) {
406 if (waitpid(mmdbr_pipe.pid, NULL, 0) != -1) {
407 /* waitpid() succeeded. We don't care about the exit status
408 * (we could log it in case it's unexpected)
410 } else {
411 /* waitpid() failed */
412 if (errno == EINTR) {
413 /* signal interrupted. Just try again */
414 continue;
415 } else if (errno == ECHILD) {
416 /* PID doesn't exist any more or isn't our child.
417 * possibly already reaped?
419 } else {
420 /* Unexpected error. */
421 ws_warning("Error from waitpid(): %s", g_strerror(errno));
424 break;
426 #endif
427 mmdbr_pipe.pid = WS_INVALID_PID;
429 // child process notices broken stdin pipe and exits (breaks stdout pipe)
430 // read_mmdbr_stdout_worker should exit
432 g_thread_join(read_mmdbr_stdout_thread);
433 read_mmdbr_stdout_thread = NULL;
435 ws_debug("closing stdout IO");
436 g_io_channel_unref(mmdbr_pipe.stdout_io);
438 while (mmdbr_response_q && (response = (mmdb_response_t *) g_async_queue_try_pop(mmdbr_response_q)) != NULL) {
439 g_free((char *) response->mmdb_val.country_iso);
440 g_free((char *) response->mmdb_val.country);
441 g_free((char *) response->mmdb_val.city);
442 g_free((char *) response->mmdb_val.as_org);
443 ws_debug("cleaned response %p", response);
444 g_free(response);
449 * Start an mmdbresolve process.
451 static void mmdb_resolve_start(void) {
452 if (!mmdbr_request_q) {
453 mmdbr_request_q = g_async_queue_new();
456 if (!mmdbr_response_q) {
457 mmdbr_response_q = g_async_queue_new();
460 if (!mmdb_ipv4_map) {
461 mmdb_ipv4_map = wmem_map_new(wmem_epan_scope(), g_direct_hash, g_direct_equal);
464 if (!mmdb_ipv6_map) {
465 mmdb_ipv6_map = wmem_map_new(wmem_epan_scope(), ipv6_oat_hash, ipv6_equal);
468 if (!mmdb_str_chunk) {
469 mmdb_str_chunk = wmem_map_new(wmem_epan_scope(), wmem_str_hash, g_str_equal);
472 if (!mmdb_ipv6_chunk) {
473 mmdb_ipv6_chunk = wmem_map_new(wmem_epan_scope(), ipv6_oat_hash, ipv6_equal);
476 if (!mmdb_file_arr) {
477 ws_debug("unexpected mmdb_file_arr == NULL");
478 return;
481 mmdb_resolve_stop();
483 if (mmdb_file_arr->len == 0) {
484 ws_debug("no GeoIP databases found");
485 return;
488 GPtrArray *args = g_ptr_array_new();
489 char *mmdbresolve = get_executable_path("mmdbresolve");
490 g_ptr_array_add(args, mmdbresolve);
491 for (unsigned i = 0; i < mmdb_file_arr->len; i++) {
492 g_ptr_array_add(args, g_strdup("-f"));
493 g_ptr_array_add(args, g_strdup((const char *)g_ptr_array_index(mmdb_file_arr, i)));
495 g_ptr_array_add(args, NULL);
497 ws_pipe_init(&mmdbr_pipe);
498 GPid pipe_pid = ws_pipe_spawn_async(&mmdbr_pipe, args);
499 ws_debug("spawned %s pid %"G_PID_FORMAT, mmdbresolve, pipe_pid);
501 for (unsigned i = 0; i < args->len; i++) {
502 char *arg = (char *)g_ptr_array_index(args, i);
503 ws_debug("args: %s", arg);
504 g_free(arg);
506 g_ptr_array_free(args, true);
508 if (pipe_pid == WS_INVALID_PID) {
509 ws_pipe_init(&mmdbr_pipe);
510 return;
512 g_io_channel_unref(mmdbr_pipe.stderr_io);
513 #ifndef _WIN32
514 /* Make sure that these close if we spawn a dumpcap process
515 * (capturing or capture stats/sparklines.)
517 fcntl(g_io_channel_unix_get_fd(mmdbr_pipe.stdin_io), F_SETFD, FD_CLOEXEC);
518 fcntl(g_io_channel_unix_get_fd(mmdbr_pipe.stdout_io), F_SETFD, FD_CLOEXEC);
519 #endif
521 write_mmdbr_stdin_thread = g_thread_new("write_mmdbr_stdin_worker", write_mmdbr_stdin_worker, NULL);
522 read_mmdbr_stdout_thread = g_thread_new("read_mmdbr_stdout_worker", read_mmdbr_stdout_worker, NULL);
526 * Scan a directory for GeoIP databases and load them
528 static void
529 maxmind_db_scan_dir(const char *dirname) {
530 WS_DIR *dir;
531 WS_DIRENT *file;
533 if ((dir = ws_dir_open(dirname, 0, NULL)) != NULL) {
534 while ((file = ws_dir_read_name(dir)) != NULL) {
535 const char *name = ws_dir_get_name(file);
536 if (g_str_has_suffix(file, ".mmdb")) {
537 char *datname = ws_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", dirname, name);
538 FILE *mmdb_f = ws_fopen(datname, "r");
539 if (mmdb_f) {
540 g_ptr_array_add(mmdb_file_arr, datname);
541 fclose(mmdb_f);
542 } else {
543 g_free(datname);
547 ws_dir_close (dir);
551 /* UAT callbacks */
552 static void* maxmind_db_path_copy_cb(void* dest, const void* orig, size_t len _U_) {
553 const maxmind_db_path_t *m = (const maxmind_db_path_t *)orig;
554 maxmind_db_path_t *d = (maxmind_db_path_t *)dest;
556 d->path = g_strdup(m->path);
558 return d;
561 static void maxmind_db_path_free_cb(void* p) {
562 maxmind_db_path_t *m = (maxmind_db_path_t *)p;
563 g_free(m->path);
566 static void maxmind_db_cleanup(void) {
567 unsigned i;
569 mmdb_resolve_stop();
571 /* If we have old data, clear out the whole thing
572 * and start again. TODO: Just update the ones that
573 * have changed for efficiency's sake. */
574 if (mmdb_file_arr) {
575 for (i = 0; i < mmdb_file_arr->len; i++) {
576 g_free(g_ptr_array_index(mmdb_file_arr, i));
578 /* finally, free the array itself */
579 g_ptr_array_free(mmdb_file_arr, true);
580 mmdb_file_arr = NULL;
584 /* called every time the user presses "Apply" or "OK in the list of
585 * GeoIP directories, and also once on startup */
586 static void maxmind_db_post_update_cb(void) {
587 unsigned i;
589 maxmind_db_cleanup();
591 /* allocate the array */
592 mmdb_file_arr = g_ptr_array_new();
594 /* First try the system paths */
595 for (i = 0; maxmind_db_system_paths[i].path != NULL; i++) {
596 maxmind_db_scan_dir(maxmind_db_system_paths[i].path);
599 /* Walk all the directories */
600 for (i = 0; i < num_maxmind_db_paths; i++) {
601 if (maxmind_db_paths[i].path) {
602 maxmind_db_scan_dir(maxmind_db_paths[i].path);
606 if (gbl_resolv_flags.maxmind_geoip) {
607 mmdb_resolve_start();
612 * Initialize GeoIP lookups
614 void
615 maxmind_db_pref_init(module_t *nameres)
617 prefs_register_bool_preference(nameres,
618 "maxmind_geoip",
619 "Enable IP geolocation",
620 "Lookup geolocation information for IPv4 and IPv6 addresses with configured MaxMind databases",
621 &gbl_resolv_flags.maxmind_geoip);
623 static uat_field_t maxmind_db_paths_fields[] = {
624 UAT_FLD_DIRECTORYNAME(maxmind_mod, path, "MaxMind Database Directory", "The MaxMind database directory path"),
625 UAT_END_FIELDS
628 maxmind_db_paths_uat = uat_new("MaxMind Database Paths",
629 sizeof(maxmind_db_path_t),
630 "maxmind_db_paths",
631 false, // Global, not per-profile
632 (void**)&maxmind_db_paths,
633 &num_maxmind_db_paths,
634 UAT_AFFECTS_DISSECTION, // Affects IP4 and IPv6 packets.
635 "ChMaxMindDbPaths",
636 maxmind_db_path_copy_cb,
637 NULL, // update_cb
638 maxmind_db_path_free_cb,
639 maxmind_db_post_update_cb,
640 maxmind_db_cleanup,
641 maxmind_db_paths_fields);
643 prefs_register_uat_preference(nameres,
644 "maxmind_db_paths",
645 "MaxMind database directories",
646 "Search paths for MaxMind address mapping databases."
647 " Wireshark will look in each directory for files ending"
648 " with \".mmdb\".",
649 maxmind_db_paths_uat);
652 void maxmind_db_pref_cleanup(void)
654 mmdb_resolve_stop();
657 void maxmind_db_pref_apply(void)
659 if (gbl_resolv_flags.maxmind_geoip) {
660 if (!mmdbr_pipe_valid()) {
661 mmdb_resolve_start();
663 } else {
664 if (mmdbr_pipe_valid()) {
665 mmdb_resolve_stop();
670 static void maxmind_db_pop_response(mmdb_response_t *response)
672 /* This is only called in the main thread */
673 if (response->fatal_err == true) {
674 mmdb_resolve_stop();
675 /* XXX: We could call mmdb_resolve_start() instead */
676 } else {
677 mmdb_lookup_t *mmdb_val = (mmdb_lookup_t *) wmem_memdup(wmem_epan_scope(), &response->mmdb_val, sizeof(mmdb_lookup_t));
678 if (response->mmdb_val.country_iso) {
679 char *country_iso = (char *) response->mmdb_val.country_iso;
680 mmdb_val->country_iso = chunkify_string(country_iso);
681 g_free(country_iso);
683 if (response->mmdb_val.country) {
684 char *country = (char *) response->mmdb_val.country;
685 mmdb_val->country = chunkify_string(country);
686 g_free(country);
688 if (response->mmdb_val.city) {
689 char *city = (char *) response->mmdb_val.city;
690 mmdb_val->city = chunkify_string(city);
691 g_free(city);
693 if (response->mmdb_val.as_org) {
694 char *as_org = (char *) response->mmdb_val.as_org;
695 mmdb_val->as_org = chunkify_string(as_org);
696 g_free(as_org);
698 ws_debug("popped response %s city %s country %s", response->is_ipv4 ? "v4" : "v6", mmdb_val->city, mmdb_val->country);
700 if (response->is_ipv4) {
701 wmem_map_insert(mmdb_ipv4_map, GUINT_TO_POINTER(response->ipv4_addr), mmdb_val);
702 } else {
703 wmem_map_insert(mmdb_ipv6_map, chunkify_v6_addr(&response->ipv6_addr), mmdb_val);
706 g_free(response);
709 static void maxmind_db_await_response(void)
711 mmdb_response_t *response;
713 if (mmdbr_response_q != NULL) {
714 ws_debug("entering blocking wait for response");
715 response = (mmdb_response_t *) g_async_queue_pop(mmdbr_response_q);
716 ws_debug("exiting blocking wait for response");
717 maxmind_db_pop_response(response);
722 * Public API
725 bool maxmind_db_lookup_process(void)
727 bool new_entries = false;
728 mmdb_response_t *response;
730 while (mmdbr_response_q && (response = (mmdb_response_t *) g_async_queue_try_pop(mmdbr_response_q)) != NULL) {
731 new_entries = true;
732 maxmind_db_pop_response(response);
735 return new_entries;
738 const mmdb_lookup_t *
739 maxmind_db_lookup_ipv4(const ws_in4_addr *addr) {
740 if (!gbl_resolv_flags.maxmind_geoip) {
741 return &mmdb_not_found;
744 mmdb_lookup_t *result = (mmdb_lookup_t *) wmem_map_lookup(mmdb_ipv4_map, GUINT_TO_POINTER(*addr));
746 if (!result) {
747 result = &mmdb_not_found;
748 wmem_map_insert(mmdb_ipv4_map, GUINT_TO_POINTER(*addr), result);
750 if (mmdbr_pipe_valid()) {
751 char addr_str[WS_INET_ADDRSTRLEN];
752 ws_inet_ntop4(addr, addr_str, WS_INET_ADDRSTRLEN);
753 ws_debug("looking up %s", addr_str);
754 g_async_queue_push(mmdbr_request_q, ws_strdup_printf("%s\n", addr_str));
755 if (resolve_synchronously) {
756 maxmind_db_await_response();
757 result = (mmdb_lookup_t *) wmem_map_lookup(mmdb_ipv4_map, GUINT_TO_POINTER(*addr));
762 return result;
765 const mmdb_lookup_t *
766 maxmind_db_lookup_ipv6(const ws_in6_addr *addr) {
767 if (!gbl_resolv_flags.maxmind_geoip) {
768 return &mmdb_not_found;
771 mmdb_lookup_t * result = (mmdb_lookup_t *) wmem_map_lookup(mmdb_ipv6_map, addr->bytes);
773 if (!result) {
774 result = &mmdb_not_found;
775 wmem_map_insert(mmdb_ipv6_map, chunkify_v6_addr(addr), result);
777 if (mmdbr_pipe_valid()) {
778 char addr_str[WS_INET6_ADDRSTRLEN];
779 ws_inet_ntop6(addr, addr_str, WS_INET6_ADDRSTRLEN);
780 ws_debug("looking up %s", addr_str);
781 g_async_queue_push(mmdbr_request_q, ws_strdup_printf("%s\n", addr_str));
782 if (resolve_synchronously) {
783 maxmind_db_await_response();
784 result = (mmdb_lookup_t *) wmem_map_lookup(mmdb_ipv6_map, addr->bytes);
789 return result;
792 char *
793 maxmind_db_get_paths(void) {
794 GString* path_str = NULL;
795 unsigned i;
797 path_str = g_string_new("");
799 for (i = 0; maxmind_db_system_paths[i].path != NULL; i++) {
800 g_string_append_printf(path_str,
801 "%s" G_SEARCHPATH_SEPARATOR_S, maxmind_db_system_paths[i].path);
804 for (i = 0; i < num_maxmind_db_paths; i++) {
805 if (maxmind_db_paths[i].path) {
806 g_string_append_printf(path_str,
807 "%s" G_SEARCHPATH_SEPARATOR_S, maxmind_db_paths[i].path);
811 g_string_truncate(path_str, path_str->len-1);
813 return g_string_free(path_str, FALSE);
816 void
817 maxmind_db_set_synchrony(bool synchronous) {
818 resolve_synchronously = synchronous;
821 #else // HAVE_MAXMINDDB
823 void
824 maxmind_db_pref_init(module_t *nameres _U_) {}
826 void
827 maxmind_db_pref_cleanup(void) {}
829 void
830 maxmind_db_pref_apply(void) {}
832 bool
833 maxmind_db_lookup_process(void)
835 return false;
838 const mmdb_lookup_t *
839 maxmind_db_lookup_ipv4(const ws_in4_addr *addr _U_) {
840 return &mmdb_not_found;
843 const mmdb_lookup_t *
844 maxmind_db_lookup_ipv6(const ws_in6_addr *addr _U_) {
845 return &mmdb_not_found;
848 char *
849 maxmind_db_get_paths(void) {
850 return g_strdup("");
853 void
854 maxmind_db_set_synchrony(bool synchronous _U_) {
855 /* Nothing to set. */
858 #endif // HAVE_MAXMINDDB
862 * Editor modelines
864 * Local Variables:
865 * c-basic-offset: 4
866 * tab-width: 8
867 * indent-tabs-mode: nil
868 * End:
870 * ex: set shiftwidth=4 tabstop=8 expandtab:
871 * :indentSize=4:tabSize=8:noTabs=true: