Esempio n. 1
0
    def _cbResponse(self, response, request, headers_processor,
                    body_processor):

        if not response:
            log.err("Got no response")
            return
        else:
            log.debug("Got response %s" % response)

        if str(response.code).startswith('3'):
            self.processRedirect(response.headers.getRawHeaders('Location')[0])

        # [!] We are passing to the headers_processor the headers dict and
        # not the Headers() object
        response_headers_dict = list(response.headers.getAllRawHeaders())
        if headers_processor:
            headers_processor(response_headers_dict)
        else:
            self.processResponseHeaders(response_headers_dict)

        finished = defer.Deferred()
        response.deliverBody(BodyReceiver(finished))
        finished.addCallback(self._processResponseBody, request, response,
                             body_processor)

        return finished
Esempio n. 2
0
    def _response(self, response):
        content_length = response.headers.getRawHeaders('content-length')

        finished = defer.Deferred()
        response.deliverBody(BodyReceiver(finished, content_length))
        finished.addCallback(self.parseResponse)
        return finished
Esempio n. 3
0
    def _cbResponse(self, response, request, headers_processor,
                    body_processor):
        """
        This callback is fired once we have gotten a response for our request.
        If we are using a RedirectAgent then this will fire once we have
        reached the end of the redirect chain.

        Args:

            response (:twisted.web.iweb.IResponse:): a provider for getting our response

            request (dict): the dict containing our response (XXX this should be dropped)

            header_processor (func): a function to be called with argument a
                dict containing the response headers. This will lead
                self.headerProcessor to not be called.

            body_processor (func): a function to be called with as argument the
                body of the response. This will lead self.bodyProcessor to not
                be called.

        """
        if not response:
            log.err("Got no response for request %s" % request)
            HTTPTest.addToReport(self, request, response)
            return
        else:
            log.debug("Got response %s" % response)

        if str(response.code).startswith('3'):
            self.processRedirect(response.headers.getRawHeaders('Location')[0])

        # [!] We are passing to the headers_processor the headers dict and
        # not the Headers() object
        response_headers_dict = list(response.headers.getAllRawHeaders())
        if headers_processor:
            headers_processor(response_headers_dict)
        else:
            self.processResponseHeaders(response_headers_dict)

        try:
            content_length = int(
                response.headers.getRawHeaders('content-length')[0])
        except Exception:
            content_length = None

        finished = defer.Deferred()
        response.deliverBody(BodyReceiver(finished, content_length))
        finished.addCallback(self._processResponseBody, request, response,
                             body_processor)
        finished.addErrback(self._processResponseBodyFail, request, response)
        return finished
Esempio n. 4
0
        def genReceiver(finished, content_length):
            def process_response(s):
                # If empty string then don't parse it.
                if not s:
                    return
                try:
                    response = json.loads(s)
                except ValueError:
                    raise e.get_error(None)
                if 'error' in response:
                    log.debug("Got this backend error message %s" % response)
                    raise e.get_error(response['error'])
                return response

            return BodyReceiver(finished, content_length, process_response)
Esempio n. 5
0
            #yield defer.fail(OONIBReportCreationError())
            raise errors.OONIBReportCreationError

        except errors.HostUnreachable:
            log.err("Host is not reachable (HostUnreachable error")
            raise errors.OONIBReportCreationError

        except Exception, e:
            log.err("Failed to connect to reporter backend")
            log.exception(e)
            raise errors.OONIBReportCreationError

        # This is a little trix to allow us to unspool the response. We create
        # a deferred and call yield on it.
        response_body = defer.Deferred()
        response.deliverBody(BodyReceiver(response_body))

        backend_response = yield response_body

        try:
            parsed_response = json.loads(backend_response)
        except Exception, e:
            log.err("Failed to parse collector response")
            log.exception(e)
            raise errors.OONIBReportCreationError

        self.reportID = parsed_response['report_id']
        self.backendVersion = parsed_response['backend_version']
        log.debug("Created report with id %s" % parsed_response['report_id'])

