[tor-commits] [doctor/master] Track when malicious relays return to the network
atagar at torproject.org
atagar at torproject.org
Sat Feb 20 20:42:16 UTC 2016
commit efa0ed43194bbc833ad91233586c40198070a94f
Author: Damian Johnson <atagar at torproject.org>
Date: Sat Feb 20 11:36:42 2016 -0800
Track when malicious relays return to the network
When BadExit relays are removed we want to continue to monitor for their return
for a time after. This is a script requested by David on...
https://trac.torproject.org/projects/tor/ticket/18246
Basic script's done but as the TODO comments show there's still some missing
bits.
---
data/tracked_relays.cfg | 22 +++++++
fingerprint_change_checker.py | 5 +-
track_relays.py | 136 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 159 insertions(+), 4 deletions(-)
diff --git a/data/tracked_relays.cfg b/data/tracked_relays.cfg
new file mode 100644
index 0000000..e25959e
--- /dev/null
+++ b/data/tracked_relays.cfg
@@ -0,0 +1,22 @@
+# Relays we want notifications for when they reappear. This can be done by
+# address (single addresses or ranges) or fingerprints.
+#
+# Anything in this file is public. All entries *MUST* have a description and an
+# expiration date for when it can be removed. The description is particularly
+# important so we can puzzle out why we were monitoring for this if it
+# reappears.
+#
+# Few examples...
+#
+# PrivacyPT.description running sslstrip on 2011-01-05
+# PrivacyPT.expires 2016-01-15
+# PrivacyPT.address 84.90.72.186
+#
+# trotsky.description sybil consisting of 383 exit relays on 2010-10-02
+# trotsky.expires 2016-02-01
+# trotsky.address 185.19.80.0/26
+#
+# Unnamed001.description harvesting hidden services on 2012-08-12
+# Unnamed001.expires 2016-03-01
+# Unnamed001.fingerprint 05AF83344B3787D0DCCD47DC4A6A4668142A5F8C
+
diff --git a/fingerprint_change_checker.py b/fingerprint_change_checker.py
index 493f51c..e13f3d0 100755
--- a/fingerprint_change_checker.py
+++ b/fingerprint_change_checker.py
@@ -89,10 +89,7 @@ def main():
body += "\n"
- try:
- util.send(EMAIL_SUBJECT, body = body, to = ['bad-relays at lists.torproject.org', 'atagar at torproject.org'])
- except Exception as exc:
- log.warn("Unable to send email: %s" % exc)
+ util.send(EMAIL_SUBJECT, body = body, to = ['bad-relays at lists.torproject.org', 'atagar at torproject.org'])
# register that we've notified for these
diff --git a/track_relays.py b/track_relays.py
new file mode 100755
index 0000000..e770006
--- /dev/null
+++ b/track_relays.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+# Copyright 2016, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
+"""
+Notifies if specific relays reappear in the network.
+"""
+
+import datetime
+import traceback
+
+import stem.descriptor.remote
+import stem.util.conf
+
+import util
+
+log = util.get_logger('track_relays')
+
+EMAIL_SUBJECT = 'Relays Returned'
+
+EMAIL_BODY = """\
+The following previously BadExit relays have returned to the network...
+
+"""
+
+
+class TrackedRelay(object):
+ """
+ Represents a relay we're keeping an eye on.
+
+ :var str identifier: brief identifier given to the entry
+ :var str description: description of why we're tracking it
+ :var datetime expires: when this entry expires
+ :var str address: address of the relay we're tracking
+ :var str fingerprint: fingerprint of the relay we're tracking
+ """
+
+ def __init__(self, identifier, config):
+ self.identifier = identifier
+ self.description = config.get('%s.description' % identifier, '')
+
+ expires_str = config.get('%s.expires' % identifier, '')
+
+ if not expires_str:
+ raise ValueError("Our config file is missing a '%s.expires' entry" % identifier)
+
+ try:
+ self.expires = datetime.datetime.strptime(expires_str, '%Y-%m-%d')
+ except ValueError:
+ raise ValueError("'%s.expires' is malformed. We expect it to be in the form 'Year-Month-Day'" % identifier)
+
+ self.address = config.get('%s.address' % identifier, None)
+ self.fingerprint = config.get('%s.fingerprint' % identifier, None)
+
+ if not self.address and not self.fingerprint:
+ raise ValueError("We need either a '%s.address' or '%s.fingerprint' to track" % (identifier, identifier))
+
+ def __str__(self):
+ attr = []
+
+ if self.address:
+ attr.append('address: %s' % self.address)
+
+ if self.fingerprint:
+ attr.append('fingerprint: %s' % self.fingerprint)
+
+ return '%s (%s)' % (self.identifier, ', '.join(attr))
+
+
+def get_tracked_relays():
+ """
+ Provides the relays we're tracking.
+
+ :returns: **list** of **TrackedRelay** we're tracking
+
+ :raises: **ValueError** if our config file is malformed
+ """
+
+ config = stem.util.conf.get_config('tracked_relays')
+ config.load(util.get_path('data', 'tracked_relays.cfg'))
+
+ # TODO: check for expired entries
+
+ identifiers = set([key.split('.')[0] for key in config.keys()])
+ return [TrackedRelay(identifier, config) for identifier in identifiers]
+
+
+def main():
+ # Map addresses and fingerprints to relays for constant time lookups. Address
+ # ranges are handled separately cuz... well, they're a pita.
+
+ tracked_addresses = {}
+ tracked_address_ranges = {}
+ tracked_fingerprints = {}
+
+ for relay in get_tracked_relays():
+ if relay.address:
+ if '/' in relay.address:
+ tracked_address_ranges[relay.address] = relay
+ else:
+ tracked_addresses[relay.address] = relay
+
+ if relay.fingerprint:
+ tracked_fingerprints[relay.fingerprint] = relay
+
+ downloader = stem.descriptor.remote.DescriptorDownloader()
+ found_relays = {} # mapping of TrackedRelay => RouterStatusEntry
+
+ for desc in downloader.get_consensus():
+ if desc.address in tracked_addresses:
+ found_relays[tracked_addresses[desc.address]] = desc
+ elif desc.fingerprint in tracked_fingerprints:
+ found_relays[tracked_fingerprints[desc.fingerprint]] = desc
+ else:
+ pass # TODO: implement for tracked_address_ranges
+
+ if found_relays:
+ log.debug("Sending a notification for %i relay entries..." % len(found_relays))
+ body = EMAIL_BODY
+
+ for tracked_relay, desc in found_relays.items():
+ log.debug('* %s' % tracked_relay)
+ body += '* %s (%s)\n' % (tracked_relay.identifier, tracked_relay.description)
+ body += ' address: %s\n' % desc.address
+ body += ' fingerprint: %s\n\n' % desc.fingerprint
+
+ util.send(EMAIL_SUBJECT, body = body, to = ['bad-relays at lists.torproject.org', 'atagar at torproject.org'])
+
+
+if __name__ == '__main__':
+ try:
+ main()
+ except:
+ msg = "track_relays.py failed with:\n\n%s" % traceback.format_exc()
+ log.error(msg)
+ util.send("Script Error", body = msg, to = [util.ERROR_ADDRESS])
More information about the tor-commits
mailing list