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