Skip to content

Instantly share code, notes, and snippets.

@MTN-RowinAndruscavage
Created March 4, 2019 17:41
Show Gist options
  • Select an option

  • Save MTN-RowinAndruscavage/e88097e6175a6b130ec81c0a02df8cb8 to your computer and use it in GitHub Desktop.

Select an option

Save MTN-RowinAndruscavage/e88097e6175a6b130ec81c0a02df8cb8 to your computer and use it in GitHub Desktop.
#!/usr/bin/python3
import argparse
import re
parser = argparse.ArgumentParser(
description = "netstat output files to graph"
)
parser.add_argument('--inputs', nargs='*')
args = parser.parse_args()
def parsefilename(filename):
return filename.split('/')[-2]
class Graph:
def __init__(self):
self.nodes = set()
self.edges = list()
self.nodeprop = dict()
self.edgeprop = dict()
def add_node(self, name, np={}):
self.nodes.add(name)
if np != {}:
self.nodeprop[name] = np
def add(self, src, target, ep={}):
"""
Append edge
"""
if src == target:
# ignore self loops
return
self.nodes.add(src)
self.nodes.add(target)
self.edges.append((src, target))
if ep != {}:
self.edgeprop[(src, target)] = ep
def find(self, node):
"""
Find all edges containing the node
"""
results = []
for edge in self.edges:
if node in edge:
results.append(edge)
return results
def removenode(self, node):
"""
Remove node from list
"""
self.nodes.remove(node)
def removeedge(self, edge):
"""
Remove edge from list
"""
self.edges.remove(edge)
def writeGraphML(self, filename):
"""
Write graphml file for yEd
"""
with open(filename, "w") as f:
f.write("""<?xml version="1.0" encoding="UTF-8"?>\n
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"
xmlns:y="http://www.yworks.com/xml/graphml">
<key id="d0" for="node" yfiles.type="nodegraphics"/>
<key id="d1" for="edge" yfiles.type="edgegraphics"/>
<key attr.name="description" attr.type="string" for="edge" id="d3"/>
<key attr.name="description" attr.type="string" for="node" id="d4"/>
""")
f.write("""<graph id="G" edgedefault="directed">\n""")
for edge in self.edges:
port = self.edgeprop[edge]['port'] if edge in self.edgeprop else ''
prog = self.edgeprop[edge]['prog'] if edge in self.edgeprop else ''
state = self.edgeprop[edge]['state'] if edge in self.edgeprop else ''
f.write("""
<edge id="e%s_e%s" source="n%s" target="n%s">
s<data key="d3" xml:space="preserve"><![CDATA[:%s %s %s]]></data>
<data key="d1" >
<y:Edge>
<y:LineStyle type="line" width="1.0" color="#101010" />
<y:Arrows source="none" target="standard"/>
</y:Edge>
</data>
</edge>
""" % (edge[0], edge[1], edge[0], edge[1], port, prog, state))
for node in sorted(self.nodes):
name = self.nodeprop[node]['name'] if node in self.nodeprop else ''
f.write("""
<node id="n%s">
<data key="d4" xml:space="preserve"><![CDATA[%s]]></data>
<data key="d0">
<y:UMLClassNode>
<y:Fill color="#ffcc00" transparent="false"/>
<y:NodeLabel>%s
[%s]</y:NodeLabel>
<y:UML>
<y:AttributeLabel></y:AttributeLabel>
<y:MethodLabel></y:MethodLabel>
</y:UML>
</y:UMLClassNode>
</data>
</node>
""" % (node, node, name, node) )
f.write("</graph>\n</graphml>")
def writeGraphViz(self, filename):
"""
Write dot file for graphviz
"""
graphheader = """
rankdir=LR; ranksep=2;
node [ width=5 shape=box style=filled fillcolor=lightgrey ];
edge [ dir=back ];
"""
with open(filename, "w") as f:
f.write("digraph MinCut {\n%s" % graphheader)
for edge in self.edges:
f.write(" \"%s\" -> \"%s\" ;\n" % (edge[0], edge[1]))
f.write("}\n")
g = Graph()
for arg in vars(args):
files = getattr(args, arg)
for arg in files:
netstatlines = []
with open(arg, 'r') as f:
netstatlines = f.read()
# Strip headings from netstat output
netstatlines = netstatlines.split('\n')[2:-1]
for l in netstatlines:
(proto, recvq, sendq, local, remote, state, pid) = l.split()[:7]
if "LISTEN" in state:
continue
pid = pid.split('/')
if pid[0] == '-':
(pid, prog) = (pid, pid)
else:
(pid, prog) = (pid[0], pid[1])
# print(state, prog)
(src, srcport) = local.split(':')[:2]
(dst, dstport) = remote.split(':')[:2]
nodeprop = {
'filename': arg,
'name': parsefilename(arg)
}
edgeprop = {
'port': dstport,
'state': state,
'prog': prog
}
g.add_node(src,nodeprop)
g.add(src,dst,edgeprop)
g.writeGraphViz('connections.dot')
g.writeGraphML('connections.graphml')
#!/bin/bash
#
# Pull netstat output from all servers in inventory
TS=`date +'%Y-%m-%d_%H%M'`
mkdir -p netstat
time ansible all \
-m shell \
-a "netstat -pant > netstat.out" \
--become
ansible all \
-m fetch \
-a "src=netstat.out dest=netstat/${TS}"
pushd netstat > /dev/null
pipenv run python ns2graph.py --inputs $TS/*/netstat.out
popd > /dev/null
@MTN-RowinAndruscavage
Copy link
Author

MTN-RowinAndruscavage commented Mar 4, 2019

Network connectivity visualization using the output of netstat -pant on all the hosts in your inventory. The update_netstat.sh example collects netstat output from all hosts in inventory using ansible ad-hoc commands.

The resulting .graphml can be loaded into yEd
https://www.yworks.com/products/yed

Sample output: https://imgur.com/a/ORW4Pr6

Then select all, "Tools | Fit Label to Node" , then select a Layout to rearrange nodes and have some fun!

Notes:

  • nodes will use ansible inventory name if available, otherwise fall back to IP address
  • Click on edges to see port and process name in Properties View... multiple connections will be collapsed to a single edge, but there will typically be at least two edges between hosts in inventory: the server side and client side view of the connection.
  • Structure View is useful for sorting by IPs, which can help you group and colorize nodes by subnet if that helps make things prettier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment