]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect/geoip: migrate to GeoIP2 database format
authorBill Meeks <bmeeks8@bellsouth.net>
Fri, 21 Jun 2019 13:26:01 +0000 (09:26 -0400)
committerVictor Julien <victor@inliniac.net>
Mon, 1 Jul 2019 13:44:30 +0000 (15:44 +0200)
Issue #2765

configure.ac
doc/userguide/rules/header-keywords.rst
src/detect-geoip.c
src/detect-geoip.h
suricata.yaml.in

index aa6864d5d283cb6ec863d4432003f540b5d52435..0975761660b240a91a4fd781c02a051ca614b5ea 100644 (file)
 
     AM_CONDITIONAL([HAVE_LUA], [test "x$enable_lua" != "xno"])
 
-  # libgeoip
+  # libmaxminddb
     AC_ARG_ENABLE(geoip,
-               AS_HELP_STRING([--enable-geoip],[Enable GeoIP support]),
-               [ enable_geoip="$enableval"],
+               AS_HELP_STRING([--enable-geoip],[Enable GeoIP2 support]),
+               [ enable_geoip="yes"],
                [ enable_geoip="no"])
-    AC_ARG_WITH(libgeoip_includes,
-            [  --with-libgeoip-includes=DIR  libgeoip include directory],
-            [with_libgeoip_includes="$withval"],[with_libgeoip_includes="no"])
-    AC_ARG_WITH(libgeoip_libraries,
-            [  --with-libgeoip-libraries=DIR    libgeoip library directory],
-            [with_libgeoip_libraries="$withval"],[with_libgeoip_libraries="no"])
+    AC_ARG_WITH(libmaxminddb_includes,
+            [  --with-libmaxminddb-includes=DIR  libmaxminddb include directory],
+            [with_libmaxminddb_includes="$withval"],[with_libmaxminddb_includes="no"])
+    AC_ARG_WITH(libmaxminddb_libraries,
+            [  --with-libmaxminddb-libraries=DIR    libmaxminddb library directory],
+            [with_libmaxminddb_libraries="$withval"],[with_libmaxminddb_libraries="no"])
 
     if test "$enable_geoip" = "yes"; then
-        if test "$with_libgeoip_includes" != "no"; then
-            CPPFLAGS="${CPPFLAGS} -I${with_libgeoip_includes}"
+        if test "$with_libmaxminddb_includes" != "no"; then
+            CPPFLAGS="${CPPFLAGS} -I${with_libmaxminddb_includes}"
         fi
 
-        AC_CHECK_HEADER(GeoIP.h,GEOIP="yes",GEOIP="no")
+        AC_CHECK_HEADER(maxminddb.h,GEOIP="yes",GEOIP="no")
         if test "$GEOIP" = "yes"; then
-            if test "$with_libgeoip_libraries" != "no"; then
-                LDFLAGS="${LDFLAGS} -L${with_libgeoip_libraries}"
+            if test "$with_libmaxminddb_libraries" != "no"; then
+                LDFLAGS="${LDFLAGS} -L${with_libmaxminddb_libraries}"
             fi
-            AC_CHECK_LIB(GeoIP, GeoIP_country_code_by_ipnum,, GEOIP="no")
+            AC_CHECK_LIB(maxminddb, MMDB_open,, GEOIP="no")
         fi
         if test "$GEOIP" = "no"; then
             echo
-            echo "   ERROR!  libgeoip library not found, go get it"
-            echo "   from http://www.maxmind.com/en/geolite or your distribution:"
+            echo "   ERROR!  libmaxminddb GeoIP2 library not found, go get it"
+            echo "   from https://github.com/maxmind/libmaxminddb or your distribution:"
             echo
-            echo "   Ubuntu: apt-get install libgeoip-dev"
-            echo "   Fedora: dnf install GeoIP-devel"
-            echo "   CentOS/RHEL: yum install GeoIP-devel"
+            echo "   Ubuntu: apt-get install libmaxminddb-dev"
+            echo "   Fedora: dnf install libmaxminddb-devel"
+            echo "   CentOS/RHEL: yum install libmaxminddb-devel"
             echo
             exit 1
         fi
 
-        AC_DEFINE([HAVE_GEOIP],[1],[libgeoip available])
+        AC_DEFINE([HAVE_GEOIP],[1],[libmaxminddb available])
         enable_geoip="yes"
     fi
 
