#!/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}"

${expr_html}

        

open this svg in browser

open this svg in browser


""") 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())