[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