Merge branch 'master' of https://github.uci.edu/rtrimana/smart_home_traffic
[pingpong.git] / origin / CAPture.py
1 #!/usr/local/bin/python2.7
2
3 """ -----------------------------------------------------------------------------
4     CAPture - a pcap file analyzer and report generator
5     (c) 2017 - Rahmadi Trimananda
6     University of California, Irvine - Programming Language and Systems
7     -----------------------------------------------------------------------------
8     Credits to tutorial: https://dpkt.readthedocs.io/en/latest/
9     -----------------------------------------------------------------------------
10 """ 
11
12 import datetime
13 import dpkt
14 from dpkt.compat import compat_ord
15
16 import socket
17 import sys
18
19 """ -----------------------------------------------------------------------------
20     Global variable declarations
21     -----------------------------------------------------------------------------
22 """
23 # Command line arguments
24 INPUT = "-i"
25 OUTPUT = "-o"
26 POINT_TO_MANY = "-pm"
27 VERBOSE = "-v"
28
29
30 def mac_addr(address):
31     # Courtesy of: https://dpkt.readthedocs.io/en/latest/
32     """ Convert a MAC address to a readable/printable string
33         Args:
34             address (str): a MAC address in hex form (e.g. '\x01\x02\x03\x04\x05\x06')
35         Returns:
36             str: Printable/readable MAC address
37     """
38     return ':'.join('%02x' % compat_ord(b) for b in address)
39
40
41 def inet_to_str(inet):
42     # Courtesy of: https://dpkt.readthedocs.io/en/latest/
43     """ Convert inet object to a string
44         Args:
45             inet (inet struct): inet network address
46         Returns:
47             str: Printable/readable IP address
48     """
49     # First try ipv4 and then ipv6
50     try:
51         return socket.inet_ntop(socket.AF_INET, inet)
52     except ValueError:
53         return socket.inet_ntop(socket.AF_INET6, inet)
54
55
56 def show_usage():
57     """ Show usage of this Python script 
58     """
59     print "Usage: python CAPture.py [ -i <file-name>.pcap ] [ -o <file-name>.pcap ] [ -pm ] [ -v ]"
60     print
61     print "[ -o ]  = output file"
62     print "[ -pm ] = point-to-many analysis"
63     print "[ -v ]  = verbose output"
64     print "By default, this script does simple statistical analysis of IP, TCP, and UDP packets."
65     print "(c) 2017 - University of California, Irvine - Programming Language and Systems"
66
67
68 def show_progress(verbose, counter):
69     """ Show packet processing progress
70         Args:
71             verbose: verbose output (True/False)
72             counter: counter of all packets
73     """
74     if verbose:
75         print "Processing packet number: ", counter
76     else:
77         if counter % 100000 == 0:
78             print "Processing %s packets..." % counter
79
80
81 def show_summary(counter, ip_counter, tcp_counter, udp_counter):
82     """ Show summary of statistics of PCAP file
83         Args:
84             counter: counter of all packets
85             ip_counter: counter of all IP packets
86             tcp_counter: counter of all TCP packets
87             udp_counter: counter of all UDP packets
88     """
89     print
90     print "Total number of packets in the pcap file: ", counter
91     print "Total number of ip packets: ", ip_counter
92     print "Total number of tcp packets: ", tcp_counter
93     print "Total number of udp packets: ", udp_counter
94     print
95
96
97 def save_to_file(tbl_header, dictionary, filename_out):
98     """ Show summary of statistics of PCAP file
99         Args:
100             tbl_header: header for the saved table
101             dictionary: dictionary to be saved
102             filename_out: file name to save
103     """
104     # Appending, not overwriting!
105     f = open(filename_out, 'a')
106     # Write the table header
107     f.write("\n\n" + str(tbl_header) + "\n");
108     # Iterate over dictionary and write (key, value) pairs
109     for key, value in dictionary.iteritems():
110         f.write(str(key) + ", " + str(value) + "\n")
111
112     f.close()
113     print "Writing output to file: ", filename_out
114
115
116 def statistical_analysis(verbose, pcap, counter, ip_counter, tcp_counter, udp_counter):
117     """ This is the default analysis of packet statistics (generic)
118         Args:
119             verbose: verbose output (True/False)
120             pcap: object that handles PCAP file content
121             counter: counter of all packets
122             ip_counter: counter of all IP packets
123             tcp_counter: counter of all TCP packets
124             udp_counter: counter of all UDP packets
125     """
126     for time_stamp, packet in pcap:
127
128         counter += 1
129         eth = dpkt.ethernet.Ethernet(packet)
130
131         if verbose:
132             # Print out the timestamp in UTC
133             print "Timestamp: ", str(datetime.datetime.utcfromtimestamp(time_stamp))
134             # Print out the MAC addresses
135             print "Ethernet frame: ", mac_addr(eth.src), mac_addr(eth.dst), eth.data.__class__.__name__
136
137         # Process only IP data
138         if not isinstance(eth.data, dpkt.ip.IP):
139
140             is_ip = False
141             if verbose:
142                 print "Non IP packet type not analyzed... skipping..."
143         else:
144             is_ip = True
145
146         if is_ip:
147             ip = eth.data
148             ip_counter += 1
149
150             # Pull out fragment information (flags and offset all packed into off field, so use bitmasks)
151             do_not_fragment = bool(ip.off & dpkt.ip.IP_DF)
152             more_fragments = bool(ip.off & dpkt.ip.IP_MF)
153             fragment_offset = ip.off & dpkt.ip.IP_OFFMASK
154
155             if verbose:
156                 # Print out the complete IP information
157                 print "IP: %s -> %s   (len=%d ttl=%d DF=%d MF=%d offset=%d)\n" % \
158                     (inet_to_str(ip.src), inet_to_str(ip.dst), ip.len, ip.ttl, do_not_fragment, 
159                      more_fragments, fragment_offset)
160
161             # Count TCP packets
162             if ip.p == dpkt.ip.IP_PROTO_TCP:  
163                 tcp_counter += 1
164
165             # Count UDP packets
166             if ip.p == dpkt.ip.IP_PROTO_UDP:
167                 udp_counter += 1
168
169         show_progress(verbose, counter)
170
171     # Print general statistics
172     show_summary(counter, ip_counter, tcp_counter, udp_counter)
173
174
175 def point_to_many_analysis(filename_out, dev_add, verbose, pcap, counter, ip_counter, 
176         tcp_counter, udp_counter):
177     """ This analysis presents how 1 device (MAC address or IP address) communicates
178         to every other device in the analyzed PCAP file.
179         Args:
180             dev_add: device address (MAC or IP address)
181             verbose: verbose output (True/False)
182             pcap: object that handles PCAP file content
183             counter: counter of all packets
184             ip_counter: counter of all IP packets
185             tcp_counter: counter of all TCP packets
186             udp_counter: counter of all UDP packets
187     """
188     # Dictionary that preserves the mapping between destination address to frequency
189     mac2freq = dict()
190     ip2freq = dict()
191     for time_stamp, packet in pcap:
192
193         counter += 1
194         eth = dpkt.ethernet.Ethernet(packet)
195
196         # Save the timestamp and MAC addresses
197         tstamp = str(datetime.datetime.utcfromtimestamp(time_stamp))
198         mac_src = mac_addr(eth.src)
199         mac_dst = mac_addr(eth.dst)
200
201         # Process only IP data
202         if not isinstance(eth.data, dpkt.ip.IP):
203
204             is_ip = False
205             if verbose:
206                 print "Non IP packet type not analyzed... skipping..."
207                 print 
208         else:
209             is_ip = True
210
211         if is_ip:
212             ip = eth.data
213             ip_counter += 1
214
215             # Pull out fragment information (flags and offset all packed into off field, so use bitmasks)
216             do_not_fragment = bool(ip.off & dpkt.ip.IP_DF)
217             more_fragments = bool(ip.off & dpkt.ip.IP_MF)
218             fragment_offset = ip.off & dpkt.ip.IP_OFFMASK
219             
220             # Save IP addresses
221             ip_src = inet_to_str(ip.src)
222             ip_dst = inet_to_str(ip.dst)
223
224             if verbose:
225                 # Print out the complete IP information
226                 print "IP: %s -> %s   (len=%d ttl=%d DF=%d MF=%d offset=%d)\n" % \
227                     (ip_src, ip_dst, ip.len, ip.ttl, do_not_fragment, 
228                      more_fragments, fragment_offset)
229
230             # Categorize packets based on source device address
231             # Save the destination device addresses (point-to-many)
232             if dev_add == ip_src:
233                 if ip_dst in ip2freq:
234                     freq = ip2freq[ip_dst]
235                     ip2freq[ip_dst] = freq + 1
236                 else:
237                     ip2freq[ip_dst] = 1
238
239             if dev_add == mac_src:
240                 if mac_dst in ip2freq:
241                     freq = mac2freq[mac_dst]
242                     mac2freq[mac_dst] = freq + 1
243                 else:
244                     mac2freq[mac_dst] = 1
245
246             # Count TCP packets
247             if ip.p == dpkt.ip.IP_PROTO_TCP:  
248                 tcp_counter += 1
249
250             # Count UDP packets
251             if ip.p == dpkt.ip.IP_PROTO_UDP:
252                 udp_counter += 1
253
254         show_progress(verbose, counter)
255
256     # Print general statistics
257     show_summary(counter, ip_counter, tcp_counter, udp_counter)
258     # Save results into file if filename_out is not empty
259     if not filename_out == "":
260         print "Saving results into file: ", filename_out
261         ip_tbl_header = "Point-to-many Analysis - IP destinations for " + dev_add
262         mac_tbl_header = "Point-to-many Analysis - MAC destinations for " + dev_add
263         save_to_file(ip_tbl_header, ip2freq, filename_out)
264         save_to_file(mac_tbl_header, mac2freq, filename_out)
265     else:
266         print "Output file name is not specified... exitting now!"
267
268
269 def parse_cli_args(argv):
270     """ Parse command line arguments and store them in a dictionary
271         Args:
272             argv: list of command line arguments and their values
273         Returns:
274             str: dictionary that maps arguments to their values
275     """
276     options = dict()
277     # First argument is "CAPture.py", so skip it
278     argv = argv[1:]
279     # Loop and collect arguments and their values
280     while argv:
281         print "Examining argument: ", argv[0]
282         # Check the first character of each argv list
283         # If it is a '-' then it is a command line argument
284         if argv[0][0] == '-':
285             if argv[0] == VERBOSE:
286                 # We don't have value for the argument VERBOSE
287                 options[argv[0]] = argv[0]
288                 # Remove one command line argument and its value
289                 argv = argv[1:]
290             else:
291                 options[argv[0]] = argv[1]
292                 # Remove one command line argument and its value
293                 argv = argv[2:]
294
295     return options
296
297
298 """ -----------------------------------------------------------------------------
299     Main Running Methods
300     -----------------------------------------------------------------------------
301 """ 
302 def main():
303     # Variable declarations
304     global CAP_EXTENSION
305     global PCAP_EXTENSION
306     global VERBOSE
307     global POINT_TO_MANY
308
309     # Counters
310     counter = 0
311     ip_counter = 0
312     tcp_counter = 0
313     udp_counter = 0
314     # Booleans as flags
315     verbose = False
316     is_ip = True
317     is_statistical_analysis = True
318     is_point_to_many_analysis = False
319     # Names
320     filename_in = ""
321     filename_out = ""
322     dev_add = ""
323
324     # Welcome message
325     print
326     print "Welcome to CAPture version 1.0 - A PCAP file instant analyzer!"
327
328     # Get file name from user input
329     # Show usage if file name is not specified (only accept 1 file name for now)
330     if len(sys.argv) < 2:
331         show_usage()
332         print
333         return
334
335     # Check and process sys.argv
336     options = parse_cli_args(sys.argv)
337     for key, value in options.iteritems():
338         # Process "-i" - input PCAP file
339         if key == INPUT:
340             filename_in = value
341         elif key == OUTPUT:
342             filename_out = value
343         elif key == VERBOSE:
344             verbose = True
345         elif key == POINT_TO_MANY:
346             is_statistical_analysis = False
347             is_point_to_many_analysis = True
348             dev_add = value
349
350     # Show manual again if input is not correct
351     if filename_in == "":
352         print "File name is empty!"
353         print
354         show_usage()
355         print
356         return
357
358     # dev_add is needed for these analyses
359     if is_point_to_many_analysis and dev_add == "":
360         print "Device address is empty!"
361         print
362         show_usage()
363         print
364         return
365
366     # One PCAP file name is specified - now analyze!
367     print "Analyzing PCAP file: ", filename_in
368
369     # Opening and analyzing PCAP file
370     f = open(filename_in,'rb')
371     pcap = dpkt.pcap.Reader(f)
372
373     # Choose from the existing options
374     if is_statistical_analysis:
375         statistical_analysis(verbose, pcap, counter, ip_counter, tcp_counter, udp_counter)
376     elif is_point_to_many_analysis:
377         point_to_many_analysis(filename_out, dev_add, verbose, pcap, counter, ip_counter, 
378                                tcp_counter, udp_counter)
379
380
381 if __name__ == "__main__":
382     # call main function since this is being run as the start
383     main()
384
385