[tor-commits] [ooni-probe/master] Re-ported echo. Fixed parameters not being passed through txscapy.sr to parent methods.

isis at torproject.org isis at torproject.org
Tue Dec 18 05:53:45 UTC 2012


commit 8af6d133c5f48ae516842674602a5198c9dcece8
Author: Isis Lovecruft <isis at torproject.org>
Date:   Fri Nov 16 15:20:49 2012 +0000

    Re-ported echo. Fixed parameters not being passed through txscapy.sr to parent methods.
---
 nettests/bridge_reachability/echo.py |  141 ++++++++++++++++++++++++---------
 ooni/utils/txscapy.py                |   12 ++-
 2 files changed, 110 insertions(+), 43 deletions(-)

diff --git a/nettests/bridge_reachability/echo.py b/nettests/bridge_reachability/echo.py
index df121b4..06c3aa4 100644
--- a/nettests/bridge_reachability/echo.py
+++ b/nettests/bridge_reachability/echo.py
@@ -16,34 +16,39 @@ import os
 import sys
 
 from twisted.python   import usage
-from twisted.internet import reactor, defer
-from ooni.nettest     import NetTestCase
+from twisted.internet import reactor, defer, address
+from ooni             import nettest
 from ooni.utils       import log, net, Storage
 
 try:
-    from scapy.all        import IP, ICMP
-    from scapy.all        import sr1
-    from ooni.lib         import txscapy
-    from ooni.lib.txscapy import txsr, txsend
-    from ooni.templates.scapyt   import BaseScapyTest
-except:
+    from scapy.all          import IP, ICMP
+    from scapy.all          import sr1
+    from ooni.utils         import txscapy
+except Exception, e:
     log.msg("This test requires scapy, see www.secdev.org/projects/scapy")
+    log.exception(e)
 
 class UsageOptions(usage.Options):
+    """
+    Options for EchoTest.
+
+    Note: 'count', 'size', and 'ttl' have yet to be implemented.
+    """
     optParameters = [
         ['dst', 'd', None, 'Host IP to ping'],
         ['file', 'f', None, 'File of list of IPs to ping'],
+        ['pcap', 'p', None, 'Save pcap to this file'],
         ['interface', 'i', None, 'Network interface to use'],
+        ['receive', 'r', True, 'Receive response packets'],
+        ['timeout', 't', 2, 'Seconds to wait if no response', int],
         ['count', 'c', 1, 'Number of packets to send', int],
-        ['size', 's', 56, 'Number of bytes to send in ICMP data field', int],
-        ['ttl', 'l', 25, 'Set the IP Time to Live', int],
-        ['timeout', 't', 2, 'Seconds until timeout if no response', int],
-        ['pcap', 'p', None, 'Save pcap to this file'],
-        ['receive', 'r', True, 'Receive response packets']]
+        ['size', 's', 56, 'Bytes to send in ICMP data field', int],
+        ['ttl', 'l', 25, 'Set the IP Time to Live', int]]
 
-class EchoTest(BaseScapyTest):
+class EchoTest(nettest.NetTestCase):
     """
     Basic ping test. This takes an input file containing one IP or hostname
+    per line.
     """
     name         = 'echo'
     author       = 'Isis Lovecruft <isis at torproject.org>'
@@ -55,6 +60,22 @@ class EchoTest(BaseScapyTest):
     #requiredOptions = ['dst']
 
     def setUp(self, *a, **kw):
+        """
+        Send an ICMP-8 packet to a host IP, and process the response.
+
+        @param timeout:
+            Seconds after sending the last packet to timeout.
+        @param interface:
+            The interface to restrict listening to.
+        @param dst:
+            A single host to ping.
+        @param file:
+            A file of hosts to ping, one per line.
+        @param receive:
+            Whether or not to receive replies. Defaults to True.
+        @param pcap:
+            The file to save packet captures to.
+        """
         self.destinations = {}
 
         if self.localOptions:
@@ -87,11 +108,10 @@ class EchoTest(BaseScapyTest):
                 self.dstProcessor(self.file)
                 for address, details in self.destinations.items():
                     for labels, data in details.items():
-                        if not 'ans' in labels:
+                        if not 'response' in labels:
                             self.dst = details['dst_ip']
         else:
             self.addDest(self.dst)
-        log.debug("self.dst is now: %s" % self.dst)
 
         log.debug("Initialization of %s test completed." % self.name)
 
@@ -107,31 +127,74 @@ class EchoTest(BaseScapyTest):
                         continue
                     self.addDest(line)
 
-    def test_icmp(self):
+    def build_packets(self):
+        """
+        Construct a list of packets to send out.
+        """
+        packets = []
+        for dest, data in self.destinations.items():
+            pkt = IP(dst=dest)/ICMP()
+            packets.append(pkt)
+            ## XXX if a domain was specified, we need a way to check that
+            ## its IP matches the one we're seeing in pkt.src
+            #try:
+            #    address.IPAddress(dest)
+            #except:
+            #    data['dst_ip'] = pkt.dst
+        return packets
 
