Add proper per-file copyright notices/licenses and top-level license.
[bluesky.git] / parsetrace / split-trace.py
1 #!/usr/bin/python
2 #
3 # Split a tcpdump trace apart into multiple files, each containing a single TCP
4 # flow.
5
6 import impacket, itertools, pcapy, re, socket, subprocess, sys
7 import impacket.ImpactDecoder, impacket.ImpactPacket
8
9 # Domain names for cloud service providers, whose traces we want to pull out.
10 DOMAINS = ['.amazon.com', '.amazonaws.com', '.core.windows.net',
11            '204.246.', '87.238.']
12
13 # The collection of flows we've seen.  The value associated with each flow is a
14 # sequence number indicating in what order we saw the flows in the trace.
15 flows = {}
16
17 def ip_lookup(host, cache={}):
18     if host not in cache:
19         try:
20             cache[host] = socket.gethostbyaddr(dst)[0]
21         except:
22             cache[host] = host
23     return cache[host]
24
25 # Step 1: Parse the input file and extract a listing of all the flows that we
26 # care about.
27 def handler(header, data):
28     pkt = decoder.decode(data)
29     ip = pkt.child()
30     tcp = ip.child()
31     src = (ip.get_ip_src(), tcp.get_th_sport())
32     dst = (ip.get_ip_dst(), tcp.get_th_dport())
33     flow = tuple(sorted([src, dst],
34                         cmp=lambda x, y: cmp(x[1], y[1]) or cmp(x[0], y[0])))
35     if flow not in flows:
36         flows[flow] = max(itertools.chain(flows.values(), [0])) + 1
37
38 def scan(filename):
39     global decoder
40     p = pcapy.open_offline(filename)
41     p.setfilter(r"ip proto \tcp")
42     assert p.datalink() == pcapy.DLT_EN10MB
43     decoder = impacket.ImpactDecoder.EthDecoder()
44     p.loop(0, handler)
45
46 for file in sys.argv[1:]:
47     print "Scanning %s..." % (file,)
48     scan(file)
49
50     filters = {}
51     for (((dst, dport), (src, sport)), seq) in flows.items():
52         # Filter out to find just the relevant flows.  Right now we want only
53         # flows to port 80 (since both S3/Azure use that as the service port
54         # when unencrypted which is what we use).  We probably ought to apply
55         # another filter on IP address in case there happened to be any other
56         # HTTP flows during the trace capture.
57         if dport != 80: continue
58         name = ip_lookup(dst)
59         matches = False
60         for d in DOMAINS:
61             if name.endswith(d): matches = True
62             if name.startswith(d): matches = True
63         if not matches:
64             print "Host", name, "not recognized, skipping"
65             continue
66
67         filter = "tcp and (host %s and host %s) and (port %d and port %d)" \
68             % (src, dst, sport, dport)
69         filters[seq] = (filter, name)
70
71     n = 0
72     for (_, (filter, name)) in sorted(filters.items()):
73         print "%d: %s" % (n, filter)
74         subprocess.check_call(['tcpdump', '-s0', '-r', file, '-w',
75                                'trace-%03d-%s' % (n, name),
76                                filter])
77         n += 1