commit a4ecadb65c467fe277f205b47a2e3c93897d9891 Author: Damian Johnson atagar@torproject.org Date: Thu May 30 11:00:31 2019 -0700
Add stem.descriptor.get_bandwidth_file()
Tor now provides bandwidth files over its DirPort. Adding our corresponding function to download them...
https://trac.torproject.org/projects/tor/ticket/26902
From what I can tell these statistics are available in practice for our next hour's consensus, but not the present one...
atagar@morrigan:~$ curl -s 128.31.0.34:9131/tor/status-vote/next/bandwidth | wc -l 8987
atagar@morrigan:~$ curl -s 128.31.0.34:9131/tor/status-vote/current/bandwidth | wc -l 0 --- docs/_static/example/bandwidth_stats.py | 11 +++++ docs/change_log.rst | 1 + docs/tutorials/double_double_toil_and_trouble.rst | 4 ++ docs/tutorials/examples/bandwidth_stats.rst | 48 ++++++++++++++++++++ stem/descriptor/remote.py | 53 +++++++++++++++++++---- 5 files changed, 109 insertions(+), 8 deletions(-)
diff --git a/docs/_static/example/bandwidth_stats.py b/docs/_static/example/bandwidth_stats.py new file mode 100644 index 00000000..e7e11ceb --- /dev/null +++ b/docs/_static/example/bandwidth_stats.py @@ -0,0 +1,11 @@ +import stem.descriptor.remote + +bandwidth_file = stem.descriptor.remote.get_bandwidth_file().run()[0] + +for fingerprint, measurement in bandwidth_file.measurements.items(): + print('Relay %s' % fingerprint) + + for attr, value in measurement.items(): + print(' %s = %s' % (attr, value)) + + print('') diff --git a/docs/change_log.rst b/docs/change_log.rst index fc09eabc..fec98f5b 100644 --- a/docs/change_log.rst +++ b/docs/change_log.rst @@ -60,6 +60,7 @@ The following are only available within Stem's `git repository * Ed25519 validity checks are now done though the cryptography module rather than PyNaCl (:trac:`22022`) * Download compressed descriptors by default (:trac:`29186`) * Added :func:`stem.descriptor.remote.get_microdescriptors` + * Added :func:`stem.descriptor.remote.get_bandwidth_file` (:trac:`26902`) * Added :class:`~stem.descriptor.networkstatus.DetachedSignature` parsing (:trac:`28495`) * Added :func:`~stem.descriptor.__init__.Descriptor.from_str` method (:trac:`28450`) * Added :func:`~stem.descriptor.__init__.Descriptor.type_annotation` method (:trac:`28397`) diff --git a/docs/tutorials/double_double_toil_and_trouble.rst b/docs/tutorials/double_double_toil_and_trouble.rst index 6e5df2cf..b631d479 100644 --- a/docs/tutorials/double_double_toil_and_trouble.rst +++ b/docs/tutorials/double_double_toil_and_trouble.rst @@ -134,6 +134,10 @@ Descriptors
Example for writing a Tor consensus to disk, and reading it back.
+* `Bandwidth Heuristics <examples/bandwidth_stats.html>`_ + + Download bandwidth authority heuristics about relay capacity. + * `Checking Digests <examples/check_digests.html>`_
Looking for additional integrity that your descriptor is properly signed? diff --git a/docs/tutorials/examples/bandwidth_stats.rst b/docs/tutorials/examples/bandwidth_stats.rst new file mode 100644 index 00000000..3cfddfea --- /dev/null +++ b/docs/tutorials/examples/bandwidth_stats.rst @@ -0,0 +1,48 @@ +Bandwidth Heuristics +==================== + +.. image:: /_static/buttons/back.png + :target: ../double_double_toil_and_trouble.html + +To select the relays it will use Tor consults several factors. Exit policies, +flags, as well as bandwidth heuristics so our circuits are zippy without +overtaxing individual relays. + +These statistics are collected by a special subset of our directory authorites +called **bandwidth authorities**. See our `bandwidth file specification +https://gitweb.torproject.org/torspec.git/tree/bandwidth-file-spec.txt`_ for +details. Statistics are publicly available and generated each hour... + +.. literalinclude:: /_static/example/bandwidth_stats.py + :language: python + +:: + + % python bandwidth_stats.py + + Relay 6AD3EA55B87C80971F353EBA710F6550202A9355 + scanner = /scanner.5/scan-data/bws-59.4:60.1-done-2019-05-29-05:44:10 + measured_at = 1559123050 + pid_delta = -0.360692869958 + updated_at = 1559123050 + pid_error_sum = -0.178566523071 + nick = OrphanOrOften + node_id = $6AD3EA55B87C80971F353EBA710F6550202A9355 + pid_bw = 538334 + bw = 538 + pid_error = -0.178566523071 + circ_fail = 0.0 + + Relay 11B6727E38D249C83E20EEB0647BAD4FACECBEB6 + scanner = /scanner.8/scan-data/bws-92.4:93.1-done-2019-05-23-16:06:26 + measured_at = 1558641986 + pid_delta = 0.0352270644197 + updated_at = 1558641986 + pid_error_sum = -0.822158700788 + nick = snap269 + node_id = $11B6727E38D249C83E20EEB0647BAD4FACECBEB6 + pid_bw = 21124 + bw = 21 + pid_error = -0.822158700788 + circ_fail = 0.0 + diff --git a/stem/descriptor/remote.py b/stem/descriptor/remote.py index c663276b..ab2fc091 100644 --- a/stem/descriptor/remote.py +++ b/stem/descriptor/remote.py @@ -61,6 +61,8 @@ content. For example... |- get_extrainfo_descriptors - provides present extrainfo descriptors |- get_microdescriptors - provides present microdescriptors with the given digests |- get_consensus - provides the present consensus or router status entries + |- get_bandwidth_file - provies bandwidth heuristics used to make the next consensus + |- get_detached_signatures - authority signatures used to make the next consensus |- get_key_certificates - provides present authority key certificates +- query - request an arbitrary descriptor resource
@@ -224,6 +226,18 @@ def get_consensus(authority_v3ident = None, microdescriptor = False, **query_arg return get_instance().get_consensus(authority_v3ident, microdescriptor, **query_args)
+def get_bandwidth_file(**query_args): + """ + Shorthand for + :func:`~stem.descriptor.remote.DescriptorDownloader.get_bandwidth_file` + on our singleton instance. + + .. versionadded:: 1.8.0 + """ + + return get_instance().get_bandwidth_file(**query_args) + + def get_detached_signatures(**query_args): """ Shorthand for @@ -291,6 +305,7 @@ class Query(object): /tor/micro/d/<hash1>-<hash2> microdescriptors with the given hashes /tor/status-vote/current/consensus present consensus /tor/status-vote/current/consensus-microdesc present microdescriptor consensus + /tor/status-vote/next/bandwidth bandwidth authority heuristics for the next consenus /tor/status-vote/next/consensus-signatures detached signature, used for making the next consenus /tor/keys/all key certificates for the authorities /tor/keys/fp/<v3ident1>+<v3ident2> key certificates for specific authorities @@ -849,6 +864,22 @@ class DescriptorDownloader(object):
return self.query(resource, **query_args)
+ def get_bandwidth_file(self, **query_args): + """ + Provides the bandwidth authority heuristics used to make the next + consensus. + + .. versionadded:: 1.8.0 + + :param query_args: additional arguments for the + :class:`~stem.descriptor.remote.Query` constructor + + :returns: :class:`~stem.descriptor.remote.Query` for the bandwidth + authority heuristics + """ + + return self.query('/tor/status-vote/next/bandwidth', **query_args) + def get_detached_signatures(self, **query_args): """ Provides the detached signatures that will be used to make the next @@ -1070,16 +1101,22 @@ def _guess_descriptor_type(resource): return 'extra-info 1.0' elif resource.startswith('/tor/micro/'): return 'microdescriptor 1.0' - elif resource.startswith('/tor/status-vote/next/consensus-signatures'): - return '%s 1.0' % DETACHED_SIGNATURE_TYPE - elif resource.startswith('/tor/status-vote/current/consensus-microdesc'): - return 'network-status-microdesc-consensus-3 1.0' - elif resource.startswith('/tor/status-vote/'): - return 'network-status-consensus-3 1.0' elif resource.startswith('/tor/keys/'): return 'dir-key-certificate-3 1.0' - else: - raise ValueError("Unable to determine the descriptor type for '%s'" % resource) + elif resource.startswith('/tor/status-vote/'): + # The following resource urls can be for the present consensus + # (/tor/status-vote/current/*) or the next (/tor/status-vote/next/*). + + if resource.endswith('/consensus'): + return 'network-status-consensus-3 1.0' + elif resource.endswith('/consensus-microdesc'): + return 'network-status-microdesc-consensus-3 1.0' + elif resource.endswith('/consensus-signatures'): + return '%s 1.0' % DETACHED_SIGNATURE_TYPE + elif resource.endswith('/bandwidth'): + return 'bandwidth-file 1.0' + + raise ValueError("Unable to determine the descriptor type for '%s'" % resource)
def get_authorities():