1 #!/usr/local/bin/python2.7
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 -----------------------------------------------------------------------------
14 from dpkt.compat import compat_ord
19 """ -----------------------------------------------------------------------------
20 Global variable declarations
21 -----------------------------------------------------------------------------
23 # Command line arguments
30 def mac_addr(address):
31 # Courtesy of: https://dpkt.readthedocs.io/en/latest/
32 """ Convert a MAC address to a readable/printable string
34 address (str): a MAC address in hex form (e.g. '\x01\x02\x03\x04\x05\x06')
36 str: Printable/readable MAC address
38 return ':'.join('%02x' % compat_ord(b) for b in address)
41 def inet_to_str(inet):
42 # Courtesy of: https://dpkt.readthedocs.io/en/latest/
43 """ Convert inet object to a string
45 inet (inet struct): inet network address
47 str: Printable/readable IP address
49 # First try ipv4 and then ipv6
51 return socket.inet_ntop(socket.AF_INET, inet)
53 return socket.inet_ntop(socket.AF_INET6, inet)
57 """ Show usage of this Python script
59 print "Usage: python CAPture.py [ -i <file-name>.pcap ] [ -o <file-name>.pcap ] [ -pm ] [ -v ]"
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"
68 def show_progress(verbose, counter):
69 """ Show packet processing progress
71 verbose: verbose output (True/False)
72 counter: counter of all packets
75 print "Processing packet number: ", counter
77 if counter % 100000 == 0:
78 print "Processing %s packets..." % counter
81 def show_summary(counter, ip_counter, tcp_counter, udp_counter):
82 """ Show summary of statistics of PCAP file
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
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
97 def save_to_file(tbl_header, dictionary, filename_out):
98 """ Show summary of statistics of PCAP file
100 tbl_header: header for the saved table
101 dictionary: dictionary to be saved
102 filename_out: file name to save
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")
113 print "Writing output to file: ", filename_out
116 def statistical_analysis(verbose, pcap, counter, ip_counter, tcp_counter, udp_counter):
117 """ This is the default analysis of packet statistics (generic)
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
126 for time_stamp, packet in pcap:
129 eth = dpkt.ethernet.Ethernet(packet)
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__
137 # Process only IP data
138 if not isinstance(eth.data, dpkt.ip.IP):
142 print "Non IP packet type not analyzed... skipping..."
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
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)
162 if ip.p == dpkt.ip.IP_PROTO_TCP:
166 if ip.p == dpkt.ip.IP_PROTO_UDP:
169 show_progress(verbose, counter)
171 # Print general statistics
172 show_summary(counter, ip_counter, tcp_counter, udp_counter)
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.
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
188 # Dictionary that preserves the mapping between destination address to frequency
191 for time_stamp, packet in pcap:
194 eth = dpkt.ethernet.Ethernet(packet)
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)
201 # Process only IP data
202 if not isinstance(eth.data, dpkt.ip.IP):
206 print "Non IP packet type not analyzed... skipping..."
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
221 ip_src = inet_to_str(ip.src)
222 ip_dst = inet_to_str(ip.dst)
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)
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
239 if dev_add == mac_src:
240 if mac_dst in ip2freq:
241 freq = mac2freq[mac_dst]
242 mac2freq[mac_dst] = freq + 1
244 mac2freq[mac_dst] = 1
247 if ip.p == dpkt.ip.IP_PROTO_TCP:
251 if ip.p == dpkt.ip.IP_PROTO_UDP:
254 show_progress(verbose, counter)
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)
266 print "Output file name is not specified... exitting now!"
269 def parse_cli_args(argv):
270 """ Parse command line arguments and store them in a dictionary
272 argv: list of command line arguments and their values
274 str: dictionary that maps arguments to their values
277 # First argument is "CAPture.py", so skip it
279 # Loop and collect arguments and their values
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
291 options[argv[0]] = argv[1]
292 # Remove one command line argument and its value
298 """ -----------------------------------------------------------------------------
300 -----------------------------------------------------------------------------
303 # Variable declarations
305 global PCAP_EXTENSION
317 is_statistical_analysis = True
318 is_point_to_many_analysis = False
326 print "Welcome to CAPture version 1.0 - A PCAP file instant analyzer!"
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:
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
345 elif key == POINT_TO_MANY:
346 is_statistical_analysis = False
347 is_point_to_many_analysis = True
350 # Show manual again if input is not correct
351 if filename_in == "":
352 print "File name is empty!"
358 # dev_add is needed for these analyses
359 if is_point_to_many_analysis and dev_add == "":
360 print "Device address is empty!"
366 # One PCAP file name is specified - now analyze!
367 print "Analyzing PCAP file: ", filename_in
369 # Opening and analyzing PCAP file
370 f = open(filename_in,'rb')
371 pcap = dpkt.pcap.Reader(f)
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)
381 if __name__ == "__main__":
382 # call main function since this is being run as the start