Update pipeline: graph now maps IoT devices' MACs to hostnames or other MACs (if...
[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 from decimal import *
21
22 import parse_dns
23
24 JSON_KEY_ETH_SRC = "eth.src"
25 JSON_KEY_ETH_DST = "eth.dst"
26
27 def parse_json(file_path):
28
29     device_dns_mappings = parse_dns.parse_json_dns("./dns.json")
30
31     # Init empty graph
32     G = nx.DiGraph() 
33     with open(file_path) as jf:
34         # Read JSON.
35         # data becomes reference to root JSON object (or in our case json array)
36         data = json.load(jf)
37         # Loop through json objects in data
38         for k in data:
39             # Fetch timestamp of packet
40             packet_timestamp = Decimal(data[k]["ts"])
41             # Fetch eth source and destination info
42             eth_src = data[k][JSON_KEY_ETH_SRC]
43             eth_dst = data[k][JSON_KEY_ETH_DST]
44             # Traffic can be both outbound and inbound.
45             # Determine which one of the two by looking up device MAC in DNS map.
46             iot_device = None
47             if eth_src in device_dns_mappings:
48                 iot_device = eth_src
49             elif eth_dst in device_dns_mappings:
50                 iot_device = eth_dst
51             else:
52                 print "[ WARNING: DNS mapping not found for device with MAC", eth_src, "OR", eth_dst, "]"
53                 # This must be local communication between two IoT devices OR an IoT device talking to a hardcoded IP.
54                 # For now let's assume local communication.
55                 # Add a node for each device and an edge between them.
56                 G.add_node(eth_src)
57                 G.add_node(eth_dst)
58                 G.add_edge(eth_src, eth_dst)
59                 # TODO add regex check on src+dst IP to figure out if hardcoded server IP (e.g. check if one of the two are NOT a 192.168.x.y IP)
60                 continue
61             # It is outbound traffic if iot_device matches src, otherwise it must be inbound traffic.
62             outbound_traffic = iot_device == eth_src
63
64             ''' Graph construction '''
65             # No need to check if the Nodes and/or Edges we add already exist:
66             # NetworkX won't add already existing nodes/edges (except in the case of a MultiGraph or MultiDiGraph (see NetworkX doc)).
67             
68             # Add a node for each host.
69             # First add node for IoT device.
70             G.add_node(iot_device)
71             # Then add node for the server.
72             # For this we need to distinguish between outbound and inbound traffic so that we look up the proper IP in our DNS map.
73             # For outbound traffic, the server's IP is the destination IP.
74             # For inbound traffic, the server's IP is the source IP.
75             server_ip = data[k]["dst_ip"] if outbound_traffic else data[k]["src_ip"]
76             hostname = device_dns_mappings[iot_device].hostname_for_ip_at_time(server_ip, packet_timestamp)
77             if hostname is None:
78                 # TODO this can occur when two local devices communicate OR if IoT device has hardcoded server IP.
79                 # However, we only get here for the DNS that have not performed any DNS lookups
80                 # We should use a regex check early in the loop to see if it is two local devices communicating.
81                 # This way we would not have to consider these corner cases later on.
82                 print "[ WARNING: no ip-hostname mapping found for ip", server_ip, " -- adding eth.src->eth.dst edge, but note that this may be incorrect if IoT device has hardcoded server IP ]"
83                 G.add_node(eth_src)
84                 G.add_node(eth_dst)
85                 G.add_edge(eth_src, eth_dst)
86                 continue
87             G.add_node(hostname)
88             # Connect the two nodes we just added.
89             if outbound_traffic:
90                 G.add_edge(iot_device, hostname)
91             else:
92                 G.add_edge(hostname, iot_device)
93     return G
94
95 # ------------------------------------------------------
96 # Not currently used.
97 # Might be useful later on if we wish to resolve IPs.
98 def get_domain(host):
99     ext_result = tldextract.extract(str(host))
100     # Be consistent with ReCon and keep suffix
101     domain = ext_result.domain + "." + ext_result.suffix
102     return domain
103
104 def is_IP(addr):
105     try:
106         socket.inet_aton(addr)
107         return True
108     except socket.error:
109         return False
110 # ------------------------------------------------------
111
112 if __name__ == '__main__':
113     if len(sys.argv) < 3:
114         print "Usage:", sys.argv[0], "input_file output_file"
115         print "outfile_file should end in .gexf"
116         sys.exit(0)
117     # Input file: Path to JSON file generated from tshark JSON output using Anastasia's script (extract_from_tshark.py).
118     input_file = sys.argv[1]
119     print "[ input_file  =", input_file, "]"
120     # Output file: Path to file where the Gephi XML should be written.
121     output_file = sys.argv[2]
122     print "[ output_file =", output_file, "]"
123     # Construct graph from JSON
124     G = parse_json(input_file)
125     # Write Graph in Graph Exchange XML format
126     nx.write_gexf(G, output_file)