Avoid giving out bridges with broken tor versions.
[tor-bridgedb.git] / scripts / create_descriptors
blob60022db349263333e93a43d2f333ef053e332964
1 #!/usr/bin/env python3
3 # This file is part of BridgeDB, a Tor bridge distribution system.
5 import os
6 import random
7 import sys
8 import time
9 import ipaddress
10 import math
11 import argparse
12 import hashlib
14 # A bunch of Tor version numbers.
15 SERVER_VERSIONS = ["0.2.2.39",
16                    "0.2.3.24-rc",
17                    "0.2.3.25",
18                    "0.2.4.5-alpha",
19                    "0.2.4.6-alpha",
20                    "0.2.4.7-alpha",
21                    "0.2.4.8-alpha",
22                    "0.2.4.9-alpha",
23                    "0.2.4.10-alpha",
24                    "0.2.4.11-alpha",
25                    "0.2.4.12-alpha",
26                    "0.2.4.14-alpha",
27                    "0.2.4.15-rc",
28                    "0.2.4.16-rc",
29                    "0.2.4.17-rc",
30                    "0.2.4.18-rc",
31                    "0.2.4.19",
32                    "0.2.4.20",
33                    "0.2.5.1-alpha",
34                    ]
36 try:
37     import stem
38     import stem.descriptor
39     from stem.descriptor.server_descriptor import RelayDescriptor
40     from stem.descriptor.extrainfo_descriptor import RelayExtraInfoDescriptor
41     from stem.descriptor.networkstatus import NetworkStatusDocumentV3
42 except ImportError:
43     print("Creating descriptors requires stem <https://stem.torproject.org>")
44     sys.exit(1)
46 if not hasattr(stem.descriptor, "create_signing_key"):
47     print("This requires stem version 1.6 or later but you are running "
48           "version %s" % stem.__version__)
49     sys.exit(1)
52 def make_output_dir():
53     if not os.path.exists(os.getcwd()):
54         os.mkdir(os.getcwd())
57 def write_descriptors(descs, filename):
58     make_output_dir()
59     with open(os.path.join(os.getcwd(), filename), "w") as descriptor_file:
60         for descriptor in descs:
61             descriptor_file.write(str(descriptor))
64 def write_descriptor(desc, filename):
65     make_output_dir()
66     with open(os.path.join(os.getcwd(), filename), "w") as descriptor_file:
67         descriptor_file.write(str(desc))
70 def check_ip_validity(ip):
71     if (ip.is_link_local or
72         ip.is_loopback or
73         ip.is_multicast or
74         ip.is_private or
75         ip.is_unspecified or
76        ((ip.version == 6) and ip.is_site_local) or
77        ((ip.version == 4) and ip.is_reserved)):
78         return False
79     return True
82 def get_transport_line(probing_resistant, addr, port):
83     """
84     If probing_resistant is True, add a transport protocol that's resistant to
85     active probing attacks.
86     """
88     # Make sure that we won't end up with a negative port.
89     if port <= 21:
90         port = 21
92     transports = []
93     if probing_resistant:
94         transports.append("obfs2 %s:%s" % (addr, port-10))
95         iat_mode = random.randint(0, 1)
96         node_id = hashlib.sha1(bytes(random.getrandbits(8))).hexdigest()
97         public_key = hashlib.sha256(bytes(random.getrandbits(8))).hexdigest()
98         transports.append("obfs4 %s:%s iat-mode=%s,node-id=%s,public-key=%s" %
99                           (addr, port-20, iat_mode, node_id, public_key))
101         # Always include obfs4 and occasionally include scramblesuit.
103         if random.randint(0, 1) > 0:
104             transports.append("scramblesuit 216.117.3.62:63174 "
105                               "password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")
106     else:
107         transports.append("obfs2 %s:%s" % (addr, port-10))
108         transports.append("obfs3 %s:%s" % (addr, port-20))
110     return "\ntransport ".join(transports)
113 def get_hex_string(size):
114     hexstr = ""
115     for _ in range(size):
116         hexstr += random.choice("ABCDEF0123456789")
117     return hexstr
120 def get_random_addr(ip_version=4):
121     valid_addr = None
122     while not valid_addr:
123         if ip_version == 4:
124             maybe = ipaddress.IPv4Address(random.getrandbits(32))
125         else:
126             maybe = ipaddress.IPv6Address(random.getrandbits(128))
127         valid = check_ip_validity(maybe)
128         if valid:
129             valid_addr = maybe
130             break
131     return str(valid_addr)
134 def get_protocol(tor_version):
135     line = ""
136     if tor_version is not None:
137         line += "opt "
138     line += "protocols Link 1 2 Circuit 1"
139     return line
142 def make_timestamp(now=None, fmt=None, variation=False, period=None):
143     now = int(now) if now is not None else int(time.time())
144     fmt = fmt if fmt else "%Y-%m-%d %H:%M:%S"
146     if variation:
147         then = 1
148         if period is not None:
149             secs = int(period) * 3600
150             then = now - secs
151         # Get a random number between one epochseconds number and another
152         diff = random.randint(then, now)
153         # Then rewind the clock
154         now = diff
156     return time.strftime(fmt, time.localtime(now))
159 def make_bandwidth(variance=30):
160     observed = random.randint(20 * 2**10, 2 * 2**30)
161     percentage = float(variance) / 100.
162     burst = int(observed + math.ceil(observed * percentage))
163     bandwidths = [burst, observed]
164     nitems = len(bandwidths) if (len(bandwidths) > 0) else float("nan")
165     avg = int(math.ceil(float(sum(bandwidths)) / nitems))
166     return "%s %s %s" % (avg, burst, observed)
169 def make_bridge_distribution_request():
171     methods = [
172         "any",
173         "none",
174         "https",
175         "email",
176         "moat",
177     ]
179     return random.choice(methods)
182 def create_server_desc(signing_key):
183     """
184     Create and return a server descriptor.
185     """
187     nickname = ("Unnamed%i" % random.randint(0, 100000000000000))[:19]
189     # We start at port 10 because we subtract from this port to get port
190     # numbers for IPv6 and obfuscation protocols.
192     port = random.randint(10, 65535)
193     tor_version = random.choice(SERVER_VERSIONS)
194     timestamp = make_timestamp(variation=True, period=36)
196     server_desc = RelayDescriptor.create({
197         "router": "%s %s %s 0 0" % (nickname, get_random_addr(ip_version=4), port),
198         "or-address": "[%s]:%s" % (get_random_addr(ip_version=6), port-1),
199         "platform": "Tor %s on Linux" % tor_version,
200         get_protocol(tor_version): "",
201         "published": timestamp,
202         "uptime": str(int(random.randint(1800, 63072000))),
203         "bandwidth": make_bandwidth(),
204         "contact": "Somebody <somebody@example.com>",
205         "bridge-distribution-request": make_bridge_distribution_request(),
206         "reject": "*:*",
207     }, signing_key=signing_key)
209     return server_desc
212 def create_extrainfo_desc(server_desc, signing_key, probing_resistant):
213     """
214     Create and return an extrainfo descriptor.
215     """
217     ts = server_desc.published
219     extrainfo_desc = RelayExtraInfoDescriptor.create({
220         "extra-info": "%s %s" % (server_desc.nickname,
221                                  server_desc.fingerprint),
222         "transport": get_transport_line(probing_resistant,
223                                         server_desc.address,
224                                         server_desc.or_port),
225         "write-history": "%s (900 s) 3188736,2226176,2866176" % ts,
226         "read-history": "%s (900 s) 3891200,2483200,2698240" % ts,
227         "dirreq-write-history": "%s (900 s) 1024,0,2048" % ts,
228         "dirreq-read-history": "%s (900 s) 0,0,0" % ts,
229         "geoip-db-digest": "%s" % get_hex_string(40),
230         "geoip6-db-digest": "%s" % get_hex_string(40),
231         "dirreq-stats-end": "%s (86400 s)" % ts,
232         "dirreq-v3-ips": "",
233         "dirreq-v3-reqs": "",
234         "dirreq-v3-resp": "ok=16,not-enough-sigs=0,unavailable=0,"
235                           "not-found=0,not-modified=0,busy=0",
236         "dirreq-v3-direct-dl": "complete=0,timeout=0,running=0",
237         "dirreq-v3-tunneled-dl": "complete=12,timeout=0,running=0",
238         "bridge-stats-end": "%s (86400 s)" % ts,
239         "bridge-ips": "ca=8",
240         "bridge-ip-versions": "v4=8,v6=0",
241         "bridge-ip-transports": "<OR>=8",
242     }, signing_key=signing_key)
244     return extrainfo_desc
247 def make_descriptors(count, num_probing_resistant):
248     """
249     Create fake descriptors and write them to the working directory.
250     """
252     consensus_entries = []
253     server_descriptors = []
254     extrainfos_old = []
255     extrainfos_new = []
257     for i in range(count):
258         signing_key = stem.descriptor.create_signing_key()
260         server_desc = create_server_desc(signing_key)
261         server_descriptors.append(server_desc)
262         consensus_entries.append(server_desc.make_router_status_entry())
264         extrainfo_desc = create_extrainfo_desc(server_desc,
265                                                signing_key,
266                                                num_probing_resistant > 0)
267         if random.random() > 0.75:
268             extrainfos_new.append(extrainfo_desc)
269         else:
270             extrainfos_old.append(extrainfo_desc)
272         if num_probing_resistant > 0:
273             num_probing_resistant -= 1
275     consensus = NetworkStatusDocumentV3.create(routers=consensus_entries)
276     write_descriptor(consensus, "networkstatus-bridges")
277     write_descriptors(server_descriptors, "bridge-descriptors")
278     write_descriptors(extrainfos_old, "cached-extrainfo")
279     write_descriptors(extrainfos_new, "cached-extrainfo.new")
282 if __name__ == "__main__":
284     parser = argparse.ArgumentParser(description="Create fake descriptors.")
285     parser.add_argument("num_descs",
286                         type=int,
287                         help="The number of descriptors to create.")
288     parser.add_argument("--num-resistant-descs",
289                         dest="num_resistant_descs",
290                         type=int,
291                         default=-1,
292                         help="The number of active probing-resistant "
293                              "descriptors to create")
294     args = parser.parse_args()
295     if args.num_resistant_descs == -1:
296         args.num_resistant_descs = args.num_descs
298     make_descriptors(args.num_descs, args.num_resistant_descs)