suricata
detect-geoip.c
Go to the documentation of this file.
1 /* Copyright (C) 2012-2019 Open Information Security Foundation
2  *
3  * You can copy, redistribute or modify this Program under the terms of
4  * the GNU General Public License version 2 as published by the Free
5  * Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * version 2 along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15  * 02110-1301, USA.
16  */
17 
18 /**
19  * \file
20  *
21  * \author Ignacio Sanchez <sanchezmartin.ji@gmail.com>
22  * \author Bill Meeks <billmeeks8@gmail.com>
23  *
24  * Implements the geoip keyword.
25  * Updated to use MaxMind GeoIP2 database.
26  */
27 
28 #include "suricata-common.h"
29 #include "debug.h"
30 #include "decode.h"
31 #include "detect.h"
32 
33 #include "detect-parse.h"
34 #include "detect-engine.h"
35 #include "detect-engine-mpm.h"
36 
37 #include "detect-geoip.h"
38 
39 #include "util-mem.h"
40 #include "util-unittest.h"
41 #include "util-unittest-helper.h"
42 
43 #ifndef HAVE_GEOIP
44 
45 static int DetectGeoipSetupNoSupport (DetectEngineCtx *a, Signature *b, const char *c)
46 {
47  SCLogError(SC_ERR_NO_GEOIP_SUPPORT, "no GeoIP support built in, needed for geoip keyword");
48  return -1;
49 }
50 
51 /**
52  * \brief Registration function for geoip keyword (no libgeoip support)
53  * \todo add support for src_only and dst_only
54  */
56 {
57  sigmatch_table[DETECT_GEOIP].name = "geoip";
58  sigmatch_table[DETECT_GEOIP].Setup = DetectGeoipSetupNoSupport;
61 }
62 
63 #else /* HAVE_GEOIP */
64 
65 #include <maxminddb.h>
66 
67 static int DetectGeoipMatch(DetectEngineThreadCtx *, Packet *,
68  const Signature *, const SigMatchCtx *);
69 static int DetectGeoipSetup(DetectEngineCtx *, Signature *, const char *);
70 static void DetectGeoipRegisterTests(void);
71 static void DetectGeoipDataFree(void *);
72 
73 /**
74  * \brief Registration function for geoip keyword
75  * \todo add support for src_only and dst_only
76  */
77 void DetectGeoipRegister(void)
78 {
79  sigmatch_table[DETECT_GEOIP].name = "geoip";
80  sigmatch_table[DETECT_GEOIP].Match = DetectGeoipMatch;
81  sigmatch_table[DETECT_GEOIP].Setup = DetectGeoipSetup;
82  sigmatch_table[DETECT_GEOIP].Free = DetectGeoipDataFree;
83  sigmatch_table[DETECT_GEOIP].RegisterTests = DetectGeoipRegisterTests;
84 }
85 
86 /**
87  * \internal
88  * \brief This function is used to initialize the geolocation MaxMind engine
89  *
90  * \retval false if the engine couldn't be initialized
91  */
92 static bool InitGeolocationEngine(DetectGeoipData *geoipdata)
93 {
94  const char *filename = NULL;
95 
96  /* Get location and name of GeoIP2 database from YAML conf */
97  (void)ConfGet("geoip-database", &filename);
98 
99  if (filename == NULL) {
100  SCLogWarning(SC_ERR_INVALID_ARGUMENT, "Unable to locate a GeoIP2"
101  "database filename in YAML conf. GeoIP rule matching "
102  "is disabled.");
103  geoipdata->mmdb_status = MMDB_FILE_OPEN_ERROR;
104  return false;
105  }
106 
107  /* Attempt to open MaxMind DB and save file handle if successful */
108  int status = MMDB_open(filename, MMDB_MODE_MMAP, &geoipdata->mmdb);
109 
110  if (status == MMDB_SUCCESS) {
111  geoipdata->mmdb_status = status;
112  return true;
113  }
114 
115  SCLogWarning(SC_ERR_INVALID_ARGUMENT, "Failed to open GeoIP2 database: %s. "
116  "Error was: %s. GeoIP rule matching is disabled.", filename,
117  MMDB_strerror(status));
118  geoipdata->mmdb_status = status;
119  return false;
120 }
121 
122 /**
123  * \internal
124  * \brief This function is used to geolocate the IP using the MaxMind libraries
125  *
126  * \param ip IPv4 to geolocate (uint32_t ip)
127  *
128  * \retval NULL if it couldn't be geolocated
129  * \retval ptr (const char *) to the country code string
130  */
131 static const char *GeolocateIPv4(const DetectGeoipData *geoipdata, uint32_t ip)
132 {
133  int mmdb_error;
134  struct sockaddr_in sa;
135  sa.sin_family = AF_INET;
136  sa.sin_port = 0;
137  sa.sin_addr.s_addr = ip;
138  MMDB_lookup_result_s result;
139  MMDB_entry_data_s entry_data;
140 
141  /* Return if no GeoIP database access available */
142  if (geoipdata->mmdb_status != MMDB_SUCCESS)
143  return NULL;
144 
145  /* Attempt to find the IPv4 address in the database */
146  result = MMDB_lookup_sockaddr((MMDB_s *)&geoipdata->mmdb,
147  (struct sockaddr*)&sa, &mmdb_error);
148  if (mmdb_error != MMDB_SUCCESS)
149  return NULL;
150 
151  /* The IPv4 address was found, so grab ISO country code if available */
152  if (result.found_entry) {
153  mmdb_error = MMDB_get_value(&result.entry, &entry_data, "country",
154  "iso_code", NULL);
155  if (mmdb_error != MMDB_SUCCESS)
156  return NULL;
157 
158  /* If ISO country code was found, then return it */
159  if (entry_data.has_data) {
160  if (entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
161  char *country_code = SCStrndup((char *)entry_data.utf8_string,
162  entry_data.data_size);
163  return country_code;
164  }
165  }
166  }
167 
168  /* The country code for the IP was not found */
169  return NULL;
170 }
171 
172 /* Match-on conditions supported */
173 #define GEOIP_MATCH_SRC_STR "src"
174 #define GEOIP_MATCH_DST_STR "dst"
175 #define GEOIP_MATCH_BOTH_STR "both"
176 #define GEOIP_MATCH_ANY_STR "any"
177 
178 #define GEOIP_MATCH_NO_FLAG 0
179 #define GEOIP_MATCH_SRC_FLAG 1
180 #define GEOIP_MATCH_DST_FLAG 2
181 #define GEOIP_MATCH_ANY_FLAG 3 /* default src and dst*/
182 #define GEOIP_MATCH_BOTH_FLAG 4
183 #define GEOIP_MATCH_NEGATED 8
184 
185 /**
186  * \internal
187  * \brief This function is used to geolocate the IP using the MaxMind libraries
188  *
189  * \param ip IPv4 to geolocate (uint32_t ip)
190  *
191  * \retval 0 no match
192  * \retval 1 match
193  */
194 static int CheckGeoMatchIPv4(const DetectGeoipData *geoipdata, uint32_t ip)
195 {
196  int i;
197 
198  /* Attempt country code lookup for the IP address */
199  const char *country = GeolocateIPv4(geoipdata, ip);
200 
201  /* Skip further checks if did not find a country code */
202  if (country == NULL)
203  return 0;
204 
205  /* Check if NOT NEGATED match-on condition */
206  if ((geoipdata->flags & GEOIP_MATCH_NEGATED) == 0)
207  {
208  for (i = 0; i < geoipdata->nlocations; i++) {
209  if (strcmp(country, (char *)geoipdata->location[i])==0) {
210  SCFree((void *)country);
211  return 1;
212  }
213  }
214  } else {
215  /* Check if NEGATED match-on condition */
216  for (i = 0; i < geoipdata->nlocations; i++) {
217  if (strcmp(country, (char *)geoipdata->location[i])==0) {
218  SCFree((void *)country);
219  return 0; /* if one matches, rule does NOT match (negated) */
220  }
221  }
222  SCFree((void *)country);
223  return 1; /* returns 1 if no location matches (negated) */
224  }
225  SCFree((void *)country);
226  return 0;
227 }
228 
229 /**
230  * \internal
231  * \brief This function is used to match packets with a IPs in an specified country
232  *
233  * \param t pointer to thread vars
234  * \param det_ctx pointer to the pattern matcher thread
235  * \param p pointer to the current packet
236  * \param m pointer to the sigmatch that we will cast into DetectGeoipData
237  *
238  * \retval 0 no match
239  * \retval 1 match
240  */
241 static int DetectGeoipMatch(DetectEngineThreadCtx *det_ctx,
242  Packet *p, const Signature *s, const SigMatchCtx *ctx)
243 {
244  const DetectGeoipData *geoipdata = (const DetectGeoipData *)ctx;
245  int matches = 0;
246 
247  if (PKT_IS_PSEUDOPKT(p))
248  return 0;
249 
250  if (PKT_IS_IPV4(p))
251  {
252  if (geoipdata->flags & ( GEOIP_MATCH_SRC_FLAG | GEOIP_MATCH_BOTH_FLAG ))
253  {
254  if (CheckGeoMatchIPv4(geoipdata, GET_IPV4_SRC_ADDR_U32(p)))
255  {
256  if (geoipdata->flags & GEOIP_MATCH_BOTH_FLAG)
257  matches++;
258  else
259  return 1;
260  }
261  }
262  if (geoipdata->flags & ( GEOIP_MATCH_DST_FLAG | GEOIP_MATCH_BOTH_FLAG ))
263  {
264  if (CheckGeoMatchIPv4(geoipdata, GET_IPV4_DST_ADDR_U32(p)))
265  {
266  if (geoipdata->flags & GEOIP_MATCH_BOTH_FLAG)
267  matches++;
268  else
269  return 1;
270  }
271  }
272  /* if matches == 2 is because match-on is "both" */
273  if (matches == 2)
274  return 1;
275  }
276 
277  return 0;
278 }
279 
280 /**
281  * \brief This function is used to parse geoipdata
282  *
283  * \param str Pointer to the geoipdata value string
284  *
285  * \retval pointer to DetectGeoipData on success
286  * \retval NULL on failure
287  */
288 static DetectGeoipData *DetectGeoipDataParse (const char *str)
289 {
290  DetectGeoipData *geoipdata = NULL;
291  uint16_t pos = 0;
292  uint16_t prevpos = 0;
293  uint16_t slen = 0;
294  int skiplocationparsing = 0;
295 
296  slen = strlen(str);
297  if (slen == 0)
298  goto error;
299 
300  /* We have a correct geoip options string */
301  geoipdata = SCMalloc(sizeof(DetectGeoipData));
302  if (unlikely(geoipdata == NULL))
303  goto error;
304 
305  memset(geoipdata, 0x00, sizeof(DetectGeoipData));
306 
307  /* Parse the geoip option string */
308  while (pos <= slen)
309  {
310  /* search for ',' or end of string */
311  if (str[pos] == ',' || pos == slen)
312  {
313  if (geoipdata->flags == GEOIP_MATCH_NO_FLAG)
314  {
315  /* Parse match-on condition */
316  if (pos == slen) /* if end of option str then there are no match-on cond. */
317  {
318  /* There was NO match-on condition! we default to ANY*/
319  skiplocationparsing = 0;
320  geoipdata->flags |= GEOIP_MATCH_ANY_FLAG;
321  } else {
322  skiplocationparsing = 1;
323  if (strncmp(&str[prevpos], GEOIP_MATCH_SRC_STR, pos-prevpos) == 0)
324  geoipdata->flags |= GEOIP_MATCH_SRC_FLAG;
325  else if (strncmp(&str[prevpos], GEOIP_MATCH_DST_STR, pos-prevpos) == 0)
326  geoipdata->flags |= GEOIP_MATCH_DST_FLAG;
327  else if (strncmp(&str[prevpos], GEOIP_MATCH_BOTH_STR, pos-prevpos) == 0)
328  geoipdata->flags |= GEOIP_MATCH_BOTH_FLAG;
329  else if (strncmp(&str[prevpos], GEOIP_MATCH_ANY_STR, pos-prevpos) == 0)
330  geoipdata->flags |= GEOIP_MATCH_ANY_FLAG;
331  else {
332  /* There was NO match-on condition! we default to ANY*/
333  skiplocationparsing = 0;
334  geoipdata->flags |= GEOIP_MATCH_ANY_FLAG;
335  }
336  }
337  }
338  if (geoipdata->flags != GEOIP_MATCH_NO_FLAG && skiplocationparsing == 0)
339  {
340  /* Parse location string: for now just the country code(s) */
341  if (str[prevpos] == '!') {
342  geoipdata->flags |= GEOIP_MATCH_NEGATED;
343  prevpos++; /* dot not copy the ! */
344  }
345 
346  if (geoipdata->nlocations >= GEOOPTION_MAXLOCATIONS) {
347  SCLogError(SC_ERR_INVALID_ARGUMENT, "too many arguements for geoip keyword");
348  goto error;
349  }
350 
351  if (pos-prevpos > GEOOPTION_MAXSIZE)
352  strlcpy((char *)geoipdata->location[geoipdata->nlocations], &str[prevpos],
353  GEOOPTION_MAXSIZE);
354  else
355  strlcpy((char *)geoipdata->location[geoipdata->nlocations], &str[prevpos],
356  pos-prevpos+1);
357 
358  if (geoipdata->nlocations < GEOOPTION_MAXLOCATIONS)
359  geoipdata->nlocations++;
360  }
361  prevpos = pos+1;
362  skiplocationparsing = 0; /* match-on condition for sure has been parsed already */
363  }
364  pos++;
365  }
366 
367  SCLogDebug("GeoIP: %"PRIu32" countries loaded", geoipdata->nlocations);
368  for (int i=0; i<geoipdata->nlocations; i++)
369  SCLogDebug("GeoIP country code: %s", geoipdata->location[i]);
370 
371  SCLogDebug("flags %02X", geoipdata->flags);
372  if (geoipdata->flags & GEOIP_MATCH_NEGATED) {
373  SCLogDebug("negated geoip");
374  }
375 
376  /* init geo engine, but not when running as unittests */
377  if (!(RunmodeIsUnittests())) {
378  /* Initialize the geolocation engine */
379  if (InitGeolocationEngine(geoipdata) == false)
380  goto error;
381  }
382 
383  return geoipdata;
384 
385 error:
386  if (geoipdata != NULL)
387  DetectGeoipDataFree(geoipdata);
388  return NULL;
389 }
390 
391 /**
392  * \internal
393  * \brief this function is used to add the geoip option into the signature
394  *
395  * \param de_ctx pointer to the Detection Engine Context
396  * \param s pointer to the Current Signature
397  * \param optstr pointer to the user provided options
398  *
399  * \retval 0 on Success
400  * \retval -1 on Failure
401  */
402 static int DetectGeoipSetup(DetectEngineCtx *de_ctx, Signature *s, const char *optstr)
403 {
404  DetectGeoipData *geoipdata = NULL;
405  SigMatch *sm = NULL;
406 
407  geoipdata = DetectGeoipDataParse(optstr);
408  if (geoipdata == NULL)
409  goto error;
410 
411  /* Get this into a SigMatch and put it in the Signature. */
412  sm = SigMatchAlloc();
413  if (sm == NULL)
414  goto error;
415 
416  sm->type = DETECT_GEOIP;
417  sm->ctx = (SigMatchCtx *)geoipdata;
418 
421 
422  return 0;
423 
424 error:
425  if (geoipdata != NULL)
426  DetectGeoipDataFree(geoipdata);
427  if (sm != NULL)
428  SCFree(sm);
429  return -1;
430 
431 }
432 
433 /**
434  * \brief this function will free memory associated with DetectGeoipData
435  *
436  * \param geoipdata pointer to DetectGeoipData
437  */
438 static void DetectGeoipDataFree(void *ptr)
439 {
440  if (ptr != NULL) {
441  DetectGeoipData *geoipdata = (DetectGeoipData *)ptr;
442  if (geoipdata->mmdb_status == MMDB_SUCCESS)
443  MMDB_close(&geoipdata->mmdb);
444  SCFree(geoipdata);
445  }
446 }
447 
448 #ifdef UNITTESTS
449 
450 static int GeoipParseTest(const char *rule, int ncountries, const char **countries, uint32_t flags)
451 {
452  DetectEngineCtx *de_ctx = NULL;
453  Signature *s = NULL;
454  DetectGeoipData *data = NULL;
455 
456  de_ctx = DetectEngineCtxInit();
457  FAIL_IF(de_ctx == NULL);
458  de_ctx->flags |= DE_QUIET;
459 
460  de_ctx->sig_list = SigInit(de_ctx, rule);
461  FAIL_IF(de_ctx->sig_list == NULL);
462 
463  s = de_ctx->sig_list;
464  FAIL_IF(s->sm_lists_tail[DETECT_SM_LIST_MATCH] == NULL);
465 
466  FAIL_IF(s->sm_lists_tail[DETECT_SM_LIST_MATCH]->type != DETECT_GEOIP);
467 
468  data = (DetectGeoipData *)s->sm_lists_tail[DETECT_SM_LIST_MATCH]->ctx;
469  FAIL_IF(data->flags != flags);
470 
471  FAIL_IF(data->nlocations!=ncountries);
472 
473  for (int i=0; i<ncountries; i++)
474  {
475  FAIL_IF(strcmp((char *)data->location[i],countries[i])!=0);
476  }
477 
478  DetectEngineCtxFree(de_ctx);
479  PASS;
480 }
481 
482 static int GeoipParseTest01(void)
483 {
484  const char *ccodes[1] = {"US"};
485  return GeoipParseTest("alert tcp any any -> any any (geoip:US;sid:1;)", 1, ccodes,
486  GEOIP_MATCH_ANY_FLAG);
487 }
488 
489 static int GeoipParseTest02(void)
490 {
491  const char *ccodes[1] = {"US"};
492  return GeoipParseTest("alert tcp any any -> any any (geoip:!US;sid:1;)", 1, ccodes,
493  GEOIP_MATCH_ANY_FLAG | GEOIP_MATCH_NEGATED);
494 }
495 
496 static int GeoipParseTest03(void)
497 {
498  const char *ccodes[1] = {"US"};
499  return GeoipParseTest("alert tcp any any -> any any (geoip:!US;sid:1;)", 1, ccodes,
500  GEOIP_MATCH_ANY_FLAG | GEOIP_MATCH_NEGATED);
501 }
502 
503 static int GeoipParseTest04(void)
504 {
505  const char *ccodes[1] = {"US"};
506  return GeoipParseTest("alert tcp any any -> any any (geoip:src,US;sid:1;)", 1, ccodes,
507  GEOIP_MATCH_SRC_FLAG);
508 }
509 
510 static int GeoipParseTest05(void)
511 {
512  const char *ccodes[1] = {"US"};
513  return GeoipParseTest("alert tcp any any -> any any (geoip:dst,!US;sid:1;)", 1, ccodes,
514  GEOIP_MATCH_DST_FLAG | GEOIP_MATCH_NEGATED);
515 }
516 
517 static int GeoipParseTest06(void)
518 {
519  const char *ccodes[3] = {"US", "ES", "UK"};
520  return GeoipParseTest("alert tcp any any -> any any (geoip:US,ES,UK;sid:1;)", 3, ccodes,
521  GEOIP_MATCH_ANY_FLAG);
522 }
523 
524 static int GeoipParseTest07(void)
525 {
526  const char *ccodes[3] = {"US", "ES", "UK"};
527  return GeoipParseTest("alert tcp any any -> any any (geoip:both,!US,ES,UK;sid:1;)", 3, ccodes,
528  GEOIP_MATCH_BOTH_FLAG | GEOIP_MATCH_NEGATED);
529 }
530 
531 
532 
533 #endif /* UNITTESTS */
534 
535 /**
536  * \internal
537  * \brief This function registers unit tests for DetectGeoip
538  */
539 static void DetectGeoipRegisterTests(void)
540 {
541 #ifdef UNITTESTS
542  UtRegisterTest("GeoipParseTest01", GeoipParseTest01);
543  UtRegisterTest("GeoipParseTest02", GeoipParseTest02);
544  UtRegisterTest("GeoipParseTest03", GeoipParseTest03);
545  UtRegisterTest("GeoipParseTest04", GeoipParseTest04);
546  UtRegisterTest("GeoipParseTest05", GeoipParseTest05);
547  UtRegisterTest("GeoipParseTest06", GeoipParseTest06);
548  UtRegisterTest("GeoipParseTest07", GeoipParseTest07);
549 
550 #endif /* UNITTESTS */
551 }
552 
553 #endif /* HAVE_GEOIP */
#define GET_IPV4_SRC_ADDR_U32(p)
Definition: decode.h:212
SigTableElmt sigmatch_table[DETECT_TBLSIZE]
Definition: detect.h:1439
uint16_t flags
int(* Setup)(DetectEngineCtx *, Signature *, const char *)
Definition: detect.h:1179
#define SCLogDebug(...)
Definition: util-debug.h:335
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: util-strlcpyu.c:43
uint32_t flags
Definition: detect.h:518
#define PASS
Pass the test.
#define unlikely(expr)
Definition: util-optimize.h:35
Signature * SigInit(DetectEngineCtx *, const char *)
Parses a signature and adds it to the Detection Engine Context.
#define GET_IPV4_DST_ADDR_U32(p)
Definition: decode.h:213
Signature * sig_list
Definition: detect.h:762
#define FAIL_IF(expr)
Fail a test if expression evaluates to false.
Definition: util-unittest.h:71
#define SIG_FLAG_REQUIRE_PACKET
Definition: detect.h:219
const char * name
Definition: detect.h:1193
Signature container.
Definition: detect.h:517
Used to start a pointer to SigMatch context Should never be dereferenced without casting to something...
Definition: detect.h:308
#define PKT_IS_IPV4(p)
Definition: decode.h:251
int ConfGet(const char *name, const char **vptr)
Retrieve the value of a configuration node.
Definition: conf.c:331
main detection engine ctx
Definition: detect.h:756
#define DE_QUIET
Definition: detect.h:287
#define str(s)
uint8_t flags
Definition: detect.h:757
void(* Free)(void *)
Definition: detect.h:1184
#define SCLogError(err_code,...)
Macro used to log ERROR messages.
Definition: util-debug.h:294
void UtRegisterTest(const char *name, int(*TestFn)(void))
Register unit test.
void DetectGeoipRegister(void)
Registration function for geoip keyword (no libgeoip support)
Definition: detect-geoip.c:55
int(* Match)(DetectEngineThreadCtx *, Packet *, const Signature *, const SigMatchCtx *)
Definition: detect.h:1163
int RunmodeIsUnittests(void)
Definition: suricata.c:265
uint8_t type
Definition: detect.h:314
#define SCStrndup(a, b)
Definition: util-mem.h:285
#define SCLogWarning(err_code,...)
Macro used to log WARNING messages.
Definition: util-debug.h:281
void SigMatchAppendSMToList(Signature *s, SigMatch *new, int list)
Append a SigMatch to the list type.
Definition: detect-parse.c:288
SigMatchCtx * ctx
Definition: detect.h:316
#define SCMalloc(a)
Definition: util-mem.h:222
#define SCFree(a)
Definition: util-mem.h:322
#define PKT_IS_PSEUDOPKT(p)
return 1 if the packet is a pseudo packet
Definition: decode.h:1129
SigMatch * SigMatchAlloc(void)
Definition: detect-parse.c:232
void DetectEngineCtxFree(DetectEngineCtx *)
Free a DetectEngineCtx::
void(* RegisterTests)(void)
Definition: detect.h:1185
a single match condition for a signature
Definition: detect.h:313
DetectEngineCtx * DetectEngineCtxInit(void)