@@ -2602,7 +2602,7 @@ SURICATA_BUILD_CONF="Suricata Configuration:
   PCRE jit:                                ${pcre_jit_available}
   LUA support:                             ${enable_lua}
   libluajit:                               ${enable_luajit}
-  libgeoip:                                ${enable_geoip}
+  GeoIP2 support:                          ${enable_geoip}
   Non-bundled htp:                         ${enable_non_bundled_htp}
   Old barnyard2 support:                   ${enable_old_barnyard2}
   Hyperscan support:                       ${enable_hyperscan}
index 6aab877a8ff94633ae05a0e95982f4eb5301cb3a..bae28658763b00d10d123a24af090e2c1fabd6aa 100644 (file)
@@ -136,9 +136,9 @@ Example of id in a rule:
 geoip
 ^^^^^
 The geoip keyword enables (you) to match on the source, destination or
-source and destination IP addresses of network traffic, and to see to
-which country it belongs. To be able to do this, Suricata uses GeoIP
-API of Maxmind.
+source and destination IPv4 addresses of network traffic, and to see to
+which country it belongs. To be able to do this, Suricata uses the GeoIP2
+API of MaxMind.
 
 The syntax of geoip::
 
@@ -156,8 +156,15 @@ direction you would like to match::
   dest: if the destination matches with the given geoip.
   src: the source matches with the given geoip.
 
-The keyword only supports IPv4. As it uses the GeoIP API of Maxmind,
-libgeoip must be compiled in.
+The keyword only supports IPv4. As it uses the GeoIP2 API of MaxMind,
+libmaxminddb must be compiled in. You must download and install the
+GeoIP2 or GeoLite2 database editions desired. Visit the MaxMind site
+at https://dev.maxmind.com/geoip/geoip2/geolite2/ for details.
+
+You must also supply the location of the GeoIP2 or GeoLite2 database
+file on the local system in the YAML-file configuration (for example)::
+
+  geoip-database: /usr/local/share/GeoIP/GeoLite2-Country.mmdb
 
 fragbits (IP fragmentation)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
index a231e52e6febc4860c4649993fed4285ffde61d3..0558b2a5f1956de05293c3b8a2f13298d8eeca27 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2012 Open Information Security Foundation
+/* Copyright (C) 2012-2019 Open Information Security Foundation
  *
  * You can copy, redistribute or modify this Program under the terms of
  * the GNU General Public License version 2 as published by the Free
  * \file
  *
  * \author Ignacio Sanchez <sanchezmartin.ji@gmail.com>
+ * \author Bill Meeks <billmeeks8@gmail.com>
  *
  * Implements the geoip keyword.
+ * Updated to use MaxMind GeoIP2 database.
  */
 
 #include "suricata-common.h"
@@ -34,6 +36,7 @@
 
 #include "detect-geoip.h"
 
+#include "util-mem.h"
 #include "util-unittest.h"
 #include "util-unittest-helper.h"
 
@@ -59,7 +62,7 @@ void DetectGeoipRegister(void)
 
 #else /* HAVE_GEOIP */
 
-#include <GeoIP.h>
+#include <maxminddb.h>
 
 static int DetectGeoipMatch(DetectEngineThreadCtx *, Packet *,
                             const Signature *, const SigMatchCtx *);
@@ -84,27 +87,85 @@ void DetectGeoipRegister(void)
  * \internal
  * \brief This function is used to initialize the geolocation MaxMind engine
  *
- * \retval NULL if the engine couldn't be initialized
- * \retval (GeoIP *) to the geolocation engine
+ * \retval false if the engine couldn't be initialized
  */
