Skip to content

Commit

Permalink
Merge pull request #127 from retropc/maxminddb
Browse files Browse the repository at this point in the history
GEOIP: rewrite to use libmaxminddb and add ipv6 support
  • Loading branch information
retropc authored Oct 13, 2023
2 parents 08373a0 + d0fea13 commit 8a5c0cc
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 186 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ccpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt-get install git gcc make flex bison liblua5.1-0-dev libmariadb-dev libpq-dev libpcre3-dev zlib1g-dev libgeoip-dev
run: sudo apt-get install git gcc make flex bison liblua5.1-0-dev libmariadb-dev libpq-dev libpcre3-dev zlib1g-dev libmaxminddb-dev
- name: configure
run: ./configure -R -v
- name: make
Expand Down
8 changes: 4 additions & 4 deletions configure.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# libraries we're gonna search for
[core]
libs=pgsql mariadb lua pcre sqlite z geoip
libs=pgsql mariadb lua pcre sqlite z maxminddb

# dummy libraries, if a module requires one of these then it needs one of the supplied libraries
# the first one will be chosen by default, override with --with-key=value
Expand Down Expand Up @@ -35,7 +35,7 @@ tutorbot=
fsck=
nterfacer=pcre
pqsql=pgsql
geoip=geoip
geoip=maxminddb
clonehistogram=
lua=lua
versionscan=
Expand Down Expand Up @@ -133,8 +133,8 @@ alwayspresent=1
[libz]
pkgconfig=zlib

[libgeoip]
pkgconfig=geoip
[libmaxminddb]
pkgconfig=libmaxminddb

# additional variables for various systems
[osvars]
Expand Down
4 changes: 2 additions & 2 deletions geoip/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
include ../build.mk

CFLAGS+=$(INCGEOIP)
LDFLAGS+=$(LIBGEOIP)
CFLAGS+=$(INCMAXMINDDB)
LDFLAGS+=$(LIBMAXMINDDB)

.PHONY: all
all: geoip.so
Expand Down
241 changes: 163 additions & 78 deletions geoip/geoip.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
Geoip module
Copyright (C) 2004-2006 Chris Porter.
Copyright (C) 2004-2023 Chris Porter.
*/

#include "../nick/nick.h"
Expand All @@ -10,123 +10,208 @@
#include "../control/control.h"
#include "../lib/version.h"

#include <strings.h>
#include <string.h>

#include <GeoIP.h>
#include <maxminddb.h>
#include "geoip.h"

MODULE_VERSION("");

int geoip_totals[COUNTRY_MAX + 1];
static int geoip_nickext = -1;
static GeoIP *gi = NULL;
#define COUNTRY_MAX (26 * 26 + 1)
#define UNKNOWN_COUNTRY (COUNTRY_MAX - 1)
#define COUNTRY_NAME_LEN 40

static void geoip_setupuser(nick *np);
struct country {
int total;
char code[3];
char name[COUNTRY_NAME_LEN + 1];
};

static void geoip_new_nick(int hook, void *args);
static void geoip_quit(int hook, void *args);
static void geoip_whois_handler(int hooknum, void *arg);
static struct country countries[COUNTRY_MAX] = { [UNKNOWN_COUNTRY] = { 0, "??", "Unknown" } };
static struct country *unknown_country = &countries[UNKNOWN_COUNTRY];

