1 # -*- coding: utf-8 ; test-case-name: bridgedb.test.test_bridgerequest ; -*-
2 #_____________________________________________________________________________
4 # This file is part of BridgeDB, a Tor bridge distribution system.
6 # :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis@torproject.org>
7 # please also see AUTHORS file
8 # :copyright: (c) 2007-2017, The Tor Project, Inc.
9 # (c) 2014-2017, Isis Lovecruft
10 # :license: see LICENSE for licensing information
11 #_____________________________________________________________________________
13 """API for creating classes which store information on the type of bridges
14 requested by a client.
16 .. inheritance-diagram:: BridgeRequestBase
23 from zope
.interface
import implementer
24 from zope
.interface
import Attribute
25 from zope
.interface
import Interface
27 from bridgedb
.crypto
import getHMACFunc
28 from bridgedb
.filters
import byIPv
29 from bridgedb
.filters
import byNotBlockedIn
30 from bridgedb
.filters
import byTransport
31 from bridgedb
.filters
import byProbingResistance
34 class IRequestBridges(Interface
):
35 """Interface specification of client options for requested bridges."""
38 "A list of callables used to filter bridges from a hashring.")
39 ipVersion
= Attribute(
40 "The IP version of bridge addresses to distribute to the client.")
41 transports
= Attribute(
42 "A list of strings of Pluggable Transport types requested.")
43 notBlockedIn
= Attribute(
44 "A list of two-character country codes. The distributed bridges "
45 "should not be blocked in these countries.")
47 "A boolean. Should be ``True`` if the client's request was valid.")
49 "This should be some information unique to the client making the "
50 "request for bridges, such that we are able to HMAC this unique "
51 "data, via :meth:`getHashringPlacement()`, in order to place the "
52 "client into a hashring (determining which bridge addresses they get "
53 "in the request response).")
56 """Add a filter to the list of ``filters``."""
59 """Clear the list of ``filters``."""
61 def generateFilters():
62 """Build the list of callables, ``filters``, according to the current
63 contents of the lists of ``transports``, ``notBlockedIn``, and the
67 def getHashringPlacement():
68 """Use some unique parameters of the client making this request to
69 obtain a value which we can use to place them into one of the hashrings
70 with :class:`~bridgedb.bridges.Bridge`s in it, in order to give that
71 client different bridges than other clients.
75 """Determine if the request is ``valid`` according to some parameters."""
78 """Set the ``ipVersion`` to IPv4."""
81 """Set the ``ipVersion`` to IPv6."""
83 def withPluggableTransportType(typeOfPT
):
84 """Add this **typeOfPT** to the list of requested ``transports``."""
86 def withoutBlockInCountry(countryCode
):
87 """Add this **countryCode** to the list of countries which distributed
88 bridges should not be blocked in (``notBlockedIn``).
92 @implementer(IRequestBridges
)
93 class BridgeRequestBase(object):
94 """A generic base class for storing options of a client bridge request.
96 :vartype filters: list
97 :ivar filters: A list of callables used to filter bridges from a hashring.
98 :vartype transports: list
99 :ivar transports: A list of strings of Pluggable Transport types requested.
100 :vartype notBlockedIn: list
101 :ivar notBlockedIn: A list of two-character country codes. The distributed
102 bridges should not be blocked in these countries.
104 :ivar client: This should be some information unique to the client making
105 the request for bridges, such that we are able to HMAC this unique
106 data in order to place the client into a hashring (determining which
107 bridge addresses they get in the request response). It defaults to the
108 string ``'default'``.
110 :ivar valid: Should be ``True`` if the client's request was valid.
113 def __init__(self
, ipVersion
=None):
114 self
.ipVersion
= ipVersion
115 self
.filters
= list()
116 self
.transports
= list()
117 self
.notBlockedIn
= list()
118 self
.client
= 'default'
122 """Return a human-readable string describing this bridge request."""
123 return "%s(ipVersion=%d, transports=%s, notBlockedIn=%s, valid=%s)" % \
124 (self
.__class
__.__name
__,
132 """The IP version of bridge addresses to distribute to the client.
135 :returns: Either ``4`` or ``6``.
137 return self
._ipVersion
140 def ipVersion(self
, ipVersion
):
141 """The IP version of bridge addresses to distribute to the client.
143 :param int ipVersion: The IP address version for the bridge lines we
144 should distribute in response to this client request.
146 if not ipVersion
in (4, 6):
148 self
._ipVersion
= ipVersion
150 def getHashringPlacement(self
, key
, client
=None):
151 """Create an HMAC of some **client** info using a **key**.
153 :param str key: The key to use for HMACing.
154 :param str client: Some (hopefully unique) information about the
155 client who is requesting bridges, such as an IP or email address.
157 :returns: A long specifying index of the first node in a hashring to
158 be distributed to the client. This value should obviously be used
159 mod the number of nodes in the hashring.
162 client
= type('')(self
.client
)
164 # Get an HMAC with the key of the client identifier:
165 digest
= getHMACFunc(key
)(client
)
166 # Take the lower 8 bytes of the digest and convert to a long:
167 position
= int(digest
[:8], 16)
170 def isValid(self
, valid
=None):
171 """Get or set the validity of this bridge request.
173 If called without parameters, this method will return the current
174 state, otherwise (if called with the **valid** parameter), it will set
175 the current state of validity for this request.
177 :param bool valid: If given, set the validity state of this
178 request. Otherwise, get the current state.
180 if valid
is not None:
181 self
.valid
= bool(valid
)
185 """Set the ``ipVersion`` to IPv4."""
189 """Set the ``ipVersion`` to IPv6."""
192 def withoutBlockInCountry(self
, country
):
193 """Add this **countryCode** to the list of countries which distributed
194 bridges should not be blocked in (``notBlockedIn``).
196 self
.notBlockedIn
.append(country
.lower())
198 def withPluggableTransportType(self
, pt
):
199 """Add this **pt** to the list of requested ``transports``.
201 :param str pt: A :class:`~bridgedb.bridges.PluggableTransport`.
202 :data:`methodname <bridgedb.bridges.PluggableTransport.methodname>`.
204 self
.transports
.append(pt
)
206 def addFilter(self
, filtre
):
207 """Add a **filtre** to the list of ``filters``.
209 :type filter: callable
210 :param filter: A filter function, e.g. one generated via
211 :mod:`bridgedb.filters`.
213 self
.filters
.append(filtre
)
215 def clearFilters(self
):
216 """Clear the list of ``filters``."""
219 def justOnePTType(self
):
220 """Get just one bridge type (e.g. a
221 :data:`methodname <bridgedb.bridges.PluggableTransport.methodname>` of
222 :class:`~bridgedb.bridges.PluggableTransport`) at a time!
226 ptType
= self
.transports
[-1] # Use the last PT requested
228 logging
.debug("No pluggable transports were requested.")
231 def generateFilters(self
):
232 """Build the list of callables, ``filters``, according to the current
233 contents of the lists of ``transports``, ``notBlockedIn``, and the
238 pt
= self
.justOnePTType()
239 msg
= ("Adding a filter to %s for %s for IPv%d"
240 % (self
.__class
__.__name
__, self
.client
, self
.ipVersion
))
242 # If this bridge runs any active probing-resistant PTs, we should
243 # *only* hand out its active probing-resistant PTs. Otherwise, a
244 # non-resistant PT would get this bridge scanned and blocked:
245 # <https://bugs.torproject.org/28655>
246 self
.addFilter(byProbingResistance(pt
, self
.ipVersion
))
248 if self
.notBlockedIn
:
249 for country
in self
.notBlockedIn
:
250 logging
.info("%s %s bridges not blocked in %s..." %
251 (msg
, pt
or "vanilla", country
))
252 self
.addFilter(byNotBlockedIn(country
, pt
or "vanilla", self
.ipVersion
))
254 logging
.info("%s %s bridges..." % (msg
, pt
))
255 self
.addFilter(byTransport(pt
, self
.ipVersion
))
257 logging
.info("%s bridges..." % msg
)
258 self
.addFilter(byIPv(self
.ipVersion
))