commit e8d6cdc3e5c97e62136a8f7df8acaaddf489bfbd Author: Arturo Filastò art@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@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@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) - -