"Fossies" - the Fresh Open Source Software Archive

Member "libmaxminddb-1.5.2/bin/mmdblookup.c" (18 Feb 2021, 23253 Bytes) of package /linux/misc/libmaxminddb-1.5.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "mmdblookup.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.5.0_vs_1.5.2.

    1 #ifdef HAVE_CONFIG_H
    2 #include <config.h>
    3 #endif
    4 #include "maxminddb.h"
    5 #include <errno.h>
    6 #include <getopt.h>
    7 #ifndef _WIN32
    8 #include <pthread.h>
    9 #endif
   10 #include <stdbool.h>
   11 #include <stdio.h>
   12 #include <stdlib.h>
   13 #include <string.h>
   14 #include <time.h>
   15 
   16 #ifdef _WIN32
   17 #ifndef UNICODE
   18 #define UNICODE
   19 #endif
   20 #include <malloc.h>
   21 #else
   22 #include <libgen.h>
   23 #include <unistd.h>
   24 #endif
   25 
   26 static void usage(char *program, int exit_code, const char *error);
   27 static const char **get_options(int argc,
   28                                 char **argv,
   29                                 char **mmdb_file,
   30                                 char **ip_address,
   31                                 int *verbose,
   32                                 int *iterations,
   33                                 int *lookup_path_length,
   34                                 int *const thread_count,
   35                                 char **const ip_file);
   36 static MMDB_s open_or_die(const char *fname);
   37 static void dump_meta(MMDB_s *mmdb);
   38 static bool lookup_from_file(MMDB_s *const mmdb,
   39                              char const *const ip_file,
   40                              bool const dump);
   41 static int lookup_and_print(MMDB_s *mmdb,
   42                             const char *ip_address,
   43                             const char **lookup_path,
   44                             int lookup_path_length,
   45                             bool verbose);
   46 static int benchmark(MMDB_s *mmdb, int iterations);
   47 static MMDB_lookup_result_s lookup_or_die(MMDB_s *mmdb, const char *ipstr);
   48 static void random_ipv4(char *ip);
   49 
   50 #ifndef _WIN32
   51 // These aren't with the automatically generated prototypes as we'd lose the
   52 // enclosing macros.
   53 static bool start_threaded_benchmark(MMDB_s *const mmdb,
   54                                      int const thread_count,
   55                                      int const iterations);
   56 static long double get_time(void);
   57 static void *thread(void *arg);
   58 #endif
   59 
   60 #ifdef _WIN32
   61 int wmain(int argc, wchar_t **wargv) {
   62     // Convert our argument list from UTF-16 to UTF-8.
   63     char **argv = (char **)calloc(argc, sizeof(char *));
   64     if (!argv) {
   65         fprintf(stderr, "calloc(): %s\n", strerror(errno));
   66         exit(1);
   67     }
   68     for (int i = 0; i < argc; i++) {
   69         int utf8_width;
   70         char *utf8_string;
   71         utf8_width =
   72             WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, NULL, 0, NULL, NULL);
   73         if (utf8_width < 1) {
   74             fprintf(
   75                 stderr, "WideCharToMultiByte() failed: %d\n", GetLastError());
   76             exit(1);
   77         }
   78         utf8_string = calloc(utf8_width, sizeof(char));
   79         if (!utf8_string) {
   80             fprintf(stderr, "calloc(): %s\n", strerror(errno));
   81             exit(1);
   82         }
   83         if (WideCharToMultiByte(
   84                 CP_UTF8, 0, wargv[i], -1, utf8_string, utf8_width, NULL, NULL) <
   85             1) {
   86             fprintf(
   87                 stderr, "WideCharToMultiByte() failed: %d\n", GetLastError());
   88             exit(1);
   89         }
   90         argv[i] = utf8_string;
   91     }
   92 #else  // _WIN32
   93 int main(int argc, char **argv) {
   94 #endif // _WIN32
   95     char *mmdb_file = NULL;
   96     char *ip_address = NULL;
   97     int verbose = 0;
   98     int iterations = 0;
   99     int lookup_path_length = 0;
  100     int thread_count = 0;
  101     char *ip_file = NULL;
  102 
  103     const char **lookup_path = get_options(argc,
  104                                            argv,
  105                                            &mmdb_file,
  106                                            &ip_address,
  107                                            &verbose,
  108                                            &iterations,
  109                                            &lookup_path_length,
  110                                            &thread_count,
  111                                            &ip_file);
  112 
  113     MMDB_s mmdb = open_or_die(mmdb_file);
  114 
  115     if (verbose) {
  116         dump_meta(&mmdb);
  117     }
  118 
  119     // The benchmarking and lookup from file modes are hidden features mainly
  120     // intended for development right now. This means there are several flags
  121     // that exist but are intentionally not mentioned in the usage or man page.
  122 
  123     // The lookup from file mode may be useful to expose publicly in the usage,
  124     // but we should have it respect the lookup_path functionality if we do so.
  125     if (ip_file) {
  126         free((void *)lookup_path);
  127         if (!lookup_from_file(&mmdb, ip_file, verbose == 1)) {
  128             MMDB_close(&mmdb);
  129             return 1;
  130         }
  131         MMDB_close(&mmdb);
  132         return 0;
  133     }
  134 
  135     if (0 == iterations) {
  136         exit(lookup_and_print(
  137             &mmdb, ip_address, lookup_path, lookup_path_length, verbose));
  138     }
  139 
  140     free((void *)lookup_path);
  141 
  142     srand((int)time(NULL));
  143 
  144 #ifndef _WIN32
  145     if (thread_count > 0) {
  146         if (!start_threaded_benchmark(&mmdb, thread_count, iterations)) {
  147             MMDB_close(&mmdb);
  148             exit(1);
  149         }
  150         MMDB_close(&mmdb);
  151         exit(0);
  152     }
  153 #endif
  154 
  155     exit(benchmark(&mmdb, iterations));
  156 }
  157 
  158 static void usage(char *program, int exit_code, const char *error) {
  159     if (NULL != error) {
  160         fprintf(stderr, "\n  *ERROR: %s\n", error);
  161     }
  162 
  163     char *usage =
  164         "\n"
  165         "  %s --file /path/to/file.mmdb --ip 1.2.3.4 [path to lookup]\n"
  166         "\n"
  167         "  This application accepts the following options:\n"
  168         "\n"
  169         "      --file (-f)     The path to the MMDB file. Required.\n"
  170         "\n"
  171         "      --ip (-i)       The IP address to look up. Required.\n"
  172         "\n"
  173         "      --verbose (-v)  Turns on verbose output. Specifically, this "
  174         "causes this\n"
  175         "                      application to output the database metadata.\n"
  176         "\n"
  177         "      --version       Print the program's version number and exit.\n"
  178         "\n"
  179         "      --help (-h -?)  Show usage information.\n"
  180         "\n"
  181         "  If an IP's data entry resolves to a map or array, you can provide\n"
  182         "  a lookup path to only show part of that data.\n"
  183         "\n"
  184         "  For example, given a JSON structure like this:\n"
  185         "\n"
  186         "    {\n"
  187         "        \"names\": {\n"
  188         "             \"en\": \"Germany\",\n"
  189         "             \"de\": \"Deutschland\"\n"
  190         "        },\n"
  191         "        \"cities\": [ \"Berlin\", \"Frankfurt\" ]\n"
  192         "    }\n"
  193         "\n"
  194         "  You could look up just the English name by calling mmdblookup with "
  195         "a lookup path of:\n"
  196         "\n"
  197         "    mmdblookup --file ... --ip ... names en\n"
  198         "\n"
  199         "  Or you could look up the second city in the list with:\n"
  200         "\n"
  201         "    mmdblookup --file ... --ip ... cities 1\n"
  202         "\n"
  203         "  Array numbering begins with zero (0).\n"
  204         "\n"
  205         "  If you do not provide a path to lookup, all of the information for "
  206         "a given IP\n"
  207         "  will be shown.\n"
  208         "\n";
  209 
  210     fprintf(stdout, usage, program);
  211     exit(exit_code);
  212 }
  213 
  214 static const char **get_options(int argc,
  215                                 char **argv,
  216                                 char **mmdb_file,
  217                                 char **ip_address,
  218                                 int *verbose,
  219                                 int *iterations,
  220                                 int *lookup_path_length,
  221                                 int *const thread_count,
  222                                 char **const ip_file) {
  223     static int help = 0;
  224     static int version = 0;
  225 
  226     while (1) {
  227         static struct option options[] = {
  228             {"file", required_argument, 0, 'f'},
  229             {"ip", required_argument, 0, 'i'},
  230             {"verbose", no_argument, 0, 'v'},
  231             {"version", no_argument, 0, 'n'},
  232             {"benchmark", required_argument, 0, 'b'},
  233 #ifndef _WIN32
  234             {"threads", required_argument, 0, 't'},
  235 #endif
  236             {"ip-file", required_argument, 0, 'I'},
  237             {"help", no_argument, 0, 'h'},
  238             {"?", no_argument, 0, 1},
  239             {0, 0, 0, 0}};
  240 
  241         int opt_index;
  242 #ifdef _WIN32
  243         char const *const optstring = "f:i:b:I:vnh?";
  244 #else
  245         char const *const optstring = "f:i:b:t:I:vnh?";
  246 #endif
  247         int opt_char = getopt_long(argc, argv, optstring, options, &opt_index);
  248 
  249         if (-1 == opt_char) {
  250             break;
  251         }
  252 
  253         if ('f' == opt_char) {
  254             *mmdb_file = optarg;
  255         } else if ('i' == opt_char) {
  256             *ip_address = optarg;
  257         } else if ('v' == opt_char) {
  258             *verbose = 1;
  259         } else if ('n' == opt_char) {
  260             version = 1;
  261         } else if ('b' == opt_char) {
  262             *iterations = strtol(optarg, NULL, 10);
  263         } else if ('h' == opt_char || '?' == opt_char) {
  264             help = 1;
  265         } else if (opt_char == 't') {
  266             *thread_count = strtol(optarg, NULL, 10);
  267         } else if (opt_char == 'I') {
  268             *ip_file = optarg;
  269         }
  270     }
  271 
  272 #ifdef _WIN32
  273     char *program = alloca(strlen(argv[0]));
  274     _splitpath(argv[0], NULL, NULL, program, NULL);
  275     _splitpath(argv[0], NULL, NULL, NULL, program + strlen(program));
  276 #else
  277     char *program = basename(argv[0]);
  278 #endif
  279 
  280     if (help) {
  281         usage(program, 0, NULL);
  282     }
  283 
  284     if (version) {
  285         fprintf(stdout, "\n  %s version %s\n\n", program, PACKAGE_VERSION);
  286         exit(0);
  287     }
  288 
  289     if (NULL == *mmdb_file) {
  290         usage(program, 1, "You must provide a filename with --file");
  291     }
  292 
  293     if (*ip_address == NULL && *iterations == 0 && !*ip_file) {
  294         usage(program, 1, "You must provide an IP address with --ip");
  295     }
  296 
  297     const char **lookup_path =
  298         calloc((argc - optind) + 1, sizeof(const char *));
  299     if (!lookup_path) {
  300         fprintf(stderr, "calloc(): %s\n", strerror(errno));
  301         exit(1);
  302     }
  303     int i;
  304     for (i = 0; i < argc - optind; i++) {
  305         lookup_path[i] = argv[i + optind];
  306         (*lookup_path_length)++;
  307     }
  308     lookup_path[i] = NULL;
  309 
  310     return lookup_path;
  311 }
  312 
  313 static MMDB_s open_or_die(const char *fname) {
  314     MMDB_s mmdb;
  315     int status = MMDB_open(fname, MMDB_MODE_MMAP, &mmdb);
  316 
  317     if (MMDB_SUCCESS != status) {
  318         fprintf(
  319             stderr, "\n  Can't open %s - %s\n", fname, MMDB_strerror(status));
  320 
  321         if (MMDB_IO_ERROR == status) {
  322             fprintf(stderr, "    IO error: %s\n", strerror(errno));
  323         }
  324 
  325         fprintf(stderr, "\n");
  326 
  327         exit(2);
  328     }
  329 
  330     return mmdb;
  331 }
  332 
  333 static void dump_meta(MMDB_s *mmdb) {
  334     const char *meta_dump = "\n"
  335                             "  Database metadata\n"
  336                             "    Node count:    %i\n"
  337                             "    Record size:   %i bits\n"
  338                             "    IP version:    IPv%i\n"
  339                             "    Binary format: %i.%i\n"
  340                             "    Build epoch:   %llu (%s)\n"
  341                             "    Type:          %s\n"
  342                             "    Languages:     ";
  343 
  344     char date[40];
  345     const time_t epoch = (const time_t)mmdb->metadata.build_epoch;
  346     strftime(date, 40, "%F %T UTC", gmtime(&epoch));
  347 
  348     fprintf(stdout,
  349             meta_dump,
  350             mmdb->metadata.node_count,
  351             mmdb->metadata.record_size,
  352             mmdb->metadata.ip_version,
  353             mmdb->metadata.binary_format_major_version,
  354             mmdb->metadata.binary_format_minor_version,
  355             mmdb->metadata.build_epoch,
  356             date,
  357             mmdb->metadata.database_type);
  358 
  359     for (size_t i = 0; i < mmdb->metadata.languages.count; i++) {
  360         fprintf(stdout, "%s", mmdb->metadata.languages.names[i]);
  361         if (i < mmdb->metadata.languages.count - 1) {
  362             fprintf(stdout, " ");
  363         }
  364     }
  365     fprintf(stdout, "\n");
  366 
  367     fprintf(stdout, "    Description:\n");
  368     for (size_t i = 0; i < mmdb->metadata.description.count; i++) {
  369         fprintf(stdout,
  370                 "      %s:   %s\n",
  371                 mmdb->metadata.description.descriptions[i]->language,
  372                 mmdb->metadata.description.descriptions[i]->description);
  373     }
  374     fprintf(stdout, "\n");
  375 }
  376 
  377 // The input file should have one IP per line.
  378 //
  379 // We look up each IP.
  380 //
  381 // If dump is true, we dump the data for each IP to stderr. This is useful for
  382 // comparison in that you can dump out the data for the IPs before and after
  383 // making changes. It goes to stderr rather than stdout so that the report does
  384 // not get included in what you will compare (since it will almost always be
  385 // different).
  386 //
  387 // In addition to being useful for comparisons, this function provides a way to
  388 // have a more deterministic set of lookups for benchmarking.
  389 static bool lookup_from_file(MMDB_s *const mmdb,
  390                              char const *const ip_file,
  391                              bool const dump) {
  392     FILE *const fh = fopen(ip_file, "r");
  393     if (!fh) {
  394         fprintf(stderr, "fopen(): %s: %s\n", ip_file, strerror(errno));
  395         return false;
  396     }
  397 
  398     clock_t const clock_start = clock();
  399     char buf[1024] = {0};
  400     // I'd normally use uint64_t, but support for it is optional in C99.
  401     unsigned long long i = 0;
  402     while (1) {
  403         if (fgets(buf, sizeof(buf), fh) == NULL) {
  404             if (!feof(fh)) {
  405                 fprintf(stderr, "fgets(): %s\n", strerror(errno));
  406                 fclose(fh);
  407                 return false;
  408             }
  409             if (fclose(fh) != 0) {
  410                 fprintf(stderr, "fclose(): %s\n", strerror(errno));
  411                 return false;
  412             }
  413             break;
  414         }
  415 
  416         char *ptr = buf;
  417         while (*ptr != '\0') {
  418             if (*ptr == '\n') {
  419                 *ptr = '\0';
  420                 break;
  421             }
  422             ptr++;
  423         }
  424         if (strlen(buf) == 0) {
  425             continue;
  426         }
  427 
  428         i++;
  429 
  430         MMDB_lookup_result_s result = lookup_or_die(mmdb, buf);
  431         if (!result.found_entry) {
  432             continue;
  433         }
  434 
  435         MMDB_entry_data_list_s *entry_data_list = NULL;
  436         int const status =
  437             MMDB_get_entry_data_list(&result.entry, &entry_data_list);
  438         if (status != MMDB_SUCCESS) {
  439             fprintf(stderr,
  440                     "MMDB_get_entry_data_list(): %s\n",
  441                     MMDB_strerror(status));
  442             fclose(fh);
  443             MMDB_free_entry_data_list(entry_data_list);
  444             return false;
  445         }
  446 
  447         if (!entry_data_list) {
  448             fprintf(stderr, "entry_data_list is NULL\n");
  449             fclose(fh);
  450             return false;
  451         }
  452 
  453         if (dump) {
  454             fprintf(stdout, "%s:\n", buf);
  455             int const status =
  456                 MMDB_dump_entry_data_list(stderr, entry_data_list, 0);
  457             if (status != MMDB_SUCCESS) {
  458                 fprintf(stderr,
  459                         "MMDB_dump_entry_data_list(): %s\n",
  460                         MMDB_strerror(status));
  461                 fclose(fh);
  462                 MMDB_free_entry_data_list(entry_data_list);
  463                 return false;
  464             }
  465         }
  466 
  467         MMDB_free_entry_data_list(entry_data_list);
  468     }
  469 
  470     clock_t const clock_diff = clock() - clock_start;
  471     double const seconds = (double)clock_diff / CLOCKS_PER_SEC;
  472 
  473     fprintf(
  474         stdout,
  475         "Looked up %llu addresses in %.2f seconds. %.2f lookups per second.\n",
  476         i,
  477         seconds,
  478         i / seconds);
  479 
  480     return true;
  481 }
  482 
  483 static int lookup_and_print(MMDB_s *mmdb,
  484                             const char *ip_address,
  485                             const char **lookup_path,
  486                             int lookup_path_length,
  487                             bool verbose) {
  488 
  489     MMDB_lookup_result_s result = lookup_or_die(mmdb, ip_address);
  490     MMDB_entry_data_list_s *entry_data_list = NULL;
  491 
  492     int exit_code = 0;
  493 
  494     if (verbose) {
  495         fprintf(stdout, "\n  Record prefix length: %d\n", result.netmask);
  496     }
  497 
  498     if (result.found_entry) {
  499         int status;
  500         if (lookup_path_length) {
  501             MMDB_entry_data_s entry_data;
  502             status = MMDB_aget_value(&result.entry, &entry_data, lookup_path);
  503             if (MMDB_SUCCESS == status) {
  504                 if (entry_data.offset) {
  505                     MMDB_entry_s entry = {.mmdb = mmdb,
  506                                           .offset = entry_data.offset};
  507                     status = MMDB_get_entry_data_list(&entry, &entry_data_list);
  508                 } else {
  509                     fprintf(stdout,
  510                             "\n  No data was found at the lookup path you "
  511                             "provided\n\n");
  512                 }
  513             }
  514         } else {
  515             status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
  516         }
  517 
  518         if (MMDB_SUCCESS != status) {
  519             fprintf(stderr,
  520                     "Got an error looking up the entry data - %s\n",
  521                     MMDB_strerror(status));
  522             exit_code = 5;
  523             goto end;
  524         }
  525 
  526         if (NULL != entry_data_list) {
  527             fprintf(stdout, "\n");
  528             MMDB_dump_entry_data_list(stdout, entry_data_list, 2);
  529             fprintf(stdout, "\n");
  530         }
  531     } else {
  532         fprintf(stderr,
  533                 "\n  Could not find an entry for this IP address (%s)\n\n",
  534                 ip_address);
  535         exit_code = 6;
  536     }
  537 
  538 end:
  539     MMDB_free_entry_data_list(entry_data_list);
  540     MMDB_close(mmdb);
  541     free((void *)lookup_path);
  542 
  543     return exit_code;
  544 }
  545 
  546 static int benchmark(MMDB_s *mmdb, int iterations) {
  547     char ip_address[16];
  548     int exit_code = 0;
  549 
  550     clock_t time = clock();
  551 
  552     for (int i = 0; i < iterations; i++) {
  553         random_ipv4(ip_address);
  554 
  555         MMDB_lookup_result_s result = lookup_or_die(mmdb, ip_address);
  556         MMDB_entry_data_list_s *entry_data_list = NULL;
  557 
  558         if (result.found_entry) {
  559 
  560             int status =
  561                 MMDB_get_entry_data_list(&result.entry, &entry_data_list);
  562 
  563             if (MMDB_SUCCESS != status) {
  564                 fprintf(stderr,
  565                         "Got an error looking up the entry data - %s\n",
  566                         MMDB_strerror(status));
  567                 exit_code = 5;
  568                 MMDB_free_entry_data_list(entry_data_list);
  569                 goto end;
  570             }
  571         }
  572 
  573         MMDB_free_entry_data_list(entry_data_list);
  574     }
  575 
  576     time = clock() - time;
  577     double seconds = ((double)time / CLOCKS_PER_SEC);
  578     fprintf(stdout,
  579             "\n  Looked up %i addresses in %.2f seconds. %.2f lookups per "
  580             "second.\n\n",
  581             iterations,
  582             seconds,
  583             iterations / seconds);
  584 
  585 end:
  586     MMDB_close(mmdb);
  587 
  588     return exit_code;
  589 }
  590 
  591 static MMDB_lookup_result_s lookup_or_die(MMDB_s *mmdb, const char *ipstr) {
  592     int gai_error, mmdb_error;
  593     MMDB_lookup_result_s result =
  594         MMDB_lookup_string(mmdb, ipstr, &gai_error, &mmdb_error);
  595 
  596     if (0 != gai_error) {
  597         fprintf(stderr,
  598                 "\n  Error from call to getaddrinfo for %s - %s\n\n",
  599                 ipstr,
  600 #ifdef _WIN32
  601                 gai_strerrorA(gai_error)
  602 #else
  603                 gai_strerror(gai_error)
  604 #endif
  605         );
  606         exit(3);
  607     }
  608 
  609     if (MMDB_SUCCESS != mmdb_error) {
  610         fprintf(stderr,
  611                 "\n  Got an error from the maxminddb library: %s\n\n",
  612                 MMDB_strerror(mmdb_error));
  613         exit(4);
  614     }
  615 
  616     return result;
  617 }
  618 
  619 static void random_ipv4(char *ip) {
  620     // rand() is perfectly fine for this use case
  621     // coverity[dont_call]
  622     int ip_int = rand();
  623     uint8_t *bytes = (uint8_t *)&ip_int;
  624 
  625     snprintf(ip,
  626              16,
  627              "%u.%u.%u.%u",
  628              *bytes,
  629              *(bytes + 1),
  630              *(bytes + 2),
  631              *(bytes + 3));
  632 }
  633 
  634 #ifndef _WIN32
  635 struct thread_info {
  636     pthread_t id;
  637     int num;
  638     MMDB_s *mmdb;
  639     int iterations;
  640 };
  641 
  642 static bool start_threaded_benchmark(MMDB_s *const mmdb,
  643                                      int const thread_count,
  644                                      int const iterations) {
  645     struct thread_info *const tinfo =
  646         calloc(thread_count, sizeof(struct thread_info));
  647     if (!tinfo) {
  648         fprintf(stderr, "calloc(): %s\n", strerror(errno));
  649         return false;
  650     }
  651 
  652     // Using clock() isn't appropriate for multiple threads. It's CPU time, not
  653     // wall time.
  654     long double const start_time = get_time();
  655     if (start_time == -1) {
  656         free(tinfo);
  657         return false;
  658     }
  659 
  660     for (int i = 0; i < thread_count; i++) {
  661         tinfo[i].num = i;
  662         tinfo[i].mmdb = mmdb;
  663         tinfo[i].iterations = iterations;
  664 
  665         if (pthread_create(&tinfo[i].id, NULL, &thread, &tinfo[i]) != 0) {
  666             fprintf(stderr, "pthread_create() failed\n");
  667             free(tinfo);
  668             return false;
  669         }
  670     }
  671 
  672     for (int i = 0; i < thread_count; i++) {
  673         if (pthread_join(tinfo[i].id, NULL) != 0) {
  674             fprintf(stderr, "pthread_join() failed\n");
  675             free(tinfo);
  676             return false;
  677         }
  678     }
  679 
  680     free(tinfo);
  681 
  682     long double const end_time = get_time();
  683     if (end_time == -1) {
  684         return false;
  685     }
  686 
  687     long double const elapsed = end_time - start_time;
  688     unsigned long long const total_ips = iterations * thread_count;
  689     long double rate = total_ips;
  690     if (elapsed != 0) {
  691         rate = total_ips / elapsed;
  692     }
  693 
  694     fprintf(stdout,
  695             "Looked up %llu addresses using %d threads in %.2Lf seconds. %.2Lf "
  696             "lookups per second.\n",
  697             total_ips,
  698             thread_count,
  699             elapsed,
  700             rate);
  701 
  702     return true;
  703 }
  704 
  705 static long double get_time(void) {
  706     // clock_gettime() is not present on OSX until 10.12.
  707 #ifdef HAVE_CLOCK_GETTIME
  708     struct timespec tp = {
  709         .tv_sec = 0,
  710         .tv_nsec = 0,
  711     };
  712     clockid_t clk_id = CLOCK_REALTIME;
  713 #ifdef _POSIX_MONOTONIC_CLOCK
  714     clk_id = CLOCK_MONOTONIC;
  715 #endif
  716     if (clock_gettime(clk_id, &tp) != 0) {
  717         fprintf(stderr, "clock_gettime(): %s\n", strerror(errno));
  718         return -1;
  719     }
  720     return tp.tv_sec + ((float)tp.tv_nsec / 1e9);
  721 #else
  722     time_t t = time(NULL);
  723     if (t == (time_t)-1) {
  724         fprintf(stderr, "time(): %s\n", strerror(errno));
  725         return -1;
  726     }
  727     return (long double)t;
  728 #endif
  729 }
  730 
  731 static void *thread(void *arg) {
  732     const struct thread_info *const tinfo = arg;
  733     if (!tinfo) {
  734         fprintf(stderr, "thread(): %s\n", strerror(EINVAL));
  735         return NULL;
  736     }
  737 
  738     char ip_address[16] = {0};
  739 
  740     for (int i = 0; i < tinfo->iterations; i++) {
  741         memset(ip_address, 0, 16);
  742         random_ipv4(ip_address);
  743 
  744         MMDB_lookup_result_s result = lookup_or_die(tinfo->mmdb, ip_address);
  745         if (!result.found_entry) {
  746             continue;
  747         }
  748 
  749         MMDB_entry_data_list_s *entry_data_list = NULL;
  750         int const status =
  751             MMDB_get_entry_data_list(&result.entry, &entry_data_list);
  752         if (status != MMDB_SUCCESS) {
  753             fprintf(stderr,
  754                     "MMDB_get_entry_data_list(): %s\n",
  755                     MMDB_strerror(status));
  756             MMDB_free_entry_data_list(entry_data_list);
  757             return NULL;
  758         }
  759 
  760         if (!entry_data_list) {
  761             fprintf(stderr, "entry_data_list is NULL\n");
  762             return NULL;
  763         }
  764 
  765         MMDB_free_entry_data_list(entry_data_list);
  766     }
  767 
  768     return NULL;
  769 }
  770 #endif