commit 75206116fe6f39538f522776a158ca79959a6e79 Author: Isis Lovecruft isis@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
tor-commits@lists.torproject.org