-
Notifications
You must be signed in to change notification settings - Fork 7
/
sybil_checker.py
executable file
·144 lines (101 loc) · 4.02 KB
/
sybil_checker.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env python
# Copyright 2013-2019, Damian Johnson and The Tor Project
# See LICENSE for licensing information
"""
Simple script that checks to see if there has been a sudden influx of new
relays. If so then this sends an email notification.
"""
import os
import time
import traceback
import util
from stem.descriptor.remote import DescriptorDownloader
EMAIL_SUBJECT = 'Possible Sybil Attack'
EMAIL_BODY = """\
Over the last hour %i new relays have appeared. New additions are...
"""
RELAY_ENTRY = """\
* %s (%s)
Address: %s:%i
Version: %s
Exit Policy: %s
"""
FINGERPRINTS_FILE = util.get_path('data', 'fingerprints')
log = util.get_logger('sybil_checker')
def main():
prior_fingerprints = load_fingerprints()
downloader = DescriptorDownloader(timeout = 60, validate = True)
dry_run = False
if not prior_fingerprints:
log.debug("We don't have any existing fingerprints so this will be a dry-run. No notifications will be sent.")
dry_run = True
else:
last_modified = os.stat(FINGERPRINTS_FILE).st_mtime # unix timestamp for when it was last modified
seconds_ago = int(time.time() - last_modified)
log.debug("Our fingerprint was last modified at %s (%i seconds ago)." % (time.ctime(last_modified), seconds_ago))
if seconds_ago > (3 * 60 * 60):
log.debug("Fingerprint file was last modified over three hours ago. No notifications will be sent for this run.")
dry_run = True
query = downloader.get_consensus()
query.run(True)
if query.error:
log.warn("Unable to retrieve the consensus: %s" % query.error)
return
# mapping of fingerprints to their router status entry
relays = dict((entry.fingerprint, entry) for entry in query)
current_fingerprints = set(relays.keys())
new_fingerprints = current_fingerprints.difference(prior_fingerprints)
log.debug("%i new relays found" % len(new_fingerprints))
if not dry_run and len(new_fingerprints) >= 50:
log.debug("Sending a notification...")
send_email([relays[fp] for fp in new_fingerprints])
save_fingerprints(prior_fingerprints.union(current_fingerprints))
def send_email(new_relays):
# Constructs a mapping of nicknames to router status entries so we can
# provide a listing that's sorted by nicknames.
nickname_to_relays = {}
for entry in new_relays:
nickname_to_relays.setdefault(entry.nickname, []).append(entry)
relay_entries = []
for nickname in sorted(nickname_to_relays.keys()):
for relay in nickname_to_relays[nickname]:
relay_entries.append(RELAY_ENTRY % (relay.nickname, relay.fingerprint, relay.address, relay.or_port, relay.version, relay.exit_policy))
try:
body = EMAIL_BODY % len(new_relays)
body += "\n".join(relay_entries)
util.send(EMAIL_SUBJECT, body = body)
except Exception as exc:
log.warn("Unable to send email: %s" % exc)
def load_fingerprints():
log.debug("Loading fingerprints...")
if not os.path.exists(FINGERPRINTS_FILE):
log.debug(" '%s' doesn't exist" % FINGERPRINTS_FILE)
return set()
try:
with open(FINGERPRINTS_FILE) as fingerprint_file:
fingerprints = fingerprint_file.read().strip()
if not fingerprints:
log.debug(" '%s' is empty" % FINGERPRINTS_FILE)
return set()
fingerprints = fingerprints.splitlines()
log.debug(" %i fingerprints found" % len(fingerprints))
return set(fingerprints)
except Exception as exc:
log.debug(" unable to read '%s': %s" % (FINGERPRINTS_FILE, exc))
return set()
def save_fingerprints(fingerprints):
data_dir = util.get_path('data')
try:
if not os.path.exists(data_dir):
os.mkdir(data_dir)
with open(FINGERPRINTS_FILE, 'w') as fingerprint_file:
fingerprint_file.write('\n'.join(fingerprints))
except Exception as exc:
log.debug("Unable to save fingerprints to '%s': %s" % (FINGERPRINTS_FILE, exc))
if __name__ == '__main__':
try:
main()
except:
msg = "sybil_checker.py failed with:\n\n%s" % traceback.format_exc()
log.error(msg)
util.send("Script Error", body = msg, to = [util.ERROR_ADDRESS])