[tor-commits] [ooni-probe/master] Start rewriting daphn3

art at torproject.org art at torproject.org
Mon Nov 12 19:14:03 UTC 2012


commit 514bb499295b6f1884cd1a5cb083522719acfe45
Author: Arturo Filastò <art at fuffa.org>
Date:   Sun Nov 11 20:31:28 2012 +0100

    Start rewriting daphn3
---
 nettests/core/daphn3.py |   89 ++++++----------
 ooni/kit/daphn3.py      |  261 +++++------------------------------------------
 2 files changed, 58 insertions(+), 292 deletions(-)

diff --git a/nettests/core/daphn3.py b/nettests/core/daphn3.py
index 8d7dbbd..ed65963 100644
--- a/nettests/core/daphn3.py
+++ b/nettests/core/daphn3.py
@@ -1,6 +1,5 @@
 from twisted.python import usage
-
-from twisted.internet import protocol, endpoints
+from twisted.internet import protocol, endpoints, reactor
 
 from ooni.kit import daphn3
 from ooni.utils import log
@@ -45,55 +44,45 @@ class Daphn3ClientFactory(protocol.ClientFactory):
         print "Connection Lost."
 
 class daphn3Args(usage.Options):
-    optParameters = [['pcap', 'f', None,
-                        'PCAP to read for generating the YAML output'],
-
-                     ['output', 'o', 'daphn3.yaml',
-                        'What file should be written'],
-
-                     ['yaml', 'y', None,
-                        'The input file to the test'],
-
+    optParameters = [
                      ['host', 'h', None, 'Target Hostname'],
-                     ['port', 'p', None, 'Target port number'],
-                     ['resume', 'r', 0, 'Resume at this index']]
+                     ['port', 'p', None, 'Target port number']]
+
+    optFlags = [['pcap', 'c', 'Specify that the input file is a pcap file'],
+                ['yaml', 'y', 'Specify that the input file is a YAML file (default)']]
 
 class daphn3Test(nettest.NetTestCase):
 
-    shortName = "daphn3"
-    description = "daphn3"
-    requirements = None
-    options = daphn3Args
-    blocking = False
+    name = "Daphn3"
+    usageOptions = daphn3Args
+    inputFile = ['file', 'f', None, 
+            'Specify the pcap or YAML file to be used as input to the test']
 
-    local_options = None
+    requiredOptions = ['file']
 
     steps = None
 
-    def initialize(self):
-        if not self.local_options:
-            self.end()
-            return
+    def inputProcessor(self, fp):
+        if self.localOptions['pcap']:
+            self.steps = daphn3.read_pcap(self.localOptions['pcap'])
+        else:
+            self.steps = daphn3.read_yaml(self.localOptions['yaml'])
 
+    def setUp(self):
         self.factory = Daphn3ClientFactory()
         self.factory.test = self
 
-        if self.local_options['pcap']:
-            self.tool = True
-
-        elif self.local_options['yaml']:
-            self.steps = daphn3.read_yaml(self.local_options['yaml'])
-
+        if self.localOptions['pcap']:
+            self.steps = daphn3.read_pcap(self.localOptions['pcap'])
+        elif self.localOptions['yaml']:
+            self.steps = daphn3.read_yaml(self.localOptions['yaml'])
         else:
-            log.msg("Not enough inputs specified to the test")
-            self.end()
+            raise usage.UsageError("You must specify either --pcap or --yaml")
 
-    def runTool(self):
-        import yaml
-        pcap = daphn3.read_pcap(self.local_options['pcap'])
-        f = open(self.local_options['output'], 'w')
-        f.write(yaml.dump(pcap))
-        f.close()
+        mutations = 0
+        for x in self.steps:
+            mutations += len(x['data'])
+        return {'mutation': range(mutations)}
 
     def control(self, exp_res, args):
         try:
@@ -106,34 +95,20 @@ class daphn3Test(nettest.NetTestCase):
                 'value': mutation}
 
     def _failure(self, *argc, **kw):
-        self.result['censored'] = True
-        self.result['error'] = ('Failed in connecting', (argc, kw))
-        self.end()
+        self.report['censored'] = True
+        self.report['mutation'] = 
+        self.report['error'] = ('Failed in connecting', (argc, kw))
 
-    def experiment(self, args):
+    def test_daphn3(self):
         log.msg("Doing mutation %s" % args['mutation'])
         self.factory.steps = self.steps
+
         host = self.local_options['host']
         port = int(self.local_options['port'])
         log.msg("Connecting to %s:%s" % (host, port))
 
-        if self.ended:
-            return
-
-        endpoint = endpoints.TCP4ClientEndpoint(self.reactor, host, port)
+        endpoint = endpoints.TCP4ClientEndpoint(reactor, host, port)
         d = endpoint.connect(self.factory)
         d.addErrback(self._failure)
         return d
 