class ReportClosed(Exception):
Esempio n. 6
0
class OONIBReporter(OReporter):
    def __init__(self, test_details, collector_address):
        self.collectorAddress = collector_address
        self.validateCollectorAddress()

        self.reportID = None

        OReporter.__init__(self, test_details)

    def validateCollectorAddress(self):
        """
        Will raise :class:ooni.errors.InvalidOONIBCollectorAddress an exception
        if the oonib reporter is not valid.
        """
        regexp = '^(http|httpo):\/\/[a-zA-Z0-9\-\.]+(:\d+)?$'
        if not re.match(regexp, self.collectorAddress):
            raise errors.InvalidOONIBCollectorAddress

    @defer.inlineCallbacks
    def writeReportEntry(self, entry):
        log.debug("Writing report with OONIB reporter")
        content = '---\n'
        if isinstance(entry, Measurement):
            content += safe_dump(entry.testInstance.report)
        elif isinstance(entry, Failure):
            content += entry.value
        elif isinstance(entry, dict):
            content += safe_dump(entry)
        content += '...\n'

        url = self.collectorAddress + '/report'

        request = {'report_id': self.reportID, 'content': content}

        log.debug("Updating report with id %s (%s)" % (self.reportID, url))
        request_json = json.dumps(request)
        log.debug("Sending %s" % request_json)

        bodyProducer = StringProducer(json.dumps(request))

        try:
            yield self.agent.request("PUT", url, bodyProducer=bodyProducer)
        except:
            # XXX we must trap this in the runner and make sure to report the
            # data later.
            log.err("Error in writing report entry")
            raise errors.OONIBReportUpdateError

    @defer.inlineCallbacks
    def createReport(self):
        """
        Creates a report on the oonib collector.
        """
        # XXX we should probably be setting this inside of the constructor,
        # however config.tor.socks_port is not set until Tor is started and the
        # reporter is instantiated before Tor is started. We probably want to
        # do this with some deferred kung foo or instantiate the reporter after
        # tor is started.

        from ooni.utils.hacks import SOCKS5Agent
        from twisted.internet import reactor

        if self.collectorAddress.startswith('httpo://'):
            self.collectorAddress = \
                self.collectorAddress.replace('httpo://', 'http://')
            proxyEndpoint = TCP4ClientEndpoint(reactor, '127.0.0.1',
                                               config.tor.socks_port)
            self.agent = SOCKS5Agent(reactor, proxyEndpoint=proxyEndpoint)

        elif self.collectorAddress.startswith('https://'):
            # XXX add support for securely reporting to HTTPS collectors.
            log.err("HTTPS based collectors are currently not supported.")

        url = self.collectorAddress + '/report'

        content = '---\n'
        content += safe_dump(self.testDetails)
        content += '...\n'

        request = {
            'software_name': self.testDetails['software_name'],
            'software_version': self.testDetails['software_version'],
            'probe_asn': self.testDetails['probe_asn'],
            'test_name': self.testDetails['test_name'],
            'test_version': self.testDetails['test_version'],
            'input_hashes': self.testDetails['input_hashes'],
            # XXX there is a bunch of redundancy in the arguments getting sent
            # to the backend. This may need to get changed in the client and
            # the backend.
            'content': content
        }

        log.msg("Reporting %s" % url)
        request_json = json.dumps(request)
        log.debug("Sending %s" % request_json)

        bodyProducer = StringProducer(json.dumps(request))

        log.msg("Creating report with OONIB Reporter. Please be patient.")
        log.msg("This may take up to 1-2 minutes...")

        try:
            response = yield self.agent.request("POST",
                                                url,
                                                bodyProducer=bodyProducer)

        except ConnectionRefusedError:
            log.err("Connection to reporting backend failed "
                    "(ConnectionRefusedError)")
            raise errors.OONIBReportCreationError

        except errors.HostUnreachable:
            log.err("Host is not reachable (HostUnreachable error")
            raise errors.OONIBReportCreationError

        except Exception, e:
            log.err("Failed to connect to reporter backend")
            log.exception(e)
            raise errors.OONIBReportCreationError

        # This is a little trix to allow us to unspool the response. We create
        # a deferred and call yield on it.
        response_body = defer.Deferred()
        response.deliverBody(BodyReceiver(response_body))

        backend_response = yield response_body

        try:
            parsed_response = json.loads(backend_response)
        except Exception, e:
            log.err("Failed to parse collector response %s" % backend_response)
            log.exception(e)
            raise errors.OONIBReportCreationError
