relay.py 38 KB

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