Skip to content

Creating Apps with OCaml and NetKAT

Craig Riecke edited this page Jun 21, 2016 · 3 revisions

There are two dialects for Frenetic-based SDN apps in OCaml: NetKAT and Ox. NetKAT is the easier dialect by far. For a more complete introduction, see the Frenetic Tutorial.

Here's a template to start off:

open Core.Std
open Async.Std
open Frenetic_NetKAT

let initial_policy : policy = Mod(Location(Pipe("controller")))

let rec handle_events (module Controller : Frenetic_NetKAT_Controller.CONTROLLER) =
  let open Controller in
  event () >>=
  fun evt ->
    let response = Frenetic_NetKAT_Json.event_to_json_string evt in
    printf "%s\n%!" response;
    handle_events (module Controller)

let _ =
  let module Controller = Frenetic_NetKAT_Controller.Make in
  Controller.start 6633;
  Controller.update_policy initial_policy;
  don't_wait_for(handle_events (module Controller));
  never_returns (Scheduler.go ());

This template is in src/frenetic/examples/template_netkat in the Frenetic User VM.

First, set up your environment so the controller can find Frenetic's OpenFlow executable. This only needs to be done once in the directory with your source code:

[email protected]:~/src/frenetic/examples$ ln -s ../_build/frenetic/openflow.native openflow.native

Then compile the code:

[email protected]:~/src/frenetic/examples$ ox-build template_netkat.d.byte

The ox-build is a bundled crutch build script, similar to Jane Street's core-build. See Building below for more sophisticated compilation methods.

In one Terminal window start up a sample Mininet network:

[email protected]:~/src/frenetic/examples$ sudo mn --topo=single,2 --controller=remote
*** Creating network
*** Adding controller
Unable to contact the remote controller at 127.0.0.1:6633
*** Adding hosts:
h1 h2
*** Adding switches:
s1
*** Adding links:
(h1, s1) (h2, s1)
*** Configuring hosts
h1 h2
*** Starting controller
c0
*** Starting 1 switches
s1 ...
*** Starting CLI:
mininet>

In another terminal window, start up your compiled application.

[email protected]:~/src/frenetic/examples$ ./template_netkat.d.byte
2016-06-15 10:25:58.465872-04:00 Info Calling create!
2016-06-15 10:25:58.465883-04:00 Info Current uid: 1000
2016-06-15 10:25:58.471469-04:00 Info Successfully launched OpenFlow controller with pid 3841
2016-06-15 10:25:58.471493-04:00 Info Connecting to first OpenFlow server socket
2016-06-15 10:25:59.141448-04:00 Info Failed to open socket to OpenFlow server: (Unix.Unix_error "Connection refused" connect 127.0.0.1:8984)
2016-06-15 10:25:59.141457-04:00 Info Retrying in 1 second
2016-06-15 10:26:00.142888-04:00 Info Successfully connected to first OpenFlow server socket
2016-06-15 10:26:00.142964-04:00 Info Connecting to second OpenFlow server socket
2016-06-15 10:26:00.143471-04:00 Info Successfully connected to second OpenFlow server socket
2016-06-15 10:26:00.147613-04:00 Info switch 282574488338432 connected
{"type":"switch_up","switch_id":282574488338432,"ports":[]}
2016-06-15 10:26:00.150971-04:00 Info switch 282574488338434 connected
{"type":"switch_up","switch_id":282574488338434,"ports":[]}
2016-06-15 10:26:00.153663-04:00 Info switch 1 connected
{"type":"switch_up","switch_id":1,"ports":[2,1]}

In the Mininet window, type pingall. The pings won't succeed, but you'll see the packets go to the controller in the application window:

