relay.py 24 KB

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