Adding protocols as the property of edges; simplifying protocol tracking through...
[pingpong.git] / base_gefx_generator.py
1 #!/usr/bin/python
2
3 """
4 Script that constructs a graph in which hosts are nodes.
5 An edge between two hosts indicate that the hosts communicate.
6 Hosts are labeled and identified by their IPs.
7 The graph is written to a file in Graph Exchange XML format for later import and visual inspection in Gephi.
8
9 The input to this script is the JSON output by extract_from_tshark.py by Anastasia Shuba.
10
11 This script is a simplification of Milad Asgari's parser_data_to_gephi.py script.
12 It serves as a baseline for future scripts that want to include more information in the graph.
13 """
14
15 import socket
16 import json
17 import tldextract
18 import networkx as nx
19 import sys
20 import csv
21 import re
22 from decimal import *
23
24 import parser.parse_dns
25
26 # List of devices
27 DEVICE_MAC_LIST = "devicelist.dat"
28 EXCLUSION_MAC_LIST = "exclusion.dat"
29 COLUMN_MAC = "MAC_address"
30 COLUMN_DEVICE_NAME = "device_name"
31 # Fields
32 JSON_KEY_SOURCE = "_source"
33 JSON_KEY_LAYERS = "layers"
34 JSON_KEY_FRAME = "frame"
35 JSON_KEY_FRAME_PROTOCOLS = "frame.protocols"
36 JSON_KEY_FRAME_TIME_EPOCH = "frame.time_epoch"
37 JSON_KEY_ETH = "eth"
38 JSON_KEY_ETH_SRC = "eth.src"
39 JSON_KEY_ETH_DST = "eth.dst"
40 JSON_KEY_IP = "ip"
41 JSON_KEY_IP_SRC = "ip.src"
42 JSON_KEY_IP_DST = "ip.dst"
43 # Checked protocols
44 JSON_KEY_UDP = "udp"
45 JSON_KEY_TCP = "tcp"
46 # List of checked protocols
47 listchkprot = [ "arp",
48                 "bootp",
49                 "dhcpv6",
50                 "dns",
51                 "llmnr",
52                 "mdns",
53                 "ssdp" ]
54
55
56 def create_device_list(dev_list_file):
57     """ Create list for smart home devices from a CSV file
58         Args:
59             dev_list_file: CSV file path that contains list of device MAC addresses
60     """
61     # Open the device MAC list file
62     with open(dev_list_file) as csvfile:
63         mac_list = csv.DictReader(csvfile, (COLUMN_MAC, COLUMN_DEVICE_NAME))
64         crude_list = list()
65         for item in mac_list:
66             crude_list.append(item)
67     # Create key-value dictionary
68     dev_list = dict()
69     for item in crude_list:
70         dev_list[item[COLUMN_MAC]] = item[COLUMN_DEVICE_NAME]
71         #print item["MAC_address"] + " => " + item["device_name"]
72     #for key, value in devlist.iteritems():
73     #    print key + " => " + value
74
75     return dev_list
76
77
78 def parse_json(file_path):
79
80     # Create a smart home device list
81     dev_list = create_device_list(DEVICE_MAC_LIST)
82     # Create an exclusion list
83     exc_list = create_device_list(EXCLUSION_MAC_LIST)
84
85     # First parse the file once, constructing a map that contains information about individual devices' DNS resolutions.
86     device_dns_mappings = parser.parse_dns.parse_json_dns(file_path) # "./json/eth1.dump.json"
87     
88     # Init empty graph
89     G = nx.DiGraph()
90     # Mapping from node to a set of protocols
91     edge_to_prot = dict()
92
93     # Parse file again, this time constructing a graph of device<->server and device<->device communication.
94     with open(file_path) as jf:
95         # Read JSON.
96         # data becomes reference to root JSON object (or in our case json array)
97         data = json.load(jf)
98
99         # Loop through json objects (packets) in data
100         for p in data:
101             # p is a JSON object, not an index
102             # Drill down to object containing data from the different layers
103             layers = p[JSON_KEY_SOURCE][JSON_KEY_LAYERS]
104
105             iscontinue = False
106             for prot in listchkprot:
107                 if prot in layers:
108                     iscontinue = True
109             if iscontinue:
110                 continue            
111
112             # Skip any non udp/non tcp traffic
113             if JSON_KEY_UDP not in layers and JSON_KEY_TCP not in layers:
114                 continue
115
116             # Fetch source and destination MACs
117             eth = layers.get(JSON_KEY_ETH, None)
118             if eth is None:
119                 print "[ WARNING: eth data not found ]"
120                 continue
121             eth_src = eth.get(JSON_KEY_ETH_SRC, None)
122             eth_dst = eth.get(JSON_KEY_ETH_DST, None)
123             # Exclude devices in the exclusion list
124             if eth_src in exc_list:
125                 print "[ WARNING: Source ", eth_src, " is excluded from graph! ]"
126                 continue
127             if eth_dst in exc_list:
128                 print "[ WARNING: Destination ", eth_dst, " is excluded from graph! ]"
129                 continue
130
131             # Fetch timestamp of packet (router's timestamp)
132             timestamp = Decimal(layers[JSON_KEY_FRAME][JSON_KEY_FRAME_TIME_EPOCH])
133             # Get the protocol and strip just the name of it
134             long_protocol = layers[JSON_KEY_FRAME][JSON_KEY_FRAME_PROTOCOLS]
135             # Split once starting from the end of the string and get it
136             #protocol = long_protocol.rsplit(':', 1)[1]
137             split_protocol = long_protocol.split(':')
138             protocol = None
139             if len(split_protocol) < 5:
140                 last_index = len(split_protocol) - 1
141                 protocol = split_protocol[last_index]
142             else:
143                 protocol = split_protocol[3] + ":" + split_protocol[4]
144             print "timestamp: ", timestamp, " - new protocol added: ", protocol, "\n"
145
146             # Store protocol into the set (source)
147             protocols = None
148             # Key to search for protocol list in the dictionary is
149             #   <src-mac-address>-<dst-mac_address>
150             protocol_key = eth_src + "-" + eth_dst
151             if protocol_key not in edge_to_prot:
152                 edge_to_prot[protocol_key] = set()
153             protocols = edge_to_prot[protocol_key]
154             protocols.add(protocol)
155             protocols_str = ', '.join(protocols)
156             print "protocols: ", protocols_str, "\n"
157             # And source and destination IPs
158             ip_src = layers[JSON_KEY_IP][JSON_KEY_IP_SRC]
159             ip_dst = layers[JSON_KEY_IP][JSON_KEY_IP_DST]
160
161             # Categorize source and destination IP addresses: local vs. non-local
162             ipre = re.compile(r'\b192.168.[0-9.]+')
163             src_is_local = ipre.search(ip_src) 
164             dst_is_local = ipre.search(ip_dst)
165             print "ip.src =", ip_src, "ip.dst =", ip_dst, "\n"
166             
167             src_node = None
168             dst_node = None
169             if src_is_local:
170                 G.add_node(eth_src, Name=dev_list[eth_src])
171                 src_node = eth_src
172             else:
173                 hostname = None
174                 # Check first if the key (eth_dst) exists in the dictionary
175                 if eth_dst in device_dns_mappings:
176                     # If the source is not local, then it's inbound traffic, and hence the eth_dst is the MAC of the IoT device.
177                     hostname = device_dns_mappings[eth_dst].hostname_for_ip_at_time(ip_src, timestamp)                   
178                 if hostname is None:
179                     # Use IP if no hostname mapping
180                     hostname = ip_src
181                 G.add_node(hostname)
182                 src_node = hostname
183
184             if dst_is_local:
185                 G.add_node(eth_dst, Name=dev_list[eth_dst])
186                 dst_node = eth_dst
187             else:
188                 hostname = None
189                 # Check first if the key (eth_dst) exists in the dictionary
190                 if eth_src in device_dns_mappings:
191                     # If the destination is not local, then it's outbound traffic, and hence the eth_src is the MAC of the IoT device.
192                     hostname = device_dns_mappings[eth_src].hostname_for_ip_at_time(ip_dst, timestamp)
193                 if hostname is None:
194                     # Use IP if no hostname mapping
195                     hostname = ip_dst
196                 G.add_node(hostname)
197                 dst_node = hostname
198             G.add_edge(src_node, dst_node, Protocol=protocols_str)
199
200     # Print DNS mapping for reference
201         for mac in device_dns_mappings:
202                 ddm = device_dns_mappings[mac]
203                 ddm.print_mappings()
204     
205     return G
206
207
208 # ------------------------------------------------------
209 # Not currently used.
210 # Might be useful later on if we wish to resolve IPs.
211 def get_domain(host):
212     ext_result = tldextract.extract(str(host))
213     # Be consistent with ReCon and keep suffix
214     domain = ext_result.domain + "." + ext_result.suffix
215     return domain
216
217 def is_IP(addr):
218     try:
219         socket.inet_aton(addr)
220         return True
221     except socket.error:
222         return False
223 # ------------------------------------------------------
224
225
226 if __name__ == '__main__':
227     if len(sys.argv) < 3:
228         print "Usage:", sys.argv[0], "input_file output_file"
229         print "outfile_file should end in .gexf"
230         sys.exit(0)
231     # Input file: Path to JSON file generated from tshark JSON output using Anastasia's script (extract_from_tshark.py).
232     input_file = sys.argv[1]
233     print "[ input_file  =", input_file, "]"
234     # Output file: Path to file where the Gephi XML should be written.
235     output_file = sys.argv[2]
236     print "[ output_file =", output_file, "]"
237     # Construct graph from JSON
238     G = parse_json(input_file)
239     # Write Graph in Graph Exchange XML format
240     nx.write_gexf(G, output_file)