aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/suricata/src/detect-geoip.c
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/suricata/src/detect-geoip.c')
-rw-r--r--framework/src/suricata/src/detect-geoip.c618
1 files changed, 618 insertions, 0 deletions
diff --git a/framework/src/suricata/src/detect-geoip.c b/framework/src/suricata/src/detect-geoip.c
new file mode 100644
index 00000000..53c950f8
--- /dev/null
+++ b/framework/src/suricata/src/detect-geoip.c
@@ -0,0 +1,618 @@
+/* Copyright (C) 2012 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
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Ignacio Sanchez <sanchezmartin.ji@gmail.com>
+ *
+ * Implements the geoip keyword.
+ */
+
+#include "suricata-common.h"
+#include "debug.h"
+#include "decode.h"
+#include "detect.h"
+
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+
+#include "detect-geoip.h"
+
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+
+#ifndef HAVE_GEOIP
+
+static int DetectGeoipSetupNoSupport (DetectEngineCtx *a, Signature *b, char *c)
+{
+ SCLogError(SC_ERR_NO_GEOIP_SUPPORT, "no GeoIP support built in, needed for geoip keyword");
+ return -1;
+}
+
+/**
+ * \brief Registration function for geoip keyword (no libgeoip support)
+ * \todo add support for src_only and dst_only
+ */
+void DetectGeoipRegister(void)
+{
+ sigmatch_table[DETECT_GEOIP].name = "geoip";
+ sigmatch_table[DETECT_GEOIP].Setup = DetectGeoipSetupNoSupport;
+ sigmatch_table[DETECT_GEOIP].Free = NULL;
+ sigmatch_table[DETECT_GEOIP].RegisterTests = NULL;
+}
+
+#else /* HAVE_GEOIP */
+
+#include <GeoIP.h>
+
+
+static int DetectGeoipMatch(ThreadVars *, DetectEngineThreadCtx *, Packet *,
+ Signature *, const SigMatchCtx *);
+static int DetectGeoipSetup(DetectEngineCtx *, Signature *, char *);
+static void DetectGeoipRegisterTests(void);
+static void DetectGeoipDataFree(void *);
+
+/**
+ * \brief Registration function for geoip keyword
+ * \todo add support for src_only and dst_only
+ */
+void DetectGeoipRegister(void)
+{
+ sigmatch_table[DETECT_GEOIP].name = "geoip";
+ sigmatch_table[DETECT_GEOIP].Match = DetectGeoipMatch;
+ sigmatch_table[DETECT_GEOIP].Setup = DetectGeoipSetup;
+ sigmatch_table[DETECT_GEOIP].Free = DetectGeoipDataFree;
+ sigmatch_table[DETECT_GEOIP].RegisterTests = DetectGeoipRegisterTests;
+}
+
+/**
+ * \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
+ */
+static GeoIP *InitGeolocationEngine(void)
+{
+ return GeoIP_new(GEOIP_MEMORY_CACHE);
+}
+
+/**
+ * \internal
+ * \brief This function is used to geolocate the IP using the MaxMind libraries
+ *
+ * \param ip IP 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)
+{
+ if (geoengine != NULL)
+ return GeoIP_country_code_by_ipnum(geoengine, ntohl(ip));
+ return NULL;
+}
+
+/* Match-on conditions supported */
+#define GEOIP_MATCH_SRC_STR "src"
+#define GEOIP_MATCH_DST_STR "dst"
+#define GEOIP_MATCH_BOTH_STR "both"
+#define GEOIP_MATCH_ANY_STR "any"
+
+#define GEOIP_MATCH_NO_FLAG 0
+#define GEOIP_MATCH_SRC_FLAG 1
+#define GEOIP_MATCH_DST_FLAG 2
+#define GEOIP_MATCH_ANY_FLAG 3 /* default src and dst*/
+#define GEOIP_MATCH_BOTH_FLAG 4
+#define GEOIP_MATCH_NEGATED 8
+
+/**
+ * \internal
+ * \brief This function is used to geolocate the IP using the MaxMind libraries
+ *
+ * \param ip IP 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);
+ /* 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)
+ 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)
+ return 0; /* if one matches, rule does NOT match (negated) */
+ return 1; /* returns 1 if no location matches (negated) */
+ }
+ return 0;
+}
+
+/**
+ * \internal
+ * \brief This function is used to match packets with a IPs in an specified country
+ *
+ * \param t pointer to thread vars
+ * \param det_ctx pointer to the pattern matcher thread
+ * \param p pointer to the current packet
+ * \param m pointer to the sigmatch that we will cast into DetectGeoipData
+ *
+ * \retval 0 no match
+ * \retval 1 match
+ */
+static int DetectGeoipMatch(ThreadVars *t, DetectEngineThreadCtx *det_ctx,
+ Packet *p, Signature *s, const SigMatchCtx *ctx)
+{
+ const DetectGeoipData *geoipdata = (const DetectGeoipData *)ctx;
+ int matches = 0;
+
+ if (PKT_IS_PSEUDOPKT(p))
+ return 0;
+
+ if (PKT_IS_IPV4(p))
+ {
+ if (geoipdata->flags & ( GEOIP_MATCH_SRC_FLAG | GEOIP_MATCH_BOTH_FLAG ))
+ {
+ if (CheckGeoMatchIPv4(geoipdata, GET_IPV4_SRC_ADDR_U32(p)))
+ {
+ if (geoipdata->flags & GEOIP_MATCH_BOTH_FLAG)
+ matches++;
+ else
+ return 1;
+ }
+ }
+ if (geoipdata->flags & ( GEOIP_MATCH_DST_FLAG | GEOIP_MATCH_BOTH_FLAG ))
+ {
+ if (CheckGeoMatchIPv4(geoipdata, GET_IPV4_DST_ADDR_U32(p)))
+ {
+ if (geoipdata->flags & GEOIP_MATCH_BOTH_FLAG)
+ matches++;
+ else
+ return 1;
+ }
+ }
+ /* if matches == 2 is because match-on is "both" */
+ if (matches == 2)
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * \brief This function is used to parse geoipdata
+ *
+ * \param str Pointer to the geoipdata value string
+ *
+ * \retval pointer to DetectGeoipData on success
+ * \retval NULL on failure
+ */
+static DetectGeoipData *DetectGeoipDataParse (char *str)
+{
+ DetectGeoipData *geoipdata = NULL;
+ uint16_t pos = 0;
+ uint16_t prevpos = 0;
+ uint16_t slen = 0;
+ int skiplocationparsing = 0;
+
+ slen = strlen(str);
+ if (slen == 0)
+ goto error;
+
+ /* We have a correct geoip options string */
+ geoipdata = SCMalloc(sizeof(DetectGeoipData));
+ if (unlikely(geoipdata == NULL))
+ goto error;
+
+ memset(geoipdata, 0x00, sizeof(DetectGeoipData));
+
+ /* Parse the geoip option string */
+ while (pos <= slen)
+ {
+ /* search for ',' or end of string */
+ if (str[pos] == ',' || pos == slen)
+ {
+ if (geoipdata->flags == GEOIP_MATCH_NO_FLAG)
+ {
+ /* Parse match-on condition */
+ if (pos == slen) /* if end of option str then there are no match-on cond. */
+ {
+ /* There was NO match-on condition! we default to ANY*/
+ skiplocationparsing = 0;
+ geoipdata->flags |= GEOIP_MATCH_ANY_FLAG;
+ } else {
+ skiplocationparsing = 1;
+ if (strncmp(&str[prevpos], GEOIP_MATCH_SRC_STR, pos-prevpos) == 0)
+ geoipdata->flags |= GEOIP_MATCH_SRC_FLAG;
+ else if (strncmp(&str[prevpos], GEOIP_MATCH_DST_STR, pos-prevpos) == 0)
+ geoipdata->flags |= GEOIP_MATCH_DST_FLAG;
+ else if (strncmp(&str[prevpos], GEOIP_MATCH_BOTH_STR, pos-prevpos) == 0)
+ geoipdata->flags |= GEOIP_MATCH_BOTH_FLAG;
+ else if (strncmp(&str[prevpos], GEOIP_MATCH_ANY_STR, pos-prevpos) == 0)
+ geoipdata->flags |= GEOIP_MATCH_ANY_FLAG;
+ else {
+ /* There was NO match-on condition! we default to ANY*/
+ skiplocationparsing = 0;
+ geoipdata->flags |= GEOIP_MATCH_ANY_FLAG;
+ }
+ }
+ }
+ if (geoipdata->flags != GEOIP_MATCH_NO_FLAG && skiplocationparsing == 0)
+ {
+ /* Parse location string: for now just the country code(s) */
+ if (str[prevpos] == '!')
+ {
+ geoipdata->flags |= GEOIP_MATCH_NEGATED;
+ prevpos++; /* dot not copy the ! */
+ }
+
+ if (geoipdata->nlocations >= GEOOPTION_MAXLOCATIONS) {
+ SCLogError(SC_ERR_INVALID_ARGUMENT, "too many arguements for geoip keyword");
+ goto error;
+ }
+
+ if (pos-prevpos > GEOOPTION_MAXSIZE)
+ strlcpy((char *)geoipdata->location[geoipdata->nlocations], &str[prevpos],
+ GEOOPTION_MAXSIZE);
+ else
+ strlcpy((char *)geoipdata->location[geoipdata->nlocations], &str[prevpos],
+ pos-prevpos+1);
+
+ if (geoipdata->nlocations < GEOOPTION_MAXLOCATIONS)
+ geoipdata->nlocations++;
+ }
+ prevpos = pos+1;
+ skiplocationparsing = 0; /* match-on condition for sure has been parsed already */
+ }
+ pos++;
+ }
+
+ SCLogDebug("GeoIP: %"PRIu32" countries loaded", geoipdata->nlocations);
+ for (int i=0; i<geoipdata->nlocations; i++)
+ SCLogDebug("GeoIP country code: %s", geoipdata->location[i]);
+
+ SCLogDebug("flags %02X", geoipdata->flags);
+ if (geoipdata->flags & GEOIP_MATCH_NEGATED) {
+ SCLogDebug("negated geoip");
+ }
+
+ /* Initialize the geolocation engine */
+ geoipdata->geoengine = InitGeolocationEngine();
+ if (geoipdata->geoengine == NULL)
+ goto error;
+
+ return geoipdata;
+
+error:
+ if (geoipdata != NULL)
+ DetectGeoipDataFree(geoipdata);
+ return NULL;
+}
+
+/**
+ * \internal
+ * \brief this function is used to add the geoip option into the signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param optstr pointer to the user provided options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectGeoipSetup(DetectEngineCtx *de_ctx, Signature *s, char *optstr)
+{
+ DetectGeoipData *geoipdata = NULL;
+ SigMatch *sm = NULL;
+
+ geoipdata = DetectGeoipDataParse(optstr);
+ if (geoipdata == NULL)
+ goto error;
+
+ /* Get this into a SigMatch and put it in the Signature. */
+ sm = SigMatchAlloc();
+ if (sm == NULL)
+ goto error;
+
+ sm->type = DETECT_GEOIP;
+ sm->ctx = (SigMatchCtx *)geoipdata;
+
+ SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_MATCH);
+ s->flags |= SIG_FLAG_REQUIRE_PACKET;
+
+ return 0;
+
+error:
+ if (geoipdata != NULL)
+ DetectGeoipDataFree(geoipdata);
+ if (sm != NULL)
+ SCFree(sm);
+ return -1;
+
+}
+
+/**
+ * \brief this function will free memory associated with DetectGeoipData
+ *
+ * \param geoipdata pointer to DetectGeoipData
+ */
+static void DetectGeoipDataFree(void *ptr)
+{
+ if (ptr != NULL) {
+ DetectGeoipData *geoipdata = (DetectGeoipData *)ptr;
+ SCFree(geoipdata);
+ }
+}
+
+#ifdef UNITTESTS
+
+static int GeoipParseTest(char *rule, int ncountries, char **countries, uint32_t flags)
+{
+ DetectEngineCtx *de_ctx = NULL;
+ int result = 0;
+ Signature *s = NULL;
+ DetectGeoipData *data = NULL;
+
+ 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) {
+ printf("sig parse failed: ");
+ goto end;
+ }
+
+ s = de_ctx->sig_list;
+ if (s->sm_lists_tail[DETECT_SM_LIST_MATCH] == NULL) {
+ printf("empty server body list: ");
+ goto end;
+ }
+
+ if (s->sm_lists_tail[DETECT_SM_LIST_MATCH]->type != DETECT_GEOIP) {
+ printf("last sm not geoip: ");
+ goto end;
+ }
+
+ data = (DetectGeoipData *)s->sm_lists_tail[DETECT_SM_LIST_MATCH]->ctx;
+ if (data->flags != flags) {
+ printf("flags not right: (flags=%d)",data->flags);
+ goto end;
+ }
+
+ if (data->nlocations!=ncountries)
+ {
+ printf("wrong number of parsed countries: ");
+ goto end;
+ }
+
+ for (int i=0; i<ncountries; i++)
+ {
+ if (strcmp((char *)data->location[i],countries[i])!=0)
+ {
+ printf("wrong parsed country code: ");
+ goto end;
+ }
+ }
+
+ result = 1;
+end:
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ return result;
+}
+
+static int GeoipParseTest01(void)
+{
+ char *ccodes[1] = {"US"};
+ return GeoipParseTest("alert tcp any any -> any any (geoip:US;sid:1;)", 1, ccodes,
+ GEOIP_MATCH_ANY_FLAG);
+}
+
+static int GeoipParseTest02(void)
+{
+ char *ccodes[1] = {"US"};
+ return GeoipParseTest("alert tcp any any -> any any (geoip:!US;sid:1;)", 1, ccodes,
+ GEOIP_MATCH_ANY_FLAG | GEOIP_MATCH_NEGATED);
+}
+
+static int GeoipParseTest03(void)
+{
+ char *ccodes[1] = {"US"};
+ return GeoipParseTest("alert tcp any any -> any any (geoip:!US;sid:1;)", 1, ccodes,
+ GEOIP_MATCH_ANY_FLAG | GEOIP_MATCH_NEGATED);
+}
+
+static int GeoipParseTest04(void)
+{
+ char *ccodes[1] = {"US"};
+ return GeoipParseTest("alert tcp any any -> any any (geoip:src,US;sid:1;)", 1, ccodes,
+ GEOIP_MATCH_SRC_FLAG);
+}
+
+static int GeoipParseTest05(void)
+{
+ char *ccodes[1] = {"US"};
+ return GeoipParseTest("alert tcp any any -> any any (geoip:dst,!US;sid:1;)", 1, ccodes,
+ GEOIP_MATCH_DST_FLAG | GEOIP_MATCH_NEGATED);
+}
+
+static int GeoipParseTest06(void)
+{
+ char *ccodes[3] = {"US", "ES", "UK"};
+ return GeoipParseTest("alert tcp any any -> any any (geoip:US,ES,UK;sid:1;)", 3, ccodes,
+ GEOIP_MATCH_ANY_FLAG);
+}
+
+static int GeoipParseTest07(void)
+{
+ char *ccodes[3] = {"US", "ES", "UK"};
+ return GeoipParseTest("alert tcp any any -> any any (geoip:both,!US,ES,UK;sid:1;)", 3, ccodes,
+ GEOIP_MATCH_BOTH_FLAG | GEOIP_MATCH_NEGATED);
+}
+
+/**
+ * \internal
+ * \brief This test tests geoip success and failure.
+ */
+static int GeoipMatchTest(char *rule, char *srcip, 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 */
+
+/**
+ * \internal
+ * \brief This function registers unit tests for DetectGeoip
+ */
+static void DetectGeoipRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("GeoipParseTest01", GeoipParseTest01, 1);
+ UtRegisterTest("GeoipParseTest02", GeoipParseTest02, 1);
+ UtRegisterTest("GeoipParseTest03", GeoipParseTest03, 1);
+ UtRegisterTest("GeoipParseTest04", GeoipParseTest04, 1);
+ UtRegisterTest("GeoipParseTest05", GeoipParseTest05, 1);
+ UtRegisterTest("GeoipParseTest06", GeoipParseTest06, 1);
+ UtRegisterTest("GeoipParseTest07", GeoipParseTest07, 1);
+
+ UtRegisterTest("GeoipMatchTest01", GeoipMatchTest01, 1);
+ UtRegisterTest("GeoipMatchTest02", GeoipMatchTest02, 1);
+ UtRegisterTest("GeoipMatchTest03", GeoipMatchTest03, 1);
+ UtRegisterTest("GeoipMatchTest04", GeoipMatchTest04, 2);
+ UtRegisterTest("GeoipMatchTest05", GeoipMatchTest05, 1);
+ UtRegisterTest("GeoipMatchTest06", GeoipMatchTest06, 1);
+ UtRegisterTest("GeoipMatchTest07", GeoipMatchTest07, 2);
+#endif /* UNITTESTS */
+}
+
+#endif /* HAVE_GEOIP */