[tor-commits] [ooni-probe/master] Do some important refactoring of scapy related functionality

art at torproject.org art at torproject.org
Sat Nov 24 20:35:50 UTC 2012


commit c44cc83cbefa9fcd6ba81bb18a6e52a1762f935f
Author: Arturo Filastò <art at fuffa.org>
Date:   Sat Nov 24 21:32:44 2012 +0100

    Do some important refactoring of scapy related functionality
    * Move functions for detecting the default network interface into txscapy
    * Make sr1 follow the syntax of scapy
    * Add notes as to why the parasitic traceroute test does not work
---
 nettests/bridge_reachability/echo.py |    4 +-
 nettests/core/dnsspoof.py            |    4 +-
 nettests/core/dnstamper.py           |   12 ++--
 nettests/core/parasitictraceroute.py |  117 ++++++++++++++++++----------------
 nettests/core/tcpconnect.py          |    2 +-
 ooni/templates/scapyt.py             |   65 +++++++++++++------
 ooni/utils/net.py                    |   42 +------------
 ooni/utils/txscapy.py                |   75 +++++++++++++---------
 ooniprobe.conf                       |    4 +
 9 files changed, 166 insertions(+), 159 deletions(-)

diff --git a/nettests/bridge_reachability/echo.py b/nettests/bridge_reachability/echo.py
index 40436e7..d4033dd 100644
--- a/nettests/bridge_reachability/echo.py
+++ b/nettests/bridge_reachability/echo.py
@@ -18,7 +18,7 @@ import sys
 from twisted.python   import usage
 from twisted.internet import reactor, defer
 from ooni             import nettest
-from ooni.utils       import log, net, Storage
+from ooni.utils       import log, net, Storage, txscapy
 
 try:
     from scapy.all             import IP, ICMP
@@ -66,7 +66,7 @@ class EchoTest(nettest.NetTestCase):
 
         if not self.interface:
             try:
-                iface = net.getDefaultIface()
+                iface = txscapy.getDefaultIface()
             except Exception, e:
                 log.msg("No network interface specified!")
                 log.err(e)
diff --git a/nettests/core/dnsspoof.py b/nettests/core/dnsspoof.py
index 8f3e6a2..48991ea 100644
--- a/nettests/core/dnsspoof.py
+++ b/nettests/core/dnsspoof.py
@@ -51,7 +51,7 @@ class DNSSpoof(scapyt.ScapyTest):
         question = IP(dst=self.resolverAddr)/UDP()/DNS(rd=1,
                 qd=DNSQR(qtype="A", qclass="IN", qname=self.hostname))
         log.msg("Performing query to %s with %s:%s" % (self.hostname, self.resolverAddr, self.resolverPort))
-        answered, unanswered = yield self.sr1(question)
+        yield self.sr1(question)
 
     @defer.inlineCallbacks
     def test_control_a_lookup(self):
@@ -59,6 +59,6 @@ class DNSSpoof(scapyt.ScapyTest):
                 qd=DNSQR(qtype="A", qclass="IN", qname=self.hostname))
         log.msg("Performing query to %s with %s:%s" % (self.hostname,
             self.controlResolverAddr, self.controlResolverPort))
-        answered, unanswered = yield self.sr1(question)
+        yield self.sr1(question)
 
 
diff --git a/nettests/core/dnstamper.py b/nettests/core/dnstamper.py
index d007d77..967ae26 100644
--- a/nettests/core/dnstamper.py
+++ b/nettests/core/dnstamper.py
@@ -28,8 +28,10 @@ from ooni.utils import log
 class UsageOptions(usage.Options):
     optParameters = [['backend', 'b', '8.8.8.8:53',
                         'The OONI backend that runs the DNS resolver'],
-                     ['testresolvers', 't', None,
-                        'file containing list of DNS resolvers to test against']
+                     ['testresolvers', 'T', None,
+                        'File containing list of DNS resolvers to test against'],
+                     ['testresolver', 't', None,
+                         'Specify a single test resolver to use for testing']
                     ]
 
 class DNSTamperTest(dnst.DNSTest):
@@ -44,12 +46,10 @@ class DNSTamperTest(dnst.DNSTest):
                  'Input file of list of hostnames to attempt to resolve']
 
     usageOptions = UsageOptions
-    requiredOptions = ['backend', 'file', 'testresolvers']
+    requiredOptions = ['backend', 'file']
 
     def setUp(self):
-
         if not self.localOptions['testresolvers']:
-            self.test_resolvers = ['8.8.8.8']
             raise usage.UsageError("You did not specify a file of DNS servers to test!"
                                    "See the '--testresolvers' option.")
 
@@ -68,7 +68,7 @@ class DNSTamperTest(dnst.DNSTest):
         self.report['control_resolver'] = self.control_dns_server
 
     @defer.inlineCallbacks
-    def test_a_queries(self):
+    def test_a_lookup(self):
         """
         We perform an A lookup on the DNS test servers for the domains to be
         tested and an A lookup on the known good DNS server.
diff --git a/nettests/core/parasitictraceroute.py b/nettests/core/parasitictraceroute.py
index 8ea27bc..631c24b 100644
--- a/nettests/core/parasitictraceroute.py
+++ b/nettests/core/parasitictraceroute.py
@@ -13,14 +13,12 @@ from scapy.all import *
 from ooni.utils import log
 
 class UsageOptions(usage.Options):
-    optParameters = [['backend', 'b', '8.8.8.8', 'Test backend to use'],
+    optParameters = [['backend', 'b', 'google.com', 'Test backend to use'],
                     ['timeout', 't', 5, 'The timeout for the traceroute test'],
-                    ['maxttl', 'm', 30, 'The maximum value of ttl to set on packets'],
+                    ['maxttl', 'm', 64, 'The maximum value of ttl to set on packets'],
                     ['dstport', 'd', 80, 'Set the destination port of the traceroute test'],
                     ['srcport', 'p', None, 'Set the source port to a specific value']]
 
-    optFlags = [['randomize','r', 'Randomize the source port']]
-
 class ParasiticalTracerouteTest(scapyt.BaseScapyTest):
     name = "Parasitic TCP Traceroute Test"
     author = "Arturo Filastò"
@@ -32,58 +30,57 @@ class ParasiticalTracerouteTest(scapyt.BaseScapyTest):
         def get_sport():
             if self.localOptions['srcport']:
                 return int(self.localOptions['srcport'])
-            elif self.localOptions['randomize']:
-                return random.randint(1024, 65535)
             else:
-                return 80
-
+                return random.randint(1024, 65535)
         self.get_sport = get_sport
-        self.dport = int(self.localOptions['dstport'])
 
-    def max_ttl_and_timeout(self):
-        max_ttl = int(self.localOptions['maxttl'])
-        timeout = int(self.localOptions['timeout'])
-        self.report['max_ttl'] = max_ttl
-        self.report['timeout'] = timeout
-        return max_ttl, timeout
+        self.dst_ip = socket.gethostbyaddr(self.localOptions['backend'])[2][0]
+
+        self.dport = int(self.localOptions['dstport'])
+        self.max_ttl = int(self.localOptions['maxttl'])
 
     @defer.inlineCallbacks
     def test_parasitic_tcp_traceroute(self):
         """
-        Establishes a TCP stream and send the packets inside of such stream.
-        Requires the backend to respond with an ACK to our SYN packet.
-        """
-        max_ttl, timeout = self.max_ttl_and_timeout()
+        Establishes a TCP stream, then sequentially sends TCP packets with
+        increasing TTL until we reach the ttl of the destination.
+
+        Requires the backend to respond with an ACK to our SYN packet (i.e.
+        the port must be open)
 
+        XXX this currently does not work properly. The problem lies in the fact
+        that we are currently using the scapy layer 3 socket. This socket makes
+        packets received be trapped by the kernel TCP stack, therefore when we
+        send out a SYN and get back a SYN-ACK the kernel stack will reply with
+        a RST because it did not send a SYN.
+
+        The quick fix to this would be to establish a TCP stream using socket
+        calls and then "cannibalizing" the TCP session with scapy.
+
+        The real fix is to make scapy use libpcap instead of raw sockets
+        obviously as we previously did... arg.
+        """
         sport = self.get_sport()
         dport = self.dport
         ipid = int(RandShort())
 
-        packet = IP(dst=self.localOptions['backend'], ttl=max_ttl,
-                id=ipid)/TCP(sport=sport, dport=dport,
-                        flags="S", seq=0)
+        ip_layer = IP(dst=self.dst_ip,
+                id=ipid, ttl=self.max_ttl)
 
-        log.msg("Sending SYN towards %s" % dport)
+        syn = ip_layer/TCP(sport=sport, dport=dport, flags="S", seq=0)
 
-        try:
-            answered, unanswered = yield self.sr(packet, timeout=timeout)
-        except Exception, e:
-            log.exception(e)
-        except:
-            log.exception()
+        log.msg("Sending...")
+        syn.show2()
 
