#!/usr/bin/env python3
"""
This program parses the output from pcap_compile() to visualize the CFG after
each optimize phase.
Usage guide:
1. Enable optimizer debugging code when configure libpcap,
and build libpcap & the test programs
./configure --enable-optimizer-dbg
make
make testprogs
2. Run filtertest to compile BPF expression and produce the CFG as a
DOT graph, save to output a.txt
testprogs/filtertest -g EN10MB host 192.168.1.1 > a.txt
3. Send a.txt to this program's standard input
cat a.txt | testprogs/visopts.py
(Graphviz must be installed)
4. Step 2&3 can be merged:
testprogs/filtertest -g EN10MB host 192.168.1.1 | testprogs/visopts.py
5. The standard output is something like this:
generated files under directory: /tmp/visopts-W9ekBw
the directory will be removed when this programs finished.
open this link: http://localhost:39062/expr1.html
6. Open the URL at the 3rd line in a browser.
Note:
1. The CFG is translated to SVG images, expr1.html embeds them as external
documents. If you open expr1.html as local file using file:// protocol, some
browsers will deny such requests so the web page will not work properly.
For Chrome, you can run it using the following command to avoid this:
chromium --disable-web-security
That's why this program starts a localhost HTTP server.
2. expr1.html uses jQuery from https://ajax.googleapis.com, so it needs Internet
access to work.
"""
import sys, os
import string
html_template = string.Template("""
BPF compiler optimization phases for "${expr_html}"
""")
def write_html(expr, gcount, logs):
import html
import json
# In the Python 2.7 version this used to be str.encode('string-escape'),
# which was a normal string, but in Python 3 the "string_escape" encoding
# no longer exists and even with the "unicode_escape" encoding encode()
# always returns a binary string. So let's just escape the single quotes
# here and hope the result is a valid JavaScript string literal.
def encode(s):
return s.replace("'", "\'")
mapping = {
'expr_html': html.escape(expr),
'expr_json': encode(expr),
'gcount': gcount,
'logs': encode(json.dumps([s.strip().replace("\n", "
") for s in logs])),
}
with open("expr1.html", "wt") as f:
f.write(html_template.safe_substitute(mapping))
def render_on_html(infile):
import subprocess
expr = None
gid = 1
log = ""
dot = ""
indot = 0
logs = []
for line in infile:
if line.startswith("machine codes for filter:"):
expr = line[len("machine codes for filter:"):].strip()
break
elif line.startswith("digraph BPF {"):
indot = 1
dot = line
elif indot:
dot += line
if line.startswith("}"):
indot = 2
else:
log += line
if indot == 2:
try:
svg=subprocess.check_output(['dot', '-Tsvg'], input=dot, universal_newlines=True)
except OSError as ose:
print("Failed to run 'dot':", ose)
print("(Is Graphviz installed?)")
return False
except subprocess.CalledProcessError as cpe:
print("Got an error from the 'dot' process: ", cpe)
return False
with open("expr1_g%03d.svg" % gid, "wt") as f:
f.write(svg)
logs.append(log)
gid += 1
log = ""
dot = ""
indot = 0
if indot != 0:
#unterminated dot graph for expression
return False
if expr is None:
# BPF parser encounter error(s)
return False
write_html(expr, gid - 1, logs)
return True
def run_httpd():
import http.server
httpd = http.server.HTTPServer(("localhost", 0), http.server.SimpleHTTPRequestHandler)
print("open this link: http://localhost:%d/expr1.html" % httpd.server_port)
try:
httpd.serve_forever()
except KeyboardInterrupt as e:
pass
def main():
import tempfile
import atexit
import shutil
os.chdir(tempfile.mkdtemp(prefix="visopts-"))
atexit.register(shutil.rmtree, os.getcwd())
print("generated files under directory: %s" % os.getcwd())
print(" the directory will be removed when this program has finished.")
if not render_on_html(sys.stdin):
return 1
run_httpd()
return 0
if __name__ == "__main__":
if '-h' in sys.argv or '--help' in sys.argv:
print(__doc__)
exit(0)
exit(main())