Event-driven Network Programming

Downloads

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.

  1. Download a VM image (preferably 64bit). See above for the download links.
  2. Start VirtualBox on your host machine.
  3. Select "File" -> "Import Appliance", and select the downloaded image.
  4. If possible, give the VM more memory/processors to more closely match our baseline machine.
  5. You may need to turn on virtualization extensions in your BIOS to enable 64bit virtualization.
  6. Upon VM startup, the events user should be logged in (username: events, password: pldi2016).
  7. 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!)

  1. Download the source tarball. See above for the download link.
  2. Extract the tarball and run the install script.

    tar -xvf tools.tgz
    cd tools/events
    ./install

III. (Difficulty: hard) Build/install manually

  1. Download the source tarball (see above for the download link), extract it, and enter the tools directory.

    tar -xvf tools.tgz
    cd tools
  2. 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
  3. Build and install the OpenFlow reference implementation.

    cd openflow-mod
    ./boot.sh
    ./configure LIBS=-lrt
    make && sudo make install
    cd ..
  4. 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 the opam 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`
  5. (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 ..
  6. 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:

  1. 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 commit 4286ff68314fe673cebdb7fbf486918407038fdd ("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
  2. 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 instruct do_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

  1. 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. The program is the Stateful NetKAT program in Figure 11(a). The init state is the initial value of the state vector. The topology 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 as host: id mac=<addr> ip=<addr> - switch:port, representing a host with identifier id, MAC-address mac, IP-address ip, which is connected to switch:port.

  2. 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, and switch: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 to runtime.py).

    ./compiler firewall.netkat -o firewall.py
  3. 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 and iperf:

    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 contact h1 (first ping fails), but after h1 contacts h4 (second ping succeeds), then h4-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
    
  4. 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. The experiments directory contains JSON files which allow this compile-run-plot sequence to be automated. As an example, let us consider experiments/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 to firewall_correct.csv, and finally plots the results in firewall_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 as git checkout and make commands in the experiment's debugging output.

  5. 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:

  1. 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).

  2. 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.

  3. 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.

  4. 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

  1. 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), and knocking.netkat (Port Knocking). These can be compiled and run as discussed in the above Basic Usage section.

  2. 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
  3. 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. The experiments 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 and firewall_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 and learning_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 and port_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 the iperf commands in experiments/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.

  4. 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