-static GeoIP *InitGeolocationEngine(void)
+static bool InitGeolocationEngine(DetectGeoipData *geoipdata)
 {
-    return GeoIP_new(GEOIP_MEMORY_CACHE);
+    const char *filename = NULL;
+
+    /* Get location and name of GeoIP2 database from YAML conf */
+    (void)ConfGet("geoip-database", &filename);
+
+    if (filename == NULL) {
+        SCLogWarning(SC_ERR_INVALID_ARGUMENT, "Unable to locate a GeoIP2"
+                     "database filename in YAML conf.  GeoIP rule matching "
+                     "is disabled.");
+        geoipdata->mmdb_status = MMDB_FILE_OPEN_ERROR;
+        return false;
+    }
+
+    /* Attempt to open MaxMind DB and save file handle if successful */
+    int status = MMDB_open(filename, MMDB_MODE_MMAP, &geoipdata->mmdb);
+
+    if (status == MMDB_SUCCESS) {
+        geoipdata->mmdb_status = status;
+        return true;
+    }
+
+    SCLogWarning(SC_ERR_INVALID_ARGUMENT, "Failed to open GeoIP2 database: %s. "
+                 "Error was: %s.  GeoIP rule matching is disabled.", filename,
+                 MMDB_strerror(status));
+    geoipdata->mmdb_status = status;
+    return false;
 }
 
 /**
  * \internal
  * \brief This function is used to geolocate the IP using the MaxMind libraries
  *
- * \param ip IP to geolocate (uint32_t ip)
+ * \param ip IPv4 to geolocate (uint32_t ip)
  *
  * \retval NULL if it couldn't be geolocated
  * \retval ptr (const char *) to the country code string
  */
-static const char *GeolocateIPv4(GeoIP *geoengine, uint32_t ip)
+static const char *GeolocateIPv4(const DetectGeoipData *geoipdata, uint32_t ip)
 {
-    if (geoengine != NULL)
-        return GeoIP_country_code_by_ipnum(geoengine,  SCNtohl(ip));
+    int mmdb_error;
+    struct sockaddr_in sa;
+    sa.sin_family = AF_INET;
+    sa.sin_port = 0;
+    sa.sin_addr.s_addr = ip;
+    MMDB_lookup_result_s result;
+    MMDB_entry_data_s entry_data;
+
+    /* Return if no GeoIP database access available */
+    if (geoipdata->mmdb_status != MMDB_SUCCESS)
+        return NULL;
+
+    /* Attempt to find the IPv4 address in the database */
+    result = MMDB_lookup_sockaddr((MMDB_s *)&geoipdata->mmdb,
+                                  (struct sockaddr*)&sa, &mmdb_error);
+    if (mmdb_error != MMDB_SUCCESS)
+        return NULL;
+
+    /* The IPv4 address was found, so grab ISO country code if available */
+    if (result.found_entry) {
+        mmdb_error = MMDB_get_value(&result.entry, &entry_data, "country",
+                                    "iso_code", NULL);
+        if (mmdb_error != MMDB_SUCCESS)
+            return NULL;
+
+        /* If ISO country code was found, then return it */
+        if (entry_data.has_data) {
+            if (entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
+                    char *country_code = SCStrndup((char *)entry_data.utf8_string,
+                                                    entry_data.data_size);
+                    return country_code;
+            }
+        }
+    }
+
+    /* The country code for the IP was not found */
     return NULL;
 }
 
@@ -125,29 +186,43 @@ static const char *GeolocateIPv4(GeoIP *geoengine, uint32_t ip)
  * \internal
  * \brief This function is used to geolocate the IP using the MaxMind libraries
  *
- * \param ip IP to geolocate (uint32_t ip)
+ * \param ip IPv4 to geolocate (uint32_t ip)
  *
  * \retval 0 no match
  * \retval 1 match
  */
 static int CheckGeoMatchIPv4(const DetectGeoipData *geoipdata, uint32_t ip)
 {
-    const char *country;
     int i;
-    country = GeolocateIPv4(geoipdata->geoengine, ip);
+
+    /* Attempt country code lookup for the IP address */
+    const char *country = GeolocateIPv4(geoipdata, ip);
+
+    /* Skip further checks if did not find a country code */
+    if (country == NULL)
+        return 0;
+
     /* Check if NOT NEGATED match-on condition */
     if ((geoipdata->flags & GEOIP_MATCH_NEGATED) == 0)
     {
-        for (i = 0; i < geoipdata->nlocations; i++)
-            if (country != NULL && strcmp(country, (char *)geoipdata->location[i])==0)
+        for (i = 0; i < geoipdata->nlocations; i++) {
+            if (strcmp(country, (char *)geoipdata->location[i])==0) {
+                SCFree((void *)country);
                 return 1;
+            }
+        }
     } else {
         /* Check if NEGATED match-on condition */
-        for (i = 0; i < geoipdata->nlocations; i++)
-            if (country != NULL && strcmp(country, (char *)geoipdata->location[i])==0)
+        for (i = 0; i < geoipdata->nlocations; i++) {
+            if (strcmp(country, (char *)geoipdata->location[i])==0) {
+                SCFree((void *)country);
                 return 0; /* if one matches, rule does NOT match (negated) */
+            }
+        }
+        SCFree((void *)country);
         return 1; /* returns 1 if no location matches (negated) */
     }
+    SCFree((void *)country);
     return 0;
 }
 
