Browse Source

Circuits can now close

A circuit can be closed manually, or will automatically be closed when
its containing channel closes, or when it receives a CloseCell.  Closing
circuits will send CloseCells to their appropriate neighbours.
Ian Goldberg 4 years ago
parent
commit
f2c6b1aa31
2 changed files with 61 additions and 7 deletions
  1. 11 4
      client.py
  2. 50 3
      relay.py

+ 11 - 4
client.py

@@ -129,10 +129,12 @@ class ClientChannelManager(relay.ChannelManager):
         # Send the message
         guardchannel.send_msg(circcreatemsg)
 
+        return circhandler
+
     def new_circuit(self):
         """Create a new circuit from this client."""
         if network.thenetwork.womode == network.WOMode.VANILLA:
-            self.new_circuit_vanilla()
+            return self.new_circuit_vanilla()
 
     def received_msg(self, msg, peeraddr, channel):
         """Callback when a NetMsg not specific to a circuit is
@@ -294,9 +296,11 @@ if __name__ == '__main__':
     # Tick the epoch
     network.thenetwork.nextepoch()
 
-    clients[0].channelmgr.new_circuit()
-    circhandler = clients[0].channelmgr.channels[clients[0].channelmgr.guardaddr].circuithandlers[2]
-    circhandler.send_cell(relay.StringCell("hello world"))
+    circs = []
+    for i in range(20):
+        circ = clients[0].channelmgr.new_circuit()
+        circs.append(circ)
+        circ.send_cell(relay.StringCell("hello world circuit %d" % i))
 
     # See what channels exist and do a consistency check
     for r in relays:
@@ -324,6 +328,9 @@ if __name__ == '__main__':
                     ch.peer.channelmgr.channels[caddr].circuithandlers.keys():
                 print('circuit asymmetry:', caddr, ad, ch.peer.channelmgr.myaddr)
 
+    for c in circs:
+        c.close()
+
     for d in dirauths:
         print(d.perfstats)
         dirasent += d.perfstats.bytes_sent

+ 50 - 3
relay.py

@@ -67,6 +67,16 @@ class StringCell(RelayCell):
         return self.data.__str__()
 
 
+class CloseCell(RelayCell):
+    """Close the circuit this cell was sent on.  It should be sent
+    _unencrypted_ (not within an EncryptedCell), and relays that receive
+    one should forward it along the adjacent circuit, then close both
+    the circuit it was received on and the adjacent one."""
+
+
+# It is intentional that VanillaCreateCircuitMsg is a RelayNetMsg and
+# not a RelayCell.  This is the message that _creates_ the circuit, so
+# it can't be sent as a cell _within_ the circuit.
 class VanillaCreateCircuitMsg(RelayNetMsg):
     """The message for requesting circuit creation in Vanilla Onion
     Routing."""
@@ -267,6 +277,21 @@ class CircuitHandler:
         # we get a cell on one, we forward it to the other (if it's not
         # meant for us to handle directly)
         self.adjacent_circuit_handler = None
+        # The function to call when this circuit closes
+        self.closer = lambda: self.channel.circuithandlers.pop(circid)
+
+    def close(self):
+        """Close the circuit.  Sends a CloseCell on the circuit (and its
+        adjacent circuit, if present) and closes both."""
+        adjcirchandler = self.adjacent_circuit_handler
+        self.adjacent_circuit_handler = None
+        if adjcirchandler is not None:
+            adjcirchandler.adjacent_circuit_handler = None
+        self.closer()
+        self.channel_send_cell(CloseCell())
+        if adjcirchandler is not None:
+            adjcirchandler.closer()
+            adjcirchandler.channel_send_cell(CloseCell())
 
     def send_cell(self, cell):
         """Send a cell on this circuit, encrypting as needed."""
@@ -295,11 +320,26 @@ class CircuitHandler:
             celltype = type(cell)
             if celltype in self.cell_dispatch_table:
                 self.cell_dispatch_table[celltype].received_cell(self, cell)
+
             elif isinstance(cell, StringCell):
                 # Default handler; just print the message in the cell
                 print("CircuitHandler: %s received '%s' on circuit %d from %s" \
                         % (self.channel.channelmgr.myaddr, cell,
                             self.circid, self.channel.peer.channelmgr.myaddr))
+
+            elif isinstance(cell, CloseCell):
+                # Forward the CloseCell (without encryption) to the
+                # adjacent circuit, if any, and close both this and the
+                # adjacent circuit
+                adjcirchandler = self.adjacent_circuit_handler
+                self.adjacent_circuit_handler = None
+                if adjcirchandler is not None:
+                    adjcirchandler.adjacent_circuit_handler = None
+                self.closer()
+                if adjcirchandler is not None:
+                    adjcirchandler.closer()
+                    adjcirchandler.channel_send_cell(cell)
+
             else:
                 # I don't know how to handle this cell?
                 raise ValueError("CircuitHandler: %s received unknown cell type %s on circuit %d from %s" \
@@ -334,7 +374,7 @@ class Channel(network.Connection):
         # The Channel at the other end
         self.peer = None
         # The function to call when the connection closes
-        self.closer = lambda: 0
+        self.closer = lambda: None
         # The next circuit id to use on this channel.  The party that
         # opened the channel uses even numbers; the receiving party uses
         # odd numbers.
@@ -344,13 +384,20 @@ class Channel(network.Connection):
         self.circuithandlers = dict()
 
     def closed(self):
+        # Close each circuithandler we're managing
+        while self.circuithandlers:
+            chitems = iter(self.circuithandlers.items())
+            circid, circhandler = next(chitems)
+            print('closing circuit', circid)
+            circhandler.close()
         self.closer()
         self.peer = None
 
     def close(self):
-        if self.peer is not None and self.peer is not self:
-            self.peer.closed()
+        peer = self.peer
         self.closed()
+        if peer is not None and peer is not self:
+            peer.closed()
 
     def new_circuit(self):
         """Allocate a new circuit on this channel, returning the new