[tor-commits] [stem/master] Support for extrainfo descriptor hidden service stats
atagar at torproject.org
atagar at torproject.org
Sun Mar 15 20:17:48 UTC 2015
commit 4ad8f60e2622e1ae5a7a46d131350c8042f02d66
Author: Damian Johnson <atagar at torproject.org>
Date: Sun Mar 15 13:17:07 2015 -0700
Support for extrainfo descriptor hidden service stats
Adding support for the new extrainfo descriptor stats for hidden services...
https://gitweb.torproject.org/torspec.git/commit/?id=ddb630d
---
docs/change_log.rst | 1 +
stem/descriptor/extrainfo_descriptor.py | 50 +++++++++++++++++++++
test/unit/descriptor/extrainfo_descriptor.py | 61 ++++++++++++++++++++++++++
3 files changed, 112 insertions(+)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index 2154341..3764c9b 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -62,6 +62,7 @@ conversion (:trac:`14075`).
* Added `support for hidden service descriptors <api/descriptor/hidden_service_descriptor.html>`_ (:trac:`15004`)
* The :class:`~stem.descriptor.networkstatus.DirectoryAuthority` 'fingerprint' attribute was actually its 'v3ident'
* Added consensus' new package attribute (:spec:`ab64534`)
+ * Added extra info' new hs_stats_end, hs_rend_cells, hs_rend_cells_attr, hs_dir_onions_seen, and hs_dir_onions_seen_attr attributes (:spec:`ddb630d`)
* Updating Faravahar's address (:trac:`14487`)
* **Utilities**
diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py
index 1a7bff2..228532f 100644
--- a/stem/descriptor/extrainfo_descriptor.py
+++ b/stem/descriptor/extrainfo_descriptor.py
@@ -502,6 +502,32 @@ def _parse_bridge_ip_transports_line(descriptor, entries):
descriptor.ip_transports = ip_transports
+def _parse_hs_stats(keyword, stat_attribute, extra_attribute, descriptor, entries):
+ # "<keyword>" num key=val key=val...
+
+ value, stat, extra = _value(keyword, entries), None, {}
+
+ if value is not None:
+ value_comp = value.split()
+
+ if not value_comp:
+ raise ValueError("'%s' line was blank" % keyword)
+ elif not value_comp[0].isdigit():
+ raise ValueError("'%s' stat was non-numeric (%s): %s %s" % (keyword, value_comp[0], keyword, value))
+
+ stat = int(value_comp[0])
+
+ for entry in value_comp[1:]:
+ if '=' not in entry:
+ raise ValueError('Entries after the stat in %s lines should only be key=val entries: %s %s' % (keyword, keyword, value))
+
+ key, val = entry.split('=', 1)
+ extra[key] = val
+
+ setattr(descriptor, stat_attribute, stat)
+ setattr(descriptor, extra_attribute, extra)
+
+
_parse_geoip_db_digest_line = _parse_forty_character_hex('geoip-db-digest', 'geoip_db_digest')
_parse_geoip6_db_digest_line = _parse_forty_character_hex('geoip6-db-digest', 'geoip6_db_digest')
_parse_dirreq_v2_resp_line = functools.partial(_parse_dirreq_line, 'dirreq-v2-resp', 'dir_v2_responses', 'dir_v2_responses_unknown')
@@ -529,6 +555,9 @@ _parse_dirreq_write_history_line = functools.partial(_parse_history_line, 'dirre
_parse_exit_kibibytes_written_line = functools.partial(_parse_port_count_line, 'exit-kibibytes-written', 'exit_kibibytes_written')
_parse_exit_kibibytes_read_line = functools.partial(_parse_port_count_line, 'exit-kibibytes-read', 'exit_kibibytes_read')
_parse_exit_streams_opened_line = functools.partial(_parse_port_count_line, 'exit-streams-opened', 'exit_streams_opened')
+_parse_hidden_service_stats_end_line = _parse_timestamp_line('hidserv-stats-end', 'hs_stats_end')
+_parse_hidden_service_rend_relayed_cells_line = functools.partial(_parse_hs_stats, 'hidserv-rend-relayed-cells', 'hs_rend_cells', 'hs_rend_cells_attr')
+_parse_hidden_service_dir_onions_seen_line = functools.partial(_parse_hs_stats, 'hidserv-dir-onions-seen', 'hs_dir_onions_seen', 'hs_dir_onions_seen_attr')
_parse_dirreq_v2_ips_line = functools.partial(_parse_geoip_to_count_line, 'dirreq-v2-ips', 'dir_v2_ips')
_parse_dirreq_v3_ips_line = functools.partial(_parse_geoip_to_count_line, 'dirreq-v3-ips', 'dir_v3_ips')
_parse_dirreq_v2_reqs_line = functools.partial(_parse_geoip_to_count_line, 'dirreq-v2-reqs', 'dir_v2_requests')
@@ -631,6 +660,14 @@ class ExtraInfoDescriptor(Descriptor):
:var dict exit_kibibytes_read: traffic per port (keys are ints or 'other')
:var dict exit_streams_opened: streams per port (keys are ints or 'other')
+ **Hidden Service Attributes:**
+
+ :var datetime hs_stats_end: end of the sampling interval
+ :var int hs_rend_cells: rounded count of the RENDEZVOUS1 cells seen
+ :var int hs_rend_cells_attr: **\*** attributes provided for the hs_rend_cells
+ :var int hs_dir_onions_seen: rounded count of the identities seen
+ :var int hs_dir_onions_seen_attr: **\*** attributes provided for the hs_dir_onions_seen
+
**Bridge Attributes:**
:var datetime bridge_stats_end: end of the period when stats were gathered
@@ -643,6 +680,10 @@ class ExtraInfoDescriptor(Descriptor):
**\*** attribute is either required when we're parsed with validation or has
a default value, others are left as **None** if undefined
+
+ .. versionchanged:: 1.4.0
+ Added the hs_stats_end, hs_rend_cells, hs_rend_cells_attr,
+ hs_dir_onions_seen, and hs_dir_onions_seen_attr attributes.
"""
ATTRIBUTES = {
@@ -714,6 +755,12 @@ class ExtraInfoDescriptor(Descriptor):
'exit_kibibytes_read': (None, _parse_exit_kibibytes_read_line),
'exit_streams_opened': (None, _parse_exit_streams_opened_line),
+ 'hs_stats_end': (None, _parse_hidden_service_stats_end_line),
+ 'hs_rend_cells': (None, _parse_hidden_service_rend_relayed_cells_line),
+ 'hs_rend_cells_attr': ({}, _parse_hidden_service_rend_relayed_cells_line),
+ 'hs_dir_onions_seen': (None, _parse_hidden_service_dir_onions_seen_line),
+ 'hs_dir_onions_seen_attr': ({}, _parse_hidden_service_dir_onions_seen_line),
+
'bridge_stats_end': (None, _parse_bridge_stats_end_line),
'bridge_stats_interval': (None, _parse_bridge_stats_end_line),
'bridge_ips': (None, _parse_bridge_ips_line),
@@ -756,6 +803,9 @@ class ExtraInfoDescriptor(Descriptor):
'exit-kibibytes-written': _parse_exit_kibibytes_written_line,
'exit-kibibytes-read': _parse_exit_kibibytes_read_line,
'exit-streams-opened': _parse_exit_streams_opened_line,
+ 'hidserv-stats-end': _parse_hidden_service_stats_end_line,
+ 'hidserv-rend-relayed-cells': _parse_hidden_service_rend_relayed_cells_line,
+ 'hidserv-dir-onions-seen': _parse_hidden_service_dir_onions_seen_line,
'dirreq-v2-ips': _parse_dirreq_v2_ips_line,
'dirreq-v3-ips': _parse_dirreq_v3_ips_line,
'dirreq-v2-reqs': _parse_dirreq_v2_reqs_line,
diff --git a/test/unit/descriptor/extrainfo_descriptor.py b/test/unit/descriptor/extrainfo_descriptor.py
index a30d47a..6049f50 100644
--- a/test/unit/descriptor/extrainfo_descriptor.py
+++ b/test/unit/descriptor/extrainfo_descriptor.py
@@ -527,6 +527,67 @@ k0d2aofcVbHr4fPQOSST0LXDrhFl5Fqo5um296zpJGvRUeO6S44U/EfJAGShtqWw
desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
self._expect_invalid_attr(desc_text, attr)
+ def test_hidden_service_stats_end(self):
+ """
+ Exercise the hidserv-stats-end, which should be a simple date.
+ """
+
+ desc = get_relay_extrainfo_descriptor({'hidserv-stats-end': '2012-05-03 12:07:50'})
+ self.assertEqual(datetime.datetime(2012, 5, 3, 12, 7, 50), desc.hs_stats_end)
+
+ test_entries = (
+ '',
+ '2012',
+ '2012-05',
+ '2012-05-03',
+ '2012-05-03 12',
+ '2012-05-03 12:07',
+ '2012-05-03 12:07:-50',
+ )
+
+ for entry in test_entries:
+ desc_text = get_relay_extrainfo_descriptor({'hidserv-stats-end': entry}, content = True)
+ self._expect_invalid_attr(desc_text, 'hs_stats_end')
+
+ def test_hidden_service_stats(self):
+ """
+ Check the 'hidserv-rend-relayed-cells' and 'hidserv-dir-onions-seen', which
+ share the same format.
+ """
+
+ attributes = (
+ ('hidserv-rend-relayed-cells', 'hs_rend_cells', 'hs_rend_cells_attr'),
+ ('hidserv-dir-onions-seen', 'hs_dir_onions_seen', 'hs_dir_onions_seen_attr'),
+ )
+
+ test_entries = (
+ '',
+ '-50',
+ 'hello',
+ ' key=value',
+ '40 key',
+ '40 key value',
+ '40 key key=value',
+ )
+
+ for keyword, stat_attr, extra_attr in attributes:
+ # just the numeric stat (no extra attributes)
+
+ desc = get_relay_extrainfo_descriptor({keyword: '345'})
+ self.assertEqual(345, getattr(desc, stat_attr))
+ self.assertEqual({}, getattr(desc, extra_attr))
+
+ # with extra attributes
+
+ desc = get_relay_extrainfo_descriptor({keyword: '345 spiffy=true snowmen=neat'})
+ self.assertEqual(345, getattr(desc, stat_attr))
+ self.assertEqual({'spiffy': 'true', 'snowmen': 'neat'}, getattr(desc, extra_attr))
+
+ for entry in test_entries:
+ desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
+ self._expect_invalid_attr(desc_text, stat_attr)
+ self._expect_invalid_attr(desc_text, extra_attr, {})
+
def test_locale_mapping_lines(self):
"""
Uses valid and invalid data to tests lines of the form...
More information about the tor-commits
mailing list