Last active
June 21, 2016 15:48
-
-
Save avaccari/7903c740ffa5169ab4fd to your computer and use it in GitHub Desktop.
Python server side script that uses D3 to generate map of a set of IPs stored as lines within a file and plots origin statistic
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# A script to graph the statistics of a set of IP blacklists generated by your favorite | |
# firewall program as long as the lists consist of a series of IP addresses one for each | |
# line. | |
import re | |
import sys | |
import json | |
import urllib2 | |
import scoket | |
import pygeoip | |
import cStringIO | |
from os import chdir | |
from glob import glob | |
from random import randint | |
from collections import Counter | |
import numpy as np | |
import matplotlib | |
matplotlib.use('Agg') | |
import matplotlib.pyplot as plt | |
# The geoip data comes from here and should be updated once every so often: | |
# | |
# wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz | |
# | |
# gunzip GeoLiteCity.dat.gz | |
GEO_DATASET = 'GeoLiteCity.dat' | |
data = pygeoip.GeoIP(GEO_DATASET) | |
GEO_API = 'http://freegeoip.net/json/' | |
timeout = 1 | |
# Blacklists should be list of IP addresses one for each line. | |
BLACKLIST_DIR = '/etc/fail2ban/' # Location of blecklists | |
BLACKLIST_FILES = '*.blacklist' # Regular expression identifying blacklists name | |
chdir(BLACKLIST_DIR) | |
files = glob(BLACKLIST_FILES) | |
n_files = len(files) | |
coo = dict() | |
tot = dict() | |
grfd = dict() | |
ip_r = [] | |
def lookup_ip(ip): | |
serv = None | |
iplat = 0.0 | |
iplng = 0.0 | |
ipcty = '' | |
iploc = data.record_by_addr(ip) | |
if iploc is not None: | |
iplat = iploc['latitude'] | |
iplng = iploc['longitude'] | |
ipcty = iploc['country_name'] | |
serv = 0 | |
else: | |
qry = GEO_API + str(ip) | |
try: | |
j = json.loads(urllib2.urlopen(qry, timeout=timeout).read()) | |
except (urllib2.HTTPError, socket.timeout): | |
j = dict() | |
j['country_code'] = 'RD' | |
if j['country_code'] != 'RD': | |
iplat = j['latitude'] | |
iplng = j['longitude'] | |
ipcty = j['country_name'] | |
serv = 1 | |
return (serv, iplat, iplng, ip, ipcty) | |
for fl in files: | |
coo[fl] = [] | |
tot[fl] = 0 | |
grfd[fl] = 0 | |
for ip in open(fl, 'rb').readlines(): | |
ip_r.append(ip) | |
tot[fl] = tot[fl] + 1 | |
res = lookup_ip(ip[:-1]) | |
if res[0] is not None: | |
coo[fl].append(res[1:]) | |
grfd[fl] += res[0] | |
print "Content-Type: text/html" | |
print """ | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Attacks map</title> | |
<style> | |
html { height: 100% } | |
body { height: 100%; | |
margin: 0; | |
padding: 0 } | |
div#map-canvas { width: 85%; | |
height: 85% } | |
div#plot-canvas { width: 85%; | |
border: 1px solid} | |
div#plot-canvas img { width: 33%; | |
vertical-align: top} | |
.graticule { | |
fill: none; | |
stroke: #777; | |
stroke-width: .5px; | |
stroke-opacity: .5; | |
} | |
.land { | |
fill: #222; | |
} | |
.boundary { | |
fill: none; | |
stroke: #fff; | |
stroke-width: .5px; | |
} | |
.pin { | |
fill: #D35400; | |
opacity: 0.3; | |
stroke: rgba(211, 84, 0, 0.1); | |
stroke-width: 5px; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Attacks statistics</h1> | |
<div id="plot-canvas">""" | |
ip_r = list(set(ip_r)) | |
width = 1.0 | |
top = 20 | |
ymax = 0.00 | |
title = {'1':'Top 20 Class-A', '2':'Top 20 Class-B', '3':'Top 20 Class-C'} | |
appnd = {'1':'0.0.0', '2':'0.0', '3':'0'} | |
for cl in ['1', '2', '3']: | |
ip_s = [re.search("([0-9]+\.){" + cl + "}", v).group(0) for i, v in enumerate(ip_r)] | |
labels, values = zip(*Counter(ip_s).most_common(top)) | |
ymax = max(ymax, max(values)) | |
countries = [lookup_ip(lbl + appnd[cl])[4] for lbl in labels] | |
indexes = np.arange(len(labels)) | |
rect = plt.bar(indexes, values, width, label=countries) | |
plt.ylim(0, 1.3 * ymax) | |
plt.xticks(indexes + 0.5 * width, labels, rotation="vertical") | |
for i, r in enumerate(rect): | |
plt.text(r.get_x() + 0.5 * r.get_width(), 1.05 * r.get_height(), countries[i], ha='center', va='bottom', rotation='vertical') | |
plt.title(title[cl]) | |
sio = cStringIO.StringIO() | |
plt.savefig(sio, format='png', bbox_inches='tight') | |
print """ | |
<img src="data:image/png;base64,%s"/>""" % sio.getvalue().encode("base64").strip() | |
plt.close() | |
sio.close() | |
print """ | |
</div> | |
The following map displays only the location of IP addresses that could be georeferenced. | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script> | |
<script> | |
var width = 1200, | |
height = 600; | |
var projection = d3.geo.equirectangular() | |
.scale(165) | |
.translate([width / 2.2, height / 2]) | |
.precision(.1); | |
var path = d3.geo.path() | |
.projection(projection); | |
var graticule = d3.geo.graticule(); | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
svg.append("path") | |
.datum(graticule) | |
.attr("class", "graticule") | |
.attr("d", path); | |
d3.json("world-50m.json", function(error, world) { | |
if (error) throw error; | |
svg.insert("path", ".graticule") | |
.datum(topojson.feature(world, world.objects.land)) | |
.attr("class", "land") | |
.attr("d", path); | |
svg.insert("path", ".graticule") | |
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; })) | |
.attr("class", "boundary") | |
.attr("d", path); | |
}); | |
d3.select(self.frameElement).style("height", height + "px"); | |
var places = [""" | |
for fl in files: | |
for itm in coo[fl]: | |
print """ | |
{ | |
name: "%s", | |
location: { | |
latitude: %s, | |
longitude: %s | |
} | |
},""" % (itm[2], itm[0], itm[1]) | |
print """ | |
] | |
svg.selectAll(".pin") | |
.data(places) | |
.enter().append("circle") | |
.attr("class", "pin") | |
.attr("r", 3) | |
.attr("transform", function(d) { | |
return "translate(" + projection([ | |
d.location.longitude, | |
d.location.latitude | |
]) + ")"; | |
}); | |
</script> | |
</br>This maps makes use of geolite data created by maxmind, available from <a href="http://www.maxmind.com">http://www.maxmind.com</a> and, where necessary, georeferencing from <a href="http://freegeoip.net">http://freegeoip.net</a> | |
</body> | |
</html>""" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment