[tor-commits] [bridgedb/develop] Add support for respecting "bridge-distribution-request" lines.

isis at torproject.org isis at torproject.org
Sat Oct 28 00:29:39 UTC 2017


commit 75206116fe6f39538f522776a158ca79959a6e79
Author: Isis Lovecruft <isis at torproject.org>
Date:   Fri Oct 27 02:16:30 2017 +0000

    Add support for respecting "bridge-distribution-request" lines.
    
     * CLOSES #23957: https://bugs.torproject.org/23957
---
 bridgedb/Bridges.py | 65 +++++++++++++++++++++++++++++++++++++++--------------
 bridgedb/Storage.py | 24 ++++++++++++++++++++
 bridgedb/bridges.py | 10 +++++++++
 3 files changed, 82 insertions(+), 17 deletions(-)

diff --git a/bridgedb/Bridges.py b/bridgedb/Bridges.py
index ddfb447..ac403b6 100644
--- a/bridgedb/Bridges.py
+++ b/bridgedb/Bridges.py
@@ -454,29 +454,60 @@ class BridgeSplitter(object):
         if not bridge.flags.running:
             return
 
-        # Determine which ring to put this bridge in if we haven't seen it
-        # before.
-        pos = self.hmac(bridge.identity)
-        n = int(pos[:8], 16) % self.totalP
-        pos = bisect.bisect_right(self.pValues, n) - 1
-        assert 0 <= pos < len(self.rings)
-        ringname = self.rings[pos]
-        logging.info("%s placing bridge %s into hashring %s (via n=%s, pos=%s)."
-                     % (self.__class__.__name__, bridge, ringname, n, pos))
-
         validRings = self.rings + self.pseudoRings
+        distribution_method = None
 
+        # If the bridge already has a distributor, use that.
         with bridgedb.Storage.getDB() as db:
-            ringname = db.insertBridgeAndGetRing(bridge, ringname, time.time(), 
-                                             validRings)
+            distribution_method = db.getBridgeDistributor(bridge, validRings)
+
+        if distribution_method:
+            logging.info("%s bridge %s was already in hashring %s" %
+                         (self.__class__.__name__, bridge, distribution_method))
+        else:
+            # Check if the bridge requested a specific distribution method.
+            if bridge.distribution_request:
+                distribution_method = bridge.distribution_request
+                logging.info("%s bridge %s requested placement in hashring %s"
+                             % (self.__class__.__name__, bridge,
+                                distribution_method))
+
+            # If they requested not to be distributed, honor the request:
+            if distribution_method == "none":
+                logging.info("%s bridge %s requested to not be distributed."
+                             % (self.__class__.__name__, bridge))
+                return
+
+            # If we didn't know what they are talking about, or they requested
+            # "any" distribution method, and we've never seen this bridge
+            # before, then determine where to place it.
+            if ((distribution_method not in validRings) or
+                (distribution_method == "any")):
+
+                pos = self.hmac(bridge.identity)
+                n = int(pos[:8], 16) % self.totalP
+                pos = bisect.bisect_right(self.pValues, n) - 1
+                assert 0 <= pos < len(self.rings)
+                distribution_method = self.rings[pos]
+                logging.info(("%s placing bridge %s into hashring %s (via n=%s,"
+                              " pos=%s).") % (self.__class__.__name__, bridge,
+                                              distribution_method, n, pos))
+
+        with bridgedb.Storage.getDB() as db:
+            ringname = db.insertBridgeAndGetRing(bridge, distribution_method,
+                                                 time.time(), validRings)
             db.commit()
 
-            # Pseudo distributors are always held in the "unallocated" ring
-            if ringname in self.pseudoRings:
-                ringname = "unallocated"
+        # Pseudo distributors are always held in the "unallocated" ring
+        if ringname in self.pseudoRings:
+            ringname = "unallocated"
+
+        ring = self.ringsByName.get(ringname)
+        ring.insert(bridge)
 
-            ring = self.ringsByName.get(ringname)
-            ring.insert(bridge)
+        if ring is None:
+            logging.warn("Couldn't recognise ring named: '%s'" % ringname)
+            logging.info("Current rings: %s" % " ".join(self.ringsByName))
 
     def dumpAssignments(self, f, description=""):
         for name,ring in self.ringsByName.iteritems():
diff --git a/bridgedb/Storage.py b/bridgedb/Storage.py
index ea9d26b..4182c4b 100644
--- a/bridgedb/Storage.py
+++ b/bridgedb/Storage.py
@@ -145,6 +145,26 @@ class Database(object):
         self._cur.close()
         self._conn.close()
 
+    def getBridgeDistributor(self, bridge, validRings):
+        """If a ``bridge`` is already in the database, get its distributor.
+
+        :rtype: None or str
+        :returns: The ``bridge`` distribution method, if one was
+            already assigned, otherwise, returns None.
+        """
+        distribution_method = None
+        cur = self._cur
+
+        cur.execute("SELECT id, distributor FROM Bridges WHERE hex_key = ?",
+                    (bridge.fingerprint,))
+        result = cur.fetchone()
+
+        if result:
+            if result[1] in validRings:
+                distribution_method = result[1]
+
+        return distribution_method
+
     def insertBridgeAndGetRing(self, bridge, setRing, seenAt, validRings,
                                defaultPool="unallocated"):
         '''Updates info about bridge, setting ring to setRing if none was set.
@@ -175,6 +195,10 @@ class Database(object):
                          timeToStr(seenAt), i))
             return ring
         else:
+            # Check if this is currently a valid ring name. If not, move back
+            # into default pool.
+            if setRing not in validRings:
+                setRing = defaultPool
             # Insert it.
             cur.execute("INSERT INTO Bridges (hex_key, address, or_port, "
                         "distributor, first_seen, last_seen) "
diff --git a/bridgedb/bridges.py b/bridgedb/bridges.py
index 4386d4e..8b4bc1b 100644
--- a/bridgedb/bridges.py
+++ b/bridgedb/bridges.py
@@ -914,6 +914,12 @@ class Bridge(BridgeBackwardsCompatibility):
         currently serving clients (e.g. if the Bridge hit its configured
         ``RelayBandwidthLimit``); ``False`` otherwise.
 
+    :vartype distribution_request: str
+    :ivar distribution_request: If the bridge specified a
+        "bridgedb-distribution-request" line in its ``@type
+        bridge-server-descriptor``, the requested distribution method will be
+        stored here.  If the line was absent, this will be set to ``"any"``.
+
     :vartype _blockedIn: dict
     :ivar _blockedIn: A dictionary of ``ADDRESS:PORT`` pairs to lists of
         lowercased, two-letter country codes (e.g. ``"us"``, ``"gb"``,
@@ -963,6 +969,7 @@ class Bridge(BridgeBackwardsCompatibility):
         self.flags = Flags()
         self.hibernating = False
         self._blockedIn = {}
+        self.distribution_request = "any"
 
         self.bandwidth = None
         self.bandwidthAverage = None
@@ -1595,6 +1602,9 @@ class Bridge(BridgeBackwardsCompatibility):
         self._updateORAddresses(descriptor.or_addresses)
         self.hibernating = descriptor.hibernating
 
+        if descriptor.bridge_distribution:
+            self.distribution_request = descriptor.bridge_distribution
+
         self.onionKey = descriptor.onion_key
         self.ntorOnionKey = descriptor.ntor_onion_key
         self.signingKey = descriptor.signing_key





More information about the tor-commits mailing list