@@ -299,8 +374,7 @@ static DetectGeoipData *DetectGeoipDataParse (const char *str)
     }
 
     /* Initialize the geolocation engine */
-    geoipdata->geoengine = InitGeolocationEngine();
-    if (geoipdata->geoengine == NULL)
+    if (InitGeolocationEngine(geoipdata) == false)
         goto error;
 
     return geoipdata;
@@ -362,6 +436,8 @@ static void DetectGeoipDataFree(void *ptr)
 {
     if (ptr != NULL) {
         DetectGeoipData *geoipdata = (DetectGeoipData *)ptr;
+        if (geoipdata->mmdb_status == MMDB_SUCCESS)
+            MMDB_close(&geoipdata->mmdb);
         SCFree(geoipdata);
     }
 }
@@ -449,113 +525,6 @@ static int GeoipParseTest07(void)
                                 GEOIP_MATCH_BOTH_FLAG | GEOIP_MATCH_NEGATED);
 }
 
-/**
- * \internal
- * \brief This test tests geoip success and failure.
- */
-static int GeoipMatchTest(const char *rule, const char *srcip, const char *dstip)
-{
-    uint8_t *buf = (uint8_t *) "GET / HTTP/1.0\r\n\r\n";
-    uint16_t buflen = strlen((char *)buf);
-    Packet *p1 = NULL;
-    ThreadVars th_v;
-    DetectEngineThreadCtx *det_ctx;
-    int result = 0;
-
-    memset(&th_v, 0, sizeof(th_v));
-
-    p1 = UTHBuildPacketSrcDst(buf, buflen, IPPROTO_TCP, srcip, dstip);
-
-    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL) {
-        goto end;
-    }
-
-    de_ctx->flags |= DE_QUIET;
-
-    de_ctx->sig_list = SigInit(de_ctx, rule);
-
-    if (de_ctx->sig_list == NULL) {
-        goto end;
-    }
-
-    SigGroupBuild(de_ctx);
-    DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
-
-    result = 2;
-
-    SigMatchSignatures(&th_v, de_ctx, det_ctx, p1);
-    if (PacketAlertCheck(p1, 1) == 0) {
-        goto cleanup;
-    }
-
-    result = 1;
-
-cleanup:
-    SigGroupCleanup(de_ctx);
-    SigCleanSignatures(de_ctx);
-
-    DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
-    DetectEngineCtxFree(de_ctx);
-
-end:
-    return result;
-}
-
-static int GeoipMatchTest01(void)
-{
-    /* Tests with IP of google DNS as US for both src and dst IPs */
-    return GeoipMatchTest("alert tcp any any -> any any (geoip:US;sid:1;)", "8.8.8.8", "8.8.8.8");
-    /* Expected result 1 = match */
-}
-
-static int GeoipMatchTest02(void)
-{
-    /* Tests with IP of google DNS as US, and m.root-servers.net as japan */
-    return GeoipMatchTest("alert tcp any any -> any any (geoip:JP;sid:1;)", "8.8.8.8",
-                    "202.12.27.33");
-    /* Expected result 1 = match */
-}
-
-static int GeoipMatchTest03(void)
-{
-    /* Tests with IP of google DNS as US, and m.root-servers.net as japan */
-    return GeoipMatchTest("alert tcp any any -> any any (geoip:dst,JP;sid:1;)",
-                    "8.8.8.8", "202.12.27.33");
-    /* Expected result 1 = match */
-}
-
-static int GeoipMatchTest04(void)
-{
-    /* Tests with IP of google DNS as US, and m.root-servers.net as japan */
-    return GeoipMatchTest("alert tcp any any -> any any (geoip:src,JP;sid:1;)",
-                    "8.8.8.8", "202.12.27.33");
-    /* Expected result 2 = NO match */
-}
-
-static int GeoipMatchTest05(void)
-{
-    /* Tests with IP of google DNS as US, and m.root-servers.net as japan */
-    return GeoipMatchTest("alert tcp any any -> any any (geoip:src,JP,US;sid:1;)",
-                    "8.8.8.8", "202.12.27.33");
-    /* Expected result 1 = match */
-}
-
-static int GeoipMatchTest06(void)
-{
-    /* Tests with IP of google DNS as US, and m.root-servers.net as japan */
-    return GeoipMatchTest("alert tcp any any -> any any (geoip:src,ES,JP,US,UK,PT;sid:1;)",
-                    "8.8.8.8", "202.12.27.33");
-    /* Expected result 1 = match */
-}
-
-static int GeoipMatchTest07(void)
-{
-    /* Tests with IP of google DNS as US, and m.root-servers.net as japan */
-    return GeoipMatchTest("alert tcp any any -> any any (geoip:src,!ES,JP,US,UK,PT;sid:1;)",
-                    "8.8.8.8", "202.12.27.33");
-    /* Expected result 2 = NO match */
-}
 
 
 #endif /* UNITTESTS */