-    def load_assets(self):
-        if not self.local_options:
-            return {}
-        if not self.steps:
-            print "Error: No assets!"
-            self.end()
-            return {}
-        mutations = 0
-        for x in self.steps:
-            mutations += len(x['data'])
-        return {'mutation': range(mutations)}
-
diff --git a/ooni/kit/daphn3.py b/ooni/kit/daphn3.py
index 37c94c7..2660af0 100644
--- a/ooni/kit/daphn3.py
+++ b/ooni/kit/daphn3.py
@@ -7,7 +7,6 @@ from twisted.internet.error import ConnectionDone
 from scapy.all import IP, Raw, rdpcap
 
 from ooni.utils import log
-from ooni.plugoo import reports
 
 def read_pcap(filename):
     """
@@ -28,9 +27,9 @@ def read_pcap(filename):
     """
     pcap assumptions:
 
-    pcap only contains packets exchanged between a Tor client and a Tor server.
-    (This assumption makes sure that there are only two IP addresses in the
-    pcap file)
+    pcap only contains packets exchanged between a Tor client and a Tor
+    server.  (This assumption makes sure that there are only two IP addresses
+    in the pcap file)
 
     The first packet of the pcap is sent from the client to the server. (This
     assumption is used to get the IP address of the client.)
@@ -39,8 +38,10 @@ def read_pcap(filename):
     establishment/teardown packets should be filtered out (no SYN/SYN+ACK)
     """
 
-    """Minimally validate the pcap and also find out what's the client
-    and server IP addresses."""
+    """
+    Minimally validate the pcap and also find out what's the client
+    and server IP addresses.
+    """
     for packet in packets:
         if checking_first_packet:
             client_ip_addr = packet[IP].src
@@ -72,240 +73,30 @@ def read_yaml(filename):
     f.close()
     return obj
 
-class Mutator:
-    idx = 0
-    step = 0
-
-    waiting = False
-    waiting_step = 0
-
-    def __init__(self, steps):
-        """
-        @param steps: array of dicts for the steps that must be gone over by
-                      the mutator. Looks like this:
-                      [{"sender": "client", "data": "\xde\xad\xbe\xef"},
-                       {"sender": "server", "data": "\xde\xad\xbe\xef"}]
-        """
-        self.steps = steps
-
-    def _mutate(self, data, idx):
-        """
-        Mutate the idx bytes by increasing it's value by one
-
-        @param data: the data to be mutated.
-
-        @param idx: what byte should be mutated.
-        """
-        print "idx: %s, data: %s" % (idx, data)
-        ret = data[:idx]
-        ret += chr(ord(data[idx]) + 1)
-        ret += data[idx+1:]
-        return ret
-
-    def state(self):
-        """
-        Return the current mutation state. As in what bytes are being mutated.
-
-        Returns a dict containg the packet index and the step number.
-        """
-        print "[Mutator.state()] Giving out my internal state."
-        current_state =  {'idx': self.idx, 'step': self.step}
-        return current_state
-
-    def next(self):
-        """
-        Increases by one the mutation state.
-
-        ex. (* is the mutation state, i.e. the byte to be mutated)
-        before [___*] [____]
-               step1   step2
-        after  [____] [*___]
-
-        Should be called every time you need to proceed onto the next mutation.
-        It changes the internal state of the mutator to that of the next
-        mutatation.
-
-        returns True if another mutation is available.
-        returns False if all the possible mutations have been done.
-        """
-        if (self.step) == len(self.steps):
-            # Hack to stop once we have gone through all the steps
-            print "[Mutator.next()] I believe I have gone over all steps"
-            print "                          Stopping!"
-            self.waiting = True
-            return False
-
-        self.idx += 1
-        current_idx = self.idx
-        current_step = self.step
-        current_data = self.steps[current_step]['data']
-
-        if 0:
-            print "current_step: %s" % current_step
-            print "current_idx: %s" % current_idx
-            print "current_data: %s" % current_data
-            print "steps: %s" % len(self.steps)
-            print "waiting_step: %s" % self.waiting_step
-
-        data_to_receive = len(self.steps[current_step]['data'])
-
-        if self.waiting and self.waiting_step == data_to_receive:
-            print "[Mutator.next()] I am no longer waiting"
-            log.debug("I am no longer waiting.")
-            self.waiting = False
-            self.waiting_step = 0
-            self.idx = 0
-
-        elif self.waiting:
-            print "[Mutator.next()] Waiting some more."
-            log.debug("Waiting some more.")
-            self.waiting_step += 1
-
-        elif current_idx >= len(current_data):
-            print "[Mutator.next()] Entering waiting mode."
-            log.debug("Entering waiting mode.")
-            self.step += 1
-            self.idx = 0
-            self.waiting = True
-
-        log.debug("current index %s" % current_idx)
-        log.debug("current data %s" % len(current_data))
-        return True
-
-    def get(self, step):
-        """
-        Returns the current packet to be sent to the wire.
-        If no mutation is necessary it will return the plain data.
-        Should be called when you are interested in obtaining the data to be
-        sent for the selected state.
-
-        @param step: the current step you want the mutation for
-
-        returns the mutated packet for the specified step.
-        """
-        if step != self.step or self.waiting:
-            log.debug("[Mutator.get()] I am not going to do anything :)")
-            return self.steps[step]['data']
-
-        data = self.steps[step]['data']
-        #print "Mutating %s with idx %s" % (data, self.idx)
-        return self._mutate(data, self.idx)
+class NoInputSpecified(Exception):
+    pass
 
 class Daphn3Protocol(protocol.Protocol):
-    """
-    This implements the Daphn3 protocol for the server side.
-    It gets instanced once for every client that connects to the oonib.
-    For every instance of protocol there is only 1 mutation.
-    Once the last step is reached the connection is closed on the serverside.
-    """
-    steps = []
-    mutator = None
-
-    current_state = None
-
-    role = 'client'
-    state = 0
-    total_states = len(steps) - 1
-    received_data = 0
-    to_receive_data = 0
-    report = reports.Report('daphn3', 'daphn3.yamlooni')
-
-    test = None
-
-    def next_state(self):
-        """
-        This is called once I have completed one step of the protocol and need
-        to proceed to the next step.
-        """
-        if not self.mutator:
-            print "[Daphn3Protocol.next_state] No mutator. There is no point to stay on this earth."
-            self.transport.loseConnection()
-            return
-
-        if self.role is self.steps[self.state]['sender']:
-            print "[Daphn3Protocol.next_state] I am a sender"
-            data = self.mutator.get(self.state)
-            self.transport.write(data)
-            self.to_receive_data = 0
-
+    def __init__(self, yaml_file=None, pcap_file=None, role="client"):
+        if yaml_file:
+            self.packets = read_yaml(yaml_file)
+        elif pcap_file:
+            self.packets = read_pcap(pcap_file)
         else:
-            print "[Daphn3Protocol.next_state] I am a receiver"
-            self.to_receive_data = len(self.steps[self.state]['data'])
-
-        self.state += 1
-        self.received_data = 0
+            raise NoInputSpecified
 
-    def dataReceived(self, data):
-        """
-        This is called every time some data is received. I transition to the
-        next step once the amount of data that I expect to receive is received.
-
-        @param data: the data that has been sent by the client.
-        """
-        if not self.mutator:
-            print "I don't have a mutator. My life means nothing."
-            self.transport.loseConnection()
-            return
-
-        if len(self.steps) == self.state:
-            self.transport.loseConnection()
-            return
-
-        self.received_data += len(data)
-        if self.received_data >= self.to_receive_data:
-            print "Moving to next state %s" % self.state
-            self.next_state()
-
-    def censorship_detected(self, report):
-        """
-        I have detected the possible presence of censorship we need to write a
-        report on it.
-
-        @param report: a dict containing the report to be written. Must contain
-                       the keys 'reason', 'proto_state' and 'mutator_state'.
-                       The reason is the reason for which the connection was
-                       closed. The proto_state is the current state of the
-                       protocol instance and mutator_state is what was being
-                       mutated.
-        """
-        print "The connection was closed because of %s" % report['reason']
-        print "State %s, Mutator %s" % (report['proto_state'],
-                                        report['mutator_state'])
-        if self.test:
-            self.test.result['censored'] = True
-            self.test.result['state'] = report
-        self.mutator.next()
+        self.role = role
+        # We use this index to keep track of where we are in the state machine
+        self.current_step = 0
 
-    def connectionLost(self, reason):
-        """
-        The connection was closed. This may be because of a legittimate reason
-        or it may be because of a censorship event.
-        """
-        if not self.mutator:
-            print "Terminated because of little interest in life."
-            return
-        report = {'reason': reason, 'proto_state': self.state,
-                'trigger': None, 'mutator_state': self.current_state}
+        # 0 indicates we are waiting to receive data, while 1 indicates we are
+        # sending data 
+        self.current_state = 0
+        self.current_data_received = 0
 
-        if self.state < self.total_states:
-            report['trigger'] = 'did not finish state walk'
-            self.censorship_detected(report)
-
-        else:
-            print "I have reached the end of the state machine"
-            print "Censorship fingerprint bruteforced!"
-            if self.test:
-                print "In the test thing"
-                self.test.result['censored'] = False
-                self.test.result['state'] = report
-                self.test.result['state_walk_finished'] = True
-                self.test.report(self.test.result)
-            return
-
-        if reason.check(ConnectionDone):
-            print "Connection closed cleanly"
-        else:
-            report['trigger'] = 'unclean connection closure'
-            self.censorship_detected(report)
+    def dataReceived(self, data):
+        self.current_data_received += len(data)
+        expected_data_in_this_state = len(self.packets[self.current_step][self.role])
+        if len(self.current_data_received)
 
 





More information about the tor-commits mailing list