client.py 26 KB

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