relay.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  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 network
  8. import dirauth
  9. class RelayNetMsg(network.NetMsg):
  10. """The subclass of NetMsg for messages between relays and either
  11. relays or clients."""
  12. class RelayGetConsensusMsg(RelayNetMsg):
  13. """The subclass of RelayNetMsg for fetching the consensus."""
  14. class RelayConsensusMsg(RelayNetMsg):
  15. """The subclass of RelayNetMsg for returning the consensus."""
  16. def __init__(self, consensus):
  17. self.consensus = consensus
  18. class RelayRandomHopMsg(RelayNetMsg):
  19. """A message used for testing, that hops from relay to relay
  20. randomly until its TTL expires."""
  21. def __init__(self, ttl):
  22. self.ttl = ttl
  23. def __str__(self):
  24. return "RandomHop TTL=%d" % self.ttl
  25. class CircuitCellMsg(RelayNetMsg):
  26. """Send a message tagged with a circuit id."""
  27. def __init__(self, circuitid, cell):
  28. self.circid = circuitid
  29. self.cell = cell
  30. def __str__(self):
  31. return "C%d:%s" % (self.circid, self.cell)
  32. class RelayFallbackTerminationError(Exception):
  33. """An exception raised when someone tries to terminate a fallback
  34. relay."""
  35. class Channel(network.Connection):
  36. """A class representing a channel between a relay and either a
  37. client or a relay, transporting cells from various circuits."""
  38. def __init__(self):
  39. super().__init__()
  40. # The CellRelay managing this Channel
  41. self.cellhandler = None
  42. # The Channel at the other end
  43. self.peer = None
  44. # The function to call when the connection closes
  45. self.closer = lambda: 0
  46. def closed(self):
  47. self.closer()
  48. self.peer = None
  49. def close(self):
  50. if self.peer is not None and self.peer is not self:
  51. self.peer.closed()
  52. self.closed()
  53. def send_cell(self, circid, cell):
  54. """Send the given message, tagged for the given circuit id."""
  55. msg = CircuitCellMsg(circid, cell)
  56. self.send_msg(msg)
  57. def send_msg(self, msg):
  58. """Send the given NetMsg on the channel."""
  59. self.peer.received(self.cellhandler.myaddr, msg)
  60. def received(self, peeraddr, msg):
  61. """Callback when a message is received from the network."""
  62. if isinstance(msg, CircuitCellMsg):
  63. circid, cell = msg.circid, msg.cell
  64. self.cellhandler.received_cell(circid, cell, peeraddr, self.peer)
  65. else:
  66. self.cellhandler.received_msg(msg, peeraddr, self.peer)
  67. class CellHandler:
  68. """The class that manages the channels to other relays and clients.
  69. Relays and clients both use subclasses of this class to both create
  70. on-demand channels to relays, to gracefully handle the closing of
  71. channels, and to handle commands received over the channels."""
  72. def __init__(self, myaddr, dirauthaddrs):
  73. # A dictionary of Channels to other hosts, indexed by NetAddr
  74. self.channels = dict()
  75. self.myaddr = myaddr
  76. self.dirauthaddrs = dirauthaddrs
  77. self.consensus = None
  78. def terminate(self):
  79. """Close all connections we're managing."""
  80. while self.channels:
  81. channelitems = iter(self.channels.items())
  82. addr, channel = next(channelitems)
  83. print('closing channel', addr, channel)
  84. channel.close()
  85. def add_channel(self, channel, peeraddr):
  86. """Add the given channel to the list of channels we are
  87. managing. If we are already managing a channel to the same
  88. peer, close it first."""
  89. if peeraddr in self.channels:
  90. self.channels[peeraddr].close()
  91. channel.cellhandler = self
  92. self.channels[peeraddr] = channel
  93. channel.closer = lambda: self.channels.pop(peeraddr)
  94. def get_channel_to(self, addr):
  95. """Get the Channel connected to the given NetAddr, creating one
  96. if none exists right now."""
  97. if addr in self.channels:
  98. return self.channels[addr]
  99. # Create the new channel
  100. newchannel = network.thenetwork.connect(self.myaddr, addr)
  101. self.channels[addr] = newchannel
  102. newchannel.closer = lambda: self.channels.pop(addr)
  103. newchannel.cellhandler = self
  104. return newchannel
  105. def received_msg(self, msg, peeraddr, peer):
  106. """Callback when a NetMsg not specific to a circuit is
  107. received."""
  108. print("Node %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
  109. def received_cell(self, circid, cell, peeraddr, peer):
  110. """Callback with a circuit-specific cell is received."""
  111. print("Node %s received cell on circ %d: %s from %s" % (self.myaddr, circid, cell, peeraddr))
  112. def send_msg(self, msg, peeraddr):
  113. """Send a message to the peer with the given address."""
  114. channel = self.get_channel_to(peeraddr)
  115. channel.send_msg(msg)
  116. def send_cell(self, circid, cell, peeraddr):
  117. """Send a cell on the given circuit to the peer with the given
  118. address."""
  119. channel = self.get_channel_to(peeraddr)
  120. channel.send_cell(circid, cell)
  121. class CellRelay(CellHandler):
  122. """The subclass of CellHandler for relays."""
  123. def __init__(self, myaddr, dirauthaddrs):
  124. super().__init__(myaddr, dirauthaddrs)
  125. def get_consensus(self):
  126. """Download a fresh consensus from a random dirauth."""
  127. a = random.choice(self.dirauthaddrs)
  128. c = network.thenetwork.connect(self, a)
  129. self.consensus = c.getconsensus()
  130. dirauth.verify_consensus(self.consensus, network.thenetwork.dirauthkeys())
  131. c.close()
  132. def received_msg(self, msg, peeraddr, peer):
  133. """Callback when a NetMsg not specific to a circuit is
  134. received."""
  135. print("Node %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
  136. if isinstance(msg, RelayRandomHopMsg):
  137. if msg.ttl > 0:
  138. # Pick a random next hop from the consensus
  139. nexthop = random.choice(self.consensus.consdict['relays'])
  140. nextaddr = nexthop.descdict['addr']
  141. self.send_msg(RelayRandomHopMsg(msg.ttl-1), nextaddr)
  142. elif isinstance(msg, RelayGetConsensusMsg):
  143. self.send_msg(RelayConsensusMsg(self.consensus), peeraddr)
  144. else:
  145. return super().received_msg(msg, peeraddr, peer)
  146. def received_cell(self, circid, cell, peeraddr, peer):
  147. """Callback with a circuit-specific cell is received."""
  148. print("Node %s received cell on circ %d: %s from %s" % (self.myaddr, circid, cell, peeraddr))
  149. return super().received_cell(circid, cell, peeraddr, peer)
  150. class Relay(network.Server):
  151. """The class representing an onion relay."""
  152. def __init__(self, dirauthaddrs, bw, flags):
  153. # Create the identity and onion keys
  154. self.idkey = nacl.signing.SigningKey.generate()
  155. self.onionkey = nacl.public.PrivateKey.generate()
  156. self.name = self.idkey.verify_key.encode(encoder=nacl.encoding.HexEncoder).decode("ascii")
  157. # Bind to the network to get a network address
  158. self.netaddr = network.thenetwork.bind(self)
  159. # Our bandwidth and flags
  160. self.bw = bw
  161. self.flags = flags
  162. # Register for epoch change notification
  163. network.thenetwork.wantepochticks(self, True, end=True)
  164. network.thenetwork.wantepochticks(self, True)
  165. # Create the CellRelay connection manager
  166. self.cellhandler = CellRelay(self.netaddr, dirauthaddrs)
  167. # Initially, we're not a fallback relay
  168. self.is_fallbackrelay = False
  169. self.uploaddesc()
  170. def terminate(self):
  171. """Stop this relay."""
  172. if self.is_fallbackrelay:
  173. # Fallback relays must not (for now) terminate
  174. raise RelayFallbackTerminationError(self)
  175. # Stop listening for epoch ticks
  176. network.thenetwork.wantepochticks(self, False, end=True)
  177. network.thenetwork.wantepochticks(self, False)
  178. # Tell the dirauths we're going away
  179. self.uploaddesc(False)
  180. # Close connections to other relays
  181. self.cellhandler.terminate()
  182. # Stop listening to our own bound port
  183. self.close()
  184. def set_is_fallbackrelay(self, isfallback = True):
  185. """Set this relay to be a fallback relay (or unset if passed
  186. False)."""
  187. self.is_fallbackrelay = isfallback
  188. def epoch_ending(self, epoch):
  189. # Download the new consensus, which will have been created
  190. # already since the dirauths' epoch_ending callbacks happened
  191. # before the relays'.
  192. self.cellhandler.get_consensus()
  193. def newepoch(self, epoch):
  194. self.uploaddesc()
  195. def uploaddesc(self, upload=True):
  196. # Upload the descriptor for the epoch to come, or delete a
  197. # previous upload if upload=False
  198. descdict = dict();
  199. descdict["epoch"] = network.thenetwork.getepoch() + 1
  200. descdict["idkey"] = self.idkey.verify_key
  201. descdict["onionkey"] = self.onionkey.public_key
  202. descdict["addr"] = self.netaddr
  203. descdict["bw"] = self.bw
  204. descdict["flags"] = self.flags
  205. desc = dirauth.RelayDescriptor(descdict)
  206. desc.sign(self.idkey)
  207. dirauth.verify_relaydesc(desc)
  208. if upload:
  209. descmsg = dirauth.DirAuthUploadDescMsg(desc)
  210. else:
  211. # Note that this relies on signatures being deterministic;
  212. # otherwise we'd need to save the descriptor we uploaded
  213. # before so we could tell the airauths to delete the exact
  214. # one
  215. descmsg = dirauth.DirAuthDelDescMsg(desc)
  216. # Upload them
  217. for a in self.cellhandler.dirauthaddrs:
  218. c = network.thenetwork.connect(self, a)
  219. c.sendmsg(descmsg)
  220. c.close()
  221. def connected(self, peer):
  222. """Callback invoked when someone (client or relay) connects to
  223. us. Create a pair of linked Channels and return the peer half
  224. to the peer."""
  225. # Create the linked pair
  226. if peer is self.netaddr:
  227. # A self-loop? We'll allow it.
  228. peerchannel = Channel()
  229. peerchannel.peer = peerchannel
  230. return peerchannel
  231. peerchannel = Channel()
  232. ourchannel = Channel()
  233. peerchannel.peer = ourchannel
  234. ourchannel.peer = peerchannel
  235. # Add our channel to the CellRelay
  236. self.cellhandler.add_channel(ourchannel, peer)
  237. return peerchannel
  238. if __name__ == '__main__':
  239. # Start some dirauths
  240. numdirauths = 9
  241. dirauthaddrs = []
  242. for i in range(numdirauths):
  243. dira = dirauth.DirAuth(i, numdirauths)
  244. dirauthaddrs.append(network.thenetwork.bind(dira))
  245. # Start some relays
  246. numrelays = 10
  247. relays = []
  248. for i in range(numrelays):
  249. # Relay bandwidths (at least the ones fast enough to get used)
  250. # in the live Tor network (as of Dec 2019) are well approximated
  251. # by (200000-(200000-25000)/3*log10(x)) where x is a
  252. # uniform integer in [1,2500]
  253. x = random.randint(1,2500)
  254. bw = int(200000-(200000-25000)/3*math.log10(x))
  255. relays.append(Relay(dirauthaddrs, bw, 0))
  256. # The fallback relays are a hardcoded list of about 5% of the
  257. # relays, used by clients for bootstrapping
  258. numfallbackrelays = int(numrelays * 0.05) + 1
  259. fallbackrelays = random.sample(relays, numfallbackrelays)
  260. for r in fallbackrelays:
  261. r.set_is_fallbackrelay()
  262. network.thenetwork.setfallbackrelays(fallbackrelays)
  263. # Tick the epoch
  264. network.thenetwork.nextepoch()
  265. dirauth.verify_consensus(dirauth.DirAuth.consensus, network.thenetwork.dirauthkeys())
  266. print('ticked; epoch=', network.thenetwork.getepoch())
  267. relays[3].cellhandler.send_msg(RelayRandomHopMsg(30), relays[5].netaddr)
  268. # See what channels exist and do a consistency check
  269. for r in relays:
  270. print("%s: %s" % (r.netaddr, [ str(k) for k in r.cellhandler.channels.keys()]))
  271. raddr = r.netaddr
  272. for ad, ch in r.cellhandler.channels.items():
  273. if ch.peer.cellhandler.myaddr != ad:
  274. print('address mismatch:', raddr, ad, ch.peer.cellhandler.myaddr)
  275. if ch.peer.cellhandler.channels[raddr].peer is not ch:
  276. print('asymmetry:', raddr, ad, ch, ch.peer.cellhandler.channels[raddr].peer)
  277. # Stop some relays
  278. relays[3].terminate()
  279. del relays[3]
  280. relays[5].terminate()
  281. del relays[5]
  282. relays[7].terminate()
  283. del relays[7]
  284. # Tick the epoch
  285. network.thenetwork.nextepoch()
  286. print(dirauth.DirAuth.consensus)
  287. # See what channels exist and do a consistency check
  288. for r in relays:
  289. print("%s: %s" % (r.netaddr, [ str(k) for k in r.cellhandler.channels.keys()]))
  290. raddr = r.netaddr
  291. for ad, ch in r.cellhandler.channels.items():
  292. if ch.peer.cellhandler.myaddr != ad:
  293. print('address mismatch:', raddr, ad, ch.peer.cellhandler.myaddr)
  294. if ch.peer.cellhandler.channels[raddr].peer is not ch:
  295. print('asymmetry:', raddr, ad, ch, ch.peer.cellhandler.channels[raddr].peer)
  296. #relays[3].cellhandler.send_cell(1, network.StringNetMsg("test"), relays[3].consensus.consdict['relays'][5].descdict['addr'])
  297. #relays[3].cellhandler.send_cell(2, network.StringNetMsg("cell"), relays[3].consensus.consdict['relays'][6].descdict['addr'])
  298. #relays[3].cellhandler.send_cell(2, network.StringNetMsg("again"), relays[3].consensus.consdict['relays'][1].descdict['addr'])
  299. #relays[3].cellhandler.send_cell(2, network.StringNetMsg("and again"), relays[3].consensus.consdict['relays'][5].descdict['addr'])