summaryrefslogtreecommitdiff
path: root/geoip.c
diff options
context:
space:
mode:
Diffstat (limited to 'geoip.c')
-rw-r--r--geoip.c595
1 files changed, 595 insertions, 0 deletions
diff --git a/geoip.c b/geoip.c
new file mode 100644
index 0000000..fc61184
--- /dev/null
+++ b/geoip.c
@@ -0,0 +1,595 @@
+/*
+ * netsniff-ng - the packet sniffing beast
+ * Copyright 2013 Daniel Borkmann.
+ * Subject to the GPL, version 2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <GeoIP.h>
+#include <GeoIPCity.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "built_in.h"
+#include "die.h"
+#include "xutils.h"
+#include "xio.h"
+#include "xmalloc.h"
+#include "zlib.h"
+#include "geoip.h"
+
+struct file {
+ const char *desc, *local;
+ const char *remote, *possible_prefix;
+};
+
+#define PRE "/download/geoip/database"
+static const struct file files[] = {
+ [GEOIP_CITY_EDITION_REV1] = {
+ .desc = "City IPv4",
+ .local = "/etc/netsniff-ng/city4.dat",
+ .remote = "/GeoLiteCity.dat.gz",
+ .possible_prefix = PRE,
+ },
+ [GEOIP_CITY_EDITION_REV1_V6] = {
+ .desc = "City IPv6",
+ .local = "/etc/netsniff-ng/city6.dat",
+ .remote = "/GeoLiteCityv6.dat.gz",
+ .possible_prefix = PRE "/GeoLiteCityv6-beta",
+ },
+ [GEOIP_COUNTRY_EDITION] = {
+ .desc = "Country IPv4",
+ .local = "/etc/netsniff-ng/country4.dat",
+ .remote = "/GeoIP.dat.gz",
+ .possible_prefix = PRE "/GeoLiteCountry",
+ },
+ [GEOIP_COUNTRY_EDITION_V6] = {
+ .desc = "Country IPv6",
+ .local = "/etc/netsniff-ng/country6.dat",
+ .remote = "/GeoIPv6.dat.gz",
+ .possible_prefix = PRE,
+ },
+ [GEOIP_ASNUM_EDITION] = {
+ .desc = "AS Numbers IPv4",
+ .local = "/etc/netsniff-ng/asname4.dat",
+ .remote = "/GeoIPASNum.dat.gz",
+ .possible_prefix = PRE "/asnum",
+ },
+ [GEOIP_ASNUM_EDITION_V6] = {
+ .desc = "AS Numbers IPv6",
+ .local = "/etc/netsniff-ng/asname6.dat",
+ .remote = "/GeoIPASNumv6.dat.gz",
+ .possible_prefix = PRE "/asnum",
+ },
+};
+
+static GeoIP *gi4_asname = NULL, *gi6_asname = NULL;
+static GeoIP *gi4_country = NULL, *gi6_country = NULL;
+static GeoIP *gi4_city = NULL, *gi6_city = NULL;
+
+static GeoIPRecord empty = { 0 };
+
+static char *servers[16] = { 0 };
+
+#define CITYV4 (1 << 0)
+#define CITYV6 (1 << 1)
+#define COUNTRYV4 (1 << 2)
+#define COUNTRYV6 (1 << 3)
+#define ASNAMV4 (1 << 4)
+#define ASNAMV6 (1 << 5)
+
+#define HAVEALL (CITYV4 | CITYV6 | COUNTRYV4 | COUNTRYV6 | ASNAMV4 | ASNAMV6)
+
+static int geoip_db_present = 0;
+
+int geoip_working(void)
+{
+ return geoip_db_present == HAVEALL;
+}
+
+static int geoip_get_remote_fd(const char *server, const char *port)
+{
+ int ret, fd = -1;
+ struct addrinfo hints, *ahead, *ai;
+
+ bug_on(!server || !port);
+
+ memset(&hints, 0, sizeof(hints));
+
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = AI_NUMERICSERV;
+
+ ret = getaddrinfo(server, port, &hints, &ahead);
+ if (ret != 0)
+ return -EIO;
+
+ for (ai = ahead; ai != NULL && fd < 0; ai = ai->ai_next) {
+ fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (fd < 0)
+ continue;
+
+ ret = connect(fd, ai->ai_addr, ai->ai_addrlen);
+ if (ret < 0) {
+ close(fd);
+ fd = -1;
+ continue;
+ }
+
+ break;
+ }
+
+ freeaddrinfo(ahead);
+
+ return fd;
+}
+
+static void geoip_inflate(int which)
+{
+ int ret, ret2 = 1;
+ gzFile fpi;
+ FILE *fpo;
+ char zfile[128], raw[4096];
+
+ slprintf(zfile, sizeof(zfile), "%s.gz", files[which].local);
+ fpi = gzopen(zfile, "rb");
+ if (fpi == NULL)
+ panic("No %s file!\n", zfile);
+
+ fpo = fopen(files[which].local, "wb");
+ if (fpo == NULL)
+ panic("Cannot create %s!\n", files[which].local);
+
+ while ((ret = gzread(fpi, raw, sizeof(raw))) && ret2)
+ ret2 = fwrite(raw, ret, 1, fpo);
+
+ gzclose(fpi);
+ fclose(fpo);
+}
+
+static int geoip_get_database(const char *host, int which)
+{
+ int found, sock, fd, i, good, retry = 0;
+ ssize_t ret, len, rtotlen = 0, totlen = 0;
+ char raw[4096], *ptr, zfile[128];
+ size_t lenl = strlen("Content-Length: ");
+ size_t lent = strlen("HTTP/1.1 200 OK");
+ size_t lenc = strlen("\r\n\r\n");
+
+again:
+ found = good = 0;
+ ptr = NULL;
+ len = 0;
+
+ sock = geoip_get_remote_fd(host, "80");
+ if (sock < 0)
+ return -EIO;
+
+ slprintf(raw, sizeof(raw), "GET %s%s HTTP/1.1\nHost: %s\r\n\r\n",
+ retry ? files[which].possible_prefix : "",
+ files[which].remote, host);
+
+ ret = write(sock, raw, strlen(raw));
+ if (ret <= 0)
+ return -EIO;
+
+ shutdown(sock, SHUT_WR);
+
+ slprintf(zfile, sizeof(zfile), "%s.gz", files[which].local);
+ fd = open_or_die_m(zfile, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
+
+ memset(raw, 0, sizeof(raw));
+ ret = read(sock, raw, sizeof(raw));
+ if (ret <= 0)
+ return -EIO;
+
+ for (i = 0; i < ret; i++) {
+ if (!strncmp(raw + i, "Content-Length: ", min(ret - i, lenl))) {
+ ptr = raw + i + lenl;
+ rtotlen = strtoul(ptr, NULL, 10);
+ }
+
+ if (!strncmp(raw + i, "HTTP/1.1 200 OK", min(ret - i, lent)))
+ good = 1;
+
+ if (!strncmp(raw + i, "\r\n\r\n", min(ret - i, lenc))) {
+ ptr = raw + i + lenc;
+ len = ret - i - lenc;
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found || ptr >= raw + ret || len < 0 || rtotlen == 0 || good == 0) {
+ if (retry == 0) {
+ retry = 1;
+ close(fd);
+ close(sock);
+ goto again;
+ }
+
+ return -ENOENT;
+ }
+
+ do {
+ write_or_die(fd, ptr, len);
+ totlen += len;
+ printf("\r%s [%.2f%%, %zd/%zd, %s]", files[which].desc,
+ 100.f * totlen / rtotlen, totlen, rtotlen, host);
+ fflush(stdout);
+
+ memset(raw, 0, sizeof(raw));
+ ret = read(sock, raw, sizeof(raw));
+
+ ptr = raw;
+ len = ret;
+ } while(ret > 0);
+
+ printf("\n");
+
+ if (totlen != rtotlen) {
+ unlink(files[which].local);
+ return -EIO;
+ }
+
+ close(fd);
+ close(sock);
+
+ geoip_inflate(which);
+
+ unlink(zfile);
+
+ return 0;
+}
+
+static GeoIPRecord *geoip4_get_record(struct sockaddr_in sa)
+{
+ bug_on(gi4_city == NULL);
+
+ return GeoIP_record_by_ipnum(gi4_city, ntohl(sa.sin_addr.s_addr)) ? : &empty;
+}
+
+static GeoIPRecord *geoip6_get_record(struct sockaddr_in6 sa)
+{
+ bug_on(gi6_city == NULL);
+
+ return GeoIP_record_by_ipnum_v6(gi6_city, sa.sin6_addr) ? : &empty;
+}
+
+const char *geoip4_as_name(struct sockaddr_in sa)
+{
+ bug_on(gi4_asname == NULL);
+
+ return GeoIP_name_by_ipnum(gi4_asname, ntohl(sa.sin_addr.s_addr));
+}
+
+const char *geoip6_as_name(struct sockaddr_in6 sa)
+{
+ bug_on(gi6_asname == NULL);
+
+ return GeoIP_name_by_ipnum_v6(gi6_asname, sa.sin6_addr);
+}
+
+float geoip4_longitude(struct sockaddr_in sa)
+{
+ return geoip4_get_record(sa)->longitude;
+}
+
+float geoip4_latitude(struct sockaddr_in sa)
+{
+ return geoip4_get_record(sa)->latitude;
+}
+
+float geoip6_longitude(struct sockaddr_in6 sa)
+{
+ return geoip6_get_record(sa)->longitude;
+}
+
+float geoip6_latitude(struct sockaddr_in6 sa)
+{
+ return geoip6_get_record(sa)->latitude;
+}
+
+const char *geoip4_city_name(struct sockaddr_in sa)
+{
+ return geoip4_get_record(sa)->city;
+}
+
+const char *geoip6_city_name(struct sockaddr_in6 sa)
+{
+ return geoip6_get_record(sa)->city;
+}
+
+const char *geoip4_region_name(struct sockaddr_in sa)
+{
+ return geoip4_get_record(sa)->region;
+}
+
+const char *geoip6_region_name(struct sockaddr_in6 sa)
+{
+ return geoip6_get_record(sa)->region;
+}
+
+const char *geoip4_country_name(struct sockaddr_in sa)
+{
+ bug_on(gi4_country == NULL);
+
+ return GeoIP_country_name_by_ipnum(gi4_country, ntohl(sa.sin_addr.s_addr));
+}
+
+const char *geoip6_country_name(struct sockaddr_in6 sa)
+{
+ bug_on(gi6_country == NULL);
+
+ return GeoIP_country_name_by_ipnum_v6(gi6_country, sa.sin6_addr);
+}
+
+static int fdout, fderr;
+
+/* GeoIP people were too stupid to come to the idea that you could set
+ * errno appropriately and return NULL instead of printing stuff from
+ * the library directly that noone can turn off.
+ */
+
+static void geoip_open_prepare(void)
+{
+ fflush(stdout);
+ fdout = dup(1);
+
+ fflush(stderr);
+ fderr = dup(2);
+
+ close(1);
+ close(2);
+}
+
+static void geoip_open_restore(void)
+{
+ dup2(fdout, 1);
+ dup2(fderr, 2);
+
+ close(fdout);
+ close(fderr);
+}
+
+static GeoIP *geoip_open_type(int type, int flags)
+{
+ GeoIP *ret;
+
+ geoip_open_prepare();
+ ret = GeoIP_open_type(type, flags);
+ geoip_open_restore();
+
+ return ret;
+}
+
+static GeoIP *geoip_open(const char *filename, int flags)
+{
+ GeoIP *ret;
+
+ geoip_open_prepare();
+ ret = GeoIP_open(filename, flags);
+ geoip_open_restore();
+
+ return ret;
+}
+
+static void init_geoip_city_open4(int enforce)
+{
+ gi4_city = geoip_open(files[GEOIP_CITY_EDITION_REV1].local, GEOIP_MMAP_CACHE);
+ if (gi4_city == NULL) {
+ gi4_city = geoip_open_type(GEOIP_CITY_EDITION_REV1, GEOIP_MMAP_CACHE);
+ if (gi4_city == NULL)
+ if (enforce)
+ panic("Cannot open GeoIP4 city database, try --update!\n");
+ }
+
+ if (gi4_city) {
+ GeoIP_set_charset(gi4_city, GEOIP_CHARSET_UTF8);
+ geoip_db_present |= CITYV4;
+ }
+}
+
+static void init_geoip_city_open6(int enforce)
+{
+ gi6_city = geoip_open(files[GEOIP_CITY_EDITION_REV1_V6].local, GEOIP_MMAP_CACHE);
+ if (gi6_city == NULL) {
+ gi6_city = geoip_open_type(GEOIP_CITY_EDITION_REV1_V6, GEOIP_MMAP_CACHE);
+ if (gi6_city == NULL)
+ if (enforce)
+ panic("Cannot open GeoIP6 city database, try --update!\n");
+ }
+
+ if (gi6_city) {
+ GeoIP_set_charset(gi6_city, GEOIP_CHARSET_UTF8);
+ geoip_db_present |= CITYV6;
+ }
+}
+
+static void init_geoip_city(int enforce)
+{
+ init_geoip_city_open4(enforce);
+ init_geoip_city_open6(enforce);
+}
+
+static void destroy_geoip_city(void)
+{
+ GeoIP_delete(gi4_city);
+ GeoIP_delete(gi6_city);
+}
+
+static void init_geoip_country_open4(int enforce)
+{
+ gi4_country = geoip_open(files[GEOIP_COUNTRY_EDITION].local, GEOIP_MMAP_CACHE);
+ if (gi4_country == NULL) {
+ gi4_country = geoip_open_type(GEOIP_COUNTRY_EDITION, GEOIP_MMAP_CACHE);
+ if (gi4_country == NULL)
+ if (enforce)
+ panic("Cannot open GeoIP4 country database, try --update!\n");
+ }
+
+ if (gi4_country) {
+ GeoIP_set_charset(gi4_country, GEOIP_CHARSET_UTF8);
+ geoip_db_present |= COUNTRYV4;
+ }
+}
+
+static void init_geoip_country_open6(int enforce)
+{
+ gi6_country = geoip_open(files[GEOIP_COUNTRY_EDITION_V6].local, GEOIP_MMAP_CACHE);
+ if (gi6_country == NULL) {
+ gi6_country = geoip_open_type(GEOIP_COUNTRY_EDITION_V6, GEOIP_MMAP_CACHE);
+ if (gi6_country == NULL)
+ if (enforce)
+ panic("Cannot open GeoIP6 country database, try --update!\n");
+ }
+
+ if (gi6_country) {
+ GeoIP_set_charset(gi6_country, GEOIP_CHARSET_UTF8);
+ geoip_db_present |= COUNTRYV6;
+ }
+}
+
+static void init_geoip_country(int enforce)
+{
+ init_geoip_country_open4(enforce);
+ init_geoip_country_open6(enforce);
+}
+
+static void destroy_geoip_country(void)
+{
+ GeoIP_delete(gi4_country);
+ GeoIP_delete(gi6_country);
+}
+
+static void init_geoip_asname_open4(int enforce)
+{
+ gi4_asname = geoip_open(files[GEOIP_ASNUM_EDITION].local, GEOIP_MMAP_CACHE);
+ if (gi4_asname == NULL) {
+ gi4_asname = geoip_open_type(GEOIP_ASNUM_EDITION, GEOIP_MMAP_CACHE);
+ if (gi4_asname == NULL)
+ if (enforce)
+ panic("Cannot open GeoIP4 AS database, try --update!\n");
+ }
+
+ if (gi4_asname) {
+ GeoIP_set_charset(gi4_asname, GEOIP_CHARSET_UTF8);
+ geoip_db_present |= ASNAMV4;
+ }
+}
+
+static void init_geoip_asname_open6(int enforce)
+{
+ gi6_asname = geoip_open(files[GEOIP_ASNUM_EDITION_V6].local, GEOIP_MMAP_CACHE);
+ if (gi6_asname == NULL) {
+ gi6_asname = geoip_open_type(GEOIP_ASNUM_EDITION_V6, GEOIP_MMAP_CACHE);
+ if (gi6_asname == NULL)
+ if (enforce)
+ panic("Cannot open GeoIP6 AS database, try --update!\n");
+ }
+
+ if (gi6_asname) {
+ GeoIP_set_charset(gi6_asname, GEOIP_CHARSET_UTF8);
+ geoip_db_present |= ASNAMV6;
+ }
+}
+
+static void init_geoip_asname(int enforce)
+{
+ init_geoip_asname_open4(enforce);
+ init_geoip_asname_open6(enforce);
+}
+
+static void destroy_geoip_asname(void)
+{
+ GeoIP_delete(gi4_asname);
+ GeoIP_delete(gi6_asname);
+}
+
+static void init_mirrors(void)
+{
+ int i = 0;
+ FILE *fp;
+ char buff[256];
+
+ fp = fopen("/etc/netsniff-ng/geoip.conf", "r");
+ if (!fp)
+ panic("Cannot open geoip.conf!\n");
+
+ fmemset(buff, 0, sizeof(buff));
+ while (fgets(buff, sizeof(buff), fp) != NULL &&
+ i < array_size(servers)) {
+ buff[sizeof(buff) - 1] = 0;
+ buff[strlen(buff) - 1] = 0;
+
+ if (buff[0] == '#') {
+ fmemset(buff, 0, sizeof(buff));
+ continue;
+ }
+
+ servers[i++] = xstrdup(buff);
+ fmemset(buff, 0, sizeof(buff));
+ }
+
+ fclose(fp);
+}
+
+static void destroy_mirrors(void)
+{
+ int i;
+
+ for (i = 0; i < array_size(servers); ++i)
+ free(servers[i]);
+}
+
+void init_geoip(int enforce)
+{
+ init_geoip_city(enforce);
+ init_geoip_country(enforce);
+ init_geoip_asname(enforce);
+}
+
+void update_geoip(void)
+{
+ int i, j, ret, good = 0;
+
+ init_mirrors();
+
+ for (i = 0; i < array_size(files); ++i) {
+ if (files[i].local && files[i].remote) {
+ good = 0;
+
+ for (j = 0; j < array_size(servers); ++j) {
+ if (servers[j] == NULL)
+ continue;
+ ret = geoip_get_database(servers[j], i);
+ if (!ret) {
+ good = 1;
+ break;
+ }
+ }
+
+ if (good == 0)
+ panic("Cannot get %s from mirrors!\n",
+ files[i].remote);
+ }
+ }
+
+ destroy_mirrors();
+}
+
+void destroy_geoip(void)
+{
+ destroy_geoip_city();
+ destroy_geoip_country();
+ destroy_geoip_asname();
+
+ geoip_db_present = 0;
+}