-
Notifications
You must be signed in to change notification settings - Fork 51
Creating Apps with OCaml and NetKAT
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:
frenetic@ubuntu-14.04:~/src/frenetic/examples$ ln -s ../_build/frenetic/openflow.native openflow.native
Then compile the code:
frenetic@ubuntu-14.04:~/src/frenetic/examples$ ox-build template_netkat.d.byte
The
ox-build
is a bundled crutch build script, similar to Jane Street'score-build
. See Building below for more sophisticated compilation methods.
In one Terminal window start up a sample Mininet network:
frenetic@ubuntu-14.04:~/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.
frenetic@ubuntu-14.04:~/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}
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 |
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.
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.
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