commit 7b880a1fe0f346c85364b255eee93995a6b31b94 Author: Isis Lovecruft isis@torproject.org Date: Wed Apr 1 08:40:50 2015 +0000
Refactor getBridgesForIP() to use b.https.request.HTTPSBridgeRequest.
* RENAME b.D.IPBasedDistributor.getBridgesForIP() → b.D.IPBasedDistributor.getBridges(). * CREATE new bridgedb.https package. * CREATE new bridgedb.https.request module. * CHANGE b.D.IPBasedDistributor.getBridges() to use bridgedb.https.request.HTTPSBridgeRequest. --- doc/sphinx/source/bridgedb.https.rst | 10 +++ doc/sphinx/source/bridgedb.rst | 1 + lib/bridgedb/Dist.py | 143 ++++++++++++++-------------------- lib/bridgedb/HTTPServer.py | 142 ++++++++++++++------------------- lib/bridgedb/https/__init__.py | 1 + lib/bridgedb/https/request.py | 143 ++++++++++++++++++++++++++++++++++ lib/bridgedb/test/legacy_Tests.py | 26 +++---- lib/bridgedb/test/test_HTTPServer.py | 27 ++++--- setup.py | 1 + 9 files changed, 301 insertions(+), 193 deletions(-)
diff --git a/doc/sphinx/source/bridgedb.https.rst b/doc/sphinx/source/bridgedb.https.rst new file mode 100644 index 0000000..b0ee95c --- /dev/null +++ b/doc/sphinx/source/bridgedb.https.rst @@ -0,0 +1,10 @@ +.. _https-pkg: + +bridgedb.https +--------------- + +.. contents:: bridgedb.https + :depth: 3 + +.. automodule:: bridgedb.https.__init__ +.. automodule:: bridgedb.https.request diff --git a/doc/sphinx/source/bridgedb.rst b/doc/sphinx/source/bridgedb.rst index 3f87647..61ab07f 100644 --- a/doc/sphinx/source/bridgedb.rst +++ b/doc/sphinx/source/bridgedb.rst @@ -17,6 +17,7 @@ BridgeDB Package and Module Documentation bridgedb.email bridgedb.Filters bridgedb.geo + bridgedb.https bridgedb.HTTPServer bridgedb.interfaces bridgedb.Main diff --git a/lib/bridgedb/Dist.py b/lib/bridgedb/Dist.py index 26e952e..3f7835b 100644 --- a/lib/bridgedb/Dist.py +++ b/lib/bridgedb/Dist.py @@ -305,118 +305,92 @@ class IPBasedDistributor(Distributor): """Assign a bridge to this distributor.""" self.splitter.insert(bridge)
- def getBridgesForIP(self, ip, epoch, N=1, countryCode=None, - bridgeFilterRules=None): + def _buildHashringFilters(self, previousFilters, clientCluster): + totalRings = self.nClusters + len(self.categories) + g = filterAssignBridgesToRing(self.splitter.hmac, totalRings, clientCluster) + previousFilters.append(g) + return frozenset(previousFilters) + + def getBridges(self, bridgeRequest, interval, N=1): """Return a list of bridges to give to a user.
- :param str ip: The user's IP address, as a dotted quad. - :param str epoch: The time period when we got this request. This can - be any string, so long as it changes with every - period. + :type bridgeRequest: :class:`bridgedb.https.request.HTTPSBridgeRequest` + :param bridgeRequest: A :class:`~bridgedb.bridgerequest.BridgeRequestBase` + with the :data:`~bridgedb.bridgerequest.BridgeRequestBase.client` + attribute set to a string containing the client's IP address. + :param str interval: The time period when we got this request. This + can be any string, so long as it changes with every period. :param int N: The number of bridges to try to give back. (default: 1) - :param str countryCode: The two-letter geoip country code of the - client's IP address. If given, it is assumed that any bridges - distributed to that client should not be blocked in that - country. (default: None) - :param list bridgeFilterRules: A list of callables used filter the - bridges returned in the response to the - client. See :mod:`~bridgedb.Filters`. :rtype: list :return: A list of :class:`~bridgedb.Bridges.Bridge`s to include in - the response. See - :meth:`bridgedb.HTTPServer.WebResource.getBridgeRequestAnswer` - for an example of how this is used. + the response. See + :meth:`bridgedb.HTTPServer.WebResourceBridges.getBridgeRequestAnswer` + for an example of how this is used. """ logging.info("Attempting to return %d bridges to client %s..." - % (N, ip)) - - if not bridgeFilterRules: - bridgeFilterRules=[] + % (N, bridgeRequest.client))
if not len(self.splitter): logging.warn("Bailing! Splitter has zero bridges!") return []
- logging.debug("Bridges in splitter:\t%d" % len(self.splitter)) - logging.debug("Client request epoch:\t%s" % epoch) - logging.debug("Active bridge filters:\t%s" - % ' '.join([x.func_name for x in bridgeFilterRules])) - - area = self.areaMapper(ip) - logging.debug("IP mapped to area:\t%s" % area) - - key1 = '' - pos = 0 - n = self.nClusters - - # only one of ip categories or area clustering is active - # try to match the request to an ip category + # The cluster the client should draw bridges from: + clientCluster = self.nClusters + # First, check if the client's IP is one of the known proxies in one + # of our :data:`catagories`: for category in self.categories: - # IP Categories - if ip in category: + if bridgeRequest.client in category: # The tag is a tag applied to a proxy IP address when it is # added to the bridgedb.proxy.ProxySet. For Tor Exit relays, # the default is 'exit_relay'. For other proxies loaded from # the PROXY_LIST_FILES config option, the default tag is the # full filename that the IP address originally came from. - tag = category.getTag(ip) - logging.info("Client was from known proxy (tag: %s): %s" % (tag, ip)) - g = filterAssignBridgesToRing(self.splitter.hmac, - self.nClusters + - len(self.categories), - n) - bridgeFilterRules.append(g) + tag = category.getTag(bridgeRequest.client) + logging.info("Client was from known proxy (tag: %s): %s" % + (tag, bridgeRequest.client)) # Cluster Tor/proxy users into four groups. This means that # no matter how many different Tor Exits or proxies a client # uses, the most they can ever get is four different sets of # bridge lines (per period). - group = (int(ipaddr.IPAddress(ip)) % 4) + 1 - logging.debug(("Assigning client hashring position based on: " - "known-proxy<%s>%s") % (epoch, group)) - pos = self.areaOrderHmac("known-proxy<%s>%s" % (epoch, group)) - key1 = getHMAC(self.splitter.key, - "Order-Bridges-In-Ring-%d" % n) + group = (int(ipaddr.IPAddress(bridgeRequest.client)) % 4) + 1 + area = "known-proxy-group-%d" % group break - n += 1 - - # if no category matches, use area clustering + clientCluster += 1 + # If the client wasn't using Tor or any other known proxy, select the + # client's cluster number based upon the /16 of the client's IP + # address: else: - # IP clustering - h = int( self.areaClusterHmac(area)[:8], 16) - # length of numClusters - clusterNum = h % self.nClusters - - g = filterAssignBridgesToRing(self.splitter.hmac, - self.nClusters + - len(self.categories), - clusterNum) - bridgeFilterRules.append(g) - pos = self.areaOrderHmac("<%s>%s" % (epoch, area)) - key1 = getHMAC(self.splitter.key, - "Order-Bridges-In-Ring-%d" % clusterNum) - - # try to find a cached copy - ruleset = frozenset(bridgeFilterRules) - - # See if we have a cached copy of the ring, - # otherwise, add a new ring and populate it - if ruleset in self.splitter.filterRings.keys(): - logging.debug("Cache hit %s" % ruleset) - _, ring = self.splitter.filterRings[ruleset] - - # else create the ring and populate it + # Areas (i.e. /16s) are grouped into the number of rings specified + # by the N_IP_CLUSTERS configuration option. + area = self.areaMapper(bridgeRequest.client) + logging.debug("IP mapped to area:\t%s" % area) + clientCluster = int(self.areaClusterHmac(area)[:8], 16) % self.nClusters + + pos = self.areaOrderHmac("<%s>%s" % (interval, area)) + filters = self._buildHashringFilters(bridgeRequest.filters, clientCluster) + + logging.debug("Assigned client hashring position based on: <%s>%s" % + (interval, area)) + logging.debug("Bridges in splitter:\t%d" % len(self.splitter)) + logging.debug("Active bridge filters:\t%s" % + ' '.join([x.func_name for x in filters])) + + # Check wheth we have a cached copy of the hashring: + if filters in self.splitter.filterRings.keys(): + logging.debug("Cache hit %s" % filters) + _, ring = self.splitter.filterRings[filters] + # Otherwise, construct a new hashring and populate it: else: - logging.debug("Cache miss %s" % ruleset) + logging.debug("Cache miss %s" % filters) + key1 = getHMAC(self.splitter.key, "Order-Bridges-In-Ring-%d" % clientCluster) ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters) - self.splitter.addRing(ring, - ruleset, - filterBridgesByRules(bridgeFilterRules), + self.splitter.addRing(ring, filters, filterBridgesByRules(filters), populate_from=self.splitter.bridges)
- # get an appropriate number of bridges - numBridgesToReturn = getNumBridgesPerAnswer(ring, - max_bridges_per_answer=N) - answer = ring.getBridges(pos, numBridgesToReturn) + # Determine the appropriate number of bridges to give to the client: + returnNum = getNumBridgesPerAnswer(ring, max_bridges_per_answer=N) + answer = ring.getBridges(pos, returnNum) + return answer
def __len__(self): @@ -425,6 +399,7 @@ class IPBasedDistributor(Distributor): def dumpAssignments(self, f, description=""): self.splitter.dumpAssignments(f, description)
+ class EmailBasedDistributor(Distributor): """Object that hands out bridges based on the email address of an incoming request and the current time period. diff --git a/lib/bridgedb/HTTPServer.py b/lib/bridgedb/HTTPServer.py index 8c58380..179c061 100644 --- a/lib/bridgedb/HTTPServer.py +++ b/lib/bridgedb/HTTPServer.py @@ -23,8 +23,6 @@ import os from functools import partial
from ipaddr import IPv4Address -from ipaddr import IPv6Address -from ipaddr import IPAddress
import mako.exceptions from mako.template import Template @@ -32,15 +30,11 @@ from mako.lookup import TemplateLookup
from twisted.internet import reactor from twisted.internet.error import CannotListenError -from twisted.python import filepath from twisted.web import resource from twisted.web import server from twisted.web import static from twisted.web.util import redirectTo
-import bridgedb.Dist -import bridgedb.geo - from bridgedb import captcha from bridgedb import crypto from bridgedb import strings @@ -50,6 +44,7 @@ from bridgedb.Filters import filterBridgesByIP4 from bridgedb.Filters import filterBridgesByIP6 from bridgedb.Filters import filterBridgesByTransport from bridgedb.Filters import filterBridgesByNotBlockedIn +from bridgedb.https.request import HTTPSBridgeRequest from bridgedb.parse import headers from bridgedb.parse.addr import isIPAddress from bridgedb.qrcodes import generateQR @@ -671,6 +666,18 @@ class WebResourceBridges(resource.Resource):
return response
+ def getClientIP(self, request): + """Get the client's IP address from the :header:`X-Forwarded-For` + header, or from the :api:`request <twisted.web.server.Request>`. + + :type request: :api:`twisted.web.http.Request` + :param request: A ``Request`` object for a + :api:`twisted.web.resource.Resource`. + :rtype: None or str + :returns: The client's IP address, if it was obtainable. + """ + return getClientIP(request, self.useForwardedHeader) + def getBridgeRequestAnswer(self, request): """Respond to a client HTTP request for bridges.
@@ -680,104 +687,75 @@ class WebResourceBridges(resource.Resource): :rtype: str :returns: A plaintext or HTML response to serve. """ - # XXX why are we getting the interval if our distributor might be - # using bridgedb.schedule.Unscheduled? + bridgeLines = None interval = self.schedule.intervalStart(time.time()) - bridges = ( ) - ip = None - countryCode = None - - ip = getClientIP(request, self.useForwardedForHeader) - - # Look up the country code of the input IP - if isIPAddress(ip): - countryCode = bridgedb.geo.getCountryCode(IPAddress(ip)) - else: - logging.warn("Invalid IP detected; skipping country lookup.") - countryCode = None - - # XXX separate function again - format = request.args.get("format", None) - if format and len(format): format = format[0] # choose the first arg - - # do want any options? - transport = ipv6 = unblocked = False - - ipv6 = request.args.get("ipv6", False) - if ipv6: ipv6 = True # if anything after ?ipv6= - - # XXX oh dear hell. why not check for the '?transport=' arg before - # regex'ing? And why not compile the regex once, somewhere outside - # this function and class? - try: - # validate method name - transport = re.match('[_a-zA-Z][_a-zA-Z0-9]*', - request.args.get("transport")[0]).group() - except (TypeError, IndexError, AttributeError): - transport = None - - try: - unblocked = re.match('[a-zA-Z]{2,4}', - request.args.get("unblocked")[0]).group() - except (TypeError, IndexError, AttributeError): - unblocked = False + ip = self.getClientIP(request)
logging.info("Replying to web request from %s. Parameters were %r" % (ip, request.args))
- rules = [] - bridgeLines = None - if ip: - if ipv6: - rules.append(filterBridgesByIP6) - addressClass = IPv6Address - else: - rules.append(filterBridgesByIP4) - addressClass = IPv4Address + bridgeRequest = HTTPSBridgeRequest() + bridgeRequest.client = ip + bridgeRequest.isValid(True) + bridgeRequest.withIPversion(request.args) + bridgeRequest.withPluggableTransportType(request.args) + bridgeRequest.withoutBlockInCountry(request) + bridgeRequest.generateFilters() + + bridges = self.distributor.getBridges(bridgeRequest, interval) + bridgeLines = [replaceControlChars(bridge.getBridgeLine( + bridgeRequest, self.includeFingerprints)) for bridge in bridges]
- if transport: - #XXX: A cleaner solution would differentiate between - # addresses by protocol rather than have separate lists - # Tor to be a transport, and selecting between them. - rules = [filterBridgesByTransport(transport, addressClass)] + return self.renderAnswer(request, bridgeLines)
- if unblocked: - rules.append(filterBridgesByNotBlockedIn(unblocked, - addressClass, transport)) + def getResponseFormat(self, request): + """Determine the requested format for the response. + + :type request: :api:`twisted.web.http.Request` + :param request: A ``Request`` object containing the HTTP method, full + URI, and any URL/POST arguments and headers present. + :rtype: ``None`` or str + :returns: The argument of the first occurence of the ``format=`` HTTP + GET parameter, if any were present. (The only one which currently + has any effect is ``format=plain``, see note in + :meth:`renderAnswer`.) Otherwise, returns ``None``. + """ + format = request.args.get("format", None) + if format and len(format): + format = format[0] # Choose the first arg + return format
- bridges = self.distributor.getBridgesForIP(ip, interval, - self.nBridgesToGive, - countryCode, - bridgeFilterRules=rules) - bridgeLines = [replaceControlChars(b.getConfigLine( - includeFingerprint=self.includeFingerprints, - addressClass=addressClass, - transport=transport, - request=bridgedb.Dist.uniformMap(ip))) for b in bridges] + def renderAnswer(self, request, bridgeLines=None): + """Generate a response for a client which includes **bridgesLines**.
- answer = self.renderAnswer(request, bridgeLines, format) - return answer + .. note: The generated response can be plain or HTML. A plain response + looks like::
- def renderAnswer(self, request, bridgeLines=None, format=None): - """Generate a response for a client which includes **bridges**. + voltron 1.2.3.4:1234 ABCDEF01234567890ABCDEF01234567890ABCDEF + voltron 5.5.5.5:5555 0123456789ABCDEF0123456789ABCDEF01234567
- The generated response can be plaintext or HTML. + That is, there is no HTML, what you see is what you get, and what + you get is suitable for pasting directly into Tor Launcher (or + into a torrc, if you prepend ``"Bridge "`` to each line). The + plain format can be requested from BridgeDB's web service by + adding an ``&format=plain`` HTTP GET parameter to the URL. Also + note that you won't get a QRCode, usage instructions, error + messages, or any other fanciness if you use the plain format.
:type request: :api:`twisted.web.http.Request` :param request: A ``Request`` object containing the HTTP method, full URI, and any URL/POST arguments and headers present. :type bridgeLines: list or None :param bridgeLines: A list of strings used to configure a Tor client - to use a bridge. - :type format: str or None - :param format: If ``'plain'``, return a plaintext response. Otherwise, - use the :file:`bridgedb/templates/bridges.html` template to render - an HTML response page which includes the **bridges**. + to use a bridge. If ``None``, then the returned page will instead + explain that there were no bridges of the type they requested, + with instructions on how to proceed. :rtype: str :returns: A plaintext or HTML response to serve. """ rtl = False + format = self.getResponseFormat(request)
if format == 'plain': request.setHeader("Content-Type", "text/plain") diff --git a/lib/bridgedb/https/__init__.py b/lib/bridgedb/https/__init__.py new file mode 100644 index 0000000..f1210ec --- /dev/null +++ b/lib/bridgedb/https/__init__.py @@ -0,0 +1 @@ +"""Servers for BridgeDB's HTTPS bridge distributor.""" diff --git a/lib/bridgedb/https/request.py b/lib/bridgedb/https/request.py new file mode 100644 index 0000000..a8f6920 --- /dev/null +++ b/lib/bridgedb/https/request.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8; test-case-name: bridgedb.test.test_https_request; -*- +#_____________________________________________________________________________ +# +# This file is part of BridgeDB, a Tor bridge distribution system. +# +# :authors: Isis Lovecruft isis@torproject.org 0xA3ADB67A2CDB8B35 +# :copyright: (c) 2007-2015, The Tor Project, Inc. +# (c) 2013-2015, Isis Lovecruft +# :license: see LICENSE for licensing information +#_____________________________________________________________________________ + +""" +.. py:module:: bridgedb.https.request + :synopsis: Classes for parsing and storing information about requests for + bridges which are sent to the HTTPS distributor. + +bridgedb.https.request +====================== + +Classes for parsing and storing information about requests for bridges +which are sent to the HTTPS distributor. + +:: + + bridgedb.https.request + | + |_ HTTPSBridgeRequest - A request for bridges which was received through + the HTTPS distributor. +.. +""" + +from __future__ import print_function +from __future__ import unicode_literals + +import ipaddr +import logging +import re + +from bridgedb import bridgerequest +from bridgedb import geo +from bridgedb.parse import addr + + +#: A regular expression for matching the Pluggable Transport methodname in +#: HTTP GET request parameters. +TRANSPORT_REGEXP = "[_a-zA-Z][_a-zA-Z0-9]*" +TRANSPORT_PATTERN = re.compile(TRANSPORT_REGEXP) + +UNBLOCKED_REGEXP = "[a-zA-Z]{2}" +UNBLOCKED_PATTERN = re.compile(UNBLOCKED_REGEXP) + + +class HTTPSBridgeRequest(bridgerequest.BridgeRequestBase): + """We received a request for bridges through the HTTPS distributor.""" + + def __init__(self, addClientCountryCode=True): + """Process a new bridge request received through the + :class:`~bridgedb.Dist.IPBasedDistributor`. + + :param bool addClientCountryCode: If ``True``, then calling + :meth:`withoutBlockInCountry` will attempt to add the client's own + country code, geolocated from her IP, to the ``notBlockedIn`` + countries list. + """ + super(HTTPSBridgeRequest, self).__init__() + self.addClientCountryCode = addClientCountryCode + + def withIPversion(self, parameters): + """Determine if the request **parameters** were for bridges with IPv6 + addresses or not. + + .. note:: If there is an ``ipv6=`` parameter with anything non-zero + after it, then we assume the client wanted IPv6 bridges. + + :param parameters: The :api:`twisted.web.http.Request.args`. + """ + if parameters.get("ipv6", False): + logging.info("HTTPS request for bridges with IPv6 addresses.") + self.withIPv6() + + def withoutBlockInCountry(self, request): + """Determine which countries the bridges for this **request** should + not be blocked in. + + .. note:: Currently, a **request** for unblocked bridges is recognized + if it contains an HTTP GET parameter ``unblocked=`` whose value is + a comma-separater list of two-letter country codes. Any + two-letter country code found in the + :api:`request <twisted.web.http.Request>` ``unblocked=`` HTTP GET + parameter will be added to the :data:`notBlockedIn` list. + + If :data:`addClientCountryCode` is ``True``, the the client's own + geolocated country code will be added to the to the + :data`notBlockedIn` list. + + :type request: :api:`twisted.web.http.Request` + :param request: A ``Request`` object containing the HTTP method, full + URI, and any URL/POST arguments and headers present. + """ + countryCodes = request.args.get("unblocked", list()) + + for countryCode in countryCodes: + try: + country = UNBLOCKED_PATTERN.match(countryCode).group() + except (TypeError, AttributeError): + pass + else: + if country: + self.notBlockedIn.append(country) + logging.info("HTTPS request for bridges not blocked in: %r" + % country) + + if self.addClientCountryCode: + # Look up the country code of the input IP, and request bridges + # not blocked in that country. + if addr.isIPAddress(self.client): + country = geo.getCountryCode(ipaddr.IPAddress(self.client)) + if country: + self.notBlockedIn.append(country) + logging.info( + ("HTTPS client's bridges also shouldn't be blocked " + "in their GeoIP country code: %s") % country) + + def withPluggableTransportType(self, parameters): + """This request included a specific Pluggable Transport identifier. + + Add any Pluggable Transport methodname found in the HTTP GET + **parameters** to the list of ``transports``. Currently, a request for + a transport is recognized if the request contains the + ``'transport='`` parameter. + + :param parameters: The :api:`twisted.web.http.Request.args`. + """ + for methodname in parameters.get("transport", list()): + try: + transport = TRANSPORT_PATTERN.match(methodname).group() + except (TypeError, AttributeError): + pass + else: + if transport: + self.transports.append(transport) + logging.info("HTTPS request for transport type: %r" + % transport) diff --git a/lib/bridgedb/test/legacy_Tests.py b/lib/bridgedb/test/legacy_Tests.py index 2e4d56f..97134e4 100644 --- a/lib/bridgedb/test/legacy_Tests.py +++ b/lib/bridgedb/test/legacy_Tests.py @@ -190,8 +190,8 @@ class IPBridgeDistTests(unittest.TestCase): d = bridgedb.Dist.IPBasedDistributor(self.dumbAreaMapper, 3, "Foo") for _ in xrange(256): d.insert(fakeBridge()) - n = d.getBridgesForIP("1.2.3.4", "x", 2) - n2 = d.getBridgesForIP("1.2.3.4", "x", 2) + n = d.getBridges("1.2.3.4", "x", 2) + n2 = d.getBridges("1.2.3.4", "x", 2) self.assertEquals(n, n2)
def testDistWithCategories(self): @@ -205,8 +205,8 @@ class IPBridgeDistTests(unittest.TestCase): # Make sure that the categories do not overlap f = lambda: ".".join([str(random.randrange(1,255)) for _ in xrange(4)]) g = lambda: ".".join([str(random.randrange(1,255)) for _ in xrange(3)] + ['255']) - n = d.getBridgesForIP(g(), "x", 10) - n2 = d.getBridgesForIP(f(), "x", 10) + n = d.getBridges(g(), "x", 10) + n2 = d.getBridges(f(), "x", 10)
assert(len(n) > 0) assert(len(n2) > 0) @@ -228,7 +228,7 @@ class IPBridgeDistTests(unittest.TestCase): # d.insert(fakeBridge()) # for _ in xrange(32): # i = randomIP() - # n = d.getBridgesForIP(i, "x", 5) + # n = d.getBridges(i, "x", 5) # count = 0 # fps = {} # for b in n: @@ -246,7 +246,7 @@ class IPBridgeDistTests(unittest.TestCase): d.insert(fakeBridge(or_addresses=True))
for i in xrange(500): - bridges = d.getBridgesForIP(randomIPv4String(), + bridges = d.getBridges(randomIPv4String(), "faketimestamp", bridgeFilterRules=[filterBridgesByIP6]) bridge = random.choice(bridges) @@ -262,7 +262,7 @@ class IPBridgeDistTests(unittest.TestCase): d.insert(fakeBridge(or_addresses=True))
for i in xrange(500): - bridges = d.getBridgesForIP(randomIPv4String(), + bridges = d.getBridges(randomIPv4String(), "faketimestamp", bridgeFilterRules=[filterBridgesByIP4]) bridge = random.choice(bridges) @@ -278,7 +278,7 @@ class IPBridgeDistTests(unittest.TestCase): d.insert(fakeBridge(or_addresses=True))
for i in xrange(50): - bridges = d.getBridgesForIP(randomIPv4String(), + bridges = d.getBridges(randomIPv4String(), "faketimestamp", 1, bridgeFilterRules=[ filterBridgesByIP4, @@ -302,7 +302,7 @@ class IPBridgeDistTests(unittest.TestCase): d.insert(fakeBridge(or_addresses=True))
for i in xrange(5): - b = d.getBridgesForIP(randomIPv4String(), "x", 1, bridgeFilterRules=[ + b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[ filterBridgesByIP4, filterBridgesByIP6]) assert len(b) == 0
@@ -325,10 +325,10 @@ class IPBridgeDistTests(unittest.TestCase): b.blockingCountries[key] = set(['cn'])
for i in xrange(5): - b = d.getBridgesForIP(randomIPv4String(), "x", 1, bridgeFilterRules=[ + b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[ filterBridgesByNotBlockedIn("cn")]) assert len(b) == 0 - b = d.getBridgesForIP(randomIPv4String(), "x", 1, bridgeFilterRules=[ + b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[ filterBridgesByNotBlockedIn("us")]) assert len(b) > 0
@@ -356,7 +356,7 @@ class IPBridgeDistTests(unittest.TestCase): # we probably will get at least one bridge back! # it's pretty unlikely to lose a coin flip 250 times in a row for i in xrange(5): - b = d.getBridgesForIP(randomIPString(), "x", 1, + b = d.getBridges(randomIPString(), "x", 1, bridgeFilterRules=[ filterBridgesByNotBlockedIn("cn", methodname='obfs2'), filterBridgesByTransport('obfs2'), @@ -364,7 +364,7 @@ class IPBridgeDistTests(unittest.TestCase): try: assert len(b) > 0 except AssertionError: print("epic fail") - b = d.getBridgesForIP(randomIPString(), "x", 1, bridgeFilterRules=[ + b = d.getBridges(randomIPString(), "x", 1, bridgeFilterRules=[ filterBridgesByNotBlockedIn("us")]) assert len(b) > 0
diff --git a/lib/bridgedb/test/test_HTTPServer.py b/lib/bridgedb/test/test_HTTPServer.py index d597c55..415fb8d 100644 --- a/lib/bridgedb/test/test_HTTPServer.py +++ b/lib/bridgedb/test/test_HTTPServer.py @@ -448,22 +448,22 @@ class DummyBridge(object): self.or_addresses = {ipaddr.IPv6Address(self._returnSix()): self._randORPort()}
- def getConfigLine(self, includeFingerprint=True, - addressClass=ipaddr.IPv4Address, - transport=None, - request=None): + def getBridgeLine(self, bridgeRequest, includeFingerprint=True): """Get a "torrc" bridge config line to give to a client.""" + if not bridgeRequest.isValid(): + return line = [] - if transport is not None: - #print("getConfigLine got transport=%r" % transport) - line.append(str(transport)) - line.append("%s:%s" % (self.ip, self.orport)) - if includeFingerprint is True: line.append(self.fingerprint) + if bridgeRequest.transports: + line.append(bridgeRequest.transports[-1]) # Just the last PT + if bridgeRequest.addressClass is ipaddr.IPv6Address: + line.append("[%s]:%s" % self.or_addresses.items()[0]) + else: + line.append("%s:%s" % (self.ip, self.orport)) + if includeFingerprint is True: + line.append(self.fingerprint) if self.ptArgs: line.append(','.join(['='.join(x) for x in self.ptArgs.items()])) - bridgeLine = " ".join([item for item in line]) - #print "Created config line: %r" % bridgeLine - return bridgeLine + return " ".join([item for item in line])
class DummyMaliciousBridge(DummyBridge): @@ -499,8 +499,7 @@ class DummyIPBasedDistributor(object): self.ipCategories = ipCategories self.answerParameters = answerParameters
- def getBridgesForIP(self, ip=None, epoch=None, N=1, - countyCode=None, bridgeFilterRules=None): + def getBridges(self, bridgeRequest=None, epoch=None, N=1): """Needed because it's called in :meth:`WebResourceBridges.getBridgesForIP`. """ diff --git a/setup.py b/setup.py index b788a7e..c75239a 100644 --- a/setup.py +++ b/setup.py @@ -392,6 +392,7 @@ setuptools.setup( package_dir={'': 'lib'}, packages=['bridgedb', 'bridgedb.email', + 'bridgedb.https', 'bridgedb.parse', 'bridgedb.test'], scripts=['scripts/bridgedb',
tor-commits@lists.torproject.org