relay.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. #!/usr/bin/env python3
  2. import random # For simulation, not cryptography!
  3. import math
  4. import nacl.utils
  5. import nacl.signing
  6. import nacl.public
  7. import nacl.hash
  8. import network
  9. import dirauth
  10. class RelayNetMsg(network.NetMsg):
  11. """The subclass of NetMsg for messages between relays and either
  12. relays or clients."""
  13. class RelayGetConsensusMsg(RelayNetMsg):
  14. """The subclass of RelayNetMsg for fetching the consensus."""
  15. class RelayConsensusMsg(RelayNetMsg):
  16. """The subclass of RelayNetMsg for returning the consensus."""
  17. def __init__(self, consensus):
  18. self.consensus = consensus
  19. class RelayRandomHopMsg(RelayNetMsg):
  20. """A message used for testing, that hops from relay to relay
  21. randomly until its TTL expires."""
  22. def __init__(self, ttl):
  23. self.ttl = ttl
  24. def __str__(self):
  25. return "RandomHop TTL=%d" % self.ttl
  26. class CircuitCellMsg(RelayNetMsg):
  27. """Send a message tagged with a circuit id."""
  28. def __init__(self, circuitid, cell):
  29. self.circid = circuitid
  30. self.cell = cell
  31. def __str__(self):
  32. return "C%d:%s" % (self.circid, self.cell)
  33. def size(self):
  34. # circuitids are 4 bytes
  35. return 4 + self.cell.size()
  36. class RelayCell(RelayNetMsg):
  37. """All cells (which are sent inside a CircuitCellMsg, and so do not
  38. need their own circuitid) should be a subclass of this class."""
  39. class StringCell(RelayCell):
  40. """Send an arbitrary string as a cell."""
  41. def __init__(self, str):
  42. self.data = str
  43. def __str__(self):
  44. return self.data.__str__()
  45. class VanillaCreateCircuitMsg(RelayNetMsg):
  46. """The message for requesting circuit creation in Vanilla Onion
  47. Routing."""
  48. def __init__(self, circid, ntor_request):
  49. self.circid = circid
  50. self.ntor_request = ntor_request
  51. class VanillaCreatedCircuitCell(RelayCell):
  52. """The message for responding to circuit creation in Vanilla Onion
  53. Routing."""
  54. def __init__(self, ntor_reply):
  55. self.ntor_reply = ntor_reply
  56. class VanillaExtendCircuitCell(RelayCell):
  57. """The message for requesting circuit creation in Vanilla Onion
  58. Routing."""
  59. def __init__(self, hopaddr, ntor_request):
  60. self.hopaddr = hopaddr
  61. self.ntor_request = ntor_request
  62. class VanillaExtendedCircuitCell(RelayCell):
  63. """The message for responding to circuit creation in Vanilla Onion
  64. Routing."""
  65. def __init__(self, ntor_reply):
  66. self.ntor_reply = ntor_reply
  67. class EncryptedCell(RelayCell):
  68. """Send a message encrypted with a symmetric key. In this
  69. implementation, the encryption is not really done. A hash of the
  70. key is stored with the message so that it can be checked at
  71. decryption time."""
  72. def __init__(self, key, msg):
  73. self.keyhash = nacl.hash.sha256(key)
  74. self.plaintext = msg
  75. def decrypt(self, key):
  76. keyhash = nacl.hash.sha256(key)
  77. if keyhash != self.keyhash:
  78. raise ValueError("EncryptedCell key mismatch")
  79. return self.plaintext
  80. def size(self):
  81. # Current Tor actually has no overhead for encryption
  82. return self.plaintext.size()
  83. class RelayFallbackTerminationError(Exception):
  84. """An exception raised when someone tries to terminate a fallback
  85. relay."""
  86. class NTor:
  87. """A class implementing the ntor one-way authenticated key agreement
  88. scheme. The details are not exactly the same as either the ntor
  89. paper or Tor's implementation, but it will agree on keys and have
  90. the same number of public key operations."""
  91. def __init__(self, perfstats):
  92. self.perfstats = perfstats
  93. def request(self):
  94. """Create the ntor request message: X = g^x."""
  95. self.client_ephem_key = nacl.public.PrivateKey.generate()
  96. self.perfstats.keygens += 1
  97. return self.client_ephem_key.public_key
  98. @staticmethod
  99. def reply(onion_privkey, idpubkey, client_pubkey, perfstats):
  100. """The server calls this static method to produce the ntor reply
  101. message: (Y = g^y, B = g^b, A = H(M, "verify")) and the shared
  102. secret S = H(M, "secret") for M = (X^y,X^b,ID,B,X,Y)."""
  103. server_ephem_key = nacl.public.PrivateKey.generate()
  104. perfstats.keygens += 1
  105. xykey = nacl.public.Box(server_ephem_key, client_pubkey).shared_key()
  106. xbkey = nacl.public.Box(onion_privkey, client_pubkey).shared_key()
  107. perfstats.dhs += 2
  108. M = xykey + xbkey + \
  109. idpubkey.encode(encoder=nacl.encoding.RawEncoder) + \
  110. onion_privkey.public_key.encode(encoder=nacl.encoding.RawEncoder) + \
  111. server_ephem_key.public_key.encode(encoder=nacl.encoding.RawEncoder)
  112. A = nacl.hash.sha256(M + b'verify', encoder=nacl.encoding.RawEncoder)
  113. S = nacl.hash.sha256(M + b'secret', encoder=nacl.encoding.RawEncoder)
  114. return ((server_ephem_key.public_key, onion_privkey.public_key, A), S)
  115. def verify(self, reply, onion_pubkey, idpubkey):
  116. """The client calls this method to verify the ntor reply
  117. message, passing the onion and id public keys for the server
  118. it's expecting to be talking to . Returns the shared secret on
  119. success, or raises ValueError on failure."""
  120. server_ephem_pubkey, server_onion_pubkey, authtag = reply
  121. if onion_pubkey != server_onion_pubkey:
  122. raise ValueError("NTor onion pubkey mismatch")
  123. xykey = nacl.public.Box(self.client_ephem_key, server_ephem_pubkey).shared_key()
  124. xbkey = nacl.public.Box(self.client_ephem_key, onion_pubkey).shared_key()
  125. self.perfstats.dhs += 2
  126. M = xykey + xbkey + \
  127. idpubkey.encode(encoder=nacl.encoding.RawEncoder) + \
  128. onion_pubkey.encode(encoder=nacl.encoding.RawEncoder) + \
  129. server_ephem_pubkey.encode(encoder=nacl.encoding.RawEncoder)
  130. Acheck = nacl.hash.sha256(M + b'verify', encoder=nacl.encoding.RawEncoder)
  131. S = nacl.hash.sha256(M + b'secret', encoder=nacl.encoding.RawEncoder)
  132. if Acheck != authtag:
  133. raise ValueError("NTor auth mismatch")
  134. return S
  135. class VanillaExtendCircuitHandler:
  136. """A handler for VanillaExtendCircuitCell cells. It allocates a new
  137. circuit id on the Channel to the requested next hop, connects the
  138. existing and new circuits together, and forwards a
  139. VanillaCreateCircuitMsg to the next hop."""
  140. def received_cell(self, circhandler, cell):
  141. # Remove ourselves from handling a second
  142. # VanillaExtendCircuitCell on this circuit
  143. circhandler.replace_celltype_handler(VanillaExtendCircuitCell, None)
  144. # Allocate a new circuit id to the requested next hop
  145. channelmgr = circhandler.channel.channelmgr
  146. nexthopchannel = channelmgr.get_channel_to(cell.hopaddr)
  147. newcircid, newcirchandler = nexthopchannel.new_circuit()
  148. # Connect the existing and new circuits together
  149. circhandler.adjacent_circuit_handler = newcirchandler
  150. newcirchandler.adjacent_circuit_handler = circhandler
  151. # Set up a handler for when the VanillaCreatedCircuitCell comes
  152. # back
  153. newcirchandler.replace_celltype_handler(
  154. VanillaCreatedCircuitCell,
  155. VanillaCreatedRelayHandler())
  156. # Forward a VanillaCreateCircuitMsg to the next hop
  157. nexthopchannel.send_msg(
  158. VanillaCreateCircuitMsg(newcircid, cell.ntor_request))
  159. class VanillaCreatedRelayHandler:
  160. """Handle a VanillaCreatedCircuitCell received by a _relay_ that
  161. recently received a VanillaExtendCircuitCell from a client, and so
  162. forwarded a VanillaCreateCircuitCell to the next hop."""
  163. def received_cell(self, circhandler, cell):
  164. # Remove ourselves from handling a second
  165. # VanillaCreatedCircuitCell on this circuit
  166. circhandler.replace_celltype_handler(VanillaCreatedCircuitCell, None)
  167. # Just forward a VanillaExtendedCircuitCell back towards the
  168. # client
  169. circhandler.adjacent_circuit_handler.send_cell(
  170. VanillaExtendedCircuitCell(cell.ntor_reply))
  171. class CircuitHandler:
  172. """A class for managing sending and receiving encrypted cells on a
  173. particular circuit."""
  174. class NoCryptLayer:
  175. def encrypt_msg(self, msg): return msg
  176. def decrypt_msg(self, msg): return msg
  177. class CryptLayer:
  178. def __init__(self, enckey, deckey, next_layer):
  179. self.enckey = enckey
  180. self.deckey = deckey
  181. self.next_layer = next_layer
  182. def encrypt_msg(self, msg):
  183. return self.next_layer.encrypt_msg(EncryptedCell(self.enckey, msg))
  184. def decrypt_msg(self, msg):
  185. return self.next_layer.decrypt_msg(msg).decrypt(self.deckey)
  186. def __init__(self, channel, circid):
  187. self.channel = channel
  188. self.circid = circid
  189. # The list of relay descriptors that form the circuit so far
  190. # (client side only)
  191. self.circuit_descs = []
  192. # The dispatch table is indexed by type, and the values are
  193. # objects with received_cell(circhandler, cell) methods.
  194. self.cell_dispatch_table = dict()
  195. # The topmost crypt layer. This is an object with
  196. # encrypt_msg(msg) and decrypt_msg(msg) methods that returns the
  197. # en/decrypted messages respectively. Messages are encrypted
  198. # starting with the last layer that was added (the keys for the
  199. # furthest away relay in the circuit) and are decrypted starting
  200. # with the first layer that was added (the keys for the guard).
  201. self.crypt_layer = self.NoCryptLayer()
  202. # The adjacent CircuitHandler that's connected to this one. If
  203. # we get a cell on one, we forward it to the other (if it's not
  204. # meant for us to handle directly)
  205. self.adjacent_circuit_handler = None
  206. def send_cell(self, cell):
  207. """Send a cell on this circuit, encrypting as needed."""
  208. self.channel_send_cell(self.crypt_layer.encrypt_msg(cell))
  209. def channel_send_cell(self, cell):
  210. """Send a cell on this circuit directly without encrypting it
  211. first."""
  212. self.channel.send_msg(CircuitCellMsg(self.circid, cell))
  213. def received_cell(self, cell):
  214. """A cell has been received on this circuit. Dispatch it
  215. according to its type."""
  216. if isinstance(cell, EncryptedCell):
  217. cell = self.crypt_layer.decrypt_msg(cell)
  218. print("CircuitHandler: %s received cell %s on circuit %d from %s" % (self.channel.channelmgr.myaddr, cell, self.circid, self.channel.peer.channelmgr.myaddr))
  219. # If it's still encrypted, it's for sure meant for forwarding to
  220. # our adjacent hop, which had better exist.
  221. if isinstance(cell, EncryptedCell):
  222. self.adjacent_circuit_handler.send_cell(cell)
  223. else:
  224. # This is a plaintext cell meant for us. Handle it
  225. # according to the table.
  226. celltype = type(cell)
  227. if celltype in self.cell_dispatch_table:
  228. self.cell_dispatch_table[celltype].received_cell(self, cell)
  229. elif isinstance(cell, StringCell):
  230. # Default handler; just print the message in the cell
  231. print("CircuitHandler: %s received '%s' on circuit %d from %s" \
  232. % (self.channel.channelmgr.myaddr, cell,
  233. self.circid, self.channel.peer.channelmgr.myaddr))
  234. else:
  235. # I don't know how to handle this cell?
  236. raise ValueError("CircuitHandler: %s received unknown cell type %s on circuit %d from %s" \
  237. % (self.channel.channelmgr.myaddr, cell,
  238. self.circid, self.channel.peer.channelmgr.myaddr))
  239. def replace_celltype_handler(self, celltype, handler):
  240. """Add an object with a received_cell(circhandler, cell) method
  241. to the cell dispatch table. It replaces anything that's already
  242. there. Passing None as the handler removes the dispatcher for
  243. that cell type."""
  244. if handler is None:
  245. del self.cell_dispatch_table[celltype]
  246. else:
  247. self.cell_dispatch_table[celltype] = handler
  248. def add_crypt_layer(self, enckey, deckey):
  249. """Add a processing layer to this CircuitHandler so that cells
  250. we send will get encrypted with the first given key, and cells
  251. we receive will be decrypted with the other given key."""
  252. current_crypt_layer = self.crypt_layer
  253. self.crypt_layer = self.CryptLayer(enckey, deckey, current_crypt_layer)
  254. class Channel(network.Connection):
  255. """A class representing a channel between a relay and either a
  256. client or a relay, transporting cells from various circuits."""
  257. def __init__(self):
  258. super().__init__()
  259. # The RelayChannelManager managing this Channel
  260. self.channelmgr = None
  261. # The Channel at the other end
  262. self.peer = None
  263. # The function to call when the connection closes
  264. self.closer = lambda: 0
  265. # The next circuit id to use on this channel. The party that
  266. # opened the channel uses even numbers; the receiving party uses
  267. # odd numbers.
  268. self.next_circid = None
  269. # A map for CircuitHandlers to use for each open circuit on the
  270. # channel
  271. self.circuithandlers = dict()
  272. def closed(self):
  273. self.closer()
  274. self.peer = None
  275. def close(self):
  276. if self.peer is not None and self.peer is not self:
  277. self.peer.closed()
  278. self.closed()
  279. def new_circuit(self):
  280. """Allocate a new circuit on this channel, returning the new
  281. circuit's id and the new CircuitHandler."""
  282. circid = self.next_circid
  283. self.next_circid += 2
  284. circuithandler = CircuitHandler(self, circid)
  285. self.circuithandlers[circid] = circuithandler
  286. return circid, circuithandler
  287. def new_circuit_with_circid(self, circid):
  288. """Allocate a new circuit on this channel, with the circuit id
  289. received from our peer. Return the new CircuitHandler"""
  290. circuithandler = CircuitHandler(self, circid)
  291. self.circuithandlers[circid] = circuithandler
  292. return circuithandler
  293. def send_cell(self, circid, cell):
  294. """Send the given message on the given circuit, encrypting or
  295. decrypting as needed."""
  296. self.circuithandlers[circid].send_cell(cell)
  297. def send_raw_cell(self, circid, cell):
  298. """Send the given message, tagged for the given circuit id. No
  299. encryption or decryption is done."""
  300. self.send_msg(CircuitCellMsg(self.circid, self.cell))
  301. def send_msg(self, msg):
  302. """Send the given NetMsg on the channel."""
  303. self.channelmgr.perfstats.bytes_sent += msg.size()
  304. self.peer.received(self.channelmgr.myaddr, msg)
  305. def received(self, peeraddr, msg):
  306. """Callback when a message is received from the network."""
  307. print('Channel: %s received %s from %s' % (self.channelmgr.myaddr, msg, peeraddr))
  308. self.channelmgr.perfstats.bytes_received += msg.size()
  309. if isinstance(msg, CircuitCellMsg):
  310. circid, cell = msg.circid, msg.cell
  311. self.circuithandlers[circid].received_cell(cell)
  312. else:
  313. self.channelmgr.received_msg(msg, peeraddr, self)
  314. class ChannelManager:
  315. """The class that manages the channels to other relays and clients.
  316. Relays and clients both use subclasses of this class to both create
  317. on-demand channels to relays, to gracefully handle the closing of
  318. channels, and to handle commands received over the channels."""
  319. def __init__(self, myaddr, dirauthaddrs, perfstats):
  320. # A dictionary of Channels to other hosts, indexed by NetAddr
  321. self.channels = dict()
  322. self.myaddr = myaddr
  323. self.dirauthaddrs = dirauthaddrs
  324. self.consensus = None
  325. self.perfstats = perfstats
  326. def terminate(self):
  327. """Close all connections we're managing."""
  328. while self.channels:
  329. channelitems = iter(self.channels.items())
  330. addr, channel = next(channelitems)
  331. print('closing channel', addr, channel)
  332. channel.close()
  333. def add_channel(self, channel, peeraddr):
  334. """Add the given channel to the list of channels we are
  335. managing. If we are already managing a channel to the same
  336. peer, close it first."""
  337. if peeraddr in self.channels:
  338. self.channels[peeraddr].close()
  339. channel.channelmgr = self
  340. self.channels[peeraddr] = channel
  341. channel.closer = lambda: self.channels.pop(peeraddr)
  342. def get_channel_to(self, addr):
  343. """Get the Channel connected to the given NetAddr, creating one
  344. if none exists right now."""
  345. if addr in self.channels:
  346. return self.channels[addr]
  347. # Create the new channel
  348. newchannel = network.thenetwork.connect(self.myaddr, addr,
  349. self.perfstats)
  350. self.channels[addr] = newchannel
  351. newchannel.closer = lambda: self.channels.pop(addr)
  352. newchannel.channelmgr = self
  353. return newchannel
  354. def received_msg(self, msg, peeraddr, channel):
  355. """Callback when a NetMsg not specific to a circuit is
  356. received."""
  357. print("ChannelManager: Node %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
  358. def received_cell(self, circid, cell, peeraddr, channel):
  359. """Callback with a circuit-specific cell is received."""
  360. print("ChannelManager: Node %s received cell on circ %d: %s from %s" % (self.myaddr, circid, cell, peeraddr))
  361. def send_msg(self, msg, peeraddr):
  362. """Send a message to the peer with the given address."""
  363. channel = self.get_channel_to(peeraddr)
  364. channel.send_msg(msg)
  365. def send_cell(self, circid, cell, peeraddr):
  366. """Send a cell on the given circuit to the peer with the given
  367. address."""
  368. channel = self.get_channel_to(peeraddr)
  369. channel.send_cell(circid, cell)
  370. class RelayChannelManager(ChannelManager):
  371. """The subclass of ChannelManager for relays."""
  372. def __init__(self, myaddr, dirauthaddrs, onionprivkey, idpubkey, perfstats):
  373. super().__init__(myaddr, dirauthaddrs, perfstats)
  374. self.onionkey = onionprivkey
  375. self.idpubkey = idpubkey
  376. def get_consensus(self):
  377. """Download a fresh consensus from a random dirauth."""
  378. a = random.choice(self.dirauthaddrs)
  379. c = network.thenetwork.connect(self, a, self.perfstats)
  380. self.consensus = c.getconsensus()
  381. dirauth.Consensus.verify(self.consensus,
  382. network.thenetwork.dirauthkeys(), self.perfstats)
  383. c.close()
  384. def received_msg(self, msg, peeraddr, channel):
  385. """Callback when a NetMsg not specific to a circuit is
  386. received."""
  387. print("RelayChannelManager: Node %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
  388. if isinstance(msg, RelayRandomHopMsg):
  389. if msg.ttl > 0:
  390. # Pick a random next hop from the consensus
  391. nexthop = random.choice(self.consensus.consdict['relays'])
  392. nextaddr = nexthop.descdict['addr']
  393. self.send_msg(RelayRandomHopMsg(msg.ttl-1), nextaddr)
  394. elif isinstance(msg, RelayGetConsensusMsg):
  395. self.send_msg(RelayConsensusMsg(self.consensus), peeraddr)
  396. elif isinstance(msg, VanillaCreateCircuitMsg):
  397. # A new circuit has arrived
  398. circhandler = channel.new_circuit_with_circid(msg.circid)
  399. # Create the ntor reply
  400. reply, secret = NTor.reply(self.onionkey, self.idpubkey,
  401. msg.ntor_request, self.perfstats)
  402. # Set up the circuit to use the shared secret
  403. enckey = nacl.hash.sha256(secret + b'downstream')
  404. deckey = nacl.hash.sha256(secret + b'upstream')
  405. circhandler.add_crypt_layer(enckey, deckey)
  406. # Add a handler for if an Extend Cell arrives (there should
  407. # be at most one on this circuit).
  408. circhandler.replace_celltype_handler(
  409. VanillaExtendCircuitCell, VanillaExtendCircuitHandler())
  410. # Send the ntor reply
  411. self.send_msg(CircuitCellMsg(msg.circid,
  412. VanillaCreatedCircuitCell(reply)), peeraddr)
  413. else:
  414. return super().received_msg(msg, peeraddr, channel)
  415. def received_cell(self, circid, cell, peeraddr, channel):
  416. """Callback with a circuit-specific cell is received."""
  417. print("RelayChannelManager: Node %s received cell on circ %d: %s from %s" % (self.myaddr, circid, cell, peeraddr))
  418. return super().received_cell(circid, cell, peeraddr, channel)
  419. class Relay(network.Server):
  420. """The class representing an onion relay."""
  421. def __init__(self, dirauthaddrs, bw, flags):
  422. # Gather performance statistics
  423. self.perfstats = network.PerfStats(network.EntType.RELAY)
  424. self.perfstats.is_bootstrapping = True
  425. # Create the identity and onion keys
  426. self.idkey = nacl.signing.SigningKey.generate()
  427. self.onionkey = nacl.public.PrivateKey.generate()
  428. self.perfstats.keygens += 2
  429. self.name = self.idkey.verify_key.encode(encoder=nacl.encoding.HexEncoder).decode("ascii")
  430. # Bind to the network to get a network address
  431. self.netaddr = network.thenetwork.bind(self)
  432. self.perfstats.name = "Relay at %s" % self.netaddr
  433. # Our bandwidth and flags
  434. self.bw = bw
  435. self.flags = flags
  436. # Register for epoch change notification
  437. network.thenetwork.wantepochticks(self, True, end=True)
  438. network.thenetwork.wantepochticks(self, True)
  439. # Create the RelayChannelManager connection manager
  440. self.channelmgr = RelayChannelManager(self.netaddr, dirauthaddrs,
  441. self.onionkey, self.idkey.verify_key, self.perfstats)
  442. # Initially, we're not a fallback relay
  443. self.is_fallbackrelay = False
  444. self.uploaddesc()
  445. def terminate(self):
  446. """Stop this relay."""
  447. if self.is_fallbackrelay:
  448. # Fallback relays must not (for now) terminate
  449. raise RelayFallbackTerminationError(self)
  450. # Stop listening for epoch ticks
  451. network.thenetwork.wantepochticks(self, False, end=True)
  452. network.thenetwork.wantepochticks(self, False)
  453. # Tell the dirauths we're going away
  454. self.uploaddesc(False)
  455. # Close connections to other relays
  456. self.channelmgr.terminate()
  457. # Stop listening to our own bound port
  458. self.close()
  459. def set_is_fallbackrelay(self, isfallback = True):
  460. """Set this relay to be a fallback relay (or unset if passed
  461. False)."""
  462. self.is_fallbackrelay = isfallback
  463. def epoch_ending(self, epoch):
  464. # Download the new consensus, which will have been created
  465. # already since the dirauths' epoch_ending callbacks happened
  466. # before the relays'.
  467. self.channelmgr.get_consensus()
  468. def newepoch(self, epoch):
  469. self.uploaddesc()
  470. def uploaddesc(self, upload=True):
  471. # Upload the descriptor for the epoch to come, or delete a
  472. # previous upload if upload=False
  473. descdict = dict();
  474. descdict["epoch"] = network.thenetwork.getepoch() + 1
  475. descdict["idkey"] = self.idkey.verify_key
  476. descdict["onionkey"] = self.onionkey.public_key
  477. descdict["addr"] = self.netaddr
  478. descdict["bw"] = self.bw
  479. descdict["flags"] = self.flags
  480. desc = dirauth.RelayDescriptor(descdict)
  481. desc.sign(self.idkey, self.perfstats)
  482. dirauth.RelayDescriptor.verify(desc, self.perfstats)
  483. if upload:
  484. descmsg = dirauth.DirAuthUploadDescMsg(desc)
  485. else:
  486. # Note that this relies on signatures being deterministic;
  487. # otherwise we'd need to save the descriptor we uploaded
  488. # before so we could tell the airauths to delete the exact
  489. # one
  490. descmsg = dirauth.DirAuthDelDescMsg(desc)
  491. # Upload them
  492. for a in self.channelmgr.dirauthaddrs:
  493. c = network.thenetwork.connect(self, a, self.perfstats)
  494. c.sendmsg(descmsg)
  495. c.close()
  496. def connected(self, peer):
  497. """Callback invoked when someone (client or relay) connects to
  498. us. Create a pair of linked Channels and return the peer half
  499. to the peer."""
  500. # Create the linked pair
  501. if peer is self.netaddr:
  502. # A self-loop? We'll allow it.
  503. peerchannel = Channel()
  504. peerchannel.peer = peerchannel
  505. peerchannel.next_circid = 2
  506. return peerchannel
  507. peerchannel = Channel()
  508. ourchannel = Channel()
  509. peerchannel.peer = ourchannel
  510. peerchannel.next_circid = 2
  511. ourchannel.peer = peerchannel
  512. ourchannel.next_circid = 1
  513. # Add our channel to the RelayChannelManager
  514. self.channelmgr.add_channel(ourchannel, peer)
  515. return peerchannel
  516. if __name__ == '__main__':
  517. perfstats = network.PerfStats(network.EntType.NONE)
  518. # Start some dirauths
  519. numdirauths = 9
  520. dirauthaddrs = []
  521. for i in range(numdirauths):
  522. dira = dirauth.DirAuth(i, numdirauths)
  523. dirauthaddrs.append(dira.netaddr)
  524. # Start some relays
  525. numrelays = 10
  526. relays = []
  527. for i in range(numrelays):
  528. # Relay bandwidths (at least the ones fast enough to get used)
  529. # in the live Tor network (as of Dec 2019) are well approximated
  530. # by (200000-(200000-25000)/3*log10(x)) where x is a
  531. # uniform integer in [1,2500]
  532. x = random.randint(1,2500)
  533. bw = int(200000-(200000-25000)/3*math.log10(x))
  534. relays.append(Relay(dirauthaddrs, bw, 0))
  535. # The fallback relays are a hardcoded list of about 5% of the
  536. # relays, used by clients for bootstrapping
  537. numfallbackrelays = int(numrelays * 0.05) + 1
  538. fallbackrelays = random.sample(relays, numfallbackrelays)
  539. for r in fallbackrelays:
  540. r.set_is_fallbackrelay()
  541. network.thenetwork.setfallbackrelays(fallbackrelays)
  542. # Tick the epoch
  543. network.thenetwork.nextepoch()
  544. dirauth.Consensus.verify(dirauth.DirAuth.consensus,
  545. network.thenetwork.dirauthkeys(), perfstats)
  546. print('ticked; epoch=', network.thenetwork.getepoch())
  547. relays[3].channelmgr.send_msg(RelayRandomHopMsg(30), relays[5].netaddr)
  548. # See what channels exist and do a consistency check
  549. for r in relays:
  550. print("%s: %s" % (r.netaddr, [ str(k) for k in r.channelmgr.channels.keys()]))
  551. raddr = r.netaddr
  552. for ad, ch in r.channelmgr.channels.items():
  553. if ch.peer.channelmgr.myaddr != ad:
  554. print('address mismatch:', raddr, ad, ch.peer.channelmgr.myaddr)
  555. if ch.peer.channelmgr.channels[raddr].peer is not ch:
  556. print('asymmetry:', raddr, ad, ch, ch.peer.channelmgr.channels[raddr].peer)
  557. # Stop some relays
  558. relays[3].terminate()
  559. del relays[3]
  560. relays[5].terminate()
  561. del relays[5]
  562. relays[7].terminate()
  563. del relays[7]
  564. # Tick the epoch
  565. network.thenetwork.nextepoch()
  566. print(dirauth.DirAuth.consensus)
  567. # See what channels exist and do a consistency check
  568. for r in relays:
  569. print("%s: %s" % (r.netaddr, [ str(k) for k in r.channelmgr.channels.keys()]))
  570. raddr = r.netaddr
  571. for ad, ch in r.channelmgr.channels.items():
  572. if ch.peer.channelmgr.myaddr != ad:
  573. print('address mismatch:', raddr, ad, ch.peer.channelmgr.myaddr)
  574. if ch.peer.channelmgr.channels[raddr].peer is not ch:
  575. print('asymmetry:', raddr, ad, ch, ch.peer.channelmgr.channels[raddr].peer)
  576. channel = relays[3].channelmgr.get_channel_to(relays[5].netaddr)
  577. circid, circhandler = channel.new_circuit()
  578. peerchannel = relays[5].channelmgr.get_channel_to(relays[3].netaddr)
  579. peerchannel.new_circuit_with_circid(circid)
  580. relays[3].channelmgr.send_cell(circid, StringCell("test"), relays[5].netaddr)
  581. idpubkey = dirauth.DirAuth.consensus.consdict["relays"][1].descdict["idkey"]
  582. onionpubkey = dirauth.DirAuth.consensus.consdict["relays"][1].descdict["onionkey"]
  583. nt = NTor(perfstats)
  584. req = nt.request()
  585. R, S = NTor.reply(relays[1].onionkey, idpubkey, req, perfstats)
  586. S2 = nt.verify(R, onionpubkey, idpubkey)
  587. print(S == S2)