commit 01bf963445daea590510044ea40269d9762498fe Author: juga0 <juga> Date: Sun Sep 20 21:09:06 2015 +0000
initial psiphon test. Failure cases doesn't fail propertly --- ooni/nettests/third_party/psiphon.py | 141 +++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+)
diff --git a/ooni/nettests/third_party/psiphon.py b/ooni/nettests/third_party/psiphon.py new file mode 100644 index 0000000..f8cb82a --- /dev/null +++ b/ooni/nettests/third_party/psiphon.py @@ -0,0 +1,141 @@ +import tempfile +import stat +import os + +from twisted.internet import defer, reactor +from twisted.internet.endpoints import TCP4ClientEndpoint +from twisted.web.client import readBody +from twisted.python import usage + +from txsocksx.http import SOCKS5Agent + +from ooni.errors import handleAllFailures, TaskTimedOut +from ooni.utils import log +from ooni.templates import process, httpt +from ooni.templates.process import ProcessTest + + +class UsageOptions(usage.Options): + log.debug("UsageOptions") + optParameters = [ + ['url', 'u', None, 'Specify a single URL to test.'], + ['psiphonpath', 'p', None, 'Specify psiphon python client path.'], + ['socksproxy', 's', None, 'Specify psiphon socks proxy ip:port.'],] + + +class PsiphonTest(httpt.HTTPTest, process.ProcessTest): + + """ + This class tests Psiphon python client + + test_psiphon: + Starts a Psiphon, check if it bootstraps successfully + (print a line in stdout). + Then, perform an HTTP request using the proxy + """ + + name = "Psiphon Test" + description = "Bootstraps Psiphon and \ + does a HTTP GET for the specified URL" + author = "juga" + version = "0.0.1" + timeout = 20 + usageOptions = UsageOptions + + def _setUp(self): + # it is necessary to do this in _setUp instead of setUp + # because it needs to happen before HTTPTest's _setUp. + # incidentally, setting this option in setUp results in HTTPTest + # *saying* it is using this proxy while not actually using it. + log.debug('PiphonTest._setUp: setting socksproxy') + self.localOptions['socksproxy'] = '127.0.0.1:1080' + super(PsiphonTest, self)._setUp() + + def setUp(self): + log.debug('PsiphonTest.setUp') + + self.bootstrapped = defer.Deferred() + if self.localOptions['url']: + self.url = self.localOptions['url'] + else: + # FIXME: use http://google.com? + # self.url = 'https://wtfismyip.com/text' + self.url = 'https://check.torproject.orggg' + + if self.localOptions['psiphonpath']: + self.psiphonpath = self.localOptions['psiphonpath'] + else: + # FIXME: search for pyclient path instead of assuming is in the + # home? + # psiphon is not installable and to run it manually, it has to be + # run from the psiphon directory, so it wouldn't make sense to + # nstall it in the PATH + from os import path, getenv + self.psiphonpath = path.join( + getenv('HOME'), + 'psiphon-circumvention-system/pyclient') + log.debug('psiphon path: %s' % self.psiphonpath) + + x = """#!/usr/bin/env python +from psi_client import connect +connect(False) +""" + f = tempfile.NamedTemporaryFile(delete=False) + f.write(x) + f.close() + os.chmod(f.name, os.stat(f.name).st_mode | stat.S_IEXEC) + self.command = [f.name] + log.debug('command: %s' % ''.join(self.command)) + + def handleRead(self, stdout, stderr): + if 'Press Ctrl-C to terminate.' in self.processDirector.stdout: + if not self.bootstrapped.called: + log.debug("PsiphonTest.test_psiphon: calling bootstrapped.callback") + self.bootstrapped.callback(None) + + def test_psiphon(self): + log.debug('PsiphonTest.test_psiphon') + + if not os.path.exists(self.psiphonpath): + log.debug('psiphon path does not exists, is it installed?') + self.report['success'] = False + self.report['psiphon_installed'] = False + log.debug("Adding %s to report" % self.report) + #FIXME: this completes the test but ooni doesn't stop running, why? + return defer.succeed(None) + + self.report['psiphon_installed'] = True + log.debug("Adding %s to report" % self.report) + + finished = self.run(self.command, + env=dict(PYTHONPATH=self.psiphonpath), + path=self.psiphonpath, + usePTY=1) + + + def addFailureToReport(failure): + log.debug("PsiphonTest.test_psiphon.addFailureToReport") + log.debug(repr(failure )) + self.report['failure'] = handleAllFailures(failure) + self.report['success'] = False + log.debug("Adding %s to report" % self.report) + # FIXME: these keys are not being wroten in the report + # probably because report is being defined in NetTestCase as + # a class attribute that is initialized again in NetTescase._setUp + + def callDoRequest(_): + return self.doRequest(self.url) + self.bootstrapped.addCallback(callDoRequest) + + def cleanup(_): + log.debug('PsiphonTest:cleanup') + self.processDirector.transport.signalProcess('INT') + os.remove(self.command[0]) + return finished + + self.bootstrapped.addErrback(addFailureToReport) + self.bootstrapped.addBoth(cleanup) + return self.bootstrapped + + +