[tor-commits] [ooni-probe/master] Start working on porting of daphn3
art at torproject.org
art at torproject.org
Mon Nov 12 19:14:03 UTC 2012
commit e8d6cdc3e5c97e62136a8f7df8acaaddf489bfbd
Author: Arturo Filastò <art at fuffa.org>
Date: Sun Nov 11 19:32:47 2012 +0100
Start working on porting of daphn3
---
nettests/core/daphn3.py | 139 +++++++++++++++++
ooni/kit/daphn3.py | 311 ++++++++++++++++++++++++++++++++++++++
oonib/oonibackend.py | 8 +-
oonib/testhelpers/daphn3.py | 47 ------
oonib/testhelpers/tcp_helpers.py | 45 ++++++-
to-be-ported/old-api/daphn3.py | 152 -------------------
to-be-ported/protocols/daphn3.py | 311 --------------------------------------
7 files changed, 498 insertions(+), 515 deletions(-)
diff --git a/nettests/core/daphn3.py b/nettests/core/daphn3.py
new file mode 100644
index 0000000..8d7dbbd
--- /dev/null
+++ b/nettests/core/daphn3.py
@@ -0,0 +1,139 @@
+from twisted.python import usage
+
+from twisted.internet import protocol, endpoints
+
+from ooni.kit import daphn3
+from ooni.utils import log
+
+class Daphn3ClientProtocol(daphn3.Daphn3Protocol):
+ def connectionMade(self):
+ self.next_state()
+
+class Daphn3ClientFactory(protocol.ClientFactory):
+ protocol = Daphn3ClientProtocol
+ mutator = None
+ steps = None
+ test = None
+
+ def buildProtocol(self, addr):
+ p = self.protocol()
+ p.factory = self
+ p.test = self.test
+
+ if self.steps:
+ p.steps = self.steps
+
+ if not self.mutator:
+ self.mutator = daphn3.Mutator(p.steps)
+
+ else:
+ print "Moving on to next mutation"
+ self.mutator.next()
+
+ p.mutator = self.mutator
+ p.current_state = self.mutator.state()
+ return p
+
+ def clientConnectionFailed(self, reason):
+ print "We failed connecting the the OONIB"
+ print "Cannot perform test. Perhaps it got blocked?"
+ print "Please report this to tor-assistants at torproject.org"
+ self.test.result['error'] = ('Failed in connecting to OONIB', reason)
+ self.test.end(d)
+
+ def clientConnectionLost(self, reason):
+ 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'],
+
+ ['host', 'h', None, 'Target Hostname'],
+ ['port', 'p', None, 'Target port number'],
+ ['resume', 'r', 0, 'Resume at this index']]
+
+class daphn3Test(nettest.NetTestCase):
+
+ shortName = "daphn3"
+ description = "daphn3"
+ requirements = None
+ options = daphn3Args
+ blocking = False
+
+ local_options = None
+
+ steps = None
+
+ def initialize(self):
+ if not self.local_options:
+ self.end()
+ return
+
+ 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'])
+
+ else:
+ log.msg("Not enough inputs specified to the test")
+ self.end()
+
+ 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()
+
+ def control(self, exp_res, args):
+ try:
+ mutation = self.factory.mutator.get(0)
+ self.result['censored'] = False
+ except:
+ mutation = None
+
+ return {'mutation_number': args['mutation'],
+ 'value': mutation}
+
+ def _failure(self, *argc, **kw):
+ self.result['censored'] = True
+ self.result['error'] = ('Failed in connecting', (argc, kw))
+ self.end()
+
+ def experiment(self, args):
+ 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)
+ 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
new file mode 100644
index 0000000..37c94c7
--- /dev/null
+++ b/ooni/kit/daphn3.py
@@ -0,0 +1,311 @@
+import sys
+import yaml
+
+from twisted.internet import protocol, defer
+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):
+ """
+ @param filename: Filesystem path to the pcap.
+
+ Returns:
+ [{"sender": "client", "data": "\x17\x52\x15"}, {"sender": "server", "data": "\x17\x15\x13"}]
+ """
+ packets = rdpcap(filename)
+
+ checking_first_packet = True
+ client_ip_addr = None
+ server_ip_addr = None
+
+ ssl_packets = []
+ messages = []
+
+ """
+ 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)
+
+ 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.)
+
+ All captured packets are TLS packets: that is TCP session
+ 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."""
+ for packet in packets:
+ if checking_first_packet:
+ client_ip_addr = packet[IP].src
+ checking_first_packet = False
+ else:
+ if packet[IP].src != client_ip_addr:
+ server_ip_addr = packet[IP].src
+
+ try:
+ if (packet[Raw]):
+ ssl_packets.append(packet)
+ except IndexError:
+ pass
+
+ """Form our list."""
+ for packet in ssl_packets:
+ if packet[IP].src == client_ip_addr:
+ messages.append({"sender": "client", "data": str(packet[Raw])})
+ elif packet[IP].src == server_ip_addr:
+ messages.append({"sender": "server", "data": str(packet[Raw])})
+ else:
+ raise("Detected third IP address! pcap is corrupted.")
+
+ return messages
+
+def read_yaml(filename):
+ f = open(filename)
+ obj = yaml.load(f)
+ 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 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
+
+ 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
+
+ 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()
+
+ 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}
+
+ 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)
+
+
diff --git a/oonib/oonibackend.py b/oonib/oonibackend.py
index 8f47e73..4dced2a 100644
--- a/oonib/oonibackend.py
+++ b/oonib/oonibackend.py
@@ -53,10 +53,10 @@ if config.helpers.dns.udp_port:
# XXX this needs to be ported
# Start the OONI daphn3 backend
-#if config.main.daphn3_port:
-# daphn3 = Daphn3Server()
-# internet.TCPServer(int(config.main.daphn3_port),
-# daphn3).setServiceParent(serviceCollection)
+if config.main.daphn3_port:
+ daphn3_helper = internet.TCPServer(int(config.helpers.daphn3.port),
+ tcp_helpers.Daphn3Server())
+ daphn3_helper.setServiceParent(serviceCollection)
if config.main.collector_port:
log.msg("Starting Collector on %s" % config.main.collector_port)
diff --git a/oonib/testhelpers/daphn3.py b/oonib/testhelpers/daphn3.py
deleted file mode 100644
index 3b59f93..0000000
--- a/oonib/testhelpers/daphn3.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# XXX this is currently broken and needs to be ported
-from twisted.internet import protocol
-from twisted.internet.error import ConnectionDone
-
-from oonib import config
-
-from ooni.protocols.daphn3 import Mutator, Daphn3Protocol
-from ooni.protocols.daphn3 import read_pcap, read_yaml
-
-class Daphn3Server(protocol.ServerFactory):
- """
- This is the main class that deals with the daphn3 server side component.
- We keep track of global state of every client here.
- Every client is identified by their IP address and the state of mutation is
- stored by using their IP address as a key. This may lead to some bugs if
- two different clients are sharing the same IP, but hopefully the
- probability of such thing is not that likely.
- """
- protocol = Daphn3Protocol
- mutations = {}
- def buildProtocol(self, addr):
- p = self.protocol()
- p.factory = self
-
- if config.daphn3.yaml_file:
- steps = read_yaml(config.daphn3.yaml_file)
- elif config.daphn3.pcap_file:
- steps = read_pcap(config.daphn3.pcap_file)
- else:
- print "Error! No PCAP, nor YAML file provided."
- steps = None
-
- p.factory.steps = steps
-
- if addr.host not in self.mutations:
- self.mutations[addr.host] = Mutator(p.steps)
- else:
- print "Moving on to next mutation"
- if not self.mutations[addr.host].next():
- self.mutations.pop(addr.host)
- try:
- p.mutator = self.mutations[addr.host]
- p.current_state = p.mutator.state()
- except:
- pass
- return p
-
diff --git a/oonib/testhelpers/tcp_helpers.py b/oonib/testhelpers/tcp_helpers.py
index 57b93d2..551e880 100644
--- a/oonib/testhelpers/tcp_helpers.py
+++ b/oonib/testhelpers/tcp_helpers.py
@@ -1,4 +1,11 @@
-from twisted.internet.protocol import Protocol, Factory
+
+from twisted.internet.protocol import Protocol, Factory, ServerFactory
+from twisted.internet.error import ConnectionDone
+
+from oonib import config
+
+from ooni.kit.daphn3 import Mutator, Daphn3Protocol
+from ooni.kit.daphn3 import read_pcap, read_yaml
class TCPEchoProtocol(Protocol):
def dataReceived(self, data):
@@ -10,5 +17,41 @@ class TCPEchoHelper(Factory):
"""
protocol = TCPEchoProtocol
+class Daphn3Server(ServerFactory):
+ """
+ This is the main class that deals with the daphn3 server side component.
+ We keep track of global state of every client here.
+ Every client is identified by their IP address and the state of mutation is
+ stored by using their IP address as a key. This may lead to some bugs if
+ two different clients are sharing the same IP, but hopefully the
+ probability of such thing is not that likely.
+ """
+ protocol = Daphn3Protocol
+ mutations = {}
+ def buildProtocol(self, addr):
+ p = self.protocol()
+ p.factory = self
+
+ if config.daphn3.yaml_file:
+ steps = read_yaml(config.daphn3.yaml_file)
+ elif config.daphn3.pcap_file:
+ steps = read_pcap(config.daphn3.pcap_file)
+ else:
+ print "Error! No PCAP, nor YAML file provided."
+ steps = None
+
+ p.factory.steps = steps
+ if addr.host not in self.mutations:
+ self.mutations[addr.host] = Mutator(p.steps)
+ else:
+ print "Moving on to next mutation"
+ if not self.mutations[addr.host].next():
+ self.mutations.pop(addr.host)
+ try:
+ p.mutator = self.mutations[addr.host]
+ p.current_state = p.mutator.state()
+ except:
+ pass
+ return p
diff --git a/to-be-ported/old-api/daphn3.py b/to-be-ported/old-api/daphn3.py
deleted file mode 100644
index bf4d60d..0000000
--- a/to-be-ported/old-api/daphn3.py
+++ /dev/null
@@ -1,152 +0,0 @@
-"""
-This is a self genrated test created by scaffolding.py.
-you will need to fill it up with all your necessities.
-Safe hacking :).
-"""
-from zope.interface import implements
-from twisted.python import usage
-from twisted.plugin import IPlugin
-from twisted.internet import protocol, endpoints
-
-from ooni.plugoo import reports
-from ooni.plugoo.tests import ITest, OONITest
-from ooni.plugoo.assets import Asset
-from ooni.protocols import daphn3
-from ooni.utils import log
-
-class Daphn3ClientProtocol(daphn3.Daphn3Protocol):
- def connectionMade(self):
- self.next_state()
-
-class Daphn3ClientFactory(protocol.ClientFactory):
- protocol = Daphn3ClientProtocol
- mutator = None
- steps = None
- test = None
-
- def buildProtocol(self, addr):
- p = self.protocol()
- p.factory = self
- p.test = self.test
-
- if self.steps:
- p.steps = self.steps
-
- if not self.mutator:
- self.mutator = daphn3.Mutator(p.steps)
-
- else:
- print "Moving on to next mutation"
- self.mutator.next()
-
- p.mutator = self.mutator
- p.current_state = self.mutator.state()
- return p
-
- def clientConnectionFailed(self, reason):
- print "We failed connecting the the OONIB"
- print "Cannot perform test. Perhaps it got blocked?"
- print "Please report this to tor-assistants at torproject.org"
- self.test.result['error'] = ('Failed in connecting to OONIB', reason)
- self.test.end(d)
-
- def clientConnectionLost(self, reason):
- 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'],
-
- ['host', 'h', None, 'Target Hostname'],
- ['port', 'p', None, 'Target port number'],
- ['resume', 'r', 0, 'Resume at this index']]
-
-class daphn3Test(OONITest):
- implements(IPlugin, ITest)
-
- shortName = "daphn3"
- description = "daphn3"
- requirements = None
- options = daphn3Args
- blocking = False
-
- local_options = None
-
- steps = None
-
- def initialize(self):
- if not self.local_options:
- self.end()
- return
-
- 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'])
-
- else:
- log.msg("Not enough inputs specified to the test")
- self.end()
-
- 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()
-
- def control(self, exp_res, args):
- try:
- mutation = self.factory.mutator.get(0)
- self.result['censored'] = False
- except:
- mutation = None
-
- return {'mutation_number': args['mutation'],
- 'value': mutation}
-
- def _failure(self, *argc, **kw):
- self.result['censored'] = True
- self.result['error'] = ('Failed in connecting', (argc, kw))
- self.end()
-
- def experiment(self, args):
- 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)
- 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)}
-
-# We need to instantiate it otherwise getPlugins does not detect it
-# XXX Find a way to load plugins without instantiating them.
-#daphn3test = daphn3Test(None, None, None)
diff --git a/to-be-ported/protocols/daphn3.py b/to-be-ported/protocols/daphn3.py
deleted file mode 100644
index 37c94c7..0000000
--- a/to-be-ported/protocols/daphn3.py
+++ /dev/null
@@ -1,311 +0,0 @@
-import sys
-import yaml
-
-from twisted.internet import protocol, defer
-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):
- """
- @param filename: Filesystem path to the pcap.
-
- Returns:
- [{"sender": "client", "data": "\x17\x52\x15"}, {"sender": "server", "data": "\x17\x15\x13"}]
- """
- packets = rdpcap(filename)
-
- checking_first_packet = True
- client_ip_addr = None
- server_ip_addr = None
-
- ssl_packets = []
- messages = []
-
- """
- 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)
-
- 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.)
-
- All captured packets are TLS packets: that is TCP session
- 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."""
- for packet in packets:
- if checking_first_packet:
- client_ip_addr = packet[IP].src
- checking_first_packet = False
- else:
- if packet[IP].src != client_ip_addr:
- server_ip_addr = packet[IP].src
-
- try:
- if (packet[Raw]):
- ssl_packets.append(packet)
- except IndexError:
- pass
-
- """Form our list."""
- for packet in ssl_packets:
- if packet[IP].src == client_ip_addr:
- messages.append({"sender": "client", "data": str(packet[Raw])})
- elif packet[IP].src == server_ip_addr:
- messages.append({"sender": "server", "data": str(packet[Raw])})
- else:
- raise("Detected third IP address! pcap is corrupted.")
-
- return messages
-
-def read_yaml(filename):
- f = open(filename)
- obj = yaml.load(f)
- 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 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
-
- 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
-
- 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()
-
- 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}
-
- 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)
-
-
More information about the tor-commits
mailing list