Extension GeoIP pour SQLite

SQLite est une base de données relationnelle légère, simple d’utilisation et très performante. On peut s’en servir pour faire de petits développements, mais également pour stocker, par exemple, des statistiques issues de logs d’accès HTTP.

Dans ce cas, il peut être intéressant de pouvoir géolocaliser les IP présentes dans ces logs. Plutôt que de le faire lors de l’insertion des logs en base de données, SQLite peut être étendue pour fournir de nouvelles fonctions.

Voici comment créer une fonction qui permettra de retrouver le pays d’origine d’une IP, grâce à l’API GeoIP de Maxmind.

Il est possible de télécharger la version gratuite de la base GeoLite2 Country en créant un compte sur le site de Maxmind. Une autre alternative est d’utiliser la version lite de la base fournie par DB-IP.

Dans un premier temps on installe les librairies de développement pour l’accès à la base GeoIP et l’extension SQLite, ainsi que le paquet sqlite3:

sudo apt install libmaxminddb-dev libsqlite3-dev sqlite3

Le code source pour l’extension SQLite est le suivant:

==main.cc==

extern "C" {
#include <maxminddb.h>
}

#include <string.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1

MMDB_s mmdb;

static void geoip_open( sqlite3_context *ctx, int argc, sqlite3_value **argv) {
  const unsigned char *dbname = NULL;
  dbname = sqlite3_value_text(argv[0]);
  int ret = MMDB_open((const char *)dbname, MMDB_MODE_MMAP, &mmdb);
  if( ret != MMDB_SUCCESS ){
    return ;
  }
}

static void geoip_close( sqlite3_context *ctx, int argc, sqlite3_value **argv) {
  MMDB_close(&mmdb);
}

static void geoip_country_code( sqlite3_context *ctx, int argc, sqlite3_value **argv) {
  const unsigned char *ip = NULL;
  ip = sqlite3_value_text(argv[0]);

  int gai_error, mmdb_error;
  MMDB_lookup_result_s result = MMDB_lookup_string(&mmdb, (const char *)ip, &gai_error, &mmdb_error);
  if(gai_error != 0) {
    return;
  }
  if(mmdb_error != MMDB_SUCCESS) {
    return;
  }

  if(result.found_entry == true) {
    MMDB_entry_data_s data;
    int ret = MMDB_get_value(&result.entry,&data, "country", "iso_code", NULL);
    if( ret == MMDB_SUCCESS && data.has_data) {
      long l = data.data_size;
      unsigned char *out = (unsigned char *)sqlite3_malloc(l+1);
      memset(out,0,l+1);
      strncpy((char *)out,data.utf8_string,l);
      sqlite3_result_text( ctx,(const char *)out,l+1,sqlite3_free);
    } 
  }
}

static void geoip_country_name( sqlite3_context *ctx, int argc, sqlite3_value **argv) {
  const unsigned char *ip = NULL;
  ip = sqlite3_value_text(argv[0]);

  int gai_error, mmdb_error;
  MMDB_lookup_result_s result = MMDB_lookup_string(&mmdb, (const char *)ip, &gai_error, &mmdb_error);
  if(gai_error != 0) {
    return;
  }
  if(mmdb_error != MMDB_SUCCESS) {
    return;
  }

  if(result.found_entry == true) {
    MMDB_entry_data_s data;
    int ret = MMDB_get_value(&result.entry,&data, "country", "names", "fr", NULL);
    if( ret == MMDB_SUCCESS && data.has_data) {
      long l = data.data_size;
      unsigned char *out = (unsigned char *)sqlite3_malloc(l+1);
      memset(out,0,l+1);
      strncpy((char *)out,data.utf8_string,l);
      sqlite3_result_text( ctx,(const char *)out,l+1,sqlite3_free);
    } 
  }
}

extern "C" {
int sqlite3_geoip_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi);  
}

int sqlite3_geoip_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi){
  int rc = SQLITE_OK;
  SQLITE_EXTENSION_INIT2(pApi);
  sqlite3_create_function(db,"geoip_open",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,geoip_open,0,0);
  sqlite3_create_function(db,"geoip_close",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,geoip_close,0,0);
  sqlite3_create_function(db,"geoip_country_code",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,geoip_country_code,0,0);
  sqlite3_create_function(db,"geoip_country_name",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,geoip_country_name,0,0);
  return rc;
}

Pour compiler ce code sous forme de librairie dynamique (en supposant que le code se trouve dans le fichier main.cc) on passe la commande suivante:

gcc -fPIC -shared -o geoip.so -l maxminddb main.cc

La commande produit le fichier geoip.so qui peut mainteant être utilisée de la manière suivante:

sqlite3 test.db
SQLite version 3.27.2 2019-02-25 16:06:06
Enter ".help" for usage hints.
sqlite> select load_extension("./geoip");
sqlite> select geoip_open("dbip.mmdb");
sqlite> select geoip_country_name("8.8.8.8");
États-Unis
sqlite> select geoip_country_code("8.8.8.8");
US

IP Geolocation by DB-IP