-        def process_response(pkt, dest):
-            try:
-                ans, unans = pkt
-                if ans:
-                    log.msg("Recieved echo-reply: %s" % pkt.summary())
-                    self.destinations[dest]['ans'] = a.show2()
-                    self.report['response'] = [a.show2() for a in ans]
-                    self.report['censored'] = False
-                else:
-                    log.msg("No reply from %s. Possible censorship event." % dest)
-                    log.debug("Unanswered packets: %s" % unans.summary())
-                    self.report['response'] = [u.show2() for u in unans]
-                    self.report['censored'] = True
-            except Exception, e:
-                log.exception(e)
+    def test_icmp(self):
+        """
+        Send the list of ICMP packets.
 
+        TODO: add end summary progress report for % answered, etc.
+        """
         try:
-            for dest, data in self.destinations.items():
-                reply = txsr(IP(dst=dest)/ICMP(),
-                           iface=self.interface,
-                           retry=self.count,
-                           multi=True,
-                           timeout=self.timeout)
-                process = process_response(reply, dest)
+            def nicely(packets):
+                """Print scapy summary nicely."""
+                return list([x.summary() for x in packets])
+
+            def process_answered((answered, sent)):
+                """Callback function for txscapy.sr()."""
+                self.report['sent'] = nicely(sent)
+                self.report['answered'] = [nicely(ans) for ans in answered]
+
+                for req, resp in answered:
+                    log.msg("Received echo-reply:\n%s" % resp.summary())
+                    for dest, data in self.destinations.items():
+                        if data['dst_ip'] == resp.src:
+                            data['response'] = resp.summary()
+                            data['censored'] = False
+                    for snd in sent:
+                        if snd.dst == resp.src:
+                            answered.remove((req, resp))
+                return (answered, sent)
+
+            def process_unanswered((unanswered, sent)):
+                """
+                Callback function for remaining packets and destinations which
+                do not have an associated response.
+                """
+                if len(unanswered) > 0:
+                    nicer = [nicely(unans) for unans in unanswered]
+                    log.msg("Unanswered/remaining packets:\n%s"
+                            % nicer)
+                    self.report['unanswered'] = nicer
+                for dest, data in self.destinations.items():
+                    if not 'response' in data:
+                        log.msg("No reply from %s. Possible censorship event."
+                                % dest)
+                        data['response'] = None
+                        data['censored'] = True
+                return (unanswered, sent)
+
+            packets = self.build_packets()
+            d = txscapy.sr(packets, iface=self.interface, multi=True)
+            d.addCallback(process_answered)
+            d.addErrback(log.exception)
+            d.addCallback(process_unanswered)
+            d.addErrback(log.exception)
+            self.report['destinations'] = self.destinations
+            return d
         except Exception, e:
             log.exception(e)
diff --git a/ooni/utils/txscapy.py b/ooni/utils/txscapy.py
index 2b108ca..9c33d18 100644
--- a/ooni/utils/txscapy.py
+++ b/ooni/utils/txscapy.py
@@ -1,4 +1,5 @@
-# -*- coding:utf8 -*-
+# -*- coding: utf-8 -*-
+#
 # txscapy
 # *******
 # Here shall go functions related to using scapy with twisted.
@@ -32,7 +33,7 @@ class TXPcapWriter(PcapWriter):
 
 class ScapyProtocol(abstract.FileDescriptor):
     def __init__(self, super_socket=None, 
-            reactor=None, timeout=None, receive=True):
+            reactor=None, timeout=None, receive=True, *a, **kw):
         abstract.FileDescriptor.__init__(self, reactor)
         # By default we use the conf.L3socket
         if not super_socket:
@@ -54,7 +55,10 @@ class ScapyProtocol(abstract.FileDescriptor):
         # This deferred will fire when we have finished sending a receiving packets.
         self.d = defer.Deferred()
         self.debug = False
+
         self.multi = False
+        if kw['multi']:
+            self.multi = kw['multi']
         # XXX this needs to be implemented. It would involve keeping track of
         # the state of the sending via the super socket file descriptor and
         # firing the callback when we have concluded sending. Check out
@@ -127,9 +131,9 @@ class ScapyProtocol(abstract.FileDescriptor):
         self.sendPackets(packets)
         return self.d
 
-def sr(x, filter=None, iface=None, nofilter=0, timeout=None):
+def sr(x, filter=None, iface=None, nofilter=0, timeout=None, *a, **kw):
     super_socket = conf.L3socket(filter=filter, iface=iface, nofilter=nofilter)
-    sp = ScapyProtocol(super_socket=super_socket, timeout=timeout)
+    sp = ScapyProtocol(super_socket=super_socket, timeout=timeout, *a, **kw)
     return sp.startSending(x)
 
 def send(x, filter=None, iface=None, nofilter=0, timeout=None):





More information about the tor-commits mailing list