[tor-commits] [ooni-probe/master] Add a ScapyTraceroute ScapyProtocol
art at torproject.org
art at torproject.org
Wed Mar 12 21:49:22 UTC 2014
commit deb507f97825bba597478053d9fc130a4f8f2df3
Author: aagbsn <aagbsn at extc.org>
Date: Mon Jan 20 22:46:23 2014 +0000
Add a ScapyTraceroute ScapyProtocol
This protocol handles creating and matching packets for conducting
ICMP, TCP, and UDP multi-port traceroute.
---
ooni/nettests/manipulation/traceroute.py | 149 ++++++++++--------------------
ooni/reporter.py | 2 +-
ooni/utils/txscapy.py | 94 +++++++++++++++++++
3 files changed, 145 insertions(+), 100 deletions(-)
diff --git a/ooni/nettests/manipulation/traceroute.py b/ooni/nettests/manipulation/traceroute.py
index 7639060..b970127 100644
--- a/ooni/nettests/manipulation/traceroute.py
+++ b/ooni/nettests/manipulation/traceroute.py
@@ -4,13 +4,16 @@
# :licence: see LICENSE
from twisted.python import usage
-from twisted.internet import defer
+from twisted.internet import defer, reactor
from ooni.templates import scapyt
+from itertools import chain
from scapy.all import *
from ooni.utils import log
+from ooni.utils.txscapy import ScapyTraceroute
+from ooni.settings import config
class UsageOptions(usage.Options):
optParameters = [
@@ -23,113 +26,61 @@ class UsageOptions(usage.Options):
class TracerouteTest(scapyt.BaseScapyTest):
name = "Multi Protocol Traceroute Test"
description = "Performs a UDP, TCP, ICMP traceroute with destination port number set to 0, 22, 23, 53, 80, 123, 443, 8080 and 65535"
- author = "Arturo Filastò"
- version = "0.2"
-
requiredTestHelpers = {'backend': 'traceroute'}
usageOptions = UsageOptions
dst_ports = [0, 22, 23, 53, 80, 123, 443, 8080, 65535]
+ timeout = 5
def setUp(self):
- def get_sport(protocol):
- if self.localOptions['srcport']:
- return int(self.localOptions['srcport'])
- else:
- return random.randint(1024, 65535)
-
- self.get_sport = get_sport
- self.report['test_tcp_traceroute'] = {}
- self.report['test_udp_traceroute'] = {}
- self.report['test_icmp_traceroute'] = {}
+ self.st = ScapyTraceroute()
+ if self.localOptions['maxttl']:
+ self.st.ttl_max = int(self.localOptions['maxttl'])
+ config.scapyFactory.registerProtocol(self.st)
+ self.done = defer.Deferred()
+ self.tcp = self.udp = self.icmp = None
- 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
+ def test_icmp_traceroute(self):
+ self.st.ICMPTraceroute(self.localOptions['backend'])
+ d = defer.Deferred()
+ reactor.callLater(self.timeout, d.callback, self.st)
+ return d
def test_tcp_traceroute(self):
- """
- Does a traceroute to the destination by sending TCP SYN packets
- with TTLs from 1 until max_ttl.
- """
- def finished(packets, port):
- log.msg("Finished running TCP traceroute test on port %s" % port)
- answered, unanswered = packets
- self.report['test_tcp_traceroute']['hops_'+str(port)] = []
- for snd, rcv in answered:
- report = {'ttl': snd.ttl,
- 'address': rcv.src,
- 'rtt': rcv.time - snd.time,
- 'sport': snd[TCP].sport
- }
- log.msg("%s: %s" % (port, report))
- self.report['test_tcp_traceroute']['hops_'+str(port)].append(report)
-
- dl = []
- max_ttl, timeout = self.max_ttl_and_timeout()
- for port in self.dst_ports:
- packets = IP(dst=self.localOptions['backend'],
- ttl=(1,max_ttl),id=RandShort())/TCP(flags=0x2, dport=port,
- sport=self.get_sport('tcp'))
-
- d = self.sr(packets, timeout=timeout)
- d.addCallback(finished, port)
- dl.append(d)
- return defer.DeferredList(dl)
+ self.st.TCPTraceroute(self.localOptions['backend'])
+ d = defer.Deferred()
+ reactor.callLater(self.timeout, d.callback, self.st)
+ return d
def test_udp_traceroute(self):
- """
- Does a traceroute to the destination by sending UDP packets with empty
- payloads with TTLs from 1 until max_ttl.
- """
- def finished(packets, port):
- log.msg("Finished running UDP traceroute test on port %s" % port)
- answered, unanswered = packets
- self.report['test_udp_traceroute']['hops_'+str(port)] = []
- for snd, rcv in answered:
- report = {'ttl': snd.ttl,
- 'address': rcv.src,
- 'rtt': rcv.time - snd.time,
- 'sport': snd[UDP].sport
- }
- log.msg("%s: %s" % (port, report))
- self.report['test_udp_traceroute']['hops_'+str(port)].append(report)
- dl = []
- max_ttl, timeout = self.max_ttl_and_timeout()
- for port in self.dst_ports:
- packets = IP(dst=self.localOptions['backend'],
- ttl=(1,max_ttl),id=RandShort())/UDP(dport=port,
- sport=self.get_sport('udp'))
-
- d = self.sr(packets, timeout=timeout)
- d.addCallback(finished, port)
- dl.append(d)
- return defer.DeferredList(dl)
-
- def test_icmp_traceroute(self):
- """
- Does a traceroute to the destination by sending ICMP echo request
- packets with TTLs from 1 until max_ttl.
- """
- def finished(packets):
- log.msg("Finished running ICMP traceroute test")
- answered, unanswered = packets
- self.report['test_icmp_traceroute']['hops'] = []
- for snd, rcv in answered:
- report = {'ttl': snd.ttl,
- 'address': rcv.src,
- 'rtt': rcv.time - snd.time
- }
- log.msg("%s" % (report))
- self.report['test_icmp_traceroute']['hops'].append(report)
- dl = []
- max_ttl, timeout = self.max_ttl_and_timeout()
- packets = IP(dst=self.localOptions['backend'],
- ttl=(1,max_ttl), id=RandShort())/ICMP()
-
- d = self.sr(packets, timeout=timeout)
- d.addCallback(finished)
+ self.st.UDPTraceroute(self.localOptions['backend'])
+ d = defer.Deferred()
+ reactor.callLater(self.timeout, d.callback, self.st)
return d
+ def postProcessor(self, measurements):
+ # should be called after all deferreds have calledback
+ self.st.stopListening()
+ self.st.matchResponses()
+
+ if measurements[0][1].result == self.st:
+ for packet in self.st.sent_packets:
+ self.report['sent_packets'].append(packet)
+ self.report['answered_packets'] = self.st.matched_packets.items()
+ self.report['received_packets'] = self.st.received_packets.values()
+
+ # display responses by hop:
+ self.report['hops'] = {}
+ for i in xrange(self.st.ttl_min, self.st.ttl_max):
+ self.report['hops'][i] = []
+ matchedPackets = filter(lambda x: x.ttl == i, self.st.matched_packets.keys())
+ routers = {}
+ for packet in matchedPackets:
+ for pkt in self.st.matched_packets[packet]:
+ router = pkt.src
+ if router in routers:
+ routers[router].append(pkt)
+ else:
+ routers[router] = [pkt]
+ for router in routers.keys():
+ self.report['hops'][i].append(router)
+ return self.report
diff --git a/ooni/reporter.py b/ooni/reporter.py
index 545997d..e976e5c 100644
--- a/ooni/reporter.py
+++ b/ooni/reporter.py
@@ -50,7 +50,7 @@ def createPacketReport(packet_list):
report = []
for packet in packet_list:
report.append({'raw_packet': str(packet),
- 'summary': str(packet.summary())})
+ 'summary': str([packet])})
return report
class OSafeRepresenter(SafeRepresenter):
diff --git a/ooni/utils/txscapy.py b/ooni/utils/txscapy.py
index f9b7dfd..9e899c8 100644
--- a/ooni/utils/txscapy.py
+++ b/ooni/utils/txscapy.py
@@ -3,6 +3,7 @@ import socket
import os
import sys
import time
+import random
from twisted.internet import protocol, base, fdesc
from twisted.internet import reactor, threads, error
@@ -11,6 +12,8 @@ from zope.interface import implements
from scapy.config import conf
from scapy.supersocket import L3RawSocket
+from scapy.all import RandShort, IP, IPerror, ICMP, ICMPerror
+from scapy.all import TCP, TCPerror, UDP, UDPerror
from ooni.utils import log
from ooni.settings import config
@@ -285,3 +288,94 @@ class ScapySniffer(ScapyProtocol):
def packetReceived(self, packet):
self.pcapwriter.write(packet)
+class ScapyTraceroute(ScapyProtocol):
+ dst_ports = [0, 22, 23, 53, 80, 123, 443, 8080, 65535]
+ ttl_min = 1
+ ttl_max = 30
+
+ def __init__(self):
+ self.sent_packets = []
+ self.received_packets = {}
+ self.matched_packets = {}
+ self.hosts = []
+
+ def ICMPTraceroute(self, host):
+ if host not in self.hosts: self.hosts.append(host)
+ self.sendPackets(IP(dst=host,ttl=(self.ttl_min,self.ttl_max), id=RandShort())/ICMP(id=RandShort()))
+
+ def UDPTraceroute(self, host):
+ if host not in self.hosts: self.hosts.append(host)
+ for dst_port in self.dst_ports:
+ self.sendPackets(IP(dst=host,ttl=(self.ttl_min,self.ttl_max), id=RandShort())/UDP(dport=dst_port, sport=RandShort()))
+
+ def TCPTraceroute(self, host):
+ if host not in self.hosts: self.hosts.append(host)
+ for dst_port in self.dst_ports:
+ self.sendPackets(IP(dst=host,ttl=(self.ttl_min,self.ttl_max), id=RandShort())/TCP(flags=2L, dport=dst_port, sport=RandShort(), seq=RandShort()))
+
+ def sendPackets(self, packets):
+ #if random.randint(0,1):
+ # random.shuffle(packets)
+ for packet in packets:
+ self.sent_packets.append(packet)
+ self.factory.super_socket.send(packet)
+
+ def matchResponses(self):
+ def _pe(k, p):
+ if k in self.received_packets:
+ if p in self.matched_packets:
+ log.debug("Matched sent packet to more than one response!")
+ self.matched_packets[p].extend(self.received_packets[k])
+ else:
+ self.matched_packets[p] = self.received_packets[k]
+ log.debug("Packet %s matched %s" % ([p], self.received_packets[k]))
+ return 1
+ return 0
+
+ for p in self.sent_packets:
+ # for each sent packet, find corresponding
+ # received packets
+ l = p.getlayer(1)
+ i = 0
+ if isinstance(l, ICMP):
+ i += _pe((ICMP, p.id), p) # match by ipid
+ i += _pe((ICMP, l.id), p) # match by icmpid
+ if isinstance(l, TCP):
+ i += _pe((TCP, p.id), p) # match by ipid
+ i += _pe((TCP, p.id, l.seq, l.ack, l.sport, l.dport), p)
+ if isinstance(l, UDP):
+ i += _pe((UDP, p.id), p)
+ if i == 0:
+ log.debug("No response for packet %s" % [p])
+
+ def packetReceived(self, packet):
+ def _ae(k, p):
+ if k in self.received_packets:
+ self.received_packets[k].append(p)
+ else:
+ self.received_packets[k] = [p]
+
+ l = packet.getlayer(2)
+ try:
+ if isinstance(l, IPerror):
+ pid = l.id
+ l = packet.getlayer(3)
+ if isinstance(l, ICMPerror):
+ _ae((ICMP, pid), packet)
+ elif isinstance(l, TCPerror):
+ _ae((TCP, pid, l.seq, l.ack, l.sport, l.dport), packet)
+ elif isinstance(l, UDPerror):
+ _ae((UDP, pid), packet)
+ elif packet.src in self.hosts:
+ l = packet.getlayer(1)
+ if isinstance(l, ICMP):
+ _ae((ICMP, l.id), packet)
+ elif isinstance(l, TCP):
+ _ae((TCP, l.seq, l.ack, l.sport, l.dport), packet)
+ elif isinstance(l, UDP):
+ _ae((UDP, l.sport, l.dport), packet)
+ except Exception, e:
+ import pdb;pdb.set_trace()
+
+ def stopListening(self):
+ self.factory.unRegisterProtocol(self)
More information about the tor-commits
mailing list