void _init(void) {
int i;
nick *np;
sstring *filename;
static int nickext = -1;
static MMDB_s db;

filename = getcopyconfigitem("geoip", "db", "GeoIP.dat", 256);
static struct country *lookup_country(struct sockaddr *addr) {
int mmdb_error;
MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&db, addr, &mmdb_error);
if (mmdb_error != MMDB_SUCCESS || !result.found_entry) {
return NULL;
}

gi = GeoIP_open(filename->content, GEOIP_MEMORY_CACHE);
if(!gi) {
Error("geoip", ERR_WARNING, "Unable to load geoip database [filename: %s]", filename->content);
freesstring(filename);
return;
MMDB_entry_data_s entry_data;
int status = MMDB_get_value(&result.entry, &entry_data, "country", "iso_code", NULL);
if (status != MMDB_SUCCESS || !entry_data.has_data || entry_data.type != MMDB_DATA_TYPE_UTF8_STRING || entry_data.data_size != 2) {
return NULL;
}
freesstring(filename);

geoip_nickext = registernickext("geoip");
if(geoip_nickext == -1)
return; /* PPA: registerchanext produces an error, however the module should stop loading */
/* not null terminated, sad */
const char *code_u = entry_data.utf8_string;
const char code[3] = { code_u[0], code_u[1], '\0' };

memset(geoip_totals, 0, sizeof(geoip_totals));
struct country *c = geoip_lookup_code(code);
if (!c) {
return NULL;
}

for(i=0;i<NICKHASHSIZE;i++)
for(np=nicktable[i];np;np=np->next)
geoip_setupuser(np);
if (c->code[0] == '\0') {
status = MMDB_get_value(&result.entry, &entry_data, "country", "names", "en", NULL);
if (status != MMDB_SUCCESS || !entry_data.has_data || entry_data.type != MMDB_DATA_TYPE_UTF8_STRING) {
return NULL;
}

/* not null terminated, sad */
const char *name = entry_data.utf8_string;
size_t name_len = entry_data.data_size;
if (name_len > COUNTRY_NAME_LEN) {
name_len = COUNTRY_NAME_LEN;
}

memcpy(c->code, code, 3);
memcpy(c->name, name, name_len);
c->name[name_len] = '\0';
}

registerhook(HOOK_NICK_LOSTNICK, &geoip_quit);
registerhook(HOOK_NICK_NEWNICK, &geoip_new_nick);
registerhook(HOOK_CONTROL_WHOISREQUEST, &geoip_whois_handler);
return c;
}

void _fini(void) {
if(gi)
GeoIP_delete(gi);
static void nick_setup(nick *np) {
union {
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
} u = {};

struct irc_in_addr *ip = &np->ipaddress;
if (irc_in_addr_is_ipv4(ip)) {
u.sin.sin_family = AF_INET;
u.sin.sin_addr.s_addr = htonl(irc_in_addr_v4_to_int(ip));
} else {
u.sin6.sin6_family = AF_INET6;
memcpy(&u.sin6.sin6_addr.s6_addr, ip->in6_16, sizeof(ip->in6_16));
}

if(geoip_nickext == -1)
return;
struct country *c = lookup_country((struct sockaddr *)&u);
if (!c) {
c = unknown_country;
}

releasenickext(geoip_nickext);
c->total++;
np->exts[nickext] = c;
}

deregisterhook(HOOK_NICK_NEWNICK, &geoip_new_nick);
deregisterhook(HOOK_NICK_LOSTNICK, &geoip_quit);
deregisterhook(HOOK_CONTROL_WHOISREQUEST, &geoip_whois_handler);
static void nick_new(int hook, void *args) {
nick_setup(args);
}

static void geoip_setupuser(nick *np) {
if (!irc_in_addr_is_ipv4(&np->ipaddress))
return; /* geoip only supports ipv4 */
static void nick_lost(int hook, void *args) {
nick *np = args;

unsigned int ip = irc_in_addr_v4_to_int(&np->ipaddress);
int country = GeoIP_id_by_ipnum(gi, ip);
if((country < COUNTRY_MIN) || (country > COUNTRY_MAX))
struct country *c = geoip_lookup_nick(np);
if (!c) {
return;
}

geoip_totals[country]++;
np->exts[geoip_nickext] = (void *)(long)country;
c->total--;
}

static void geoip_new_nick(int hook, void *args) {
geoip_setupuser((nick *)args);
static void whois_handler(int hooknum, void *arg) {
nick *np = arg;
if (!np) {
return;
}

struct country *c = geoip_lookup_nick(np);
if (!c) {
return;
}

char buf[512];
snprintf(buf, sizeof(buf), "Country : %s (%s)", geoip_code(c), geoip_name(c));
triggerhook(HOOK_CONTROL_WHOISREPLY, buf);
}

static void geoip_quit(int hook, void *args) {
int item;
nick *np = (nick *)args;
struct country *geoip_lookup_code(const char *code) {
if (code[0] < 'A' || code[0] > 'Z' || code[1] < 'A' || code[1] > 'Z' || code[2] != '\0') {
if (!strcmp("??", code)) {
return unknown_country;
}

item = (int)((long)np->exts[geoip_nickext]);

if((item < COUNTRY_MIN) || (item > COUNTRY_MAX))
return;
return NULL;
}

geoip_totals[item]--;
return &countries[(code[0] - 'A') * 26 + (code[1] - 'A')];
}

static void geoip_whois_handler(int hooknum, void *arg) {
int item;
char message[512];
const char *longcountry, *shortcountry;
nick *np = (nick *)arg;
const char *geoip_code(struct country *c) {
return c->code;
}

if(!np)
return;
const char *geoip_name(struct country *c) {
return c->name;
}

int geoip_total(struct country *c) {
return c->total;
}

struct country *geoip_next(struct country *c) {
int pos;
if (c == NULL) {
pos = 0;
} else {
pos = c - countries + 1;
}

for (; pos < COUNTRY_MAX; pos++) {
c = &countries[pos];
if (!c->total) {
continue;
}

item = (int)((long)np->exts[geoip_nickext]);
if((item < COUNTRY_MIN) || (item > COUNTRY_MAX))
return c;
}

return NULL;
}

void _init(void) {
nickext = registernickext("geoip");
if (nickext == -1) {
return;
}

shortcountry = GeoIP_code_by_id(item);
longcountry = GeoIP_name_by_id(item);
sstring *filename = getcopyconfigitem("geoip", "db", "GeoLite2-Country.mmdb", 256);
int result = MMDB_open(filename->content, MMDB_MODE_MMAP, &db);
freesstring(filename);
if (result != MMDB_SUCCESS) {
Error("geoip", ERR_WARNING, "Unable to load geoip database [filename: %s] [code: %d]", filename->content, result);
return;
}

if(shortcountry && longcountry) {
snprintf(message, sizeof(message), "Country : %s (%s)", shortcountry, longcountry);
triggerhook(HOOK_CONTROL_WHOISREPLY, message);
for (int i = 0; i < NICKHASHSIZE; i++) {
for (nick *np = nicktable[i]; np; np=np->next) {
nick_setup(np);
}
}

registerhook(HOOK_NICK_NEWNICK, nick_new);
registerhook(HOOK_NICK_LOSTNICK, nick_lost);
registerhook(HOOK_CONTROL_WHOISREQUEST, whois_handler);
}

int geoip_lookupcode(char *code) {
int i;
for(i=COUNTRY_MIN;i<COUNTRY_MAX;i++)
if(!strcasecmp(code, GeoIP_country_code[i]))
return i;
void _fini(void) {
if (nickext != -1) {
releasenickext(nickext);
}

MMDB_close(&db);

deregisterhook(HOOK_NICK_NEWNICK, nick_new);
deregisterhook(HOOK_NICK_LOSTNICK, nick_lost);
deregisterhook(HOOK_CONTROL_WHOISREQUEST, whois_handler);
}

return -1;
struct country *geoip_lookup_nick(nick *np) {
return np->exts[nickext];
}
21 changes: 16 additions & 5 deletions geoip/geoip.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
#define COUNTRY_MIN 0
#define COUNTRY_MAX 253
#ifndef __GEOIP_H
#define __GEOIP_H

extern int geoip_totals[COUNTRY_MAX + 1];
struct country;

int geoip_lookupcode(char *code);
typedef int (*GeoIP_LookupCode)(char *);
/* "code" -> GB/FR */
/* "name" -> United Kingdom/France */

struct country *geoip_lookup_code(const char *);
struct country *geoip_lookup_nick(nick *);

struct country *geoip_next(struct country *);

const char *geoip_code(struct country *c);
const char *geoip_name(struct country *c);
int geoip_total(struct country *c);

#endif
Loading

0 comments on commit 8a5c0cc

Please sign in to comment.