client.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. #!/usr/bin/env python3
  2. import random # For simulation, not cryptography!
  3. import math
  4. import sys
  5. import logging
  6. import network
  7. import dirauth
  8. import relay
  9. import nacl.hash
  10. class VanillaCreatedExtendedHandler:
  11. """A handler for VanillaCreatedCircuitCell and
  12. VanillaExtendedCircuitCell cells."""
  13. def __init__(self, channelmgr, ntor, expecteddesc):
  14. self.channelmgr = channelmgr
  15. self.ntor = ntor
  16. self.expecteddesc = expecteddesc
  17. self.onionkey = expecteddesc.descdict['onionkey']
  18. self.idkey = expecteddesc.descdict['idkey']
  19. def received_cell(self, circhandler, cell):
  20. secret = self.ntor.verify(cell.ntor_reply, self.onionkey, self.idkey)
  21. enckey = nacl.hash.sha256(secret + b'upstream')
  22. deckey = nacl.hash.sha256(secret + b'downstream')
  23. circhandler.add_crypt_layer(enckey, deckey)
  24. if len(circhandler.circuit_descs) == 0:
  25. # This was a VanillaCreatedCircuitCell
  26. circhandler.replace_celltype_handler(
  27. relay.VanillaCreatedCircuitCell, None)
  28. else:
  29. # This was a VanillaExtendedCircuitCell
  30. circhandler.replace_celltype_handler(
  31. relay.VanillaExtendedCircuitCell, None)
  32. circhandler.circuit_descs.append(self.expecteddesc)
  33. # Are we done building the circuit?
  34. if len(circhandler.circuit_descs) == 3:
  35. # Yes!
  36. return
  37. nexthop = None
  38. while nexthop is None:
  39. nexthop = self.channelmgr.relaypicker.pick_weighted_relay()
  40. if nexthop.descdict['addr'] in \
  41. [ desc.descdict['addr'] \
  42. for desc in circhandler.circuit_descs ]:
  43. nexthop = None
  44. # Construct the VanillaExtendCircuitCell
  45. ntor = relay.NTor(self.channelmgr.perfstats)
  46. ntor_request = ntor.request()
  47. circextendmsg = relay.VanillaExtendCircuitCell(
  48. nexthop.descdict['addr'], ntor_request)
  49. # Set up the reply handler
  50. circhandler.replace_celltype_handler(
  51. relay.VanillaExtendedCircuitCell,
  52. VanillaCreatedExtendedHandler(self.channelmgr, ntor, nexthop))
  53. # Send the cell
  54. circhandler.send_cell(circextendmsg)
  55. class TelescopingCreatedHandler:
  56. """A handler for TelescopingCreatedCircuitCell cells; this will only always
  57. communicate with the client's guard."""
  58. def __init__(self, channelmgr, ntor):
  59. self.channelmgr = channelmgr
  60. self.ntor = ntor
  61. self.onionkey = self.channelmgr.guard.snipdict["onionkey"]
  62. self.idkey = self.channelmgr.guard.snipdict["idkey"]
  63. def received_cell(self, circhandler, cell):
  64. logging.debug("Received cell in TelescopingCreatedHandler")
  65. secret = self.ntor.verify(cell.ntor_reply, self.onionkey, self.idkey)
  66. enckey = nacl.hash.sha256(secret + b'upstream')
  67. deckey = nacl.hash.sha256(secret + b'downstream')
  68. circhandler.add_crypt_layer(enckey, deckey)
  69. circhandler.replace_celltype_handler(relay.TelescopingCreatedCircuitCell, None)
  70. circhandler.circuit_descs.append(self.channelmgr.guard)
  71. nexthopidx = None
  72. while nexthopidx is None:
  73. nexthopidx = self.channelmgr.relaypicker.pick_weighted_relay_index()
  74. #print("WARNING: Unimplemented! Need to check if this idx is in the list of circhandlers idxs")
  75. # TODO verify we don't need to do the above
  76. # Construct the TelescopingExtendCircuitCell
  77. ntor = relay.NTor(self.channelmgr.perfstats)
  78. ntor_request = ntor.request()
  79. circextendmsg = relay.TelescopingExtendCircuitCell(
  80. nexthopidx, ntor_request)
  81. # Set up the reply handler
  82. circhandler.replace_celltype_handler(
  83. relay.TelescopingExtendedCircuitCell,
  84. TelescopingExtendedHandler(self.channelmgr, ntor))
  85. # Send the cell
  86. circhandler.send_cell(circextendmsg)
  87. class TelescopingExtendedHandler:
  88. """A handler for TelescopingExtendedCircuitCell cells."""
  89. def __init__(self, channelmgr, ntor):
  90. self.channelmgr = channelmgr
  91. self.ntor = ntor
  92. def received_cell(self, circhandler, cell):
  93. logging.debug("Received cell in TelescopingExtendedHandler")
  94. # Validate the SNIP
  95. dirauth.SNIP.verify(cell.snip, self.channelmgr.consensus,
  96. network.thenetwork.dirauthkeys()[0],
  97. self.channelmgr.perfstats)
  98. onionkey = cell.snip.snipdict['onionkey']
  99. idkey = cell.snip.snipdict['idkey']
  100. secret = self.ntor.verify(cell.ntor_reply, onionkey, idkey)
  101. enckey = nacl.hash.sha256(secret + b'upstream')
  102. deckey = nacl.hash.sha256(secret + b'downstream')
  103. circhandler.add_crypt_layer(enckey, deckey)
  104. circhandler.replace_celltype_handler(
  105. relay.TelescopingExtendedCircuitCell, None)
  106. circhandler.circuit_descs.append(cell.snip)
  107. # Are we done building the circuit?
  108. logging.warning("we may need another circhandler structure for snips")
  109. if len(circhandler.circuit_descs) == 3:
  110. logging.debug("Circuit [%s] is long enough; exiting.", [str(x.snipdict['addr']) for x in circhandler.circuit_descs])
  111. # Yes!
  112. return
  113. nexthopidx = None
  114. guardrange = circhandler.circuit_descs[0].snipdict["range"]
  115. while nexthopidx is None:
  116. # Relays make sure that when the extend to a relay, they are not
  117. # extending to themselves. So here, we just need to make sure that
  118. # this ID is not the same as the guard ID, to protect against the
  119. # guard and exit being the same relay
  120. nexthopidx = self.channelmgr.relaypicker.pick_weighted_relay_index()
  121. if guardrange[0] <= nexthopidx and nexthopidx < guardrange[1]:
  122. # We've picked this relay already. Try again.
  123. nexthopidx = None
  124. # Construct the VanillaExtendCircuitCell
  125. ntor = relay.NTor(self.channelmgr.perfstats)
  126. ntor_request = ntor.request()
  127. circextendmsg = relay.TelescopingExtendCircuitCell(
  128. nexthopidx, ntor_request)
  129. # Set up the reply handler
  130. circhandler.replace_celltype_handler(
  131. relay.TelescopingExtendedCircuitCell,
  132. TelescopingExtendedHandler(self.channelmgr, ntor))
  133. # Send the cell
  134. circhandler.send_cell(circextendmsg)
  135. class SinglePassCreatedHandler:
  136. """A handler for SinglePassCreatedCircuitCell cells."""
  137. def __init__(self, channelmgr, ntor, client_key):
  138. self.channelmgr = channelmgr
  139. self.ntor = ntor
  140. self.client_key = client_key
  141. def received_cell(self, circhandler, cell):
  142. # We should only get one relay.SinglePassCreatedCircuitCell per
  143. # circuit
  144. circhandler.replace_celltype_handler(relay.SinglePassCreatedCircuitCell, None)
  145. # The circuit always starts with the guard
  146. circhandler.circuit_descs.append(self.channelmgr.guard)
  147. # Process each layer of the message
  148. while cell is not None:
  149. lasthop = circhandler.circuit_descs[-1]
  150. onionkey = lasthop.snipdict["onionkey"]
  151. idkey = lasthop.snipdict["idkey"]
  152. pathselkey = lasthop.snipdict["pathselkey"]
  153. if cell.enc is None:
  154. secret = self.ntor.verify(cell.ntor_reply, onionkey, idkey)
  155. enckey = nacl.hash.sha256(secret + b'upstream')
  156. deckey = nacl.hash.sha256(secret + b'downstream')
  157. circhandler.add_crypt_layer(enckey, deckey)
  158. cell = None
  159. else:
  160. secret = self.ntor.verify(cell.ntor_reply, onionkey, \
  161. idkey, b'circuit')
  162. enckey = nacl.hash.sha256(secret + b'upstream')
  163. deckey = nacl.hash.sha256(secret + b'downstream')
  164. createdkey = nacl.hash.sha256(secret + b'created')
  165. circhandler.add_crypt_layer(enckey, deckey)
  166. (snip, vrfout, nextlayer) = cell.enc.decrypt(createdkey)
  167. # Check the signature on the SNIP
  168. dirauth.SNIP.verify(snip, self.channelmgr.consensus,
  169. network.thenetwork.dirauthkeys()[0],
  170. self.channelmgr.perfstats)
  171. # TODO: compute the index, check the VRF, ensure the
  172. # SNIP is the correct one
  173. circhandler.circuit_descs.append(snip)
  174. cell = nextlayer
  175. class ClientChannelManager(relay.ChannelManager):
  176. """The subclass of ChannelManager for clients."""
  177. def __init__(self, myaddr, dirauthaddrs, perfstats):
  178. super().__init__(myaddr, dirauthaddrs, perfstats)
  179. self.guardaddr = None
  180. self.guard = None
  181. def get_consensus_from_fallbackrelay(self):
  182. """Download a fresh consensus from a random fallbackrelay."""
  183. fb = network.thenetwork.getfallbackrelay()
  184. logging.debug("Chose fallback %s", fb)
  185. if network.thenetwork.womode == network.WOMode.VANILLA:
  186. if self.consensus is not None and \
  187. len(self.consensus.consdict['relays']) > 0:
  188. self.send_msg(relay.RelayGetConsensusDiffMsg(), fb.netaddr)
  189. else:
  190. self.send_msg(relay.RelayGetConsensusMsg(), fb.netaddr)
  191. else:
  192. self.send_msg(relay.RelayGetConsensusMsg(), fb.netaddr)
  193. def ensure_guard_vanilla(self):
  194. """Ensure that we have a channel to a guard (Vanilla Onion
  195. Routing version)."""
  196. while True:
  197. if self.guardaddr is None:
  198. # Pick a guard from the consensus
  199. self.guard = self.relaypicker.pick_weighted_relay()
  200. self.guardaddr = self.guard.descdict['addr']
  201. self.test_guard_connection()
  202. if self.guardaddr is not None:
  203. break
  204. logging.debug('chose guard=%s', self.guardaddr)
  205. def test_guard_connection(self):
  206. # Connect to the guard
  207. try:
  208. self.get_channel_to(self.guardaddr)
  209. except network.NetNoServer:
  210. # Our guard is gone
  211. self.guardaddr = None
  212. self.guard = None
  213. def ensure_guard_walking_onions(self):
  214. """Ensure we have a channel to a guard (Walking Onions version).
  215. For the first implementation, we assume an out-of-band mechanism
  216. that just simply hands us a guard; we don't count the number of
  217. operations or bandwidth as this operation in practice occurs
  218. infrequently."""
  219. while True:
  220. if self.guardaddr is None:
  221. #randomly-sample a guard
  222. logging.warning("Unimplemented! guard should be selected from any relays.")
  223. self.guard = self.relaypicker.pick_weighted_relay()
  224. # here, we have a SNIP instead of a relay descriptor
  225. self.guardaddr = self.guard.snipdict['addr']
  226. self.test_guard_connection()
  227. if self.guardaddr is not None:
  228. break
  229. logging.debug('chose guard=%s', self.guardaddr)
  230. def ensure_guard(self):
  231. """Ensure that we have a channel to a guard."""
  232. if network.thenetwork.womode == network.WOMode.VANILLA:
  233. self.ensure_guard_vanilla()
  234. return
  235. # At this point, we are either in Telescoping or Single-Pass mode
  236. self.ensure_guard_walking_onions()
  237. def new_circuit_vanilla(self):
  238. """Create a new circuit from this client. (Vanilla Onion Routing
  239. version)"""
  240. # Get our channel to the guard
  241. guardchannel = self.get_channel_to(self.guardaddr)
  242. # Allocate a new circuit id on it
  243. circid, circhandler = guardchannel.new_circuit()
  244. # Construct the VanillaCreateCircuitMsg
  245. ntor = relay.NTor(self.perfstats)
  246. ntor_request = ntor.request()
  247. circcreatemsg = relay.VanillaCreateCircuitMsg(circid, ntor_request)
  248. # Set up the reply handler
  249. circhandler.replace_celltype_handler(
  250. relay.VanillaCreatedCircuitCell,
  251. VanillaCreatedExtendedHandler(self, ntor, self.guard))
  252. # Send the message
  253. guardchannel.send_msg(circcreatemsg)
  254. return circhandler
  255. def new_circuit_telescoping(self):
  256. """Create a new circuit from this client (Telescoping Walking Onions
  257. version). If an error occurs and the circuit is deleted from the guard
  258. channel, return None, otherwise, return the circuit handler."""
  259. # Get our channel to the guard
  260. guardchannel = self.get_channel_to(self.guardaddr)
  261. # Allocate a new circuit id on it
  262. circid, circhandler = guardchannel.new_circuit()
  263. # Construct the TelescopingCreateCircuitMsg
  264. ntor = relay.NTor(self.perfstats)
  265. ntor_request = ntor.request()
  266. circcreatemsg = relay.TelescopingCreateCircuitMsg(circid, ntor_request)
  267. # Set up the reply handler
  268. circhandler.replace_celltype_handler(
  269. relay.TelescopingCreatedCircuitCell,
  270. TelescopingCreatedHandler(self, ntor))
  271. # Send the message
  272. guardchannel.send_msg(circcreatemsg)
  273. # Check to make sure the circuit is open before sending it- if there
  274. # was an error when establishing it, the circuit could already be
  275. # closed.
  276. if not guardchannel.is_circuit_open(circid):
  277. logging.debug("Circuit was already closed, not sending bytes. circid: " + str(circid))
  278. return None
  279. # In Telescoping Walking Onions, it should never happen that the
  280. # guard and exit are the same node, as the
  281. # TelescopingExtendedHandler takes care to not pick an index for
  282. # the exit that matches the guard's range. So this test should
  283. # never trigger. In Single-Pass Walking Onions, however, the
  284. # equivalent test is needed here (but should just log a debug,
  285. # not an error, since the client cannot control the index value
  286. # selected for the exit.
  287. if circhandler.circuit_descs[0].snipdict["addr"] == \
  288. circhandler.circuit_descs[2].snipdict["addr"]:
  289. logging.error("CIRCUIT IN A LOOP")
  290. circhandler.close()
  291. circhandler = None
  292. return circhandler
  293. def new_circuit_singlepass(self):
  294. """Create a new circuit from this client (Single-Pass Walking Onions
  295. version). If an error occurs and the circuit is deleted from the guard
  296. channel, return None, otherwise, return the circuit handler."""
  297. # Get our channel to the guard
  298. guardchannel = self.get_channel_to(self.guardaddr)
  299. # Allocate a new circuit id on it
  300. circid, circhandler = guardchannel.new_circuit()
  301. # first, create the path-selection key used for Sphinx
  302. client_pathsel_key = nacl.public.PrivateKey.generate()
  303. self.perfstats.keygens += 1
  304. # Construct the SinglePassCreateCircuitMsg
  305. ntor = relay.NTor(self.perfstats)
  306. ntor_request = ntor.request()
  307. circcreatemsg = relay.SinglePassCreateCircuitMsg(circid, ntor_request,
  308. client_pathsel_key.public_key)
  309. # Set up the reply handler
  310. circhandler.replace_celltype_handler(
  311. relay.SinglePassCreatedCircuitCell,
  312. SinglePassCreatedHandler(self, ntor, client_pathsel_key))
  313. # Send the message
  314. guardchannel.send_msg(circcreatemsg)
  315. # Check to make sure the circuit is open before sending it- if there
  316. # was an error when establishing it, the circuit could already be
  317. # closed.
  318. if not guardchannel.is_circuit_open(circid):
  319. logging.debug("Circuit was already closed, not sending bytes. circid: " + str(circid))
  320. return None
  321. # In Single-Pass Walking Onions, we need to check whether the
  322. # circuit got into a loop (guard equals exit); each node will
  323. # refuse to extend to itself, so this is the only possible loop
  324. # in a circuit of length 3
  325. if circhandler.circuit_descs[0].snipdict["addr"] == \
  326. circhandler.circuit_descs[2].snipdict["addr"]:
  327. logging.debug("circuit in a loop")
  328. circhandler.close()
  329. circhandler = None
  330. return circhandler
  331. def new_circuit(self):
  332. """Create a new circuit from this client."""
  333. circhandler = None
  334. # If an error occured, circhandler will still be None, so we should
  335. # try again.
  336. while circhandler is None:
  337. if network.thenetwork.womode == network.WOMode.VANILLA:
  338. circhandler = self.new_circuit_vanilla()
  339. elif network.thenetwork.womode == network.WOMode.TELESCOPING:
  340. circhandler = self.new_circuit_telescoping()
  341. elif network.thenetwork.womode == network.WOMode.SINGLEPASS:
  342. circhandler = self.new_circuit_singlepass()
  343. return circhandler
  344. def received_msg(self, msg, peeraddr, channel):
  345. """Callback when a NetMsg not specific to a circuit is
  346. received."""
  347. logging.debug("Client %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
  348. if isinstance(msg, relay.RelayConsensusMsg) or \
  349. isinstance(msg, relay.RelayConsensusDiffMsg):
  350. self.relaypicker = dirauth.Consensus.verify(msg.consensus,
  351. network.thenetwork.dirauthkeys(), self.perfstats)
  352. self.consensus = msg.consensus
  353. else:
  354. return super().received_msg(msg, peeraddr, channel)
  355. def received_cell(self, circid, cell, peeraddr, channel):
  356. """Callback with a circuit-specific cell is received."""
  357. logging.debug("Client %s received cell on circ %d: %s from %s" % (self.myaddr, circid, cell, peeraddr))
  358. if isinstance(msg, relay.CloseCell):
  359. logging.debug("Log: Client received close cell; closing circuit")
  360. # TODO close cell
  361. return super().received_cell(circid, cell, peeraddr, channel)
  362. class Client:
  363. """A class representing a Tor client."""
  364. def __init__(self, dirauthaddrs):
  365. # Get a network address for client-side use only (do not bind it
  366. # to the network)
  367. self.netaddr = network.NetAddr()
  368. self.perfstats = network.PerfStats(network.EntType.CLIENT)
  369. self.perfstats.name = "Client at %s" % self.netaddr
  370. self.perfstats.is_bootstrapping = True
  371. self.channelmgr = ClientChannelManager(self.netaddr, dirauthaddrs,
  372. self.perfstats)
  373. # Register for epoch tick notifications
  374. network.thenetwork.wantepochticks(self, True)
  375. def terminate(self):
  376. """Quit this client."""
  377. # Stop listening for epoch ticks
  378. network.thenetwork.wantepochticks(self, False)
  379. # Close relay connections
  380. self.channelmgr.terminate()
  381. def get_consensus(self):
  382. """Fetch a new consensus."""
  383. # We're going to want a new consensus from our guard. In order
  384. # to get that, we'll need a channel to our guard. In order to
  385. # get that, we'll need a guard address. In order to get that,
  386. # we'll need a consensus (uh, oh; in that case, fetch the
  387. # consensus from a fallback relay).
  388. guardaddr = self.channelmgr.guardaddr
  389. guardchannel = None
  390. if guardaddr is not None:
  391. try:
  392. guardchannel = self.channelmgr.get_channel_to(guardaddr)
  393. except network.NetNoServer:
  394. guardaddr = None
  395. if guardchannel is None:
  396. logging.debug("In bootstrapping mode")
  397. self.channelmgr.get_consensus_from_fallbackrelay()
  398. logging.debug('client consensus=%s', self.channelmgr.consensus)
  399. return
  400. if network.thenetwork.womode == network.WOMode.VANILLA:
  401. if self.channelmgr.consensus is not None and len(self.channelmgr.consensus.consdict['relays']) > 0:
  402. guardchannel.send_msg(relay.RelayGetConsensusDiffMsg())
  403. logging.debug('got consensus diff, client consensus=%s', self.channelmgr.consensus)
  404. return
  405. # At this point, we are in one of the following scenarios:
  406. # 1. This is a walking onions protocol, and the client fetches the
  407. # complete consensus each epoch
  408. # 2. This is Vanilla Onion Routing and the client doesn't have a
  409. # consensus and needs to bootstrap it.
  410. guardchannel.send_msg(relay.RelayGetConsensusMsg())
  411. logging.debug('client consensus=%s', self.channelmgr.consensus)
  412. def newepoch(self, epoch):
  413. """Callback that fires at the start of each epoch"""
  414. # We'll need a new consensus
  415. self.get_consensus()
  416. # If we don't have a guard, pick one and make a channel to it
  417. self.channelmgr.ensure_guard()
  418. if __name__ == '__main__':
  419. perfstats = network.PerfStats(network.EntType.NONE)
  420. totsent = 0
  421. totrecv = 0
  422. dirasent = 0
  423. dirarecv = 0
  424. relaysent = 0
  425. relayrecv = 0
  426. clisent = 0
  427. clirecv = 0
  428. if len(sys.argv) < 3:
  429. print("Must pass in network mode and snip auth mode!")
  430. print("Network options are vanilla, telescoping, or single-pass.")
  431. print("SNIP auth options are merkle or threshold.")
  432. sys.exit(0)
  433. logging.basicConfig(level=logging.DEBUG)
  434. network_mode = network.WOMode.string_to_type(sys.argv[1])
  435. if network_mode == -1:
  436. print("Not a valid network mode: " + network_mode)
  437. sys.exit(0)
  438. snipauth_mode = network.SNIPAuthMode.string_to_type(sys.argv[2])
  439. if network_mode == -1:
  440. print("Not a valid SNIP authentication mode: " + snipauth_mode)
  441. sys.exit(0)
  442. # Initialize the (non-cryptographic) random seed
  443. random.seed(1)
  444. if network_mode == network.WOMode.VANILLA:
  445. network.thenetwork.set_wo_style(network.WOMode.VANILLA,
  446. network.SNIPAuthMode.NONE)
  447. elif network_mode == network.WOMode.TELESCOPING:
  448. if snipauth_mode == network.SNIPAuthMode.MERKLE:
  449. network.thenetwork.set_wo_style(network.WOMode.TELESCOPING,
  450. network.SNIPAuthMode.MERKLE)
  451. else:
  452. network.thenetwork.set_wo_style(network.WOMode.TELESCOPING,
  453. network.SNIPAuthMode.THRESHSIG)
  454. elif network_mode == network.WOMode.SINGLEPASS:
  455. if snipauth_mode == network.SNIPAuthMode.MERKLE:
  456. network.thenetwork.set_wo_style(network.WOMode.SINGLEPASS,
  457. network.SNIPAuthMode.MERKLE)
  458. else:
  459. network.thenetwork.set_wo_style(network.WOMode.SINGLEPASS,
  460. network.SNIPAuthMode.THRESHSIG)
  461. else:
  462. sys.exit("Received unsupported network mode, exiting.")
  463. # Start some dirauths
  464. numdirauths = 9
  465. dirauthaddrs = []
  466. dirauths = []
  467. for i in range(numdirauths):
  468. dira = dirauth.DirAuth(i, numdirauths)
  469. dirauths.append(dira)
  470. dirauthaddrs.append(dira.netaddr)
  471. # Start some relays
  472. numrelays = 100
  473. relays = []
  474. for i in range(numrelays):
  475. # Relay bandwidths (at least the ones fast enough to get used)
  476. # in the live Tor network (as of Dec 2019) are well approximated
  477. # by (200000-(200000-25000)/3*log10(x)) where x is a
  478. # uniform integer in [1,2500]
  479. x = random.randint(1,2500)
  480. bw = int(200000-(200000-25000)/3*math.log10(x))
  481. relays.append(relay.Relay(dirauthaddrs, bw, 0))
  482. # The fallback relays are a hardcoded list of about 5% of the
  483. # relays, used by clients for bootstrapping
  484. numfallbackrelays = int(numrelays * 0.05) + 1
  485. fallbackrelays = random.sample(relays, numfallbackrelays)
  486. for r in fallbackrelays:
  487. r.set_is_fallbackrelay()
  488. network.thenetwork.setfallbackrelays(fallbackrelays)
  489. # Tick the epoch
  490. network.thenetwork.nextepoch()
  491. dirauth.Consensus.verify(dirauth.DirAuth.consensus, network.thenetwork.dirauthkeys(), perfstats)
  492. print('ticked; epoch=', network.thenetwork.getepoch())
  493. relays[3].channelmgr.send_msg(relay.RelayRandomHopMsg(30), relays[5].netaddr)
  494. # See what channels exist and do a consistency check
  495. for r in relays:
  496. print("%s: %s" % (r.netaddr, [ str(k) for k in r.channelmgr.channels.keys()]))
  497. raddr = r.netaddr
  498. for ad, ch in r.channelmgr.channels.items():
  499. if ch.peer.channelmgr.myaddr != ad:
  500. print('address mismatch:', raddr, ad, ch.peer.channelmgr.myaddr)
  501. if ch.peer.channelmgr.channels[raddr].peer is not ch:
  502. print('asymmetry:', raddr, ad, ch, ch.peer.channelmgr.channels[raddr].peer)
  503. # Start some clients
  504. numclients = 1
  505. clients = []
  506. for i in range(numclients):
  507. clients.append(Client(dirauthaddrs))
  508. # Tick the epoch
  509. network.thenetwork.nextepoch()
  510. # See what channels exist and do a consistency check
  511. for c in clients:
  512. print("%s: %s" % (c.netaddr, [ str(k) for k in c.channelmgr.channels.keys()]))
  513. caddr = c.netaddr
  514. for ad, ch in c.channelmgr.channels.items():
  515. if ch.peer.channelmgr.myaddr != ad:
  516. print('address mismatch:', caddr, ad, ch.peer.channelmgr.myaddr)
  517. if ch.peer.channelmgr.channels[caddr].peer is not ch:
  518. print('asymmetry:', caddr, ad, ch, ch.peer.channelmgr.channels[caddr].peer)
  519. # Pick a bunch of bw-weighted random relays and look at the
  520. # distribution
  521. for i in range(100):
  522. r = relays[0].channelmgr.relaypicker.pick_weighted_relay()
  523. if network.thenetwork.womode == network.WOMode.VANILLA:
  524. print("relay",r.descdict["addr"])
  525. else:
  526. print("relay",r.snipdict["addr"])
  527. relays[3].terminate()
  528. relaysent += relays[3].perfstats.bytes_sent
  529. relayrecv += relays[3].perfstats.bytes_received
  530. del relays[3]
  531. # Tick the epoch
  532. network.thenetwork.nextepoch()
  533. circs = []
  534. for i in range(20):
  535. circ = clients[0].channelmgr.new_circuit()
  536. if circ is None:
  537. sys.exit("ERR: Client unable to create circuits")
  538. circs.append(circ)
  539. circ.send_cell(relay.StringCell("hello world circuit %d" % i))
  540. # Tick the epoch
  541. network.thenetwork.nextepoch()
  542. # See what channels exist and do a consistency check
  543. for r in relays:
  544. print("%s: %s" % (r.netaddr, [ str(k) + str([ck for ck in r.channelmgr.channels[k].circuithandlers.keys()]) for k in r.channelmgr.channels.keys()]))
  545. raddr = r.netaddr
  546. for ad, ch in r.channelmgr.channels.items():
  547. if ch.peer.channelmgr.myaddr != ad:
  548. print('address mismatch:', raddr, ad, ch.peer.channelmgr.myaddr)
  549. if ch.peer.channelmgr.channels[raddr].peer is not ch:
  550. print('asymmetry:', raddr, ad, ch, ch.peer.channelmgr.channels[raddr].peer)
  551. # See what channels exist and do a consistency check
  552. for c in clients:
  553. print("%s: %s" % (c.netaddr, [ str(k) + str([ck for ck in c.channelmgr.channels[k].circuithandlers.keys()]) for k in c.channelmgr.channels.keys()]))
  554. caddr = c.netaddr
  555. for ad, ch in c.channelmgr.channels.items():
  556. if ch.peer.channelmgr.myaddr != ad:
  557. print('address mismatch:', caddr, ad, ch.peer.channelmgr.myaddr)
  558. if ch.peer.channelmgr.channels[caddr].peer is not ch:
  559. print('asymmetry:', caddr, ad, ch, ch.peer.channelmgr.channels[caddr].peer)
  560. if ch.circuithandlers.keys() != \
  561. ch.peer.channelmgr.channels[caddr].circuithandlers.keys():
  562. print('circuit asymmetry:', caddr, ad, ch.peer.channelmgr.myaddr)
  563. for c in circs:
  564. c.close()
  565. for d in dirauths:
  566. print(d.perfstats)
  567. dirasent += d.perfstats.bytes_sent
  568. dirarecv += d.perfstats.bytes_received
  569. print("DirAuths sent=%s recv=%s" % (dirasent, dirarecv))
  570. totsent += dirasent
  571. totrecv += dirarecv
  572. for r in relays:
  573. print(r.perfstats)
  574. relaysent += r.perfstats.bytes_sent
  575. relayrecv += r.perfstats.bytes_received
  576. print("Relays sent=%s recv=%s" % (relaysent, relayrecv))
  577. totsent += relaysent
  578. totrecv += relayrecv
  579. for c in clients:
  580. print(c.perfstats)
  581. clisent += c.perfstats.bytes_sent
  582. clirecv += c.perfstats.bytes_received
  583. print("Client sent=%s recv=%s" % (clisent, clirecv))
  584. totsent += clisent
  585. totrecv += clirecv
  586. print("Total sent=%s recv=%s" % (totsent, totrecv))