diff --git a/baddie-detector/baddiefinder/__main__.py b/baddie-detector/baddiefinder/__main__.py new file mode 100644 index 0000000..7a13cec --- /dev/null +++ b/baddie-detector/baddiefinder/__main__.py @@ -0,0 +1,29 @@ +# +# +# + +import netdb + + +from argparse import ArgumentParser as AP + +from . import settings +from . import filter +from . import processor + +def main(): + ap = AP() + ap.add_argument("--settings", default="baddies.ini") + + args = ap.parse_args() + s = settings.load(args.settings) + fmax = s.get("thresholds", "max_floodfills_per_ip", fallback=5) + f = filter.FloodfillFilter(fmax) + p = processor.BaddieProcessor([f]) + netdb.inspect(p.hook) + with open(s.get("output", "file", fallback="baddies.txt"), 'w') as f: + p.write_blocklist(f) + + +if __name__ == "__main__": + main() diff --git a/baddie-detector/baddiefinder/filter.py b/baddie-detector/baddiefinder/filter.py new file mode 100644 index 0000000..b23673e --- /dev/null +++ b/baddie-detector/baddiefinder/filter.py @@ -0,0 +1,32 @@ + +from . import util + +class Filter: + + name = "unnamed filter" + + def process(self, info): + """ + process an info and return True if it should be added to blocklist + any other return value will cause this info to NOT be added to blocklist + """ + +class FloodfillFilter(Filter): + + name = "floodfill sybil detector" + + def __init__(self, fmax): + self._floodfills = dict() + self.fmax = int(fmax) + + def process(self, info): + caps = util.getcaps(info) + if not caps: + return False + if b'f' not in caps: + return False + h = util.getaddress(info) + if h not in self._floodfills: + self._floodfills[h] = 0 + self._floodfills[h] += 1 + return self._floodfills[h] > self.fmax diff --git a/baddie-detector/baddiefinder/processor.py b/baddie-detector/baddiefinder/processor.py new file mode 100644 index 0000000..a3ca87c --- /dev/null +++ b/baddie-detector/baddiefinder/processor.py @@ -0,0 +1,26 @@ +from . import util + +import datetime + +class BaddieProcessor: + + def __init__(self, filters): + self._filters = filters + self._baddies = dict() + + + def hook(self, entry): + for f in self._filters: + if f.process(entry) is True: + self.add_baddie(entry, 'detected by {}'.format(f.name)) + + def add_baddie(self, entry, reason): + addr = util.getaddress(entry) + if addr not in self._baddies: + self._baddies[addr] = '' + self._baddies[addr] += reason + ' ' + + def write_blocklist(self, f): + f.write('# baddies blocklist generated on {}'.format(datetime.datetime.now())) + for k in self._baddies: + f.write('{}:{}\n'.format(self._baddies[k], k)) diff --git a/baddie-detector/baddiefinder/settings.py b/baddie-detector/baddiefinder/settings.py new file mode 100644 index 0000000..8b13afe --- /dev/null +++ b/baddie-detector/baddiefinder/settings.py @@ -0,0 +1,11 @@ +# +# baddiefinder settings wrapper +# + +from configparser import ConfigParser + +def load(fname): + c = ConfigParser() + with open(fname) as f: + c.read_file(f, fname) + return c diff --git a/baddie-detector/baddiefinder/util.py b/baddie-detector/baddiefinder/util.py new file mode 100644 index 0000000..64bf22f --- /dev/null +++ b/baddie-detector/baddiefinder/util.py @@ -0,0 +1,15 @@ +def getaddress(info): + """ + get ip address from router info dict + """ + for addr in info.addrs: + opts = addr.options + if b'host' in opts: + return opts[b'host'] + +def getcaps(info): + """ + extract router caps + """ + if b'caps' in info.options: + return info.options[b'caps'] diff --git a/baddie-detector/baddies.ini b/baddie-detector/baddies.ini new file mode 100644 index 0000000..5b2a330 --- /dev/null +++ b/baddie-detector/baddies.ini @@ -0,0 +1,2 @@ +[thresholds] +max_floodfills_per_ip = 2 \ No newline at end of file diff --git a/baddie-detector/netdb/netdb.py b/baddie-detector/netdb/netdb.py index db597aa..8de54f6 100644 --- a/baddie-detector/netdb/netdb.py +++ b/baddie-detector/netdb/netdb.py @@ -5,9 +5,11 @@ ## MIT Liecense 2014 ## import os,sys,struct,time,hashlib,fnmatch,io -from geoip import geolite2 import base64 import logging +import pygeoip + +geo = pygeoip.GeoIP('/usr/share/GeoIP/GeoIPCity.dat') b64encode = lambda x : base64.b64encode(x, b'~-').decode('ascii') @@ -152,6 +154,10 @@ class Entry: li = struct.unpack('!Q', d)[0] return li + @staticmethod + def geolookup(entry): + return geo.record_by_addr(entry) + @staticmethod def _read_addr(fd): """ @@ -168,7 +174,7 @@ class Entry: # This is a try because sometimes hostnames show up. # TODO: Make it allow host names. try: - addr.location = geolite2.lookup(addr.options.get('host', None)) + addr.location = geolookup(addr.options.get('host', None)) except: addr.location = None @@ -179,7 +185,7 @@ class Entry: # If there are introducers then it's probably firewalled. addr.firewalled = True try: - addr.location = geolite2.lookup(addr.options.get('ihost0', None)) + addr.location = geolookup(addr.options.get('ihost0', None)) except: addr.location = None diff --git a/baddie-detector/setup.py b/baddie-detector/setup.py index 0d56706..70210d5 100644 --- a/baddie-detector/setup.py +++ b/baddie-detector/setup.py @@ -2,8 +2,8 @@ from setuptools import setup setup(name = 'baddiedetector', - version = '0.0', - description = 'i2p netdb blocklist ', + version = '0.1', + description = 'i2p netdb blocklist tool', author = 'Jeff Becker', author_email = 'ampernand@gmail.com', install_requires = ['python-geoip','python-geoip-geolite2'],