commit 1f59be94b0d8bf20f522296ca9b5a895d36884ac
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Tue Dec 13 20:42:01 2011 +0100
Add a LICENSE.
---
LICENSE | 30 ++++++++++++++++++++++++++++++
1 files changed, 30 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d5b67cd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright 2011 The Tor Project
+
+Redistribution and use in source and binary forms, with or without
+…
[View More]modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the names of the copyright owners nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
[View Less]
commit 8164af5ba0e571dfcd735274e1c4079c79a6fd8d
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Dec 12 18:24:44 2011 -0800
Using input version string for __str__
Originally we reconstructed version strings from its components since that's
all the Version class had (a separate factory translated strings into Version
objects). However, that's no longer the case and much simpler if we just have
the __str__ method provide the constructing string rather …
[View More]than try to recreate
it.
patch by gsathya
---
stem/version.py | 14 +++-----------
1 files changed, 3 insertions(+), 11 deletions(-)
diff --git a/stem/version.py b/stem/version.py
index 214e06a..26080cc 100644
--- a/stem/version.py
+++ b/stem/version.py
@@ -94,6 +94,7 @@ class Version:
ValueError if input isn't a valid tor version
"""
+ self.version_str = version_str
m = re.match(r'^([0-9]+).([0-9]+).([0-9]+)(.[0-9]+)?(-\S*)?$', version_str)
if m:
@@ -114,19 +115,10 @@ class Version:
def __str__(self):
"""
- Provides the normal representation for the version, for instance:
- "0.2.2.23-alpha"
+ Provides the string used to construct the Version.
"""
- suffix = ""
-
- if self.patch:
- suffix += ".%i" % self.patch
-
- if self.status:
- suffix += "-%s" % self.status
-
- return "%i.%i.%i%s" % (self.major, self.minor, self.micro, suffix)
+ return self.version_str
def __cmp__(self, other):
"""
[View Less]
commit 4d0d5598eec4936f103293802c217377ada8751f
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Dec 12 18:51:03 2011 -0800
Joining get_protocolinfo_* functions
Having a joined socket constructor and PROTOCOLINFO query doesn't make sense.
It allowed the user to make a PROTOCOLINFO query in a single line without a
leftover socket but that use case is so rare that it's fine for it to take a
couple more lines. This api is simpler and better for the …
[View More]common case.
---
stem/connection.py | 90 +++++++-------------------------
test/integ/connection/protocolinfo.py | 23 ++++++---
2 files changed, 35 insertions(+), 78 deletions(-)
diff --git a/stem/connection.py b/stem/connection.py
index 07dc50e..71b6e5b 100644
--- a/stem/connection.py
+++ b/stem/connection.py
@@ -25,8 +25,7 @@ authenticate_none - Authenticates to an open control socket.
authenticate_password - Authenticates to a socket supporting password auth.
authenticate_cookie - Authenticates to a socket supporting cookie auth.
-get_protocolinfo_by_port - PROTOCOLINFO query via a control port.
-get_protocolinfo_by_socket - PROTOCOLINFO query via a control socket.
+get_protocolinfo - issues a PROTOCOLINFO query
ProtocolInfoResponse - Reply from a PROTOCOLINFO query.
|- Attributes:
| |- protocol_version
@@ -292,63 +291,19 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
if not suppress_ctl_errors: raise exc
else: raise CookieAuthRejected("Socket failed (%s)" % exc)
-def get_protocolinfo_by_port(control_addr = "127.0.0.1", control_port = 9051, get_socket = False):
- """
- Issues a PROTOCOLINFO query to a control port, getting information about the
- tor process running on it.
-
- Arguments:
- control_addr (str) - ip address of the controller
- control_port (int) - port number of the controller
- get_socket (bool) - provides the socket with the response if True,
- otherwise the socket is closed when we're done
-
- Returns:
- stem.connection.ProtocolInfoResponse provided by tor, if get_socket is True
- then this provides a tuple instead with both the response and connected
- socket (stem.socket.ControlPort)
-
- Raises:
- stem.socket.ProtocolError if the PROTOCOLINFO response is malformed
- stem.socket.SocketError if problems arise in establishing or using the
- socket
- """
-
- control_socket = stem.socket.ControlPort(control_addr, control_port, False)
-
- try:
- control_socket.connect()
- control_socket.send("PROTOCOLINFO 1")
- protocolinfo_response = control_socket.recv()
- ProtocolInfoResponse.convert(protocolinfo_response)
-
- # attempt to expand relative cookie paths using our port to infer the pid
- if control_addr == "127.0.0.1":
- _expand_cookie_path(protocolinfo_response, stem.util.system.get_pid_by_port, control_port)
-
- if get_socket:
- return (protocolinfo_response, control_socket)
- else:
- control_socket.close()
- return protocolinfo_response
- except stem.socket.ControllerError, exc:
- control_socket.close()
- raise exc
-
-def get_protocolinfo_by_socket(socket_path = "/var/run/tor/control", get_socket = False):
+def get_protocolinfo(control_socket):
"""
Issues a PROTOCOLINFO query to a control socket, getting information about
the tor process running on it.
+ Tor hangs up on sockets after receiving a PROTOCOLINFO query if it isn't next
+ followed by authentication.
+
Arguments:
- socket_path (str) - path where the control socket is located
- get_socket (bool) - provides the socket with the response if True,
- otherwise the socket is closed when we're done
+ control_socket (stem.socket.ControlSocket) - connected tor control socket
Returns:
- stem.connection.ProtocolInfoResponse provided by tor, if get_socket is True
- then this provides a tuple instead with both the response and connected
- socket (stem.socket.ControlSocketFile)
+ stem.connection.ProtocolInfoResponse provided by tor
Raises:
stem.socket.ProtocolError if the PROTOCOLINFO response is malformed
@@ -356,25 +311,20 @@ def get_protocolinfo_by_socket(socket_path = "/var/run/tor/control", get_socket
socket
"""
- control_socket = stem.socket.ControlSocketFile(socket_path, False)
+ control_socket.send("PROTOCOLINFO 1")
+ protocolinfo_response = control_socket.recv()
+ ProtocolInfoResponse.convert(protocolinfo_response)
- try:
- control_socket.connect()
- control_socket.send("PROTOCOLINFO 1")
- protocolinfo_response = control_socket.recv()
- ProtocolInfoResponse.convert(protocolinfo_response)
-
- # attempt to expand relative cookie paths using our port to infer the pid
- _expand_cookie_path(protocolinfo_response, stem.util.system.get_pid_by_open_file, socket_path)
-
- if get_socket:
- return (protocolinfo_response, control_socket)
- else:
- control_socket.close()
- return protocolinfo_response
- except stem.socket.ControllerError, exc:
- control_socket.close()
- raise exc
+ # attempt ot expand relative cookie paths via the control port or socket file
+ if isinstance(control_socket, stem.socket.ControlPort):
+ if control_socket.get_address() == "127.0.0.1":
+ pid_method = stem.util.system.get_pid_by_port
+ _expand_cookie_path(protocolinfo_response, pid_method, control_socket.get_port())
+ elif isinstance(control_socket, stem.socket.ControlSocketFile):
+ pid_method = stem.util.system.get_pid_by_open_file
+ _expand_cookie_path(protocolinfo_response, pid_method, control_socket.get_socket_path())
+
+ return protocolinfo_response
def _expand_cookie_path(protocolinfo_response, pid_resolver, pid_resolution_arg):
"""
diff --git a/test/integ/connection/protocolinfo.py b/test/integ/connection/protocolinfo.py
index 02d097d..dfc1c01 100644
--- a/test/integ/connection/protocolinfo.py
+++ b/test/integ/connection/protocolinfo.py
@@ -49,7 +49,8 @@ class TestProtocolInfo(unittest.TestCase):
def test_get_protocolinfo_by_port(self):
"""
- Exercises the stem.connection.get_protocolinfo_by_port function.
+ Exercises the stem.connection.get_protocolinfo function with a control
+ port.
"""
# If we have both the 'RELATIVE' target and a cookie then test_parsing
@@ -74,15 +75,21 @@ class TestProtocolInfo(unittest.TestCase):
connection_type = test.runner.get_runner().get_connection_type()
if test.runner.OPT_PORT in test.runner.CONNECTION_OPTS[connection_type]:
- protocolinfo_response = stem.connection.get_protocolinfo_by_port(control_port = test.runner.CONTROL_PORT)
+ control_socket = stem.socket.ControlPort(control_port = test.runner.CONTROL_PORT)
+ protocolinfo_response = stem.connection.get_protocolinfo(control_socket)
self.assert_protocolinfo_attr(protocolinfo_response, connection_type)
+
+ # we should have a usable socket at this point
+ self.assertTrue(control_socket.is_alive())
+ control_socket.close()
else:
# we don't have a control port
- self.assertRaises(stem.socket.SocketError, stem.connection.get_protocolinfo_by_port, "127.0.0.1", test.runner.CONTROL_PORT)
+ self.assertRaises(stem.socket.SocketError, stem.socket.ControlPort, "127.0.0.1", test.runner.CONTROL_PORT)
def test_get_protocolinfo_by_socket(self):
"""
- Exercises the stem.connection.get_protocolinfo_by_socket function.
+ Exercises the stem.connection.get_protocolinfo function with a control
+ socket.
"""
cwd_by_socket_lookup_prefixes = (
@@ -100,16 +107,16 @@ class TestProtocolInfo(unittest.TestCase):
connection_type = test.runner.get_runner().get_connection_type()
if test.runner.OPT_SOCKET in test.runner.CONNECTION_OPTS[connection_type]:
- protocolinfo_response, control_socket = stem.connection.get_protocolinfo_by_socket(socket_path = test.runner.CONTROL_SOCKET_PATH, get_socket = True)
+ control_socket = stem.socket.ControlSocketFile(test.runner.CONTROL_SOCKET_PATH)
+ protocolinfo_response = stem.connection.get_protocolinfo(control_socket)
self.assert_protocolinfo_attr(protocolinfo_response, connection_type)
- # also exercising the get_socket argument - we should have a usable
- # socket at this point
+ # we should have a usable socket at this point
self.assertTrue(control_socket.is_alive())
control_socket.close()
else:
# we don't have a control socket
- self.assertRaises(stem.socket.SocketError, stem.connection.get_protocolinfo_by_socket, test.runner.CONTROL_SOCKET_PATH)
+ self.assertRaises(stem.socket.SocketError, stem.socket.ControlSocketFile, test.runner.CONTROL_SOCKET_PATH)
def assert_protocolinfo_attr(self, protocolinfo_response, connection_type):
"""
[View Less]
commit 297a41b085a92eb1c5b5c92464d3bc9cd27cef94
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue Dec 13 10:06:53 2011 -0800
Reconnecting socket after auth failures
Tor disconnects the control socket after Failed AUTHENTICATE calls in an effort
to mitigate the issue discussed in...
http://archives.seul.org/or/announce/Sep-2007/msg00000.html
This is unintuitive to stem users so I'm making a best effort attempt to
reconnect the socket in those …
[View More]cases. This isn't guaranteed to succeed, but
checking is_alive() is far nicer for callers than a try/connect/except block.
---
stem/connection.py | 43 ++++++++++++++++++++-----------
test/integ/connection/authentication.py | 12 ++++++--
2 files changed, 37 insertions(+), 18 deletions(-)
diff --git a/stem/connection.py b/stem/connection.py
index 71b6e5b..e9bd5f7 100644
--- a/stem/connection.py
+++ b/stem/connection.py
@@ -126,8 +126,11 @@ def authenticate_none(control_socket, suppress_ctl_errors = True):
authenticate before they can be used, even if tor hasn't been configured to
use any authentication.
- For general usage use the authenticate function instead. If authentication
- fails then tor will close the control socket.
+ If authentication fails tor will disconnect and we'll make a best effort
+ attempt to re-establish the connection. This may not succeed, so check
+ is_alive() before using the socket further.
+
+ For general usage use the authenticate() function instead.
Arguments:
control_socket (stem.socket.ControlSocket) - socket to be authenticated
@@ -145,10 +148,13 @@ def authenticate_none(control_socket, suppress_ctl_errors = True):
# if we got anything but an OK response then error
if str(auth_response) != "OK":
- control_socket.close()
+ try: control_socket.connect()
+ except: pass
+
raise OpenAuthRejected(str(auth_response), auth_response)
except stem.socket.ControllerError, exc:
- control_socket.close()
+ try: control_socket.connect()
+ except: pass
if not suppress_ctl_errors: raise exc
else: raise OpenAuthRejected("Socket failed (%s)" % exc)
@@ -158,8 +164,11 @@ def authenticate_password(control_socket, password, suppress_ctl_errors = True):
Authenticates to a control socket that uses a password (via the
HashedControlPassword torrc option). Quotes in the password are escaped.
- For general usage use the authenticate function instead. If authentication
- fails then tor will close the control socket.
+ If authentication fails tor will disconnect and we'll make a best effort
+ attempt to re-establish the connection. This may not succeed, so check
+ is_alive() before using the socket further.
+
+ For general usage use the authenticate() function instead.
note: If you use this function directly, rather than authenticate(), we may
mistakenly raise a PasswordAuthRejected rather than IncorrectPassword. This
@@ -191,7 +200,8 @@ def authenticate_password(control_socket, password, suppress_ctl_errors = True):
# if we got anything but an OK response then error
if str(auth_response) != "OK":
- control_socket.close()
+ try: control_socket.connect()
+ except: pass
# all we have to go on is the error message from tor...
# Password did not match HashedControlPassword value value from configuration...
@@ -202,7 +212,8 @@ def authenticate_password(control_socket, password, suppress_ctl_errors = True):
else:
raise PasswordAuthRejected(str(auth_response), auth_response)
except stem.socket.ControllerError, exc:
- control_socket.close()
+ try: control_socket.connect()
+ except: pass
if not suppress_ctl_errors: raise exc
else: raise PasswordAuthRejected("Socket failed (%s)" % exc)
@@ -217,8 +228,11 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
The IncorrectCookieSize and UnreadableCookieFile exceptions take precidence
over the other types.
- For general usage use the authenticate function instead. If authentication
- fails then tor will close the control socket.
+ If authentication fails tor will disconnect and we'll make a best effort
+ attempt to re-establish the connection. This may not succeed, so check
+ is_alive() before using the socket further.
+
+ For general usage use the authenticate() function instead.
note: If you use this function directly, rather than authenticate(), we may
mistakenly raise a CookieAuthRejected rather than IncorrectCookieValue. This
@@ -241,7 +255,6 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
"""
if not os.path.exists(cookie_path):
- control_socket.close()
raise UnreadableCookieFile("Authentication failed: '%s' doesn't exist" % cookie_path)
# Abort if the file isn't 32 bytes long. This is to avoid exposing arbitrary
@@ -256,7 +269,6 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
auth_cookie_size = os.path.getsize(cookie_path)
if auth_cookie_size != 32:
- control_socket.close()
exc_msg = "Authentication failed: authentication cookie '%s' is the wrong size (%i bytes instead of 32)" % (cookie_path, auth_cookie_size)
raise IncorrectCookieSize(exc_msg)
@@ -265,7 +277,6 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
auth_cookie_contents = auth_cookie_file.read()
auth_cookie_file.close()
except IOError, exc:
- control_socket.close()
raise UnreadableCookieFile("Authentication failed: unable to read '%s' (%s)" % (cookie_path, exc))
try:
@@ -274,7 +285,8 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
# if we got anything but an OK response then error
if str(auth_response) != "OK":
- control_socket.close()
+ try: control_socket.connect()
+ except: pass
# all we have to go on is the error message from tor...
# ... Authentication cookie did not match expected value.
@@ -286,7 +298,8 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
else:
raise CookieAuthRejected(str(auth_response), auth_response)
except stem.socket.ControllerError, exc:
- control_socket.close()
+ try: control_socket.connect()
+ except: pass
if not suppress_ctl_errors: raise exc
else: raise CookieAuthRejected("Socket failed (%s)" % exc)
diff --git a/test/integ/connection/authentication.py b/test/integ/connection/authentication.py
index ee36946..3d7d199 100644
--- a/test/integ/connection/authentication.py
+++ b/test/integ/connection/authentication.py
@@ -246,8 +246,9 @@ class TestAuthenticate(unittest.TestCase):
control_socket.close()
self.fail()
except stem.connection.AuthenticationFailure, exc:
- self.assertFalse(control_socket.is_alive())
+ self.assertTrue(control_socket.is_alive())
self.assertEqual(failure_msg, str(exc))
+ control_socket.close()
def _check_auth(self, auth_type, *auth_args):
"""
@@ -268,8 +269,13 @@ class TestAuthenticate(unittest.TestCase):
control_socket = runner.get_tor_socket(False)
auth_function = self._get_auth_function(control_socket, auth_type, *auth_args)
- # run the authentication, letting this raise if there's a problem
- auth_function()
+ # run the authentication, re-raising if there's a problem
+ try:
+ auth_function()
+ except stem.connection.AuthenticationFailure, exc:
+ self.assertTrue(control_socket.is_alive())
+ control_socket.close()
+ raise exc
# issues a 'GETINFO config-file' query to confirm that we can use the socket
control_socket.send("GETINFO config-file")
[View Less]
commit 7d5d5d5857b65b8a0009ae7c5ea8fd222009d874
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Tue Dec 13 13:16:38 2011 +0100
Add a LICENSE file.
---
LICENSE | 30 ++++++++++++++++++++++++++++++
1 files changed, 30 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d5b67cd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright 2011 The Tor Project
+
+Redistribution and use in source and binary forms, with or without
+…
[View More]modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the names of the copyright owners nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
[View Less]
commit bbdfc6f942e58aa9191acc5609f73660f608051a
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Tue Dec 13 14:22:22 2011 +0100
Clean up a comments and use saner output filenames.
---
.gitignore | 1 +
README | 36 +++++++++++++
src/org/torproject/doctor/Checker.java | 15 +++---
src/org/torproject/doctor/DownloadStatistics.java | 16 ++++++-
src/org/torproject/doctor/…
[View More]Downloader.java | 24 ++++++---
src/org/torproject/doctor/Main.java | 11 ++--
.../torproject/doctor/MetricsWebsiteReport.java | 8 +--
src/org/torproject/doctor/StatusFileReport.java | 54 +++++++++++--------
src/org/torproject/doctor/Warning.java | 2 +-
9 files changed, 114 insertions(+), 53 deletions(-)
diff --git a/.gitignore b/.gitignore
index 64253cb..14a8cb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
classes/
lib/
+out/
diff --git a/README b/README
index 0a9a9b4..1b900d2 100644
--- a/README
+++ b/README
@@ -8,3 +8,39 @@ from the Tor directory authorities and checks them for consensus problems.
DocTor writes its findings to local files which can then be sent to a
mailing list or IRC bot, or which can be served by an HTTP server.
+
+Howto
+-----
+
+Create a lib/ directory.
+
+Download Apache Commons Codec 1.4 or higher and put it in the lib/
+directory. If the filename is not commons-codec-1.4.jar, update the
+build.xml file.
+
+Clone metrics-lib, create a .jar file using `ant jar`, and put it in the
+lib/ directory, too.
+
+Compile the Java classes using `ant compile`.
+
+Run the application using `ant run`.
+
+Output files are:
+
+ - out/status/new-warnings: Consensus warnings that have been found in
+ this execution or that were found long enough before to not be
+ rate-limited anymore.
+
+ - out/status/all-warnings: All warnings found in this execution, but only
+ if at least one of them is in new-warnings, too.
+
+ - out/website/consensus-health.html: HTML file containing a full
+ comparison of consensuses to its votes, marking problems in red.
+
+Generated temp files (read: don't mess with them) are:
+
+ - out/stats/download-stats.csv: Raw consensus download times.
+
+ - out/stats/last-warned: Warning messages and when they were last
+ contained in new-warnings or all-warnings.
+
diff --git a/src/org/torproject/doctor/Checker.java b/src/org/torproject/doctor/Checker.java
index ee4e29e..c78ebf6 100644
--- a/src/org/torproject/doctor/Checker.java
+++ b/src/org/torproject/doctor/Checker.java
@@ -14,7 +14,6 @@ public class Checker {
/* Warning messages consisting of type and details. */
private SortedMap<Warning, String> warnings =
new TreeMap<Warning, String>();
-
public SortedMap<Warning, String> getWarnings() {
return this.warnings;
}
@@ -26,13 +25,7 @@ public class Checker {
dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
- /* Downloaded consensus and corresponding votes for processing. */
- private SortedMap<String, RelayNetworkStatusConsensus>
- downloadedConsensuses = new TreeMap<String,
- RelayNetworkStatusConsensus>();
- private RelayNetworkStatusConsensus downloadedConsensus;
- private List<RelayNetworkStatusVote> downloadedVotes =
- new ArrayList<RelayNetworkStatusVote>();
+ /* Check consensuses and votes. */
public void processDownloadedConsensuses(
List<DescriptorRequest> downloads) {
this.storeDownloads(downloads);
@@ -57,6 +50,12 @@ public class Checker {
/* Store consensuses and votes in a way that we can process them more
* easily. */
+ private SortedMap<String, RelayNetworkStatusConsensus>
+ downloadedConsensuses = new TreeMap<String,
+ RelayNetworkStatusConsensus>();
+ private RelayNetworkStatusConsensus downloadedConsensus;
+ private List<RelayNetworkStatusVote> downloadedVotes =
+ new ArrayList<RelayNetworkStatusVote>();
private void storeDownloads(List<DescriptorRequest> downloads) {
for (DescriptorRequest request : downloads) {
for (Descriptor descriptor : request.getDescriptors()) {
diff --git a/src/org/torproject/doctor/DownloadStatistics.java b/src/org/torproject/doctor/DownloadStatistics.java
index 9df0333..e30aed8 100644
--- a/src/org/torproject/doctor/DownloadStatistics.java
+++ b/src/org/torproject/doctor/DownloadStatistics.java
@@ -6,9 +6,15 @@ import java.io.*;
import java.util.*;
import org.torproject.descriptor.*;
+/* Provide simple statistics about consensus download times. */
public class DownloadStatistics {
+
+ /* Add a new set of download times by append them to the history
+ * file. */
+ private File statisticsFile = new File("out/state/download-stats.csv");
public void memorizeFetchTimes(List<DescriptorRequest> downloads) {
try {
+ this.statisticsFile.getParentFile().mkdirs();
BufferedWriter bw = new BufferedWriter(new FileWriter(
this.statisticsFile, true));
for (DescriptorRequest request : downloads) {
@@ -31,10 +37,12 @@ public class DownloadStatistics {
+ this.statisticsFile.getAbsolutePath() + ". Ignoring.");
}
}
+
+ /* Prepare statistics by reading the download history and sorting to
+ * calculate percentiles more easily. */
private SortedMap<String, List<Long>> downloadData =
new TreeMap<String, List<Long>>();
private int maxDownloadsPerAuthority = 0;
- private File statisticsFile = new File("download-stats.csv");
public void prepareStatistics() {
if (this.statisticsFile.exists()) {
long cutOffMillis = System.currentTimeMillis()
@@ -71,9 +79,13 @@ public class DownloadStatistics {
}
}
}
+
+ /* Return the list of authorities that we have statistics for. */
public SortedSet<String> getKnownAuthorities() {
return new TreeSet<String>(this.downloadData.keySet());
}
+
+ /* Return the download time percentile for a directory authority. */
public String getPercentile(String authority, int percentile) {
if (percentile < 0 || percentile > 100 ||
!this.downloadData.containsKey(authority)) {
@@ -84,6 +96,8 @@ public class DownloadStatistics {
return String.valueOf(fetchTimes.get(index));
}
}
+
+ /* Return the number of NAs (timeouts) for a directory authority. */
public String getNAs(String authority) {
if (!this.downloadData.containsKey(authority)) {
return "NA";
diff --git a/src/org/torproject/doctor/Downloader.java b/src/org/torproject/doctor/Downloader.java
index 317b3b6..f20885f 100644
--- a/src/org/torproject/doctor/Downloader.java
+++ b/src/org/torproject/doctor/Downloader.java
@@ -2,23 +2,22 @@
* See LICENSE for licensing information */
package org.torproject.doctor;
-import java.io.*;
-import java.net.*;
-import java.text.*;
import java.util.*;
-import java.util.zip.*;
import org.torproject.descriptor.*;
/* Download the latest network status consensus and corresponding
- * votes. */
+ * votes using metrics-lib. */
public class Downloader {
/* Download the current consensus and corresponding votes. */
public List<DescriptorRequest> downloadFromAuthorities() {
+ /* Create a descriptor downloader instance that will do all the hard
+ * download work for us. */
RelayDescriptorDownloader downloader =
DescriptorSourceFactory.createRelayDescriptorDownloader();
+ /* Configure the currently known directory authorities. */
downloader.addDirectoryAuthority("gabelmoo", "212.112.245.170", 80);
downloader.addDirectoryAuthority("tor26", "86.59.21.38", 80);
downloader.addDirectoryAuthority("ides", "216.224.124.114", 9030);
@@ -28,20 +27,28 @@ public class Downloader {
downloader.addDirectoryAuthority("moria1", "128.31.0.34", 9131);
downloader.addDirectoryAuthority("dizum", "194.109.206.212", 80);
+ /* Instruct the downloader to include the current consensus and all
+ * referenced votes in the downloads. The consensus shall be
+ * downloaded from all directory authorities, not just from one. */
downloader.setIncludeCurrentConsensusFromAllDirectoryAuthorities();
downloader.setIncludeCurrentReferencedVotes();
+ /* Set a per-request timeout of 60 seconds. */
downloader.setRequestTimeout(60L * 1000L);
- List<DescriptorRequest> allRequests =
- new ArrayList<DescriptorRequest>();
+ /* Iterate over the finished (or aborted) requests and memorize the
+ * included consensuses or votes. The processing will take place
+ * later. */
Iterator<DescriptorRequest> descriptorRequests =
downloader.downloadDescriptors();
+ List<DescriptorRequest> allRequests =
+ new ArrayList<DescriptorRequest>();
while (descriptorRequests.hasNext()) {
try {
allRequests.add(descriptorRequests.next());
} catch (NoSuchElementException e) {
- /* TODO In theory, this exception shouldn't be thrown. */
+ /* TODO In theory, this exception shouldn't be thrown. This is a
+ * bug in metrics-lib. */
System.err.println("Internal error: next() doesn't provide an "
+ "element even though hasNext() returned true. Got "
+ allRequests.size() + " elements so far. Stopping to "
@@ -50,6 +57,7 @@ public class Downloader {
}
}
+ /* We downloaded everything we wanted. */
return allRequests;
}
}
diff --git a/src/org/torproject/doctor/Main.java b/src/org/torproject/doctor/Main.java
index 40973b4..f723e0b 100644
--- a/src/org/torproject/doctor/Main.java
+++ b/src/org/torproject/doctor/Main.java
@@ -5,13 +5,13 @@ package org.torproject.doctor;
import java.util.*;
import org.torproject.descriptor.*;
-/* Coordinate the process of downloading consensus and votes to check
- * Tor's consensus health. */
+/* Coordinate the process of downloading the current consensus and votes
+ * to check Tor's consensus health. */
public class Main {
public static void main(String[] args) {
- /* Download consensus and corresponding votes from the directory
- * authorities. */
+ /* Download the current consensus from all directory authorities and
+ * all referenced votes from any directory authority. */
Downloader downloader = new Downloader();
List<DescriptorRequest> downloads =
downloader.downloadFromAuthorities();
@@ -26,8 +26,7 @@ public class Main {
statusFile.writeReport();
/* Write a complete consensus-health report to an HTML file. */
- MetricsWebsiteReport website =
- new MetricsWebsiteReport("website/consensus-health.html");
+ MetricsWebsiteReport website = new MetricsWebsiteReport();
website.processDownloadedConsensuses(downloads);
DownloadStatistics fetchStatistics = new DownloadStatistics();
fetchStatistics.memorizeFetchTimes(downloads);
diff --git a/src/org/torproject/doctor/MetricsWebsiteReport.java b/src/org/torproject/doctor/MetricsWebsiteReport.java
index 8d0bb0b..ac60ae7 100644
--- a/src/org/torproject/doctor/MetricsWebsiteReport.java
+++ b/src/org/torproject/doctor/MetricsWebsiteReport.java
@@ -19,12 +19,8 @@ public class MetricsWebsiteReport {
}
/* Output file to write report to. */
- private File htmlOutputFile;
-
- /* Initialize this report. */
- public MetricsWebsiteReport(String htmlOutputFilename) {
- this.htmlOutputFile = new File(htmlOutputFilename);
- }
+ private File htmlOutputFile =
+ new File("out/website/consensus-health.html");
/* Store the downloaded consensus and corresponding votes for later
* processing. */
diff --git a/src/org/torproject/doctor/StatusFileReport.java b/src/org/torproject/doctor/StatusFileReport.java
index 7890005..a8838b6 100644
--- a/src/org/torproject/doctor/StatusFileReport.java
+++ b/src/org/torproject/doctor/StatusFileReport.java
@@ -7,7 +7,9 @@ import java.text.*;
import java.util.*;
/* Check a given consensus and votes for irregularities and write results
- * to stdout while rate-limiting warnings based on severity. */
+ * to status files while rate-limiting warnings based on severity. There
+ * will be a 'all-warnings' file with all warnings and a 'new-warnings'
+ * file with only the warnings that haven't been emitted recently. */
public class StatusFileReport {
/* Date-time format to format timestamps. */
@@ -23,32 +25,32 @@ public class StatusFileReport {
this.warnings = warnings;
}
- /* Check consensuses and votes for irregularities and write output to
- * stdout. */
+ /* Write warnings to the status files. */
public void writeReport() {
this.readLastWarned();
- this.prepareReports();
+ this.prepareStatusFiles();
this.writeStatusFiles();
this.writeLastWarned();
}
- /* Warning messages of the last 24 hours that is used to implement
- * rate limiting. */
+ /* Map of warning message strings of the last 24 hours and when they
+ * were last included in the 'new-warnings' file. This map is used to
+ * implement rate limiting. */
private Map<String, Long> lastWarned = new HashMap<String, Long>();
/* Read when we last emitted a warning to rate-limit some of them. */
+ private File lastWarnedFile = new File("out/state/last-warned");
private void readLastWarned() {
long now = System.currentTimeMillis();
- File lastWarnedFile = new File("stats/chc-last-warned");
try {
- if (lastWarnedFile.exists()) {
+ if (this.lastWarnedFile.exists()) {
BufferedReader br = new BufferedReader(new FileReader(
- lastWarnedFile));
+ this.lastWarnedFile));
String line;
while ((line = br.readLine()) != null) {
if (!line.contains(": ")) {
- System.err.println("Bad line in stats/chc-last-warned: '" + line
- + "'. Ignoring this line.");
+ System.err.println("Bad line in stats/chc-last-warned: '"
+ + line + "'. Ignoring this line.");
continue;
}
long warnedMillis = Long.parseLong(line.substring(0,
@@ -63,20 +65,21 @@ public class StatusFileReport {
}
} catch (IOException e) {
System.err.println("Could not read file '"
- + lastWarnedFile.getAbsolutePath() + "' to learn which "
+ + this.lastWarnedFile.getAbsolutePath() + "' to learn which "
+ "warnings have been sent out before. Ignoring.");
}
}
- /* Prepare a report to be written to stdout. */
+ /* Prepare status files to be written. */
private String allWarnings = null, newWarnings = null;
- private void prepareReports() {
+ private void prepareStatusFiles() {
SortedMap<String, Long> warningStrings = new TreeMap<String, Long>();
for (Map.Entry<Warning, String> e : this.warnings.entrySet()) {
Warning type = e.getKey();
String details = e.getValue();
switch (type) {
case NoConsensusKnown:
+ warningStrings.put("No consensus known.", 0L);
break;
case ConsensusDownloadTimeout:
warningStrings.put("The following directory authorities did "
@@ -158,12 +161,17 @@ public class StatusFileReport {
}
}
- /* Write report to stdout. */
+ /* Write status files to disk. */
+ private File allWarningsFile = new File("out/status/all-warnings");
+ private File newWarningsFile = new File("out/status/new-warnings");
private void writeStatusFiles() {
try {
+ this.allWarningsFile.getParentFile().mkdirs();
+ this.newWarningsFile.getParentFile().mkdirs();
BufferedWriter allBw = new BufferedWriter(new FileWriter(
- "all-warnings")), newBw = new BufferedWriter(new FileWriter(
- "new-warnings"));
+ this.allWarningsFile));
+ BufferedWriter newBw = new BufferedWriter(new FileWriter(
+ this.newWarningsFile));
if (this.allWarnings != null) {
allBw.write(this.allWarnings);
}
@@ -173,25 +181,25 @@ public class StatusFileReport {
allBw.close();
newBw.close();
} catch (IOException e) {
- System.err.println("Could not write status files 'all-warnings' "
- + "and/or 'new-warnings'. Ignoring.");
+ System.err.println("Could not write status files '"
+ + this.allWarningsFile.getAbsolutePath() + "' and/or '"
+ + this.newWarningsFile.getAbsolutePath() + "'. Ignoring.");
}
}
/* Write timestamps when warnings were last sent to disk. */
private void writeLastWarned() {
- File lastWarnedFile = new File("stats/chc-last-warned");
try {
- lastWarnedFile.getParentFile().mkdirs();
+ this.lastWarnedFile.getParentFile().mkdirs();
BufferedWriter bw = new BufferedWriter(new FileWriter(
- lastWarnedFile));
+ this.lastWarnedFile));
for (Map.Entry<String, Long> e : lastWarned.entrySet()) {
bw.write(String.valueOf(e.getValue()) + ": " + e.getKey() + "\n");
}
bw.close();
} catch (IOException e) {
System.err.println("Could not write file '"
- + lastWarnedFile.getAbsolutePath() + "' to remember which "
+ + this.lastWarnedFile.getAbsolutePath() + "' to remember which "
+ "warnings have been sent out before. Ignoring.");
}
}
diff --git a/src/org/torproject/doctor/Warning.java b/src/org/torproject/doctor/Warning.java
index 0cb8cd8..1684f89 100644
--- a/src/org/torproject/doctor/Warning.java
+++ b/src/org/torproject/doctor/Warning.java
@@ -49,7 +49,7 @@ public enum Warning {
ConsensusMissingVotes,
/* The consensuses downloaded from one or more authorities are missing
- * signatures from other, previously voting authorities. */
+ * signatures from previously voting authorities. */
ConsensusMissingSignatures
}
[View Less]