[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [ooni-probe/master] Start rewriting daphn3
commit 514bb499295b6f1884cd1a5cb083522719acfe45
Author: Arturo Filastò <art@xxxxxxxxx>
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)
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits