├── .gitignore ├── README.md ├── doc └── README.md ├── main.py ├── pcapviz ├── __init__.py ├── core.py └── sources.py ├── requirements.txt └── tests ├── __init__.py ├── core.py ├── test.pcap ├── test2.png ├── test3.png └── test4.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PcapViz 2 | PcapViz draws networks as device topologies and as information flows using the packet information in pcap files captured from a network 3 | device using tcpcap or other capture software. It filters and optionally displays the captured packets at any one of 3 "layers". These are: 4 | 5 | - device level traffic topology, 6 | - ip communication and 7 | - tcp/udp communication 8 | 9 | Each yields a distinct network graph from the same set of network packets. This separation makies it much easier to see the data flows at each level rather than mixing them up 10 | as many other visualisation packages do. It should be possible to determine key topological nodes or to spot patterns of data exfiltration attempts more easily. 11 | 12 | 13 | ## Features 14 | - Network topology graphs - 2 = device; conversation information flow graphs: 3 = ip, 4 = tcp/udp 15 | - Communication graph node labels show country information and connection stats 16 | - Lists the most frequently contacted and frequently sending machines 17 | - Node labels include the host domain name if available from a reverse DNS lookup. 18 | - command line choice of Graphviz graph layout engine such as dot or sfdp. 19 | 20 | 21 | ## Usage 22 | 23 | ``` 24 | usage: main.py [-h] [-i [PCAPS [PCAPS ...]]] [-o OUT] [-g GRAPHVIZ] [--layer2] 25 | [--layer3] [--layer4] [-fi] [-fo] [-G GEOPATH] [-l GEOLANG] 26 | [-E LAYOUTENGINE] [-s SHAPE] 27 | 28 | pcap topology and message mapper 29 | 30 | optional arguments: 31 | -h, --help show this help message and exit 32 | -i [PCAPS [PCAPS ...]], --pcaps [PCAPS [PCAPS ...]] 33 | space delimited list of capture files to be analyzed 34 | -o OUT, --out OUT topology will be stored in the specified file 35 | -g GRAPHVIZ, --graphviz GRAPHVIZ 36 | graph will be exported to the specified file (dot 37 | format) 38 | --layer2 device topology network graph 39 | --layer3 ip message graph. Default 40 | --layer4 tcp/udp message graph 41 | -fi, --frequent-in print frequently contacted nodes to stdout 42 | -fo, --frequent-out print frequent source nodes to stdout 43 | -G GEOPATH, --geopath GEOPATH 44 | path to maxmind geodb data 45 | -l GEOLANG, --geolang GEOLANG 46 | Language to use for geoIP names 47 | -E LAYOUTENGINE, --layoutengine LAYOUTENGINE 48 | Graph layout method - dot, sfdp etc. 49 | -s SHAPE, --shape SHAPE 50 | Graphviz node shape - circle, diamond, box etc. 51 | ``` 52 | 53 | ## Examples from running tests/core.py on the test.pcap file 54 | 55 | **Drawing a communication graph (layer 2), segment** 56 | ``` 57 | python main.py -i tests/test.pcap -o test2.png --layer2 58 | ``` 59 | 60 | ![layer 2 sample](tests/test2.png) 61 | 62 | **Layer3 with default sfdp layout** 63 | 64 | ![layer 3 sample](tests/test3.png) 65 | 66 | **Layer4 with default sfdp layout** 67 | 68 | ![layer 4 sample](tests/test4.png) 69 | 70 | 71 | Return hosts with largest numbers of incoming packets: 72 | 73 | ``` 74 | python3 main.py -i tests/test.pcap -fi --layer3 75 | 4 172.16.11.12 76 | 1 74.125.19.17 77 | 1 216.34.181.45 slashdot.org 78 | 1 172.16.11.1 79 | 1 96.17.211.172 a96-17-211-172.deploy.static.akamaitechnologies.com 80 | 81 | ``` 82 | 83 | ## Installation 84 | 85 | **Required:** 86 | 87 | * GraphViz 88 | See system notes below 89 | 90 | * Pip package requirements 91 | The Maxmind Python API and other dependencies will be installed when you run: 92 | 93 | ``` 94 | pip3 install -r requirements.txt 95 | ``` 96 | 97 | so of course, please run that! You are using a python virtual environment aren't you? 98 | 99 | ``` 100 | 101 | ### Installation Debian 102 | 103 | For Debian-based distros you have to install GraphViz with some additional dependencies: 104 | 105 | ``` 106 | apt-get install python3-dev 107 | apt-get install graphviz libgraphviz-dev pkg-config 108 | ``` 109 | 110 | ### Installation OSX 111 | 112 | Scapy does not work out-of-the-box on OSX. Follow the platform specific instruction from the [scapy website](http://scapy.readthedocs.io/en/latest/installation.html#platform-specific-instructions) 113 | 114 | ``` 115 | brew install graphviz 116 | brew install --with-python libdnet 117 | brew install https://raw.githubusercontent.com/secdev/scapy/master/.travis/pylibpcap.rb 118 | ``` 119 | 120 | ## Testing 121 | 122 | Unit tests can be run from the tests directory: 123 | ``` 124 | python3 core.py 125 | ``` 126 | The sample images above are the test output graphs. 127 | 128 | Note that there are at present 2 warnings about deprecated features in graphviz and for tests to work, you may need to adjust the fake args to point to your copy of the geoIP data file. 129 | Without access to the geoIP data, two of the tests will always fail. 130 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Project Notes 2 | 3 | ## Objective 4 | PcapViz aims to gather and visualize information from existing pcap files with respect to: 5 | 6 | * basic statistic information about nodes, connections and traffic 7 | 8 | * most frequently contacted nodes 9 | * most outgoing connections 10 | * most outgoing traffic 11 | * most incoming connections 12 | * most incoming traffic 13 | 14 | * information about protocols and content 15 | * enrich nodes with further information such as 16 | 17 | * geo locations 18 | * virus total IP information 19 | * comparing samples against white listed ones 20 | * NEW timeline of traffic/network behaviour? (dynamic graph) 21 | 22 | 23 | PcapViz is not suited for: 24 | 25 | * querying data or interacting with the results 26 | * perform real-time analysis 27 | * storing and merging multiple results 28 | * big datasets (single threaded etc.) 29 | 30 | 31 | ## Use Cases 32 | A potential use case is 33 | 34 | 1. understanding how participating network peers communicate and which protocols are used. 35 | 2. quickly identifying malicious traffic or data leakage by comparing some samples against white listed ones. 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | 3 | from pcapviz.core import GraphManager 4 | from pcapviz.sources import ScapySource 5 | from scapy.all import * 6 | from scapy.layers.http import HTTP 7 | 8 | 9 | # make this global so we can use it for tests 10 | 11 | parser = ArgumentParser(description='pcap topology and message mapper') 12 | parser.add_argument('-i', '--pcaps', nargs='*', default='test/test.pcap',help='space delimited list of capture files to be analyzed') 13 | parser.add_argument('-o', '--out', help='topology will be stored in the specified file') 14 | parser.add_argument('-g', '--graphviz', help='graph will be exported to the specified file (dot format)') 15 | parser.add_argument('--layer2', action='store_true', help='device topology network graph') 16 | parser.add_argument('--layer3', action='store_true', help='ip message graph. Default') 17 | parser.add_argument('--layer4', action='store_true', help='tcp/udp message graph') 18 | parser.add_argument('-d','--DEBUG', action='store_true', help='show debug messages') 19 | parser.add_argument('-w', '--whitelist', nargs='*', help='Whitelist of protocols - only packets matching these layers shown') 20 | parser.add_argument('-b', '--blacklist', nargs='*', help='Blacklist of protocols - NONE of the packets having these layers shown') 21 | parser.add_argument('-fi', '--frequent-in', action='store_true', help='print frequently contacted nodes to stdout') 22 | parser.add_argument('-fo', '--frequent-out', action='store_true', help='print frequent source nodes to stdout') 23 | parser.add_argument('-G', '--geopath', default='/usr/share/GeoIP/GeoLite2-City.mmdb', help='path to maxmind geodb data') 24 | parser.add_argument('-l', '--geolang', default='en', help='Language to use for geoIP names') 25 | parser.add_argument('-E', '--layoutengine', default='sfdp', help='Graph layout method - dot, sfdp etc.') 26 | parser.add_argument('-s', '--shape', default='diamond', help='Graphviz node shape - circle, diamond, box etc.') 27 | parser.add_argument('-n', '--nmax', default=100, help='Automagically draw individual protocols where useful if more than --nmax nodes. 100 seems too many for any one graph.') 28 | 29 | args = parser.parse_args() 30 | 31 | llook = {'DNS':DNS,'UDP':UDP,'ARP':ARP,'NTP':NTP,'IP':IP,'TCP':TCP,'Raw':Raw,'HTTP':HTTP,'RIP':RIP,'RTP':RTP} 32 | 33 | if __name__ == '__main__': 34 | if args.pcaps: 35 | bl=[] 36 | wl=[] 37 | pin = ScapySource.load(args.pcaps) 38 | if args.whitelist != None and args.blacklist != None: 39 | print('### Parameter error: Specify --blacklist or specify --whitelist but not both together please.') 40 | sys.exit(1) 41 | packets = pin 42 | if args.whitelist: # packets are returned from ScapySource.load as a list so cannot use pcap.filter(lambda...) 43 | wl = [llook[x] for x in args.whitelist] 44 | packets = [x for x in pin if sum([x.haslayer(y) for y in wl]) > 0 and x != None] 45 | elif args.blacklist: 46 | bl = [llook[x] for x in args.blacklist] 47 | packets = [x for x in pin if sum([x.haslayer(y) for y in bl]) == 0 and x != None] 48 | if args.DEBUG and (args.blacklist or args.whitelist): 49 | print('### Read', len(pin), 'packets. After applying supplied filters,',len(packets),'are left. wl=',wl,'bl=',bl) 50 | layer = 3 51 | if args.layer2: 52 | layer = 2 53 | elif args.layer4: 54 | layer = 4 55 | args.nmax = int(args.nmax) 56 | g = GraphManager(packets, layer=layer, args=args) 57 | nn = len(g.graph.nodes()) 58 | if nn > args.nmax: 59 | print('Asked to draw %d nodes with --nmax set to %d. Will also do useful protocols separately' % (nn,args.nmax)) 60 | for kind in llook.keys(): 61 | subset = [x for x in packets if x.haslayer(kind) and x != None] 62 | if len(subset) > 2: 63 | sg = GraphManager(subset,layer=layer, args=args) 64 | nn = len(sg.graph.nodes()) 65 | if nn > 2: 66 | ofn = '%s_%d_%s' % (kind,nn,args.out) 67 | sg.draw(filename = ofn) 68 | print('drew %s %d nodes' % (ofn,nn)) 69 | if args.out: 70 | g.draw(filename=args.out) 71 | 72 | if args.frequent_in: 73 | g.get_in_degree() 74 | 75 | if args.frequent_out: 76 | g.get_out_degree() 77 | 78 | if args.graphviz: 79 | g.get_graphviz_format(args.graphviz) 80 | -------------------------------------------------------------------------------- /pcapviz/__init__.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * -------------------------------------------------------------------------------- /pcapviz/core.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | ross lazarus december 2019 4 | forked from mateuszk87/PcapViz 5 | changed geoIP lookup to use maxminddb 6 | added reverse DNS lookup and cache with host names added to node labels 7 | added CL parameters to adjust image layout and shapes 8 | """ 9 | 10 | 11 | from collections import OrderedDict 12 | 13 | import networkx 14 | import itertools 15 | from networkx import DiGraph 16 | 17 | from scapy.layers.inet import TCP, IP, UDP 18 | from scapy.all import * 19 | from scapy.layers.http import * 20 | import logging 21 | 22 | import os 23 | import socket 24 | import maxminddb 25 | 26 | 27 | 28 | class GraphManager(object): 29 | """ Generates and processes the graph based on packets 30 | """ 31 | 32 | def __init__(self, packets, layer=3, args=None): 33 | self.graph = DiGraph() 34 | self.layer = layer 35 | self.geo_ip = None 36 | self.args = args 37 | self.data = {} 38 | self.deeNS = {} # cache for reverse lookups 39 | try: 40 | self.geo_ip = maxminddb.open_database(self.args.geopath) # command line -G 41 | except: 42 | logging.warning("could not load GeoIP data from supplied parameter geopath %s" % self.args.geopath) 43 | 44 | if self.layer == 2: 45 | edges = map(self._layer_2_edge, packets) 46 | elif self.layer == 3: 47 | edges = map(self._layer_3_edge, packets) 48 | elif self.layer == 4: 49 | edges = map(self._layer_4_edge, packets) 50 | else: 51 | raise ValueError("Other layers than 2,3 and 4 are not supported yet!") 52 | 53 | for src, dst, packet in filter(lambda x: not (x is None), edges): 54 | if src in self.graph and dst in self.graph[src]: 55 | self.graph[src][dst]['packets'].append(packet) 56 | else: 57 | self.graph.add_edge(src, dst) 58 | self.graph[src][dst]['packets'] = [packet] 59 | 60 | for node in self.graph.nodes(): 61 | self._retrieve_node_info(node) 62 | 63 | for src, dst in self.graph.edges(): 64 | self._retrieve_edge_info(src, dst) 65 | 66 | def lookup(self,ip): 67 | """deeNS caches all slow! fqdn reverse dns lookups from ip""" 68 | kname = self.deeNS.get(ip,None) 69 | if kname == None: 70 | kname = socket.getfqdn(ip) 71 | self.deeNS[ip] = kname 72 | return (kname) 73 | 74 | 75 | def get_in_degree(self, print_stdout=True): 76 | unsorted_degrees = self.graph.in_degree() 77 | return self._sorted_results(unsorted_degrees, print_stdout) 78 | 79 | def get_out_degree(self, print_stdout=True): 80 | unsorted_degrees = self.graph.out_degree() 81 | return self._sorted_results(unsorted_degrees, print_stdout) 82 | 83 | def _sorted_results(self,unsorted_degrees, print_stdout): 84 | sorted_degrees = OrderedDict(sorted(list(unsorted_degrees), key=lambda t: t[1], reverse=True)) 85 | for i in sorted_degrees: 86 | if print_stdout: 87 | nn = self.lookup(i) 88 | if (nn == i): 89 | print(sorted_degrees[i], i) 90 | else: 91 | print(sorted_degrees[i],i,nn) 92 | return sorted_degrees 93 | 94 | def _retrieve_node_info(self, node): 95 | self.data[node] = {} 96 | city = None 97 | country = None 98 | if self.layer >= 3 and self.geo_ip: 99 | if self.layer == 3: 100 | self.data[node]['ip'] = node 101 | elif self.layer == 4: 102 | self.data[node]['ip'] = node.split(':')[0] 103 | node_ip = self.data[node]['ip'] 104 | try: 105 | mmdbrec = self.geo_ip.get(node_ip) 106 | if mmdbrec != None: 107 | countryrec = mmdbrec.get('city',None) 108 | cityrec = mmdbrec.get('country',None) 109 | if countryrec: # some records have one but not the other.... 110 | country = countryrec['names'].get(self.args.geolang,None) 111 | if cityrec: 112 | city = cityrec['names'].get(self.args.geolang,None) 113 | self.data[node]['country'] = country if country else 'private' 114 | self.data[node]['city'] = city if city else 'private' 115 | except: 116 | logging.debug("could not load GeoIP data for node %s" % node_ip) 117 | # no lookup so not much data available 118 | #del self.data[node] 119 | 120 | #TODO layer 2 info? 121 | 122 | 123 | def _retrieve_edge_info(self, src, dst): 124 | edge = self.graph[src][dst] 125 | if edge: 126 | packets = edge['packets'] 127 | edge['layers'] = set(list(itertools.chain(*[set(GraphManager.get_layers(p)) for p in packets]))) 128 | edge['transmitted'] = sum(len(p) for p in packets) 129 | edge['connections'] = len(packets) 130 | 131 | @staticmethod 132 | def get_layers(packet): 133 | return list(GraphManager.expand(packet)) 134 | 135 | @staticmethod 136 | def expand(x): 137 | yield x.name 138 | while x.payload: 139 | x = x.payload 140 | yield x.name 141 | 142 | @staticmethod 143 | def _layer_2_edge(packet): 144 | return packet[0].src, packet[0].dst, packet 145 | 146 | @staticmethod 147 | def _layer_3_edge(packet): 148 | if packet.haslayer(IP): 149 | return packet[1].src, packet[1].dst, packet 150 | 151 | @staticmethod 152 | def _layer_4_edge(packet): 153 | if any(map(lambda p: packet.haslayer(p), [TCP, UDP])): 154 | src = packet[1].src 155 | dst = packet[1].dst 156 | _ = packet[2] 157 | return "%s:%i" % (src, _.sport), "%s:%i" % (dst, _.dport), packet 158 | 159 | def draw(self, filename=None): 160 | self.graph.label ="Layer %d traffic graph for packets from %s" % (self.layer,str(self.args.pcaps)) 161 | 162 | graph = self.get_graphviz_format() 163 | 164 | for node in graph.nodes(): 165 | if node not in self.data: 166 | # node might be deleted, because it's not legit etc. 167 | continue 168 | snode = str(node) 169 | nnode = self.lookup(snode) 170 | node.attr['shape'] = self.args.shape 171 | node.attr['fontsize'] = '10' 172 | node.attr['width'] = '0.5' 173 | node.attr['color'] = 'linen' 174 | node.attr['style'] = 'filled,rounded' 175 | if 'country' in self.data[snode]: 176 | country_label = self.data[snode]['country'] 177 | city_label = self.data[snode]['city'] 178 | if nnode != snode: 179 | nodelab = '%s\n%s' % (nnode,snode) 180 | else: 181 | nodelab = snode 182 | if country_label != 'private': 183 | if city_label == 'private': 184 | nodelab += "\n(%s)" % (country_label) 185 | else: 186 | nodelab += "\n(%s, %s)" % (city_label, country_label) 187 | node.attr['label'] = nodelab 188 | if not (country_label == 'private'): 189 | node.attr['color'] = 'lightyellow' 190 | #TODO add color based on country or scan? 191 | for edge in graph.edges(): 192 | connection = self.graph[edge[0]][edge[1]] 193 | edge.attr['label'] = 'transmitted: %i bytes\n%s ' % (connection['transmitted'], ' | '.join(connection['layers'])) 194 | edge.attr['fontsize'] = '8' 195 | edge.attr['minlen'] = '2' 196 | edge.attr['penwidth'] = min(max(0.05,connection['connections'] * 1.0 / len(self.graph.nodes())), 2.0) 197 | graph.layout(prog=self.args.layoutengine) 198 | graph.draw(filename) 199 | 200 | def get_graphviz_format(self, filename=None): 201 | agraph = networkx.drawing.nx_agraph.to_agraph(self.graph) 202 | # remove packet information (blows up file size) 203 | for edge in agraph.edges(): 204 | del edge.attr['packets'] 205 | if filename: 206 | agraph.write(filename) 207 | return agraph 208 | -------------------------------------------------------------------------------- /pcapviz/sources.py: -------------------------------------------------------------------------------- 1 | from scapy.utils import rdpcap 2 | from itertools import chain 3 | 4 | 5 | class ScapySource(object): 6 | 7 | @staticmethod 8 | def load(pcap_file_paths=[]): 9 | return list(chain(*[rdpcap(_file) for _file in pcap_file_paths])) 10 | 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | scapy>=2.4.0 2 | matplotlib 3 | networkx 4 | maxminddb 5 | pygraphviz 6 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1ultimat3/PcapViz/a940c9f6b4c2de27645c236598ff094fc3685eec/tests/__init__.py -------------------------------------------------------------------------------- /tests/core.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | import os 4 | 5 | # finesse the imports - ugh - so we can run from the tests directory 6 | 7 | PACKAGE_PARENT = '..' 8 | SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__)))) 9 | sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT))) 10 | 11 | from pcapviz.core import GraphManager 12 | from pcapviz.sources import ScapySource 13 | from main import args 14 | args.pcaps = 'test.pcap' 15 | 16 | import os 17 | 18 | 19 | class PcapProcessingTests(unittest.TestCase): 20 | 21 | def test_load_pcap(self): 22 | loaded = ScapySource.load(['test.pcap', 'test.pcap']) 23 | self.assertEqual(282, len(loaded)) 24 | 25 | def test_build_graph_layer2(self): 26 | packets = ScapySource.load(['test.pcap']) 27 | g = GraphManager(packets, layer=2,args=args) 28 | self.assertEqual(3, g.graph.number_of_edges()) 29 | 30 | def test_build_graph_layer3(self): 31 | packets = ScapySource.load(['test.pcap']) 32 | g = GraphManager(packets,args=args) 33 | self.assertEqual(8, g.graph.number_of_edges()) 34 | 35 | def test_build_graph_layer4(self): 36 | packets = ScapySource.load(['test.pcap']) 37 | g = GraphManager(packets, layer=4,args=args) 38 | self.assertEqual(36, g.graph.number_of_edges()) 39 | 40 | def test_get_frequent_ips_in(self): 41 | packets = ScapySource.load(['test.pcap']) 42 | g = GraphManager(packets, layer=3, args=args) 43 | ips = g.get_in_degree(print_stdout=True) 44 | self.assertIsNotNone(ips) 45 | 46 | def test_get_frequent_ips_out(self): 47 | packets = ScapySource.load(['test.pcap']) 48 | g = GraphManager(packets, layer=3, args=args) 49 | ips = g.get_out_degree(print_stdout=True) 50 | self.assertIsNotNone(ips) 51 | 52 | def _draw(self, png, layer): 53 | try: 54 | os.remove(png) 55 | except OSError: 56 | pass 57 | packets = ScapySource.load(['test.pcap']) 58 | g = GraphManager(packets, layer=layer, args=args) 59 | g.draw(filename=png) 60 | self.assertTrue(os.path.exists(png)) 61 | 62 | def test_layer2(self): 63 | self._draw('test2.png', 2) 64 | 65 | def test_layer3(self): 66 | self._draw('test3.png', 3) 67 | 68 | def test_layer4(self): 69 | self._draw('test4.png', 4) 70 | 71 | def test_retrieve_geoip2(self): 72 | packets = ScapySource.load(['test.pcap']) 73 | g = GraphManager(packets, layer=2, args=args) 74 | node = list(g.data.keys())[0] 75 | g._retrieve_node_info(node) 76 | self.assertNotIn('country', g.data[node]) 77 | 78 | def test_retrieve_geoip3(self): 79 | packets = ScapySource.load(['test.pcap']) 80 | g = GraphManager(packets, layer=3, args=args) 81 | node = list(g.data.keys())[0] 82 | g._retrieve_node_info(node) 83 | self.assertIn('country', g.data[node]) 84 | 85 | def test_retrieve_geoip4(self): 86 | packets = ScapySource.load(['test.pcap']) 87 | g = GraphManager(packets, layer=4, args=args) 88 | node = list(g.data.keys())[0] 89 | g._retrieve_node_info(node) 90 | self.assertIn('country', g.data[node]) 91 | 92 | def test_graphviz(self): 93 | packets = ScapySource.load(['test.pcap']) 94 | g = GraphManager(packets, layer=3,args=args) 95 | self.assertIsNotNone(g.get_graphviz_format()) 96 | 97 | 98 | if __name__ == '__main__': 99 | unittest.main() 100 | -------------------------------------------------------------------------------- /tests/test.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1ultimat3/PcapViz/a940c9f6b4c2de27645c236598ff094fc3685eec/tests/test.pcap -------------------------------------------------------------------------------- /tests/test2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1ultimat3/PcapViz/a940c9f6b4c2de27645c236598ff094fc3685eec/tests/test2.png -------------------------------------------------------------------------------- /tests/test3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1ultimat3/PcapViz/a940c9f6b4c2de27645c236598ff094fc3685eec/tests/test3.png -------------------------------------------------------------------------------- /tests/test4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1ultimat3/PcapViz/a940c9f6b4c2de27645c236598ff094fc3685eec/tests/test4.png --------------------------------------------------------------------------------