relay.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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 CircuitCellMsg(network.NetMsg):
  10. """Send a message tagged with a circuit id."""
  11. def __init__(self, circuitid, msg):
  12. self.circid = circuitid
  13. self.msg = msg
  14. def __str__(self):
  15. return "C%d:%s" % (self.circid, self.msg)
  16. class MultiplexedCircuitConnection(network.Connection):
  17. """A class representing a connection between a relay and either a
  18. client or a relay, transporting cells from various circuits."""
  19. def __init__(self):
  20. super().__init__()
  21. # The CellRelay managing this MultiplexedCircuitConnection
  22. self.cellrelay = None
  23. # The MultiplexedCircuitConnection at the other end
  24. self.peer = None
  25. # The function to call when the connection closes
  26. self.closer = lambda: 0
  27. def closed(self):
  28. self.closer()
  29. self.peer = None
  30. def close(self):
  31. if self.peer is not None:
  32. self.peer.closed()
  33. self.closed()
  34. def send_cell(self, circid, msg):
  35. """Send the given message, tagged for the given circuit id."""
  36. cell = CircuitCellMsg(circid, msg)
  37. self.peer.received(self.cellrelay.myaddr, cell)
  38. def received(self, peeraddr, cell):
  39. """Callback when a cell is received from the network."""
  40. circid, msg = cell.circid, cell.msg
  41. print("received", msg, "on circuit", circid, "from", peeraddr)
  42. class CellRelay:
  43. """The class that manages the connections to other relays and
  44. clients. Relays and clients both use this class to both create
  45. on-demand connections to relays, to gracefully handle the closing of
  46. connections, and to handle commands received over the
  47. connections."""
  48. def __init__(self, myaddr):
  49. # A dictionary of MultiplexedCircuitConnections to other hosts,
  50. # indexed by NetAddr
  51. self.connections = dict()
  52. self.myaddr = myaddr
  53. def get_connection_to(self, addr):
  54. """Get the MultiplexedCircuitConnection connected to the given
  55. NetAddr, creating one if none exists right now."""
  56. if addr in self.connections:
  57. return self.connections[addr]
  58. # Create the new connection
  59. newconn = network.thenetwork.connect(self.myaddr, addr)
  60. self.connections[addr] = newconn
  61. newconn.closer = lambda: self.connections.pop(addr)
  62. newconn.cellrelay = self
  63. return newconn
  64. class Relay(network.Server):
  65. """The class representing an onion relay."""
  66. def __init__(self, dirauthaddrs, bw, flags):
  67. self.consensus = None
  68. self.dirauthaddrs = dirauthaddrs
  69. # Create the identity and onion keys
  70. self.idkey = nacl.signing.SigningKey.generate()
  71. self.onionkey = nacl.public.PrivateKey.generate()
  72. self.name = self.idkey.verify_key.encode(encoder=nacl.encoding.HexEncoder).decode("ascii")
  73. # Bind to the network to get a network address
  74. self.netaddr = network.thenetwork.bind(self)
  75. # Our bandwidth and flags
  76. self.bw = bw
  77. self.flags = flags
  78. # Register for epoch change notification
  79. network.thenetwork.wantepochticks(self, True, end=True)
  80. network.thenetwork.wantepochticks(self, True)
  81. # Create the CellRelay connection manager
  82. self.cellrelay = CellRelay(self.netaddr)
  83. self.uploaddesc()
  84. def epoch_ending(self, epoch):
  85. # Download the new consensus, which will have been created
  86. # already since the dirauths' epoch_ending callbacks happened
  87. # before the relays'.
  88. a = random.choice(self.dirauthaddrs)
  89. c = network.thenetwork.connect(self, a)
  90. self.consensus = c.getconsensus()
  91. c.close()
  92. def newepoch(self, epoch):
  93. self.uploaddesc()
  94. def uploaddesc(self):
  95. # Upload the descriptor for the epoch to come
  96. descdict = dict();
  97. descdict["epoch"] = network.thenetwork.getepoch() + 1
  98. descdict["idkey"] = self.idkey.verify_key
  99. descdict["onionkey"] = self.onionkey.public_key
  100. descdict["addr"] = self.netaddr
  101. descdict["bw"] = self.bw
  102. descdict["flags"] = self.flags
  103. desc = dirauth.RelayDescriptor(descdict)
  104. desc.sign(self.idkey)
  105. desc.verify()
  106. descmsg = dirauth.DirAuthUploadDescMsg(desc)
  107. # Upload them
  108. for a in self.dirauthaddrs:
  109. c = network.thenetwork.connect(self, a)
  110. c.sendmsg(descmsg)
  111. c.close()
  112. def connected(self, peer):
  113. """Callback invoked when someone (client or relay) connects to
  114. us. Create a pair of linked MultiplexedCircuitConnections and
  115. return the peer half to the peer."""
  116. # Create the linked pair
  117. peerconn = MultiplexedCircuitConnection()
  118. ourconn = MultiplexedCircuitConnection()
  119. peerconn.peer = ourconn
  120. ourconn.peer = peerconn
  121. return peerconn
  122. if __name__ == '__main__':
  123. # Start some dirauths
  124. numdirauths = 9
  125. dirauthaddrs = []
  126. for i in range(numdirauths):
  127. dira = dirauth.DirAuth(i, numdirauths)
  128. dirauthaddrs.append(network.thenetwork.bind(dira))
  129. # Start some relays
  130. numrelays = 10
  131. relays = []
  132. for i in range(numrelays):
  133. # Relay bandwidths (at least the ones fast enough to get used)
  134. # in the live Tor network (as of Dec 2019) are well approximated
  135. # by (200000-(200000-25000)/3*log10(x)) where x is a
  136. # uniform integer in [1,2500]
  137. x = random.randint(1,2500)
  138. bw = int(200000-(200000-25000)/3*math.log10(x))
  139. relays.append(Relay(dirauthaddrs, bw, 0))
  140. # Tick the epoch
  141. network.thenetwork.nextepoch()
  142. dirauth.DirAuth.consensus.verify(network.thenetwork.dirauthkeys())
  143. print('ticked; epoch=', network.thenetwork.getepoch())
  144. c = relays[3].cellrelay.get_connection_to(relays[3].consensus.consdict['relays'][5].descdict['addr'])
  145. c.send_cell(1, network.StringNetMsg("test"))
  146. c.close()
  147. c2 = relays[3].cellrelay.get_connection_to(relays[3].consensus.consdict['relays'][6].descdict['addr'])
  148. c = relays[3].cellrelay.get_connection_to(relays[3].consensus.consdict['relays'][5].descdict['addr'])
  149. c.send_cell(2, network.StringNetMsg("cell"))
  150. c3 = relays[3].cellrelay.get_connection_to(relays[3].consensus.consdict['relays'][1].descdict['addr'])
  151. c = relays[3].cellrelay.get_connection_to(relays[3].consensus.consdict['relays'][5].descdict['addr'])
  152. c.send_cell(3, network.StringNetMsg("again"))
  153. c.close()