From: Bill Meeks Date: Fri, 21 Jun 2019 13:26:01 +0000 (-0400) Subject: detect/geoip: migrate to GeoIP2 database format X-Git-Tag: suricata-5.0.0-rc1~222 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a291209e473a261546363f0d9051f1e3b82e699a;p=thirdparty%2Fsuricata.git detect/geoip: migrate to GeoIP2 database format Issue #2765 --- diff --git a/configure.ac b/configure.ac index aa6864d5d2..0975761660 100644 --- a/configure.ac +++ b/configure.ac @@ -2180,43 +2180,43 @@ 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} diff --git a/doc/userguide/rules/header-keywords.rst b/doc/userguide/rules/header-keywords.rst index 6aab877a8f..bae2865876 100644 --- a/doc/userguide/rules/header-keywords.rst +++ b/doc/userguide/rules/header-keywords.rst @@ -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) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/detect-geoip.c b/src/detect-geoip.c index a231e52e6f..0558b2a5f1 100644 --- a/src/detect-geoip.c +++ b/src/detect-geoip.c @@ -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,8 +19,10 @@ * \file * * \author Ignacio Sanchez + * \author Bill Meeks * * 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 +#include 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 */ } diff --git a/src/detect-geoip.h b/src/detect-geoip.h index 1bfc7aff0d..24ba075734 100644 --- a/src/detect-geoip.h +++ b/src/detect-geoip.h @@ -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 + * \author Bill Meeks */ #ifndef __DETECT_GEOIP_H__ @@ -26,7 +27,7 @@ #ifdef HAVE_GEOIP -#include +#include #include "util-spm-bm.h" #define GEOOPTION_MAXSIZE 3 /* Country Code (2 chars) + NULL */ @@ -34,9 +35,10 @@ 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 diff --git a/suricata.yaml.in b/suricata.yaml.in index 3d564ee8dd..9c338762ae 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -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