Solved geoiplookup for the new GeoLite2 country db

Since the deprecation of, then removal of, the geoiplookup port and the removal by maxmind of the legacy databases, I've been looking for a "drop-in" replacement C program for the new GeoLite2 formatted databases to no avail. So, yesterday, I decided to write my own.

Code:
$ ./geoiplookup2 1.1.1.1
AU, Australia
$ ./geoiplookup2 2001:19f0:5000:4f59:5300:1ff0:fe75:c54d
US, United States

Having spent an hour writing my replacement, I thought others might be interested, so I've posted it below. Note: you need to install the net/libmaxminddb library from ports.

Makefile

Code:
CC = cc
CFLAGS = -O2 -Wall -pedantic -I/usr/local/include
LIBS = -lmaxminddb
LIBPATH= -L/usr/local/lib/
OBJS = geoiplookup2.o
BINARIES = geoiplookup2

all: clean ${BINARIES}

geoiplookup2: geoiplookup2.o
        $(CC) $(CFLAGS) $(OBJS) -o geoiplookup2 ${LIBPATH} $(LIBS)

clean:
        rm -f ${BINARIES}
        rm -f *.o

geoiplookup2.c

Code:
/* Includes */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <maxminddb.h>

/* Prototypes */
static char *get_myvalues (MMDB_lookup_result_s result, ...);

/********************
*  main()          *
********************/

int main(int argc, char **argv)
{
    char filename[100] = "";    /* arbitrary size; default GeoLite2-Country.mmdb pathname is 44 */
    char ip_address[39] = "";   /* max size of an IPv6 address */
    char *country = NULL;
    char *iso_code = NULL;
    int status = 0;
    int gai_error = 0;
    int mmdb_error = 0;
    int exit_code = 0;
    MMDB_s mmdb;

    /* check number of command line args */
    if(argc < 2)
      {
    printf("Usage: %s ip_addr [db_file]\n", argv[0]);
    exit(1);
      }

    strncpy(ip_address, argv[1], 39);

    /* if no db file specified, use the default */
    if(argc == 2)
      strncpy(filename, "/usr/local/share/GeoIP/GeoLite2-Country.mmdb", 100);
    else
      strncpy(filename, argv[2], 100);

    status = MMDB_open(filename, MMDB_MODE_MMAP, &mmdb);

    if (MMDB_SUCCESS != status)
      {
        fprintf(stderr, "\nCan't open %s - %s\n", filename, MMDB_strerror(status));

        if (MMDB_IO_ERROR == status)
      {
            fprintf(stderr, "IO error: %s\n", strerror(errno));
      }
        exit(1);
      }

    MMDB_lookup_result_s result = MMDB_lookup_string(&mmdb, ip_address, &gai_error, &mmdb_error);

    if (0 != gai_error)
      {
        fprintf(stderr,"\nError from getaddrinfo for %s - %s\n\n", ip_address, gai_strerror(gai_error));
        exit(2);
      }

    if (MMDB_SUCCESS != mmdb_error)
      {
        fprintf(stderr, "\nError from libmaxminddb: %s\n\n", MMDB_strerror(mmdb_error));
        exit(3);
      }

    MMDB_entry_data_s entry_data;

    if (result.found_entry)
      {
        status = MMDB_get_value(&result.entry, &entry_data, NULL);

        if (MMDB_SUCCESS != status)
         {
            fprintf(stderr, "Error looking up the entry data - %s\n", MMDB_strerror(status));
            exit_code = 4;
            goto end;
         }

       if(entry_data.has_data)
        {
          country = get_myvalues(result, "country", "names", "en",  NULL);
          iso_code= get_myvalues(result, "country", "iso_code", NULL);
          printf("%s, %s\n", iso_code, country);
         }
      }
    else
      {
        fprintf(stderr, "\nNo entry for this IP address (%s) was found\n\n", ip_address);
        exit_code = 5;
      }

    end:
        MMDB_close(&mmdb);
        exit(exit_code);
}

/********************
*  get_myvalues()  *
********************/

static char *get_myvalues (MMDB_lookup_result_s result, ...)
{
MMDB_entry_data_s entry_data;
char *value = NULL;
int status = 0;
va_list keys;

va_start (keys, result);

status = MMDB_vget_value (&result.entry, &entry_data, keys);

if (status != MMDB_SUCCESS)
   printf("Error from libmaxminddb: %s\n", MMDB_strerror (status));

va_end (keys);

if (entry_data.type != MMDB_DATA_TYPE_UTF8_STRING)
  printf("Invalid data UTF8 GeoIP2 data %d:\n", entry_data.type);

if ((value = strndup (entry_data.utf8_string, entry_data.data_size)) == NULL)
  printf("Unable to allocate buffer %s: ", strerror (errno));

return value;
}
 
After some research, I thought it best to clarify the situation regarding the GeoIP Legacy databases.

It should be noted that if your are paying customer of Maxmind for the GeoIP Legacy databases, they are in fact still updated and available to you. However, they are not available to "new" paying customers and the less accurate, free versions are not available at all.

All of which leads me to wonder about the accuracy of the /usr/ports/UPDATING note:

"20190113:
AFFECTS: users of net/GeoIP
AUTHOR: [email redacted]

Maxmind no longer provides geolocation data in the legacy format used by net/GeoIP. All GEOIP-related OPTIONS have been removed, and all GeoIP-dependent ports will be removed soon.

Where possible, you must switch to net/libmaxminddb, which uses the newer (and fully supported) GeoIP 2 format. Unfortunately, this is not a drop-in replacement. To fetch the GeoIP 2 geolocation databases, whether the free or paid versions, you must use net/geoipupdate (pkg install geoipupdate).

The legacy database is no longer available, and we cannot distribute it by the Maxmind license, so the legacy GeoIP format is essentially dead."
 
To retrieve city codes only needs some trivial changes to the code. Here's a diff showing the changes.

Code:
17c17
<     char filename[100] = "";    /* arbitrary size; default GeoLite2-Country.mmdb pathname is 44 */
---
>     char filename[100] = "";    /* arbitrary size; default GeoLite2-City.mmdb pathname is 42 */
19,20c19
<     char *country = NULL;
<     char *iso_code = NULL;
---
>     char *city = NULL;
38c37
<       strncpy(filename, "/usr/local/share/GeoIP/GeoLite2-Country.mmdb", 100);
---
>       strncpy(filename, "/usr/local/share/GeoIP/GeoLite2-City.mmdb", 100);
84,86c83,84
<           country = get_myvalues(result, "country", "names", "en",  NULL);
<           iso_code= get_myvalues(result, "country", "iso_code", NULL);
<           printf("%s, %s\n", iso_code, country);
---
>           city = get_myvalues(result, "city", "names", "en",  NULL);
>           printf("%s\n", city);

How to implement this approach into Apache24 instead of pecl-geoip?

I have no familiarity with pecl-geoip and how or what it does, so have no idea.

My command line program, as modified, simply returns the city. For example:

./geoiplookup3 207.148.30.48 GeoLite2-City.mmdb Piscataway
 
Back
Top