1 # -*- coding: utf-8 ; test-case-name: bridgedb.test.test_filters ; -*-
2 #_____________________________________________________________________________
4 # This file is part of BridgeDB, a Tor bridge distribution system.
6 # :authors: Nick Mathewson <nickm@torproject.org>
7 # Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis@torproject.org>
8 # please also see AUTHORS file
9 # :copyright: (c) 2007-2017, The Tor Project, Inc.
10 # (c) 2013-2017, Isis Lovecruft
11 # :license: see LICENSE for licensing information
12 #_____________________________________________________________________________
14 """Functions for filtering :class:`Bridges <bridgedb.bridges.Bridge>`."""
19 from ipaddr
import IPv4Address
20 from ipaddr
import IPv6Address
22 from bridgedb
.parse
.addr
import isIPv
28 def bySubring(hmac
, assigned
, total
):
29 """Create a filter function which filters for only the bridges which fall
30 into the same **assigned** subhashring (based on the results of an **hmac**
34 :param hmac: An HMAC function, i.e. as returned from
35 :func:`bridgedb.crypto.getHMACFunc`.
36 :param int assigned: The subring number that we wish to draw bridges from.
37 For example, if a user is assigned to subring 2of3 based on their IP
38 address, then this function should only return bridges which would
39 also be assigned to subring 2of3.
40 :param int total: The total number of subrings.
42 :returns: A filter function for :class:`Bridges <bridgedb.bridges.Bridge>`.
44 logging
.debug(("Creating a filter for assigning bridges to subhashring "
45 "%s-of-%s...") % (assigned
, total
))
47 name
= "-".join([binascii
.hexlify(str(hmac("")[:8]).encode('utf-8')).decode('utf-8'),
48 str(assigned
), "of", str(total
)])
52 def _bySubring(bridge
):
53 position
= int(hmac(bridge
.identity
)[:8], 16)
54 which
= (position
% total
) + 1
55 return True if which
== assigned
else False
56 # The `description` attribute must contain an `=`, or else
57 # dumpAssignments() will not work correctly.
58 setattr(_bySubring
, "description", "ring=%d" % assigned
)
59 _bySubring
.__name
__ = ("bySubring%sof%s" % (assigned
, total
))
60 _bySubring
.name
= name
61 _cache
[name
] = _bySubring
64 def byFilters(filtres
):
65 """Returns a filter which filters by multiple **filtres**.
67 :param list filtres: A list (or other iterable) of callables which some
68 :class:`Bridges <bridgedb.bridges.Bridge>` should be filtered
71 :returns: A filter function for :class:`Bridges <bridgedb.bridges.Bridge>`.
74 for filtre
in filtres
:
75 name
.extend(filtre
.name
.split(" "))
76 name
= " ".join(set(name
))
81 def _byFilters(bridge
):
82 results
= [f(bridge
) for f
in filtres
]
86 setattr(_byFilters
, "description",
87 " ".join([getattr(f
, "description", "") for f
in filtres
]))
88 _byFilters
.name
= name
89 _cache
[name
] = _byFilters
92 def byIPv(ipVersion
=None):
93 """Return ``True`` if at least one of the **bridge**'s addresses has the
94 specified **ipVersion**.
96 :param int ipVersion: Either ``4`` or ``6``.
98 if not ipVersion
in (4, 6):
101 name
= "ipv%d" % ipVersion
106 """Determine if the **bridge** has an IPv{0} address.
108 :type bridge: :class:`bridgedb.bridges.Bridge`
109 :param bridge: A bridge to filter.
111 :returns: ``True`` if the **bridge** has an address with the
112 correct IP version; ``False`` otherwise.
114 if isIPv(ipVersion
, bridge
.address
):
117 for address
, port
, version
in bridge
.allVanillaAddresses
:
118 if version
== ipVersion
or isIPv(ipVersion
, address
):
121 setattr(_byIPv
, "description", "ip=%d" % ipVersion
)
122 _byIPv
.__name
__ = "byIPv%d()" % ipVersion
123 _byIPv
.__doc
__ = _byIPv
.__doc
__.format(ipVersion
)
125 _cache
[name
] = _byIPv
131 def byProbingResistance(methodname
=None, ipVersion
=None):
132 """Return ``True`` if the bridge can be given out safely without
133 jeopardizing a probing-resistant transport that runs on the same bridge.
135 :param str methodname: A Pluggable Transport
136 :data:`~bridgedb.bridges.PluggableTransport.methodname`.
137 :param int ipVersion: Either ``4`` or ``6``. The IP version that the
138 ``Bridge``'s ``PluggableTransport``
139 :attr:`address <bridgedb.bridges.PluggableTransport.address>` should
142 :returns: A filter function for :class:`Bridges <bridgedb.bridges.Bridge>`.
145 if ipVersion
not in (4, 6):
148 methodname
= "vanilla" if methodname
is None else methodname
.lower()
149 name
= "by-probing-resistance-%s ipv%d" % (methodname
, ipVersion
)
154 def _byProbingResistance(bridge
):
155 # If we're dealing with a vanilla bridge, make sure that the bridge
156 # has the correct IP version.
157 if methodname
== "vanilla":
158 validVersion
= byIPv(ipVersion
)
159 if not validVersion(bridge
):
162 if bridge
.hasProbingResistantPT():
163 return methodname
in ('scramblesuit', 'obfs4')
166 setattr(_byProbingResistance
, "description", "probing_resistance")
167 _byProbingResistance
.__name
__ = "byProbingResistance(%s,%s)" % (methodname
, ipVersion
)
168 _byProbingResistance
.name
= name
169 _cache
[name
] = _byProbingResistance
170 return _byProbingResistance
172 def byTransport(methodname
=None, ipVersion
=None):
173 """Returns a filter function for a :class:`~bridgedb.bridges.Bridge`.
175 The returned filter function should be called on a
176 :class:`~bridgedb.bridges.Bridge`. It returns ``True`` if the
177 :class:`~bridgedb.bridges.Bridge` has a
178 :class:`~bridgedb.bridges.PluggableTransport` such that:
180 1. The :data:`methodname <bridgedb.bridges.PluggableTransport.methodname>`
181 matches **methodname**, and,
183 2. The :attr:`bridgedb.bridges.PluggableTransport.address.version`
184 equals the **ipVersion**.
186 :param str methodname: A Pluggable Transport
187 :data:`~bridgedb.bridges.PluggableTransport.methodname`.
188 :param int ipVersion: Either ``4`` or ``6``. The IP version that the
189 ``Bridge``'s ``PluggableTransport``
190 :attr:`address <bridgedb.bridges.PluggableTransport.address>` should
193 :returns: A filter function for :class:`Bridges <bridgedb.bridges.Bridge>`.
195 if not ipVersion
in (4, 6):
198 return byIPv(ipVersion
)
200 methodname
= methodname
.lower()
201 name
= "transport-%s ipv%d" % (methodname
, ipVersion
)
206 def _byTransport(bridge
):
207 for transport
in bridge
.transports
:
208 if transport
.methodname
== methodname
:
209 if transport
.address
.version
== ipVersion
:
212 setattr(_byTransport
, "description", "transport=%s" % methodname
)
213 _byTransport
.__name
__ = "byTransport(%s,%s)" % (methodname
, ipVersion
)
214 _byTransport
.name
= name
215 _cache
[name
] = _byTransport
218 def byNotBlockedIn(countryCode
=None, methodname
=None, ipVersion
=4):
219 """Returns a filter function for :class:`Bridges <bridgedb.bridges.Bridge>`.
221 If a Pluggable Transport **methodname** was not specified, the returned
222 filter function returns ``True`` if any of the ``Bridge``'s addresses or
223 :class:`~bridgedb.bridges.PluggableTransport` addresses aren't blocked in
224 **countryCode**. See :meth:`~bridgedb.bridges.Bridge.isBlockedIn`.
226 Otherwise, if a Pluggable Transport **methodname** was specified, it
227 returns ``True`` if the :class:`~bridgedb.bridges.Bridge` has a
228 :class:`~bridgedb.bridges.PluggableTransport` such that:
230 1. The :data:`methodname <bridgedb.bridges.PluggableTransport.methodname>`
231 matches **methodname**,
233 2. The :attr:`bridgedb.bridges.PluggableTransport.address.version`
234 equals the **ipVersion**, and,
236 3. The :class:`~bridgedb.bridges.PluggableTransport`.
237 :attr:`address <bridgedb.bridges.PluggableTransport.address>` isn't
238 known to be blocked in **countryCode**.
240 :type countryCode: str or ``None``
241 :param countryCode: A two-letter country code which the filtered
242 :class:`PluggableTransports <bridgedb.bridges.PluggableTransport>`
243 should not be blocked in.
244 :param str methodname: A Pluggable Transport
245 :data:`methodname <bridgedb.bridges.PluggableTransport.methodname>`.
246 :param int ipVersion: Either ``4`` or ``6``. The IP version that the
247 ``PluggableTransports``'s addresses should have.
249 :returns: A filter function for :class:`Bridges <bridgedb.bridges.Bridge>`.
251 if not ipVersion
in (4, 6):
254 return byTransport(methodname
, ipVersion
)
256 methodname
= methodname
.lower() if methodname
else methodname
257 countryCode
= countryCode
.lower()
261 name
.append("transport-%s" % methodname
)
262 name
.append("ipv%d" % ipVersion
)
263 name
.append("not-blocked-in-%s" % countryCode
)
264 name
= " ".join(name
)
269 def _byNotBlockedIn(bridge
):
271 return not bridge
.isBlockedIn(countryCode
)
272 elif methodname
== "vanilla":
273 validVersion
= byIPv(ipVersion
)
274 if validVersion(bridge
):
275 if not bridge
.addressIsBlockedIn(countryCode
,
280 # Since bridge.transportIsBlockedIn() will return True if the
281 # bridge has that type of transport AND that transport is
282 # blocked, we can "fail fast" here by doing this faster check
283 # before iterating over all the transports testing for the
285 if bridge
.transportIsBlockedIn(countryCode
, methodname
):
288 for transport
in bridge
.transports
:
289 if transport
.methodname
== methodname
:
290 if transport
.address
.version
== ipVersion
:
293 setattr(_byNotBlockedIn
, "description", "unblocked=%s" % countryCode
)
294 _byNotBlockedIn
.__name
__ = ("byTransportNotBlockedIn(%s,%s,%s)"
295 % (methodname
, countryCode
, ipVersion
))
296 _byNotBlockedIn
.name
= name
297 _cache
[name
] = _byNotBlockedIn
298 return _byNotBlockedIn