@@ -575,13 +544,6 @@ static void DetectGeoipRegisterTests(void)
     UtRegisterTest("GeoipParseTest06", GeoipParseTest06);
     UtRegisterTest("GeoipParseTest07", GeoipParseTest07);
 
-    UtRegisterTest("GeoipMatchTest01", GeoipMatchTest01);
-    UtRegisterTest("GeoipMatchTest02", GeoipMatchTest02);
-    UtRegisterTest("GeoipMatchTest03", GeoipMatchTest03);
-    UtRegisterTest("GeoipMatchTest04", GeoipMatchTest04);
-    UtRegisterTest("GeoipMatchTest05", GeoipMatchTest05);
-    UtRegisterTest("GeoipMatchTest06", GeoipMatchTest06);
-    UtRegisterTest("GeoipMatchTest07", GeoipMatchTest07);
 #endif /* UNITTESTS */
 }
 
index 1bfc7aff0d30b9d8b8fa8895e548d2a94eca9d99..24ba075734d0d516c7fa7979dfd8835da8d7995c 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2012 Open Information Security Foundation
+/* Copyright (C) 2012-2019 Open Information Security Foundation
  *
  * You can copy, redistribute or modify this Program under the terms of
  * the GNU General Public License version 2 as published by the Free
@@ -19,6 +19,7 @@
  * \file
  *
  * \author Ignacio Sanchez <sanchezmartin.ji@gmail.com>
+ * \author Bill Meeks <billmeeks8@gmail.com>
  */
 
 #ifndef __DETECT_GEOIP_H__
@@ -26,7 +27,7 @@
 
 #ifdef HAVE_GEOIP
 
-#include <GeoIP.h>
+#include <maxminddb.h>
 #include "util-spm-bm.h"
 
 #define GEOOPTION_MAXSIZE 3 /* Country Code (2 chars) + NULL */
 
 typedef struct DetectGeoipData_ {
     uint8_t location[GEOOPTION_MAXLOCATIONS][GEOOPTION_MAXSIZE];  /** country code for now, null term.*/
-    int nlocations; /** number of location strings parsed */
+    int nlocations;  /** number of location strings parsed */
     uint32_t flags;
-    GeoIP *geoengine;
+    int mmdb_status; /** Status of DB open call, MMDB_SUCCESS or error */
+    MMDB_s mmdb;     /** MaxMind DB file handle structure */
 } DetectGeoipData;
 
 #endif
index 3d564ee8dd5012018e94218d3cecd5d22140ba82..9c338762ae54244582d516fd1be26aa803f6e273 100644 (file)
@@ -1105,6 +1105,10 @@ unix-command:
 #magic-file: /usr/share/file/magic
 @e_magic_file_comment@magic-file: @e_magic_file@
 
+# GeoIP2 database file. Specify path and filename of GeoIP2 database
+# if using rules with "geoip" rule option.
+#geoip-database: /usr/local/share/GeoLite2/GeoLite2-Country.mmdb
+
 legacy:
   uricontent: enabled