Esempio n. 7
0
class OONIBReporter(OReporter):
    def __init__(self, test_details, collector_address):
        self.collectorAddress = collector_address
        self.validateCollectorAddress()

        self.reportID = None
        self.supportedFormats = ["yaml"]

        if self.collectorAddress.startswith('https://'):
            # not sure if there's something else it needs.  Seems to work.
            # Very difficult to get it to work with self-signed certs.
            self.agent = Agent(reactor)

        elif self.collectorAddress.startswith('http://'):
            log.msg("Warning using unencrypted collector")
            self.agent = Agent(reactor)

        OReporter.__init__(self, test_details)

    def validateCollectorAddress(self):
        """
        Will raise :class:ooni.errors.InvalidOONIBCollectorAddress an exception
        if the oonib reporter is not valid.
        """
        regexp = '^(http|https|httpo):\/\/[a-zA-Z0-9\-\.]+(:\d+)?$'
        if not re.match(regexp, self.collectorAddress):
            raise errors.InvalidOONIBCollectorAddress

    def serializeEntry(self, entry, serialisation_format="yaml"):
        if serialisation_format == "json":
            if isinstance(entry, Measurement):
                report_entry = {
                    'input':
                    entry.testInstance.report.pop('input', None),
                    'id':
                    str(uuid.uuid4()),
                    'test_start_time':
                    entry.testInstance.report.pop('test_start_time', None),
                    'measurement_start_time':
                    entry.testInstance.report.pop('measurement_start_time',
                                                  None),
                    'test_runtime':
                    entry.testInstance.report.pop('test_runtime', None),
                    'test_keys':
                    entry.testInstance.report
                }
            elif isinstance(entry, dict):
                report_entry = {
                    'input':
                    entry.pop('input', None),
                    'id':
                    str(uuid.uuid4()),
                    'test_start_time':
                    entry.pop('test_start_time', None),
                    'measurement_start_time':
                    entry.pop('measurement_start_time', None),
                    'test_runtime':
                    entry.pop('test_runtime', None),
                    'test_keys':
                    entry
                }
            else:
                raise Exception("Failed to serialise entry")
            report_entry.update(self.testDetails)
            return report_entry
        else:
            content = '---\n'
            if isinstance(entry, Measurement):
                report_entry = entry.testInstance.report
            elif isinstance(entry, dict):
                report_entry = entry
            else:
                raise Exception("Failed to serialise entry")
            content += safe_dump(report_entry)
            content += '...\n'
            return content

    @defer.inlineCallbacks
    def writeReportEntry(self, entry):
        log.debug("Writing report with OONIB reporter")

        url = self.collectorAddress + '/report/' + self.reportID

        if "json" in self.supportedFormats:
            serialisation_format = 'json'
        else:
            serialisation_format = 'yaml'

        request = {
            'format': serialisation_format,
            'content': self.serializeEntry(entry, serialisation_format)
        }

        log.debug("Updating report with id %s (%s)" % (self.reportID, url))
        request_json = json.dumps(request)
        log.debug("Sending %s" % request_json)

        bodyProducer = StringProducer(request_json)

        try:
            yield self.agent.request("POST",
                                     str(url),
                                     bodyProducer=bodyProducer)
        except Exception as exc:
            log.err("Error in writing report entry")
            log.exception(exc)
            raise errors.OONIBReportUpdateError

    @defer.inlineCallbacks
    def createReport(self):
        """
        Creates a report on the oonib collector.
        """
        # XXX we should probably be setting this inside of the constructor,
        # however config.tor.socks_port is not set until Tor is started and the
        # reporter is instantiated before Tor is started. We probably want to
        # do this with some deferred kung foo or instantiate the reporter after
        # tor is started.

        if self.collectorAddress.startswith('httpo://'):
            self.collectorAddress = \
                self.collectorAddress.replace('httpo://', 'http://')
            proxyEndpoint = TCP4ClientEndpoint(reactor, '127.0.0.1',
                                               config.tor.socks_port)
            self.agent = SOCKS5Agent(reactor, proxyEndpoint=proxyEndpoint)

        url = self.collectorAddress + '/report'

        request = {
            'software_name': self.testDetails['software_name'],
            'software_version': self.testDetails['software_version'],
            'probe_asn': self.testDetails['probe_asn'],
            'probe_cc': self.testDetails['probe_cc'],
            'test_name': self.testDetails['test_name'],
            'test_version': self.testDetails['test_version'],
            'test_start_time': self.testDetails['test_start_time'],
            'input_hashes': self.testDetails['input_hashes'],
            'data_format_version': self.testDetails['data_format_version'],
            'format': 'json'
        }
        # import values from the environment
        request.update([(k.lower(), v) for (k, v) in os.environ.iteritems()
                        if k.startswith('PROBE_')])

        log.msg("Reporting %s" % url)
        request_json = json.dumps(request)
        log.debug("Sending %s" % request_json)

        bodyProducer = StringProducer(request_json)

        log.msg("Creating report with OONIB Reporter. Please be patient.")
        log.msg("This may take up to 1-2 minutes...")

        try:
            response = yield self.agent.request("POST",
                                                url,
                                                bodyProducer=bodyProducer)

        except ConnectionRefusedError:
            log.err("Connection to reporting backend failed "
                    "(ConnectionRefusedError)")
            raise errors.OONIBReportCreationError

        except errors.HostUnreachable:
            log.err("Host is not reachable (HostUnreachable error")
            raise errors.OONIBReportCreationError

        except Exception, e:
            log.err("Failed to connect to reporter backend")
            log.exception(e)
            raise errors.OONIBReportCreationError

        # This is a little trix to allow us to unspool the response. We create
        # a deferred and call yield on it.
        response_body = defer.Deferred()
        response.deliverBody(BodyReceiver(response_body))

        backend_response = yield response_body

        try:
            parsed_response = json.loads(backend_response)
        except Exception, e:
            log.err("Failed to parse collector response %s" % backend_response)
            log.exception(e)
            raise errors.OONIBReportCreationError