[tor-commits] [ooni-probe/master] Ported tcpsyn to the new api.
isis at torproject.org
isis at torproject.org
Tue Dec 18 05:53:46 UTC 2012
commit 69b43e9867c118fc796e76b11bcb6555f6e54f4e
Author: Isis Lovecruft <isis at torproject.org>
Date: Mon Nov 19 11:43:39 2012 +0000
Ported tcpsyn to the new api.
---
nettests/bridge_reachability/tcpsyn.py | 216 ++++++++++++++++++++++++++++++++
ooni/runner.py | 3 +-
2 files changed, 218 insertions(+), 1 deletions(-)
diff --git a/nettests/bridge_reachability/tcpsyn.py b/nettests/bridge_reachability/tcpsyn.py
new file mode 100644
index 0000000..bc79a93
--- /dev/null
+++ b/nettests/bridge_reachability/tcpsyn.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# +-----------+
+# | tcpsyn.py |
+# +-----------+
+# Send a TCP SYN packet to a test server to check that
+# it is reachable.
+#
+# @authors: Isis Lovecruft, <isis at torproject.org>
+# @version: 0.0.1-pre-alpha
+# @license: copyright (c) 2012 Isis Lovecruft
+# see attached LICENCE file
+#
+
+import os
+import sys
+
+from ipaddr import IPAddress
+from twisted.python import usage
+from twisted.internet import reactor, defer, address
+from ooni import nettest
+from ooni.utils import net, log
+
+try:
+ from scapy.all import TCP, IP
+ from scapy.all import sr1
+ from ooni.utils import txscapy
+except:
+ log.msg("This test requires scapy, see www.secdev.org/projects/scapy")
+
+
+class UsageOptions(usage.Options):
+ """Options for TCPSynTest."""
+ optParameters = [['dst', 'd', None, 'Host IP to ping'],
+ ['port', 'p', None, 'Host port'],
+ ['count', 'c', 3, 'Number of SYN packets to send', int],
+ ['interface', 'i', None, 'Network interface to use'],
+ ['verbose', 'v', False, 'Show hexdump of responses']]
+
+class TCPSynTest(nettest.NetTestCase):
+ """
+ Sends only a TCP SYN packet to a host IP:PORT, and waits for either a
+ SYN/ACK, a RST, or an ICMP error.
+
+ TCPSynTest can take an input file containing one IP:Port pair per line, or
+ the commandline switches --dst <IP> and --port <PORT> can be used.
+ """
+ name = 'TCP SYN'
+ author = 'Isis Lovecruft <isis at torproject.org>'
+ description = 'A TCP SYN test to see if a host is reachable.'
+ version = '0.0.1'
+ requiresRoot = True
+
+ usageOptions = UsageOptions
+ inputFile = ['file', 'f', None, 'File of list of IP:PORTs to ping']
+
+ destinations = {}
+
+ def setUp(self, *a, **kw):
+ """Configure commandline parameters for TCPSynTest."""
+ if self.localOptions:
+ for key, value in self.localOptions.items():
+ log.debug("setting self.%s = %s" % (key, value))
+ setattr(self, key, value)
+
+ if not self.interface:
+ try:
+ iface = net.getDefaultIface()
+ except net.IfaceError, ie:
+ log.msg("Could not find a working network interface!")
+ except Exception, ex:
+ log.exception(ex)
+ else:
+ log.msg("Using system default interface: %s" % iface)
+ self.interface = iface
+
+ def addToDestinations(self, addr, port):
+ try:
+ dst, dport = net.checkIPandPort(addr, port)
+ if not dst in self.destinations.keys():
+ self.destinations[dst] = {'dst': dst, 'dport': dport}
+ return (dst, dport)
+ except Exception, ex:
+ log.exception(ex)
+
+ def inputProcessor(self, input_file=None):
+ """
+ Pull the IPs and PORTs from the input file, and place them in a dict
+ for storing test results as they arrive.
+ """
+ try:
+ ## get the commandline input, if there is one:
+ if self.localOptions['dst'] is not None and self.localOptions['port'] is not None:
+ log.debug("processing commandline destination input")
+ yield self.addToDestinations(self.localOptions['dst'],
+ self.localOptions['port'])
+
+ ## get the inputs from inputFile:
+ if input_file and os.path.isfile(input_file):
+ log.debug("processing input file %s" % input_file)
+ with open(input_file) as f:
+ for line in f.readlines():
+ if line.startswith('#'):
+ continue
+ one = line.strip()
+ raw_ip, raw_port = one.rsplit(':', 1) ## XXX not ipv6 safe!
+ yield self.addToDestinations(raw_ip, raw_port)
+ except Exception, ex:
+ log.exception(ex)
+
+ def test_tcp_syn(self):
+ """Send the list of SYN packets."""
+ try:
+ def build_packets(addr, port):
+ """Construct a list of packets to send out."""
+ packets = []
+ for x in xrange(self.count):
+ pkt = IP(dst=addr)/TCP(dport=port, flags="S")
+ packets.append(pkt)
+ return packets
+
+ def sort_nicely(packets):
+ """Print the summary of each packet in a list."""
+ return [pkt.summary() for pkt in packets]
+
+ def tcp_flags(responses):
+ """Print summary of hosts which responded with a SYN/ACK."""
+ for response in responses:
+ layer = response.getlayer('TCP') if response.haslayer('TCP') else None
+ yield layer.sprintf("{TCP:%TCP.flags%}") if layer else None
+
+ def received_syn(responses, flags):
+ yield responses.filter(
+ lambda x: (x for x in responses if str(flags) in ['S','SA']))
+
+ def process_packets(packet_list):
+ results, unanswered = packet_list
+
+ log.debug("RESULTS ARE: %s" % results)
+ log.debug("UNANSWERED: %s" % unanswered)
+
+ for (q, re) in results:
+ request_data = {'summary': q.summary(),
+ 'command': q.command(),
+ 'object': export_object(q),
+ 'hash': q.hashret(),
+ 'display': q.display(),
+ 'sent_time': q.time}
+ response_data = {'summary': r.summary(),
+ 'command': r.command(),
+ 'object': export_object(r)
+ 'hash': r.hashret(),
+ 'src': r['IP'].src,
+ 'flags': r['IP'].flags,
+ 'display': r.display(),
+ 'recv_time': r.time,
+ 'delay': r.time - q.time}
+ if self.verbose:
+ request_data['hexdump'] = q.hexdump()
+ response_data['hexdump'] = r.hexdump()
+
+ result_data = (request_data, response_data)
+
+ flags = tcp_flags(response)
+ for dest, data in self.destinations.items():
+ if data['dst'] == response.src:
+ if not 'response' in data:
+ log.msg("%s" % request.summary())
+ log.msg("%s" % response.summary())
+ data['response'] = [response.summary()]
+ data['reachable'] = True
+ else:
+ data['response'].append(response.summary())
+ if self.verbose:
+ log.msg("%s" % request.summary())
+ log.msg("%s" % response.hexdump())
+
+ for unans in unanswered:
+ process_unanswered(unans)
+
+ #try:
+ # response.make_table(
+ # lambda x:(
+ # (x.src for x in received_syn(response)),
+ # (x.dport for x in request),
+ # (x for x in tcp.flags(response)) )
+ # )
+ #except Exception, ex:
+ # log.exception(ex)
+
+ def process_unanswered(unanswer):
+ """Callback function to process unanswered packets."""
+ log.msg("unanswered packets:\n%s"
+ % sort_nicely(unanswer))
+ self.report['unanswered'] = sort_nicely(unanswer)
+
+ for dest, data in self.destinations.items():
+ if not 'response' in data:
+ log.msg("No reply from %s." % dest)
+ data['response'] = None
+ data['reachable'] = False
+ return unanswer
+
+ (addr, port) = self.input
+ packets = build_packets(addr, port)
+
+ results = []
+
+ d = txscapy.sr(packets, iface=self.interface)
+ d.addCallbacks(process_packets, log.exception)
+ self.report['destinations'] = self.destinations
+ return d
+
+ except Exception, e:
+ log.exception(e)
diff --git a/ooni/runner.py b/ooni/runner.py
index f1321cd..41f3178 100644
--- a/ooni/runner.py
+++ b/ooni/runner.py
@@ -255,7 +255,8 @@ def runTestCases(test_cases, options,
# XXX we probably want to add a log.warn() at some point
log.msg("Problem in running test")
log.exception(ex)
- reactor.stop()
+ if reactor.running:
+ reactor.stop()
oreporter.allDone()
More information about the tor-commits
mailing list