Browse Source

Clients can now bootstrap a consensus from fallback relays

They do not yet select or use guards
Ian Goldberg 4 years ago
parent
commit
9dab25df18
2 changed files with 210 additions and 64 deletions
  1. 135 8
      client.py
  2. 75 56
      relay.py

+ 135 - 8
client.py

@@ -1,20 +1,147 @@
 #!/usr/bin/env python3
 
+import random # For simulation, not cryptography!
+import math
+
 import network
 import dirauth
 import relay
 
+class CellClient(relay.CellHandler):
+    """The subclass of CellHandler for clients."""
+
+    def __init__(self, myaddr, dirauthaddrs):
+        super().__init__(myaddr, dirauthaddrs)
+        self.guardaddr = None
+
+    def get_consensus_from_fallbackrelay(self):
+        """Download a fresh consensus from a random fallbackrelay."""
+        fb = random.choice(network.thenetwork.getfallbackrelays())
+        self.send_msg(relay.RelayGetConsensusMsg(), fb.netaddr)
+
+    def received_msg(self, msg, peeraddr, peer):
+        """Callback when a NetMsg not specific to a circuit is
+        received."""
+        print("Client %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
+        if isinstance(msg, relay.RelayConsensusMsg):
+            self.consensus = msg.consensus
+        else:
+            return super().received_msg(msg, peeraddr, peer)
+
+    def received_cell(self, circid, cell, peeraddr, peer):
+        """Callback with a circuit-specific cell is received."""
+        print("Client %s received cell on circ %d: %s from %s" % (self.myaddr, circid, cell, peeraddr))
+        return super().received_cell(circid, cell, peeraddr, peer)
+
+
+
 class Client:
     """A class representing a Tor client."""
 
-    def __init__(self):
-        self.consensus = None
-
+    def __init__(self, dirauthaddrs):
         # Get a network address for client-side use only (do not bind it
         # to the network)
-        self.myaddr = network.NetAddr()
-        self.guardaddr = None
+        self.netaddr = network.NetAddr()
+        self.cellhandler = CellClient(self.netaddr, dirauthaddrs)
+
+        # Register for epoch tick notifications
+        network.thenetwork.wantepochticks(self, True)
+
+    def terminate(self):
+        """Quit this client."""
+
+        # Stop listening for epoch ticks
+        network.thenetwork.wantepochticks(self, False)
+
+        # Close relay connections
+        self.cellhandler.terminate()
+
+    def get_consensus(self):
+        """Fetch a new consensus."""
+
+        # We're going to want a new consensus from our guard.  In order
+        # to get that, we'll need a channel to our guard.  In order to
+        # get that, we'll need a guard address.  In order to get that,
+        # we'll need a consensus (uh, oh; in that case, fetch the
+        # consensus from a fallback relay).
+
+        self.cellhandler.get_consensus_from_fallbackrelay()
+        print('client consensus=', self.cellhandler.consensus)
+
+    def newepoch(self, epoch):
+        """Callback that fires at the start of each epoch"""
+
+        if self.cellhandler.consensus is None or \
+                self.cellhandler.consensus.consdict['epoch'] != \
+                network.thenetwork.getepoch():
+            # We'll need a new consensus
+            self.get_consensus()
+
+
+if __name__ == '__main__':
+    # Start some dirauths
+    numdirauths = 9
+    dirauthaddrs = []
+    for i in range(numdirauths):
+        dira = dirauth.DirAuth(i, numdirauths)
+        dirauthaddrs.append(network.thenetwork.bind(dira))
+
+    # Start some relays
+    numrelays = 10
+    relays = []
+    for i in range(numrelays):
+        # Relay bandwidths (at least the ones fast enough to get used)
+        # in the live Tor network (as of Dec 2019) are well approximated
+        # by (200000-(200000-25000)/3*log10(x)) where x is a
+        # uniform integer in [1,2500]
+        x = random.randint(1,2500)
+        bw = int(200000-(200000-25000)/3*math.log10(x))
+        relays.append(relay.Relay(dirauthaddrs, bw, 0))
+
+    # The fallback relays are a hardcoded list of about 5% of the
+    # relays, used by clients for bootstrapping
+    numfallbackrelays = int(numrelays * 0.05) + 1
+    fallbackrelays = random.sample(relays, numfallbackrelays)
+    for r in fallbackrelays:
+        r.set_is_fallbackrelay()
+    network.thenetwork.setfallbackrelays(fallbackrelays)
+
+    # Tick the epoch
+    network.thenetwork.nextepoch()
+
+    dirauth.DirAuth.consensus.verify(network.thenetwork.dirauthkeys())
+
+    print('ticked; epoch=', network.thenetwork.getepoch())
+
+    relays[3].cellhandler.send_msg(relay.RelayRandomHopMsg(30), relays[5].netaddr)
+
+    # See what channels exist and do a consistency check
+    for r in relays:
+        print("%s: %s" % (r.netaddr, [ str(k) for k in r.cellhandler.channels.keys()]))
+        raddr = r.netaddr
+        for ad, ch in r.cellhandler.channels.items():
+            if ch.peer.cellhandler.myaddr != ad:
+                print('address mismatch:', raddr, ad, ch.peer.cellhandler.myaddr)
+
+            if ch.peer.cellhandler.channels[raddr].peer is not ch:
+                print('asymmetry:', raddr, ad, ch, ch.peer.cellhandler.channels[raddr].peer)
+
+    # Start some clients
+    numclients = 1
+    clients = []
+    for i in range(numclients):
+        clients.append(Client(dirauthaddrs))
+
+    # Tick the epoch
+    network.thenetwork.nextepoch()
+
+    # See what channels exist and do a consistency check
+    for c in clients:
+        print("%s: %s" % (c.netaddr, [ str(k) for k in c.cellhandler.channels.keys()]))
+        caddr = c.netaddr
+        for ad, ch in c.cellhandler.channels.items():
+            if ch.peer.cellhandler.myaddr != ad:
+                print('address mismatch:', caddr, ad, ch.peer.cellhandler.myaddr)
 
-    def initial_bootstrap(self):
-        """This method will use the network's fallback relays to enable
-        this client to choose a guard."""
+            if ch.peer.cellhandler.channels[caddr].peer is not ch:
+                print('asymmetry:', caddr, ad, ch, ch.peer.cellhandler.channels[caddr].peer)

+ 75 - 56
relay.py

@@ -58,7 +58,7 @@ class Channel(network.Connection):
     def __init__(self):
         super().__init__()
         # The CellRelay managing this Channel
-        self.cellrelay = None
+        self.cellhandler = None
         # The Channel at the other end
         self.peer = None
         # The function to call when the connection closes
@@ -80,22 +80,22 @@ class Channel(network.Connection):
 
     def send_msg(self, msg):
         """Send the given NetMsg on the channel."""
-        self.peer.received(self.cellrelay.myaddr, msg)
+        self.peer.received(self.cellhandler.myaddr, msg)
 
     def received(self, peeraddr, msg):
         """Callback when a message is received from the network."""
         if isinstance(msg, CircuitCellMsg):
             circid, cell = msg.circid, msg.cell
-            self.cellrelay.received_cell(circid, cell, peeraddr, self.peer)
+            self.cellhandler.received_cell(circid, cell, peeraddr, self.peer)
         else:
-            self.cellrelay.received_msg(msg, peeraddr, self.peer)
+            self.cellhandler.received_msg(msg, peeraddr, self.peer)
 
 
-class CellRelay:
+class CellHandler:
     """The class that manages the channels to other relays and clients.
-    Relays and clients both use this class to both create on-demand
-    channels to relays, to gracefully handle the closing of channels,
-    and to handle commands received over the channels."""
+    Relays and clients both use subclasses of this class to both create
+    on-demand channels to relays, to gracefully handle the closing of
+    channels, and to handle commands received over the channels."""
 
     def __init__(self, myaddr, dirauthaddrs):
         # A dictionary of Channels to other hosts, indexed by NetAddr
@@ -112,14 +112,6 @@ class CellRelay:
             print('closing channel', addr, channel)
             channel.close()
 
-    def get_consensus(self):
-        """Download a fresh consensus from a random dirauth."""
-        a = random.choice(self.dirauthaddrs)
-        c = network.thenetwork.connect(self, a)
-        self.consensus = c.getconsensus()
-        print('consensus downloaded:', self.consensus)
-        c.close()
-
     def add_channel(self, channel, peeraddr):
         """Add the given channel to the list of channels we are
         managing.  If we are already managing a channel to the same
@@ -127,7 +119,7 @@ class CellRelay:
         if peeraddr in self.channels:
             self.channels[peeraddr].close()
 
-        channel.cellrelay = self
+        channel.cellhandler = self
         self.channels[peeraddr] = channel
         channel.closer = lambda: self.channels.pop(peeraddr)
 
@@ -137,25 +129,18 @@ class CellRelay:
         if addr in self.channels:
             return self.channels[addr]
 
-        # Create the new connection
-        newconn = network.thenetwork.connect(self.myaddr, addr)
-        self.channels[addr] = newconn
-        newconn.closer = lambda: self.channels.pop(addr)
-        newconn.cellrelay = self
+        # Create the new channel
+        newchannel = network.thenetwork.connect(self.myaddr, addr)
+        self.channels[addr] = newchannel
+        newchannel.closer = lambda: self.channels.pop(addr)
+        newchannel.cellhandler = self
 
-        return newconn
+        return newchannel
 
     def received_msg(self, msg, peeraddr, peer):
         """Callback when a NetMsg not specific to a circuit is
         received."""
         print("Node %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
-        if isinstance(msg, RelayRandomHopMsg):
-            if msg.ttl > 0:
-                # Pick a random next hop from the consensus
-                nexthop = random.choice(self.consensus.consdict['relays'])
-                nextaddr = nexthop.descdict['addr']
-                self.send_msg(RelayRandomHopMsg(msg.ttl-1), nextaddr)
-
 
     def received_cell(self, circid, cell, peeraddr, peer):
         """Callback with a circuit-specific cell is received."""
@@ -163,14 +148,48 @@ class CellRelay:
 
     def send_msg(self, msg, peeraddr):
         """Send a message to the peer with the given address."""
-        conn = self.get_channel_to(peeraddr)
-        conn.send_msg(msg)
+        channel = self.get_channel_to(peeraddr)
+        channel.send_msg(msg)
 
     def send_cell(self, circid, cell, peeraddr):
         """Send a cell on the given circuit to the peer with the given
         address."""
-        conn = self.get_channel_to(peeraddr)
-        conn.send_cell(circid, cell)
+        channel = self.get_channel_to(peeraddr)
+        channel.send_cell(circid, cell)
+
+
+class CellRelay(CellHandler):
+    """The subclass of CellHandler for relays."""
+
+    def __init__(self, myaddr, dirauthaddrs):
+        super().__init__(myaddr, dirauthaddrs)
+
+    def get_consensus(self):
+        """Download a fresh consensus from a random dirauth."""
+        a = random.choice(self.dirauthaddrs)
+        c = network.thenetwork.connect(self, a)
+        self.consensus = c.getconsensus()
+        c.close()
+
+    def received_msg(self, msg, peeraddr, peer):
+        """Callback when a NetMsg not specific to a circuit is
+        received."""
+        print("Node %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
+        if isinstance(msg, RelayRandomHopMsg):
+            if msg.ttl > 0:
+                # Pick a random next hop from the consensus
+                nexthop = random.choice(self.consensus.consdict['relays'])
+                nextaddr = nexthop.descdict['addr']
+                self.send_msg(RelayRandomHopMsg(msg.ttl-1), nextaddr)
+        elif isinstance(msg, RelayGetConsensusMsg):
+            self.send_msg(RelayConsensusMsg(self.consensus), peeraddr)
+        else:
+            return super().received_msg(msg, peeraddr, peer)
+
+    def received_cell(self, circid, cell, peeraddr, peer):
+        """Callback with a circuit-specific cell is received."""
+        print("Node %s received cell on circ %d: %s from %s" % (self.myaddr, circid, cell, peeraddr))
+        return super().received_cell(circid, cell, peeraddr, peer)
 
 
 class Relay(network.Server):
@@ -194,7 +213,7 @@ class Relay(network.Server):
         network.thenetwork.wantepochticks(self, True)
 
         # Create the CellRelay connection manager
-        self.cellrelay = CellRelay(self.netaddr, dirauthaddrs)
+        self.cellhandler = CellRelay(self.netaddr, dirauthaddrs)
 
         # Initially, we're not a fallback relay
         self.is_fallbackrelay = False
@@ -216,7 +235,7 @@ class Relay(network.Server):
         self.uploaddesc(False)
 
         # Close connections to other relays
-        self.cellrelay.terminate()
+        self.cellhandler.terminate()
 
         # Stop listening to our own bound port
         self.close()
@@ -230,7 +249,7 @@ class Relay(network.Server):
         # Download the new consensus, which will have been created
         # already since the dirauths' epoch_ending callbacks happened
         # before the relays'.
-        self.cellrelay.get_consensus()
+        self.cellhandler.get_consensus()
 
     def newepoch(self, epoch):
         self.uploaddesc()
@@ -259,7 +278,7 @@ class Relay(network.Server):
             descmsg = dirauth.DirAuthDelDescMsg(desc)
 
         # Upload them
-        for a in self.cellrelay.dirauthaddrs:
+        for a in self.cellhandler.dirauthaddrs:
             c = network.thenetwork.connect(self, a)
             c.sendmsg(descmsg)
             c.close()
@@ -282,7 +301,7 @@ class Relay(network.Server):
         ourchannel.peer = peerchannel
 
         # Add our channel to the CellRelay
-        self.cellrelay.add_channel(ourchannel, peer)
+        self.cellhandler.add_channel(ourchannel, peer)
 
         return peerchannel
 
@@ -321,18 +340,18 @@ if __name__ == '__main__':
 
     print('ticked; epoch=', network.thenetwork.getepoch())
 
-    relays[3].cellrelay.send_msg(RelayRandomHopMsg(30), relays[5].netaddr)
+    relays[3].cellhandler.send_msg(RelayRandomHopMsg(30), relays[5].netaddr)
 
     # See what channels exist and do a consistency check
     for r in relays:
-        print("%s: %s" % (r.netaddr, [ str(k) for k in r.cellrelay.channels.keys()]))
+        print("%s: %s" % (r.netaddr, [ str(k) for k in r.cellhandler.channels.keys()]))
         raddr = r.netaddr
-        for ad, ch in r.cellrelay.channels.items():
-            if ch.peer.cellrelay.myaddr != ad:
-                print('address mismatch:', raddr, ad, ch.peer.cellrelay.myaddr)
+        for ad, ch in r.cellhandler.channels.items():
+            if ch.peer.cellhandler.myaddr != ad:
+                print('address mismatch:', raddr, ad, ch.peer.cellhandler.myaddr)
 
-            if ch.peer.cellrelay.channels[raddr].peer is not ch:
-                print('asymmetry:', raddr, ad, ch, ch.peer.cellrelay.channels[raddr].peer)
+            if ch.peer.cellhandler.channels[raddr].peer is not ch:
+                print('asymmetry:', raddr, ad, ch, ch.peer.cellhandler.channels[raddr].peer)
 
     # Stop some relays
     relays[3].terminate()
@@ -349,16 +368,16 @@ if __name__ == '__main__':
 
     # See what channels exist and do a consistency check
     for r in relays:
-        print("%s: %s" % (r.netaddr, [ str(k) for k in r.cellrelay.channels.keys()]))
+        print("%s: %s" % (r.netaddr, [ str(k) for k in r.cellhandler.channels.keys()]))
         raddr = r.netaddr
-        for ad, ch in r.cellrelay.channels.items():
-            if ch.peer.cellrelay.myaddr != ad:
-                print('address mismatch:', raddr, ad, ch.peer.cellrelay.myaddr)
+        for ad, ch in r.cellhandler.channels.items():
+            if ch.peer.cellhandler.myaddr != ad:
+                print('address mismatch:', raddr, ad, ch.peer.cellhandler.myaddr)
 
-            if ch.peer.cellrelay.channels[raddr].peer is not ch:
-                print('asymmetry:', raddr, ad, ch, ch.peer.cellrelay.channels[raddr].peer)
+            if ch.peer.cellhandler.channels[raddr].peer is not ch:
+                print('asymmetry:', raddr, ad, ch, ch.peer.cellhandler.channels[raddr].peer)
 
-    #relays[3].cellrelay.send_cell(1, network.StringNetMsg("test"), relays[3].consensus.consdict['relays'][5].descdict['addr'])
-    #relays[3].cellrelay.send_cell(2, network.StringNetMsg("cell"), relays[3].consensus.consdict['relays'][6].descdict['addr'])
-    #relays[3].cellrelay.send_cell(2, network.StringNetMsg("again"), relays[3].consensus.consdict['relays'][1].descdict['addr'])
-    #relays[3].cellrelay.send_cell(2, network.StringNetMsg("and again"), relays[3].consensus.consdict['relays'][5].descdict['addr'])
+    #relays[3].cellhandler.send_cell(1, network.StringNetMsg("test"), relays[3].consensus.consdict['relays'][5].descdict['addr'])
+    #relays[3].cellhandler.send_cell(2, network.StringNetMsg("cell"), relays[3].consensus.consdict['relays'][6].descdict['addr'])
+    #relays[3].cellhandler.send_cell(2, network.StringNetMsg("again"), relays[3].consensus.consdict['relays'][1].descdict['addr'])
+    #relays[3].cellhandler.send_cell(2, network.StringNetMsg("and again"), relays[3].consensus.consdict['relays'][5].descdict['addr'])