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