commit 03a8462714d0609d0548d24fb5f8762b8d12d358 Author: Arturo Filastò art@fuffa.org Date: Mon Nov 12 20:12:18 2012 +0100
Finish reafactoring and porting daphn3. * XXX not fully tested --- nettests/core/daphn3.py | 71 +++++++++++++++------- ooni/kit/daphn3.py | 125 ++++++++++++++++++++++--------------- oonib/config.py | 4 +- oonib/testhelpers/tcp_helpers.py | 46 ++++++++++++-- 4 files changed, 164 insertions(+), 82 deletions(-)
diff --git a/nettests/core/daphn3.py b/nettests/core/daphn3.py index cc4803c..c277c56 100644 --- a/nettests/core/daphn3.py +++ b/nettests/core/daphn3.py @@ -6,18 +6,42 @@ from ooni import nettest from ooni.kit import daphn3 from ooni.utils import log
+class Daphn3ClientProtocol(daphn3.Daphn3Protocol): + def nextStep(self): + log.debug("Moving on to next step in the state walk") + self.current_data_received = 0 + if self.current_step >= (len(self.steps) - 1): + log.msg("Reached the end of the state machine") + log.msg("Censorship fingerpint bisected!") + step_idx, mutation_idx = self.factory.mutation + log.msg("step_idx: %s | mutation_id: %s" % (step_idx, mutation_idx)) + #self.transport.loseConnection() + if self.report: + self.report['mutation_idx'] = mutation_idx + self.report['step_idx'] = step_idx + self.d.callback(None) + return + else: + self.current_step += 1 + if self._current_step_role() == self.role: + # We need to send more data because we are again responsible for + # doing so. + self.sendPayload() + + class Daphn3ClientFactory(protocol.ClientFactory): protocol = daphn3.Daphn3Protocol - def __init__(self, steps): - self.steps = steps + mutation = [0,0] + steps = None
def buildProtocol(self, addr): - p = self.protocol(steps=self.steps) + p = self.protocol() + p.steps = self.steps p.factory = self return p
def startedConnecting(self, connector): - print "Started connecting %s" % connector + log.msg("Started connecting %s" % connector)
def clientConnectionFailed(self, reason, connector): log.err("We failed connecting the the OONIB") @@ -31,8 +55,8 @@ class Daphn3ClientFactory(protocol.ClientFactory):
class daphn3Args(usage.Options): optParameters = [ - ['host', 'h', None, 'Target Hostname'], - ['port', 'p', None, 'Target port number']] + ['host', 'h', '127.0.0.1', 'Target Hostname'], + ['port', 'p', 57003, '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)']] @@ -68,21 +92,16 @@ class daphn3Test(nettest.NetTestCase): else: daphn3Steps = [{'client': 'testing'}, {'server': 'antani'}]
- for idx, step in enumerate(daphn3Steps): - current_packet = step.values()[0] - for mutation_idx in range(len(current_packet)): - if step.keys()[0] == "client": - mutated_step = daphn3.daphn3Mutate(daphn3Steps, - idx, mutation_idx) - yield mutated_step - else: - yield daphn3Steps - - def setUp(self): - self.factory = Daphn3ClientFactory(self.input) - self.factory.report = self.report - print "Just set the factory to %s with %s" % (self.factory, - self.input) + #for idx, step in enumerate(daphn3Steps): + # current_packet = step.values()[0] + # for mutation_idx in range(len(current_packet)): + # if step.keys()[0] == "client": + # mutated_step = daphn3.daphn3Mutate(daphn3Steps, + # idx, mutation_idx) + # yield mutated_step + # else: + # yield daphn3Steps + yield daphn3Steps
def test_daphn3(self): host = self.localOptions['host'] @@ -95,11 +114,17 @@ class daphn3Test(nettest.NetTestCase):
def success(protocol): log.msg("Successfully connected") - protocol.sendMutation() + protocol.sendPayload() + return protocol.d
log.msg("Connecting to %s:%s" % (host, port)) endpoint = endpoints.TCP4ClientEndpoint(reactor, host, port) - d = endpoint.connect(self.factory) + daphn3_factory = Daphn3ClientFactory() + #daphn3_factory.steps = self.input + daphn3_factory.steps = [{'client': 'client_packet'}, + {'server': 'server_packet'}] + daphn3_factory.report = self.report + d = endpoint.connect(daphn3_factory) d.addErrback(failure) d.addCallback(success) return d diff --git a/ooni/kit/daphn3.py b/ooni/kit/daphn3.py index 1c340b4..8d655c3 100644 --- a/ooni/kit/daphn3.py +++ b/ooni/kit/daphn3.py @@ -76,7 +76,7 @@ def read_yaml(filename): class NoInputSpecified(Exception): pass
-class StateError(Exception): +class StepError(Exception): pass
def daphn3MutateString(string, i): @@ -88,7 +88,7 @@ def daphn3MutateString(string, i): if y == i: mutated += chr(ord(string[i]) + 1) else: - mutated += string[i] + mutated += string[y] return mutated
def daphn3Mutate(steps, step_idx, mutation_idx): @@ -109,77 +109,100 @@ def daphn3Mutate(steps, step_idx, mutation_idx): return mutated_steps
class Daphn3Protocol(protocol.Protocol): - def __init__(self, steps=None, - yaml_file=None, pcap_file=None, - role="client"): - if yaml_file: - self.steps = read_yaml(yaml_file) - elif pcap_file: - self.steps = read_pcap(pcap_file) - elif steps: - self.steps = steps - else: - raise NoInputSpecified - - # XXX remove me - #self.steps = [{'client': 'antani'}, {'server': 'sblinda'}] - self.role = role - # We use this index to keep track of where we are in the state machine - self.current_step = 0 - - # 0 indicates we are waiting to receive data, while 1 indicates we are - # sending data - self.current_state = 0 - self.current_data_received = 0 - - def sendMutation(self): - self.debug("Sending mutation") - current_step_role = self.steps[self.current_step].keys()[0] - current_step_data = self.steps[self.current_step].values()[0] + steps = None + role = "client" + report = None + # We use this index to keep track of where we are in the state machine + current_step = 0 + current_data_received = 0 + + # We use this to keep track of the mutated steps + mutated_steps = None + d = defer.Deferred() + + def _current_step_role(self): + return self.steps[self.current_step].keys()[0] + + def _current_step_data(self): + step_idx, mutation_idx = self.factory.mutation + log.debug("Mutating %s %s" % (step_idx, mutation_idx)) + mutated_step = daphn3Mutate(self.steps, + step_idx, mutation_idx) + log.debug("Mutated packet into %s" % mutated_step) + return mutated_step[self.current_step].values()[0] + + def sendPayload(self): + self.debug("Sending payload") + current_step_role = self._current_step_role() + current_step_data = self._current_step_data() if current_step_role == self.role: print "In a state to do shit %s" % current_step_data self.transport.write(current_step_data) - self.nextState() + self.nextStep() else: print "Not in a state to do anything"
def connectionMade(self): print "Got connection" - self.sendMutation()
def debug(self, msg): - print "Current step %s" % self.current_step - print "Current data received %s" % self.current_data_received - print "Current role %s" % self.role - print "Current steps %s" % self.steps - print "Current state %s" % self.current_state - - def nextState(self): - print "Moving on to next state" - self.current_data_received = 0 - self.current_step += 1 - if self.current_step >= len(self.steps): - print "Going to loose this connection" - self.transport.loseConnection() - return - self.sendMutation() + log.debug("Current step %s" % self.current_step) + log.debug("Current data received %s" % self.current_data_received) + log.debug("Current role %s" % self.role) + log.debug("Current steps %s" % self.steps) + log.debug("Current step data %s" % self._current_step_data()) + + def nextStep(self): + """ + XXX this method is overwritten individually by client and server transport. + There is probably a smarter way to do this and refactor the common + code into one place, but for the moment like this is good. + """ + pass
def dataReceived(self, data): current_step_role = self.steps[self.current_step].keys()[0] log.debug("Current step role %s" % current_step_role) if current_step_role == self.role: log.debug("Got a state error!") - raise StateError("I should not have gotten data, while I did, \ - perhaps there is a wrong state machine?") + raise StepError("I should not have gotten data, while I did, \ + perhaps there is something wrong with the state machine?")
self.current_data_received += len(data) expected_data_in_this_state = len(self.steps[self.current_step].values()[0])
log.debug("Current data received %s" % self.current_data_received) if self.current_data_received >= expected_data_in_this_state: - self.nextState() + self.nextStep() + + def nextMutation(self): + log.debug("Moving onto next mutation") + # [step_idx, mutation_idx] + c_step_idx, c_mutation_idx = self.factory.mutation + log.debug("[%s]: c_step_idx: %s | c_mutation_idx: %s" % (self.role, + c_step_idx, c_mutation_idx)) + + if c_step_idx >= (len(self.steps) - 1): + log.err("No censorship fingerprint bisected.") + log.err("Givinig up.") + self.transport.loseConnection() + return + + # This means we have mutated all bytes in the step + # we should proceed to mutating the next step. + log.debug("steps: %s | %s" % (self.steps, self.steps[c_step_idx])) + if c_mutation_idx >= (len(self.steps[c_step_idx].values()[0]) - 1): + log.debug("Finished mutating step") + # increase step + self.factory.mutation[0] += 1 + # reset mutation idx + self.factory.mutation[1] = 0 + else: + log.debug("Mutating next byte in step") + # increase mutation index + self.factory.mutation[1] += 1
def connectionLost(self, reason): - self.debug("Lost the connection") - print reason + self.debug("--- Lost the connection ---") + self.nextMutation()
diff --git a/oonib/config.py b/oonib/config.py index cf2e362..1a70b85 100644 --- a/oonib/config.py +++ b/oonib/config.py @@ -27,8 +27,8 @@ helpers.tcp_echo = Storage() helpers.tcp_echo.port = 57002
helpers.daphn3 = Storage() -helpers.daphn3.yaml_file = "/path/to/data/oonib/daphn3.yaml" -helpers.daphn3.pcap_file = "/path/to/data/server.pcap" +#helpers.daphn3.yaml_file = "/path/to/data/oonib/daphn3.yaml" +#helpers.daphn3.pcap_file = "/path/to/data/server.pcap" helpers.daphn3.port = 57003
helpers.dns = Storage() diff --git a/oonib/testhelpers/tcp_helpers.py b/oonib/testhelpers/tcp_helpers.py index 4287a59..4d32ae0 100644 --- a/oonib/testhelpers/tcp_helpers.py +++ b/oonib/testhelpers/tcp_helpers.py @@ -3,7 +3,7 @@ from twisted.internet.protocol import Protocol, Factory, ServerFactory from twisted.internet.error import ConnectionDone
from oonib import config - +from ooni.utils import log from ooni.kit.daphn3 import Daphn3Protocol from ooni.kit.daphn3 import read_pcap, read_yaml
@@ -17,8 +17,37 @@ class TCPEchoHelper(Factory): """ protocol = TCPEchoProtocol
-daphn3Steps = [{'client': '\x00\x00\x00'}, - {'server': '\x00\x00\x00'}] +if config.helpers.daphn3.yaml_file: + daphn3Steps = read_pcap(config.helpers.daphn3.yaml_file) + +elif config.helpers.daphn3.pcap_file: + daphn3Steps = read_yaml(config.helpers.daphn3.pcap_file) + +else: + daphn3Steps = [{'client': 'client_packet'}, + {'server': 'server_packet'}] + +class Daphn3ServerProtocol(Daphn3Protocol): + def nextStep(self): + log.debug("Moving on to next step in the state walk") + self.current_data_received = 0 + # Python why? + if self.current_step >= (len(self.steps) - 1): + log.msg("Reached the end of the state machine") + log.msg("Censorship fingerpint bisected!") + step_idx, mutation_idx = self.factory.mutation + log.msg("step_idx: %s | mutation_id: %s" % (step_idx, mutation_idx)) + #self.transport.loseConnection() + if self.report: + self.report['mutation_idx'] = mutation_idx + self.report['step_idx'] = step_idx + return + else: + self.current_step += 1 + if self._current_step_role() == self.role: + # We need to send more data because we are again responsible for + # doing so. + self.sendPayload()
class Daphn3Server(ServerFactory): """ @@ -29,10 +58,15 @@ class Daphn3Server(ServerFactory): two different clients are sharing the same IP, but hopefully the probability of such thing is not that likely. """ - protocol = Daphn3Protocol + protocol = Daphn3ServerProtocol + # step_idx, mutation_idx + mutation = [0, 0] def buildProtocol(self, addr): - p = self.protocol(steps=daphn3Steps, - role="server") + p = self.protocol() + p.steps = daphn3Steps + p.role = "server" p.factory = self return p
+ +