-        try:
-            snd, rcv = answered[0]
-            synack = rcv[0]
+        synack = yield self.sr1(syn)
 
-        except IndexError:
-            print answered, unanswered
+        log.msg("Got response...")
+        synack.show2()
+
+        if not synack:
             log.err("Got no response. Try increasing max_ttl")
             return
 
-        except Exception, e:
-            log.exception(e)
-
         if synack[TCP].flags == 11:
             log.msg("Got back a FIN ACK. The destination port is closed")
             return
@@ -92,33 +89,41 @@ class ParasiticalTracerouteTest(scapyt.BaseScapyTest):
             log.msg("Got a SYN ACK. All is well.")
         else:
             log.err("Got an unexpected result")
+            return
 
-        self.report['hops'] = []
-        for ttl in range(1, max_ttl):
-            log.msg("Sending ACK with ttl %s" % ttl)
-            # We generate an ack for the syn-ack we got with increasing ttl
-            packet = IP(dst=self.localOptions['backend'],
-                    ttl=ttl, id=ipid)/TCP(sport=synack.dport,
+        ack = ip_layer/TCP(sport=synack.dport,
                             dport=dport, flags="A",
                             seq=synack.ack, ack=synack.seq + 1)
 
-            answered, unanswered = yield self.sr(packet, timeout=timeout)
-            try:
-                snd, rcv = answered[0]
-            except IndexError:
-                log.err("Got no response.")
+        yield self.send(ack)
 
-            try:
-                icmp = rcv[ICMP]
+        self.report['hops'] = []
+        # For the time being we make the assumption that we are NATted and
+        # that the NAT will forward the packet to the destination even if the TTL has 
+        for ttl in range(1, self.max_ttl):
+            log.msg("Sending packet with ttl of %s" % ttl)
+            ip_layer.ttl = ttl
+            empty_tcp_packet = ip_layer/TCP(sport=synack.dport,
+                    dport=dport, flags="A",
+                    seq=synack.ack, ack=synack.seq + 1)
+
+            answer = yield self.sr1(empty_tcp_packet)
+            if not answer:
+                log.err("Got no response for ttl %s" % ttl)
+                continue
 
-            except IndexError:
-                report = {'ttl': snd.ttl,
-                        'address': rcv.src,
-                        'rtt': rcv.time - snd.time
+            try:
+                icmp = answer[ICMP]
+                report = {'ttl': empty_tcp_packet.ttl,
+                    'address': answer.src,
+                    'rtt': answer.time - empty_tcp_packet.time
                 }
-                log.debug("%s: %s" % (dport, report))
+                log.msg("%s: %s" % (dport, report))
                 self.report['hops'].append(report)
-                if rcv.src == self.localOptions['backend']:
+
+            except IndexError:
+                if answer.src == self.dst_ip:
+                    answer.show()
                     log.msg("Reached the destination. We have finished the traceroute")
                     return
 
diff --git a/nettests/core/tcpconnect.py b/nettests/core/tcpconnect.py
index b763bf8..d0a53f8 100644
--- a/nettests/core/tcpconnect.py
+++ b/nettests/core/tcpconnect.py
@@ -16,10 +16,10 @@ class TCPConnectTest(nettest.NetTestCase):
     name = "TCP Connect"
     author = "Arturo Filastò"
     version = "0.1"
-
     inputFile = ['file', 'f', None,
             'File containing the IP:PORT combinations to be tested, one per line']
 
+    requiredOptions = ['file']
     def test_connect(self):
         """
         This test performs a TCP connection to the remote host on the specified port.
diff --git a/ooni/templates/scapyt.py b/ooni/templates/scapyt.py
index d1ffb36..11b4381 100644
--- a/ooni/templates/scapyt.py
+++ b/ooni/templates/scapyt.py
@@ -12,13 +12,11 @@ from twisted.internet import protocol, defer, threads
 from scapy.all import send, sr, IP, TCP, config
 
 from ooni.reporter import createPacketReport
-
 from ooni.nettest import NetTestCase
 from ooni.utils import log
-
 from ooni import config
 
-from ooni.utils.txscapy import ScapyProtocol
+from ooni.utils.txscapy import ScapyProtocol, getDefaultIface
 
 class BaseScapyTest(NetTestCase):
     """
@@ -35,9 +33,12 @@ class BaseScapyTest(NetTestCase):
 
     requiresRoot = True
     baseFlags = [
-            ['ipsrc', 's', 'Does *not* check if IP src and ICMP IP citation matches when processing answers'],
-            ['seqack', 'k', 'Check if TCP sequence number and ACK match in the ICMP citation when processing answers'],
-            ['ipid', 'i', 'Check if the IPID matches when processing answers']
+            ['ipsrc', 's',
+                'Does *not* check if IP src and ICMP IP citation matches when processing answers'],
+            ['seqack', 'k',
+                'Check if TCP sequence number and ACK match in the ICMP citation when processing answers'],
+            ['ipid', 'i',
+                'Check if the IPID matches when processing answers']
             ]
 
     def _setUp(self):
@@ -65,15 +66,26 @@ class BaseScapyTest(NetTestCase):
         else:
             config.check_TCPerror_seqack = 0
 
+        if config.advanced.interface == 'auto':
+            self.interface = getDefaultIface()
+        else:
+            self.interface = config.advanced.interface
+
+    def reportSentPacket(self, packet):
+        if 'sent_packets' not in self.report:
+            self.report['sent_packets'] = []
+        self.report['sent_packets'].append(packet)
+
+    def reportReceivedPacket(self, packet):
+        if 'answered_packets' not in self.report:
+            self.report['answered_packets'] = []
+        self.report['answered_packets'].append(packet)
+
     def finishedSendReceive(self, packets):
         """
         This gets called when all packets have been sent and received.
         """
         answered, unanswered = packets
-        if 'answered_packets' not in self.report:
-            self.report['answered_packets'] = []
-        if 'sent_packets' not in self.report:
-            self.report['sent_packets'] = []
 
         for snd, rcv in answered:
             log.debug("Writing report for scapy test")
@@ -86,11 +98,8 @@ class BaseScapyTest(NetTestCase):
                 sent_packet.src = '127.0.0.1'
                 received_packet.dst = '127.0.0.1'
 
-            #pkt_report_r = createPacketReport(received_packet)
-            #pkt_report_s = createPacketReport(sent_packet)
-            self.report['answered_packets'].append(received_packet)
-            self.report['sent_packets'].append(sent_packet)
-            log.debug("Done")
+            self.reportSentPacket(sent_packet)
+            self.reportReceivedPacket(received_packet)
         return packets
 
     def sr(self, packets, *arg, **kw):
@@ -98,25 +107,41 @@ class BaseScapyTest(NetTestCase):
         Wrapper around scapy.sendrecv.sr for sending and receiving of packets
         at layer 3.
         """
-        scapyProtocol = ScapyProtocol(*arg, **kw)
+        scapyProtocol = ScapyProtocol(interface=self.interface, *arg, **kw)
         d = scapyProtocol.startSending(packets)
         d.addCallback(self.finishedSendReceive)
         return d
 
     def sr1(self, packets, *arg, **kw):
-        scapyProtocol = ScapyProtocol(*arg, **kw)
+        def done(packets):
+            """
+            We do this so that the returned value is only the one packet that
+            we expected a response for, identical to the scapy implementation
+            of sr1.
+            """
+            try:
+                return packets[0][0][1]
+            except IndexError:
+                log.err("Got no response...")
+                return None
+
+        scapyProtocol = ScapyProtocol(interface=self.interface, *arg, **kw)
         scapyProtocol.expected_answers = 1
         log.debug("Running sr1")
         d = scapyProtocol.startSending(packets)
         log.debug("Started to send")
         d.addCallback(self.finishedSendReceive)
+        d.addCallback(done)
         return d
 
-    def send(self, pkts, *arg, **kw):
+    def send(self, packets, *arg, **kw):
         """
         Wrapper around scapy.sendrecv.send for sending of packets at layer 3
         """
-        raise Exception("Not implemented")
+        scapyProtocol = ScapyProtocol(interface=self.interface, *arg, **kw)
+        scapyProtocol.sendPackets(packets)
+        scapyProtocol.stopSending()
+        for packet in packets:
+            self.reportSentPacket(packet)
 
 ScapyTest = BaseScapyTest
-
diff --git a/ooni/utils/net.py b/ooni/utils/net.py
index 155abd2..e828977 100644
--- a/ooni/utils/net.py
+++ b/ooni/utils/net.py
@@ -22,13 +22,6 @@ from ooni.utils import log, txscapy
 #if sys.platform.system() == 'Windows':
 #    import _winreg as winreg
 
-PLATFORMS = {'LINUX': sys.platform.startswith("linux"),
-             'OPENBSD': sys.platform.startswith("openbsd"),
-             'FREEBSD': sys.platform.startswith("freebsd"),
-             'NETBSD': sys.platform.startswith("netbsd"),
-             'DARWIN': sys.platform.startswith("darwin"),
-             'SOLARIS': sys.platform.startswith("sunos"),
-             'WINDOWS': sys.platform.startswith("win32")}
 
 userAgents = [
     ("Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6", "Firefox 2.0, Windows XP"),
@@ -44,7 +37,6 @@ userAgents = [
     ("Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20060127 Netscape/8.1", "Netscape 8.1, Windows XP")
     ]
 
-
 class UnsupportedPlatform(Exception):
     """Support for this platform is not currently available."""
 
@@ -54,7 +46,6 @@ class IfaceError(Exception):
 class PermissionsError(SystemExit):
     """This test requires admin or root privileges to run. Exiting..."""
 
-
 PLATFORMS = {'LINUX': sys.platform.startswith("linux"),
              'OPENBSD': sys.platform.startswith("openbsd"),
              'FREEBSD': sys.platform.startswith("freebsd"),
@@ -63,14 +54,6 @@ PLATFORMS = {'LINUX': sys.platform.startswith("linux"),
              'SOLARIS': sys.platform.startswith("sunos"),
              'WINDOWS': sys.platform.startswith("win32")}
 
-class UnsupportedPlatform(Exception):
-    """Support for this platform is not currently available."""
-
-class IfaceError(Exception):
-    """Could not find default network interface."""
-
-class PermissionsError(SystemExit):
-    """This test requires admin or root privileges to run. Exiting..."""
 
 class StringProducer(object):
     implements(IBodyProducer)
@@ -128,7 +111,6 @@ def getSystemResolver():
     XXX implement a function that returns the resolver that is currently
     default on the system.
     """
-    pass
 
 def getClientPlatform(platform_name=None):
     for name, test in PLATFORMS.items():
@@ -225,30 +207,8 @@ def getNonLoopbackIfaces(platform_name=None):
         else:
             return interfaces
 
-def getNetworksFromRoutes():
-    from scapy.all import conf, ltoa, read_routes
-    from ipaddr    import IPNetwork, IPAddress
-
-    ## Hide the 'no routes' warnings
-    conf.verb = 0
-
-    networks = []
-    for nw, nm, gw, iface, addr in read_routes():
-        n = IPNetwork( ltoa(nw) )
-        (n.netmask, n.gateway, n.ipaddr) = [IPAddress(x) for x in [nm, gw, addr]]
-        n.iface = iface
-        if not n.compressed in networks:
-            networks.append(n)
-
-    return networks
-
-def getDefaultIface():
-    networks = getNetworksFromRoutes()
-    for net in networks:
-        if net.is_private:
-            return net.iface
-    raise IfaceError
 
 def getLocalAddress():
     default_iface = getDefaultIface()
     return default_iface.ipaddr
+
diff --git a/ooni/utils/txscapy.py b/ooni/utils/txscapy.py
index 4341f68..e41d649 100644
--- a/ooni/utils/txscapy.py
+++ b/ooni/utils/txscapy.py
@@ -17,28 +17,59 @@ from twisted.internet import reactor, threads, error
 from twisted.internet import defer, abstract
 from zope.interface import implements
 
-
 from scapy.all import PcapWriter, MTU
 from scapy.all import BasePacketList, conf, PcapReader
 
 from scapy.all import conf, Gen, SetGen
+from scapy.arch import pcapdnet
 
 from ooni.utils import log
 
+conf.use_pcap = True
+conf.use_dnet = True
+
+def getNetworksFromRoutes():
+    from scapy.all import conf, ltoa, read_routes
+    from ipaddr    import IPNetwork, IPAddress
+
+    ## Hide the 'no routes' warnings
+    conf.verb = 0
+
+    networks = []
+    for nw, nm, gw, iface, addr in read_routes():
+        n = IPNetwork( ltoa(nw) )
+        (n.netmask, n.gateway, n.ipaddr) = [IPAddress(x) for x in [nm, gw, addr]]
+        n.iface = iface
+        if not n.compressed in networks:
+            networks.append(n)
+
+    return networks
+
+def getDefaultIface():
+    networks = getNetworksFromRoutes()
+    for net in networks:
+        if net.is_private:
+            return net.iface
+    raise IfaceError
+
 class TXPcapWriter(PcapWriter):
     def __init__(self, *arg, **kw):
         PcapWriter.__init__(self, *arg, **kw)
         fdesc.setNonBlocking(self.f)
 
 class ScapyProtocol(abstract.FileDescriptor):
-    def __init__(self, super_socket=None, 
-            reactor=None, timeout=4, receive=True):
+    def __init__(self, interface, super_socket=None, timeout=5):
         abstract.FileDescriptor.__init__(self, reactor)
         # By default we use the conf.L3socket
         if not super_socket:
-            super_socket = conf.L3socket()
+            super_socket = pcapdnet.L3dnetSocket(iface=interface)
+        print super_socket
+        log.msg("Creating layer 3 socket with interface %s" % interface)
+
+        #fdesc._setCloseOnExec(super_socket.ins.fileno())
         self.super_socket = super_socket
 
+        self.interface = interface
         self.timeout = timeout
 
         # This dict is used to store the unique hashes that allow scapy to
@@ -53,13 +84,8 @@ class ScapyProtocol(abstract.FileDescriptor):
 
         # This deferred will fire when we have finished sending a receiving packets.
         self.d = defer.Deferred()
-        self.debug = False
+        # Should we look for multiple answers for the same sent packet?
         self.multi = False
-        # 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
-        # twisted.internet.udp to see how this is done.
-        self.receive = receive
 
         # When 0 we stop when all the packets we have sent have received an
         # answer
@@ -85,7 +111,6 @@ class ScapyProtocol(abstract.FileDescriptor):
 
         if len(self.answered_packets) == len(self.sent_packets):
             log.debug("All of our questions have been answered.")
-            log.debug("%s" % self.__hash__)
             self.stopSending()
             return
 
@@ -94,12 +119,11 @@ class ScapyProtocol(abstract.FileDescriptor):
             log.debug("Got the number of expected answers")
             self.stopSending()
 
-
     def doRead(self):
         timeout = time.time() - self._start_time
         if self.timeout and time.time() - self._start_time > self.timeout:
             self.stopSending()
-        packet = self.super_socket.recv()
+        packet = self.super_socket.recv(MTU)
         if packet:
             self.processPacket(packet)
             # A string that has the same value for the request than for the
@@ -110,7 +134,6 @@ class ScapyProtocol(abstract.FileDescriptor):
                 self.processAnswer(packet, answer_hr)
 
     def stopSending(self):
-        log.debug("Stopping sending")
         self.stopReading()
         self.super_socket.close()
         if hasattr(self, "d"):
@@ -120,20 +143,20 @@ class ScapyProtocol(abstract.FileDescriptor):
 
     def write(self, packet):
         """
-        Write a scapy packet to the wire
+        Write a scapy packet to the wire.
         """
-        hashret = packet.hashret()
-        if hashret in self.hr_sent_packets:
-            self.hr_sent_packets[hashret].append(packet)
-        else:
-            self.hr_sent_packets[hashret] = [packet]
-        self.sent_packets.append(packet)
         return self.super_socket.send(packet)
 
     def sendPackets(self, packets):
         if not isinstance(packets, Gen):
             packets = SetGen(packets)
         for packet in packets:
+            hashret = packet.hashret()
+            if hashret in self.hr_sent_packets:
+                self.hr_sent_packets[hashret].append(packet)
+            else:
+                self.hr_sent_packets[hashret] = [packet]
+            self.sent_packets.append(packet)
             self.write(packet)
 
     def startSending(self, packets):
@@ -142,14 +165,4 @@ class ScapyProtocol(abstract.FileDescriptor):
         self.sendPackets(packets)
         return self.d
 
-def sr(x, filter=None, iface=None, nofilter=0, timeout=None):
-    super_socket = conf.L3socket(filter=filter, iface=iface, nofilter=nofilter)
-    sp = ScapyProtocol(super_socket=super_socket, timeout=timeout)
-    return sp.startSending(x)
-
-def send(x, filter=None, iface=None, nofilter=0, timeout=None):
-    super_socket = conf.L3socket(filter=filter, iface=iface, nofilter=nofilter)
-    sp = ScapyProtocol(super_socket=super_socket, timeout=timeout)
-    return sp.startSending(x)
-
 
diff --git a/ooniprobe.conf b/ooniprobe.conf
index 1998c93..e9f208f 100644
--- a/ooniprobe.conf
+++ b/ooniprobe.conf
@@ -25,4 +25,8 @@ advanced:
     debug: true
     threadpool_size: 10
     tor_socksport: 9050
+    # For auto detection
+    interface: auto 
+    # Of specify a specific interface
+    #interface: wlan0
 



More information about the tor-commits mailing list