{"type":"packet_in","pipe":"controller","switch_id":1,"port_id":1,"payload":{"buffer":"////////GrA3rmkOCAYAAQgABgQAARqwN65pDgoAAAEAAAAAAAAKAAAC","id":256},"length":42}
{"type":"packet_in","pipe":"controller","switch_id":1,"port_id":1,"payload":{"buffer":"////////GrA3rmkOCAYAAQgABgQAARqwN65pDgoAAAEAAAAAAAAKAAAC","id":257},"length":42}
{"type":"packet_in","pipe":"controller","switch_id":1,"port_id":1,"payload":{"buffer":"////////GrA3rmkOCAYAAQgABgQAARqwN65pDgoAAAEAAAAAAAAKAAAC","id":258},"length":42}
{"type":"packet_in","pipe":"controller","switch_id":1,"port_id":2,"payload":{"buffer":"////////gmHZS6HyCAYAAQgABgQAAYJh2Uuh8goAAAIAAAAAAAAKAAAB","id":259},"length":42}
{"type":"packet_in","pipe":"controller","switch_id":1,"port_id":2,"payload":{"buffer":"////////gmHZS6HyCAYAAQgABgQAAYJh2Uuh8goAAAIAAAAAAAAKAAAB","id":260},"length":42}
{"type":"packet_in","pipe":"controller","switch_id":1,"port_id":2,"payload":{"buffer":"////////gmHZS6HyCAYAAQgABgQAAYJh2Uuh8goAAAIAAAAAAAAKAAAB","id":261},"length":42}

Events

Frenetic events are the equivalent of OpenFlow Switch-to-Controller messages. In the sample code above, we simply convert events to a JSON equivalent and print them. In a real app, you would match these events against one of the following types, then do the appropriate action.

The following constructors are defined in lib/Frenetic_NetKAT.mli. The types associated with these are defined in that header file.

Constructor
PacketIn of string * switchId * portId * payload * int
Query of string * int64 * int64
SwitchUp of switchId * portId list
SwitchDown of switchId
PortUp of switch_port
PortDown of switch_port
LinkUp of switch_port * switch_port
LinkDown of switch_port * switch_port
HostUp of switch_port * host
HostDown of switch_port * host

Commands

Frenetic commands are equivalent to OpenFlow controller-to-switch messages. They are implemented in Frenetic as functions defined in the module type CONTROLLER in async/Frenetic_NetKAT_Controller.mli

Function
update_policy : policy -> unit Deferred.t
send_packet_out : switchId -> Frenetic_OpenFlow.pktOut -> unit Deferred.t
event : unit -> event Deferred.t
query : string -> (Int64.t * Int64.t) Deferred.t
port_stats : switchId -> portId -> OF10.portStats list Deferred.t
is_query : string -> bool
start : int -> unit
current_switches : unit -> (switchId * portId list) list Deferred.t
set_current_compiler_options : Frenetic_NetKAT_Compiler.compiler_options -> unit

The update_policy call is the equivalent of OpenFlow's Flow Modification call. Here, you send a NetKAT policy which is then compiled to OpenFlow tables and sent to the switches. Each time update_policy is called, all tables on all switches are completely replaced.

NetKAT function-call-based predicates and policies for pure OCaml are described in NetKAT OCaml Syntax

The send_packet_out is the equivalent of OpenFlow's Packet Out message. You must send a Frenetic_OpenFlow.pktOut record with the appropriate fields filled in.

About half the commands send back Deferred.t types, meaning they are called asynchronously. The call returns right away, but the actual switch action happens in the background. The Deferred.t type comes from the Jane Street Async library, so you can use its functions to wait for the results, etc.

Using the NetKAT Camlp4 Syntax Extension

Note: the syntax extension currently doesn't work. See https://github.com/frenetic-lang/frenetic/issues/507

Function-based NetKAT can be verbose and difficult to trace. You can use a variant of NetKAT Surface Syntax inside your OCaml program by using the NetKAT Syntax Extension. The above template program written with the extension differs in one line:

open Core.Std
open Async.Std
open Frenetic_NetKAT

let initial_policy : policy = <:netkat<port := pipe("controller")>>

(* Remaining program .... *)

You compile this program with the netkat-build command of the Frenetic User VM.

Building

The supplied ox-build script is nice for starting out. The script looks like this:

ocamlbuild \
  -use-ocamlfind -classic-display \
  -pkg core -pkg async -pkg frenetic -pkg frenetic.async \
  -tag thread -tag debug -tag annot -tag bin_annot -tag short_paths \
  -cflags "-w -40" \
  $@

You can add your own OPAM packages to the third line with the -pkg flag.

If this script starts getting too large, it's more scalable to use Oasis instead. A section for an _oasis file would look like this:

Executable mynetkat 
  Path: integration
  MainIs: My_NetKAT_App.ml
  Install: false
  CompiledObject: native
  BuildDepends:
    async,
    core,
    frenetic,
    frenetic.async,
    async_extended