Event-driven Network Programming
Downloads
- Accepted Paper (509.2 KB): paper.pdf
- "Preprint" Version of Paper (513.3 KB): paper-preprint.pdf
- Virtual Machine (VM) Image (5.0 GB): events64.ova, events32.ova (username:
events
, password:pldi2016
) - Source Tarball (3.9 MB): tools.tgz
The "preprint" version contains the same content/results, but is un-anonymized, and includes minor formatting improvements and typo fixes.
Backup mirrors: paper.pdf, paper.pdf, paper-preprint.pdf, paper-preprint.pdf, events64.ova, events32.ova, tools.tgz
AEC Conflicts
- Jean-Baptiste Jeannin
Our Experimental Platform:
- x86_64 Ubuntu (14.04.1) workstation with 20 GB RAM and quad-core i5-4570 CPU (3.2 GHz)
We recommend using a 64bit host machine with at least 2+ processors and 8+ GB RAM.
Installation
There are several options for installation:
I. (Difficulty: easy) Use a provided Virtual Machine (VM) image.
- Download a VM image (preferably 64bit). See above for the download links.
- Start VirtualBox on your host machine.
- Select "File" -> "Import Appliance", and select the downloaded image.
- If possible, give the VM more memory/processors to more closely match our baseline machine.
- You may need to turn on virtualization extensions in your BIOS to enable 64bit virtualization.
- Upon VM startup, the
events
user should be logged in (username:events
, password:pldi2016
). - Open a terminal and navigate to the
~/tools/events
directory.
II. (Difficulty: moderate) Build/install automatically using the provided installer script.
(WARNING: this may change your OCaml installation!)
- Download the source tarball. See above for the download link.
-
Extract the tarball and run the install script.
tar -xvf tools.tgz cd tools/events ./install
III. (Difficulty: hard) Build/install manually
-
Download the source tarball (see above for the download link), extract it, and enter the
tools
directory.tar -xvf tools.tgz cd tools
-
Install all of the prerequisite packages.
sudo apt-get -y install git dh-autoreconf sudo apt-get -y install mininet m4 ncurses-dev zlib1g-dev sudo apt-get -y install gnuplot python texlive texlive-pictures
-
Build and install the OpenFlow reference implementation.
cd openflow-mod ./boot.sh ./configure LIBS=-lrt make && sudo make install cd ..
-
Install Frenetic from its Git repository. We use the version corresponding to commit
8c64e57d78e73f168ea6c8d780bd3c339e59d62b
. The following commands assume you currently do not have OPAM or Frenetic installed. If Frenetic is already installed, you must first remove it. If OPAM is already installed, begin with theopam switch 4.02.3
command. NOTE: it can be very difficult to successfully complete this step using an existing OPAM installation (due to version conflicts with existing packages, etc.) -- we highly recommend starting with a fresh OPAM installation.sudo add-apt-repository -y ppa:avsm/ppa sudo apt-get update sudo apt-get -y install opam opam init -y opam switch 4.02.3 eval `opam config env` echo "eval \`opam config env\`" >> ~/.bashrc opam pin -y add frenetic https://github.com/frenetic-lang/frenetic#8c64e57d78e73f168ea6c8d780bd3c339e59d62b -k git eval `opam config env`
-
(Optional) Install Uruz, a tool for building OCaml parser/lexer definitions from a high-level EBNF grammar. This is only needed if you plan to make modifications to the Stateful NetKAT syntax etc.
git clone https://github.com/jrmcclurg/uruz.git cd uruz export PATH=$PATH:`pwd`/ver1:`pwd`/ver2 echo "export PATH=\$PATH:`pwd`/ver1:`pwd`/ver2" >> ~/.bashrc cd ver1 && make && cd .. cd ver2 && make && cd .. cd ..
-
Enter the
events
directory, and build the compiler.cd events make
You can check that the compiler was built correctly by typing
./compiler --help
Source-Code and Tool Organization
The tools
directory (~/tools
on the VM) is where the two main components
are located:
-
The
tools/openflow-mod
directory contains the (modified) OpenFlow implementation.It is provided as a local Git repository, so that we can switch back and forth between the standard implementation and our modified one. The commit
166c48c71d0b9f2bc5631429b3969a5f5d079087
("reply for flow-mod") is essentially the original reference version (with a few bug fixes), and the commit4286ff68314fe673cebdb7fbf486918407038fdd
("debugging output") corresponds to our modified version, providing the functionality needed in Section 4 of the paper.At a high level, the code in this directory implements the various OpenFlow tools, e.g. the OpenFlow controller, switch, configuration tool, etc. The following commands can be used to check that these tools (respectively) have been properly installed.
controller --help ofdatapath --help dpctl --help
-
The
tools/events
directory contains the compiler and its source code, etc.-
compiler
- the binary for the compiler (run./compiler --help
to see options). -
do_exp
- Python script for running experiments (run./do_exp --help
to see options). -
compiler.gra
- the high-level specification of the Stateful NetKAT / topology input file format. It has already been compiled to the OCaml parser/lexer definitions (compiler_*.ml*
). -
main.ml
- the entry-point (main OCaml function) of the compiler. -
flags.ml
- command-line parsing and global flags for the compiler. -
code.ml
- the compiler's primary code-transformation/generation functionality. This provides functionality to build static NetKAT programs and flow tables from Stateful NetKAT programs, produce Mininet configuration scripts, etc. -
experiments/*.json
- these instructdo_exp
which commands to run for for the experiments. -
experiments/*.pg
- GnuPlot scripts to generate plots from experimental data. -
experiments/*.py
- Python code for parsing/manipulating OpenFlow log-file output. -
cached/*.csv
- data produced by running all of the experiments on our experimental platform (Figures in the paper were plotted from this data).
-
Basic Usage
-
Writing Stateful NetKAT programs.
The syntax of Stateful NetKAT is detailed in Section 3.2 of the paper. We provide an ASCII input format for writing these programs, as well as for specifying the desired network topology. The
compiler.gra
file roughly corresponds to an EBNF grammar for this input format, but we can explain things more simply by looking at a specific example.program: pt=2 & eth_type=0x0800 & ip_dst=10.0.0.4; pt:=1; (state=[0]; 1:1 -> 4:1 -> state:=[1] + !state=[0]; 1:1 -> 4:1); pt:=2 + pt=2 & eth_type=2048 & ip_dst=10.0.0.1; state=[1]; pt:=1; 4:1 -> 1:1; pt:=2 init state: [0] topology: { (4:1 - 1:1) } host: 4 mac=00:00:00:00:00:44 ip=10.0.0.4 - 4:2 host: 1 mac=00:00:00:00:00:11 ip=10.0.0.1 - 1:2
The above
firewall.netkat
file corresponds to the Stateful Firewall example in Section 5.1 of the paper. Theprogram
is the Stateful NetKAT program in Figure 11(a). Theinit state
is the initial value of the state vector. Thetopology
is a set of (bidirectional) links of the form(switch:port - switch:port)
(the topology in the above example corresponds to Figure 7(a) in the paper). Hosts can be specified ashost: id mac=<addr> ip=<addr> - switch:port
, representing a host with identifierid
, MAC-addressmac
, IP-addressip
, which is connected toswitch:port
. -
Using the complier.
Our compiler takes as input a Stateful NetKAT program and network topology (in the form of a
.netkat
file, as described above), and produces (1) a Network Event Structure (NES) corresponding to the program (compilation of Stateful NetKAT programs is described in Section 3.3 of the paper), and (2) a custom Mininet setup script which builds (a simulation of) a network implementing the NES (the implementation techniques are described in Section 4 of the paper).Returning to the
firewall.netkat
example, we can compile it as follows:./compiler -quiet firewall.netkat
This produces the NES corresponding to the Stateful Firewall program (Section 5.1 of the paper). The resulting NES will be displayed as follows:
1 {(eth_type=2048,ip_dst=167772164 @ 4:1):1} (pt=2;eth_type=2048;ip_dst=167772164;pt:=1;(false;1:1->4:1+true;1:1->4:1);pt:=2+pt=2;eth_type=2048;ip_dst=167772161;true;pt:=1;4:1->1:1;pt:=2) -> [] *0 {} (pt=2;eth_type=2048;ip_dst=167772164;pt:=1;(true;1:1->4:1+false;1:1->4:1);pt:=2+pt=2;eth_type=2048;ip_dst=167772161;false;pt:=1;4:1->1:1;pt:=2) -> [(eth_type=2048,ip_dst=167772164 @ 4:1):1]
Each line corresponds to a single NES vertex, and has the following format
optional_star id {(event_condition @ switch:port):event_id, ...} (static_netkat_program) -> [(event_condition @ switch:port):next_vertex, ...]
where
optional_star
is either blank or*
(marking the initial NES vertex),id
is the vertex identifier, the set{...}
is the vertex's event-set,static_netkat_program
is the vertex's static configuration, and the elements in[...]
are the event-annotated edges connecting the vertex to other vertices. For each of the events,event_condition
is a first-order formula over packet header fields, andswitch:port
is the location of the event.If the
-quiet
option is omitted, the compiler additionally displays the flow tables corresponding to each vertex's static configuration.As mentioned in Section 5 of the paper, we have built a modified OpenFlow reference switch/controller implementation which enables the stateful-switch behavior described in Section 4.1-4.2. In addition to displaying the NES, our compiler produces a Python script which configures Mininet to set up a network with the specified topology, using the modified switch/controller implementations. The
-o
option tells the compiler where to place this Mininet "runtime" (if-o
is omitted, the runtime is dumped toruntime.py
)../compiler firewall.netkat -o firewall.py
-
Running a compiled program in Mininet.
The Mininet runtime script (introduced above) tells Mininet how to correctly "run" the event-driven program (encoded as an NES) in the network, as discussed in Section 4 of the paper. Specifically, the runtime script instructs Mininet to set up a simulated network with the specified topology, and loads the static configurations of the NES onto the switches. The script also instructs Mininet to use our customized OpenFlow switch/controller implementations, which provide the functionality to do packet tagging, local register modification, etc. (Section 4.1 of the paper).
Returning again to the firewall example, after compiling
firewall.netkat
, we can run the resulting NES in Mininet as follows:sudo ./firewall.py
This will list the rules installed in each switch's flow table, and will then produce a Mininet prompt. At the prompt, it is possible to perform networking commands such as
ping
andiperf
:mininet> h4 ping h1 -w 1 PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data. --- 10.0.0.1 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 999ms mininet> h1 ping h4 -w 1 PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data. 64 bytes from 10.0.0.4: icmp_seq=1 ttl=64 time=1.03 ms --- 10.0.0.4 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.037/1.037/1.037/0.000 ms mininet> h4 ping h1 -w 1 PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data. 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.704 ms 64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=1.32 ms --- 10.0.0.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 999ms rtt min/avg/max/mdev = 0.704/1.015/1.326/0.311 ms mininet> exit
In the above example, note the correct firewall behavior (as described in Section 5.1 of the paper): initially
h4
is not allowed to contacth1
(first ping fails), but afterh1
contactsh4
(second ping succeeds), thenh4
-to-h1
communication is enabled (third ping succeeds).In Section 5.1 of the paper, we describe a simple "best-effort" update strategy, and compare it with our correct event-driven update implementation techniques. We can tell the compiler to give us a Mininet runtime which operates in this best-effort mode.
./compiler -disable-digest -interval 4000 firewall.netkat -o firewall_bad.py
Note that upon running this instance (
sudo ./firewall_bad.py
), we may see incorrect behavior (as in Figure 8 of the paper):mininet> h4 ping h1 -w 1 PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data. --- 10.0.0.1 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0ms mininet> h1 ping h4 -w 1 PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data. --- 10.0.0.4 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0ms mininet> h4 ping h1 -w 1 PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data. 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.673 ms --- 10.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.673/0.673/0.673/0.000 ms mininet> exit
-
Running an experiment.
The above sequences of Mininet commands (
ping
, etc.) can be automated by inserting additional Python code into the generated runtime file, and the resulting success/failure can be emitted in Comma-Separated Values (CSV) format and plotted to produce a PDF figure. Theexperiments
directory contains JSON files which allow this compile-run-plot sequence to be automated. As an example, let us considerexperiments/firewall_correct.json
, which allows us to perform the experiment which produces Figure 8(a) (Correct Stateful Firewall) in the paper../do_exp firewall_correct
This above command first compiles the Stateful Firewall example, producing the custom Mininet runtime
firewall_correct.py
. It then runs Mininet and dumps the results tofirewall_correct.csv
, and finally plots the results infirewall_correct.pdf
.Note that some of the experiments (e.g. the
bandwidth*.json
ones) need to switch (and recompile) the OpenFlow code between the standard (reference) implementation and our modified implementation. This will show up asgit checkout
andmake
commands in the experiment's debugging output. -
Cleaning up the experimental results.
The following command can be used to remove all of the generated experimental results (CSV files, PDF files, etc.):
make reset
Claims and Assumptions Made in the Paper
The Research Questions paragraph at the beginning of Section 5 in the paper enumerates the items we seek to demonstrate via the experimental results. Specifically:
-
Our approach is useful -- it allows programmers to write real-world network programs, and achieve the expected program behavior from the running program in the network.
We show this by building and evaluating three real-world applications (Stateful Firewall, Learning Switch, and Port Knocking in Section 5.1).
-
The performance of the tools (compiler, etc.) is reasonable.
We show this by measuring the runtime of the tools operating on the various input programs.
-
Our correctness guarantee is helpful -- for instance, our running network programs behave more predictably/intuitively than simple best-effort implementations of the same programs.
We show this using the experiments corresponding to Figures 8-10.
-
Our SDN-implementation strategy is reasonably efficient. Specifically, message overhead, state-change convergence time, and number of rules used is reasonable, even when compared to other implementation strategies (simple best-effort).
We show this using the experiments corresponding to Figure 12, and by evaluating our rule-reduction heuristic (Figure 13 and Section 5.3).
As mentioned immediately before Section 5.1 of the paper, our prototype implementation makes the following assumptions: (1) the programmer (not the compiler) ensures that the Section 3.1 conditions are satisfied (enabling proper compilation to an NES), and (2) the resulting NES does not contain loops (enabling proper implementation of the NES using bounded event-sets).
Reproducing Results from the Paper
-
Testing the Stateful NetKAT programs discussed in Section 5 of the paper.
We have included the three examples:
firewall.netkat
(Stateful Firewall),learning.netkat
(Learning Switch), andknocking.netkat
(Port Knocking). These can be compiled and run as discussed in the above Basic Usage section. -
Plotting cached results (i.e. data used in the paper).
The
cached
directory contains the exact CSV data files used to produce the Figures in Section 5 of the paper (these were generated on our experimental platform, using the below calls to./do_exp
). The following command can be used to simply reproduce the PDF plots from this data:make cached
-
Generating the figures in Section 5 of the paper from scratch.
In the Usage section, we saw how to use
./do_exp
to run an experiment. Theexperiments
directory contains a JSON file(s) corresponding to each experiment in the paper. Each JSON file specifies (1) how to compile the Stateful NetKAT program, (2) what Mininet commands to run during the simulation, and (3) how to plot the resulting output from the Mininet simulation.Figure 8 (Stateful Firewall):
./do_exp firewall_correct ./do_exp firewall_bad
This takes about 1 minute on our experimental platform, and produces files
firewall_correct.pdf
andfirewall_bad.pdf
, corresponding to the left/right of the figure respectively.Figure 9 (Learning Switch):
./do_exp learning_correct ./do_exp learning_bad
This takes about 25 seconds on our experimental platform, and produces files
learning_correct.pdf
andlearning_bad.pdf
.Figure 10 (Port Knocking):
./do_exp port_knocking_correct ./do_exp port_knocking_bad
This takes about 1 minute on our experimental platform, and produces files
port_knocking_correct.pdf
andport_knocking_bad.pdf
.Left side of Figure 12 (Circular Example):
./do_exp bandwidth_tcp bandwidth_tcp_old bandwidth_udp bandwidth_udp_old
This takes about 18.5 minutes on our experimental platform, and produces file
bandwidth.pdf
. A quicker approximation of this experiment can be created by reducing the-t
values for theiperf
commands inexperiments/bandwidth*.json
(the-t
values are measured in seconds).Right side of Figure 12 (Circular Example):
./do_exp convergence convergence_with_ctrl
This takes about 1.5 minutes on our experimental platform, and produces file
convergence.pdf
.Figure 13 (Polynomial heuristic):
./do_exp rule_num
This takes about 2 seconds on our experimental platform, and produces file
rule_num.pdf
. -
Confirming rule reduction results for the case studies (end of Section 5.3).
The compiler always displays the (numeric) amount of rule reduction possible:
./compiler firewall.netkat | grep RULES ./compiler learning.netkat | grep RULES ./compiler knocking.netkat | grep RULES
We can also instruct the compiler to use the reduced set of rules in the generated Mininet runtime:
./compiler -opt knocking.netkat -o knocking.py
We can then run the example (
sudo ./knocking.py
), and double-check that it still works as expected (e.g. Figure 10(a) in the paper):mininet> h4 ping h3 -w 1 PING 10.0.0.3 (10.0.0.3) 56(84) bytes of data. --- 10.0.0.3 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0ms mininet> h4 ping h2 -w 1 PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data. --- 10.0.0.2 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 999ms mininet> h4 ping h1 -w 1 PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data. 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.559 ms --- 10.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.559/0.559/0.559/0.000 ms mininet> h4 ping h3 -w 1 PING 10.0.0.3 (10.0.0.3) 56(84) bytes of data. --- 10.0.0.3 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 999ms mininet> h4 ping h2 -w 1 PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data. 64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=1.30 ms --- 10.0.0.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.303/1.303/1.303/0.000 ms mininet> h4 ping h3 -w 1 PING 10.0.0.3 (10.0.0.3) 56(84) bytes of data. 64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=1.17 ms --- 10.0.0.3 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.177/1.177/1.177/0.000 ms mininet> exit