Skip to content

Instantly share code, notes, and snippets.

@avaccari
Last active June 21, 2016 15:48
Show Gist options
  • Save avaccari/7903c740ffa5169ab4fd to your computer and use it in GitHub Desktop.
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
#!/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
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