commit 716913e40e6737b49263d43fbdcaf98effdf8d4d Author: srvetus srvetus@users.noreply.github.com Date: Thu Sep 24 01:43:14 2015 +0200
OpenVPN connection circumvention test
This test attempts to connect to an OpenVPN server specified in an OpenVPN config file. A HTTP request is sent if the VPN connection is successful.
This basic test can be expanded to parse more of the output from the OpenVPN client. The output could provide more information about why a connection might be failing. --- ooni/nettests/third_party/openvpn.py | 117 +++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+)
diff --git a/ooni/nettests/third_party/openvpn.py b/ooni/nettests/third_party/openvpn.py new file mode 100644 index 0000000..b681222 --- /dev/null +++ b/ooni/nettests/third_party/openvpn.py @@ -0,0 +1,117 @@ +from twisted.internet import defer, reactor +from twisted.python import usage +from twisted.web.client import Agent, readBody +from ooni.templates.process import ProcessTest +from ooni.utils import log +from ooni.errors import handleAllFailures + +import distutils.spawn +import re + + +class UsageOptions(usage.Options): + optParameters = [ + ['url', 'u', None, 'Specify a single URL on the OpenVPN subnet to test.'], + ['openvpn-config', 'c', None, 'Specify an OpenVPN configuration file.'], + ] + + +class OpenVPNTest(ProcessTest): + + """ + This class tests OpenVPN connections. + + test_openvpn_circumvent + Starts an OpenVPN client on Linux and determines + if it connects successfully to an OpenVPN server. + Then, it make a HTTP request for http://google.com + and records the response body or failure string. + + """ + + name = "OpenVPN Client Test" + description = "Connects to an OpenVPN server and does a HTTP GET for the specified URL" + author = "srvetus " + version = "0.0.1" + timeout = 20 + usageOptions = UsageOptions + requiredOptions = ['url', 'openvpn-config'] + requiresRoot = True + + def setUp(self): + self.bootstrapped = defer.Deferred() + self.command = [distutils.spawn.find_executable("openvpn")] + self.exited = False + self.url = self.localOptions.get('url') + + if self.localOptions.get('openvpn-config'): + openvpn_config = self.localOptions.get('openvpn-config') + self.command.extend(['--config', openvpn_config]) + + def stop(self, reason=None): + """Stop the running OpenVPN process and close the connection""" + if not self.exited: + self.processDirector.close() + + # OpenVPN needs to be sent SIGTERM to end cleanly + self.processDirector.transport.signalProcess('TERM') + self.exited = True + + def handleRead(self, stdout=None, stderr=None): + """handleRead is called with each chunk of data from stdout and stderr + + stdout only contains the latest data chunk, self.processDirector.stdout + contains the combined stdout data. + """ + + # Read OpenVPN output until bootstrapping succeeds or fails + if not self.bootstrapped.called: + + # TODO: Determine other OpenVPN messages which indicate connection failure + if re.search(r'connect to .* failed', self.processDirector.stdout): + log.debug("OpenVPN connection failed") + + # Bootstrapping failed + self.bootstrapped.errback(Exception("openvpn_connection_failed")) + + # Check if OpenVPN has bootstrapped and connected successfully + elif "Initialization Sequence Completed" in self.processDirector.stdout: + log.debug("OpenVPN connection successful") + self.processDirector.cancelTimer() + self.bootstrapped.callback("bootstrapped") + + def test_openvpn_circumvent(self): + + def addResultToReport(result): + log.debug("request_successful") + self.report['body'] = result + self.report['success'] = True + + def addFailureToReport(failure): + log.debug("failed: %s" % handleAllFailures(failure)) + self.report['failure'] = handleAllFailures(failure) + self.report['success'] = False + + def doRequest(noreason): + """Make a HTTP request over initialized VPN connection""" + agent = Agent(reactor) + + log.debug("Doing HTTP request to the OpenVPN subnet: %s" % self.url) + request = agent.request("GET", self.url) + request.addCallback(readBody) + request.addCallback(addResultToReport) + request.addErrback(addFailureToReport) + return request + + log.debug("Spawning OpenVPN") + self.d = self.run(self.command) + + # Try to make a request when the OpenVPN connection successfully bootstraps + self.bootstrapped.addCallback(doRequest) + + # Fire failure if OpenVPN connection fails + self.bootstrapped.addErrback(addFailureToReport) + + # Close OpenVPN after each successful or failed test + self.bootstrapped.addBoth(self.stop) + return self.d