Chapter 2: Repeater with Ox

OpenFlow Introduction

In software-defined network, all switches connect to a logically centralized controller. The controller maintains a global view of the network and programs the switches to implement a unified, network-wide policy. The controller and switches communicate using a standard protocol such as OpenFlow.

A switch processes packets using a flow table, which is a list of prioritized rules. Each rule has several components:

For example, consider the following flow table:

Priority Pattern Actions Packets Bytes
50 ICMP   2 148
40 TCP Output 2, Output 5 5 1230
30 UDP Controller 3 284
20 ICMP Output 2 0 0

Read from top to bottom, these rules can be understood as follows:

Note that, in principle, we could implement any function we like using the controller—e.g., deep packet inspection. But processing packets on the controller is typically orders of manitude slower compared to processing packets on switches. Hence, programmers typically install forwarding rules that handle the vast majority of all traffic, and make limited use of the Controller action.

Warmup: Programming a Repeater

Repeater

As a first exercise, let us build a simple repeater. A repeater is a network element that forwards all packets received as input on all of its other ports. We will build our repeater in two steps:

This two-step exercise may seem contrived for a simple repeater. But, we will quickly escalate to programs where the interaction between controller and switches gets tricky. For these programs, the first naive implementation will serve as a reference implementation to help determine if the more efficient implementation is correct. We will also see that there are sometimes corner cases where it is necessary to process packets on both the controller and switches. So, in practice, one typically does need both implementations.

Exercise 1: A Naive Repeater

Solution

In this part, you will write a repeater that processes all packets at the controller. By default, when an OpenFlow switch does not contain any rules, it diverts all packets to the controller in a packet_in message. Therefore, this repeater only needs to provide a packet_in handler. We have provided some starter code in a template below.

Fill in the body of this function and save it in a file called ox-tutorial-solutions/Repeater1.ml.

open Frenetic_Ox
open Frenetic_OpenFlow0x01
open Core.Std
open Async.Std

module MyApplication = struct
  include DefaultHandlers
  open Platform

  let packet_in (sw : switchId) (xid : xid) (pk : packetIn) : unit =
     ...

end

let _ =
  let module C = Make (MyApplication) in
  C.start ();

You will need to use the send_packet_out command, which takes a list of actions (apply_actions) to apply to the packet:

let packet_in (sw : switchId) (xid : xid) (pk : packetIn) : unit =
  Printf.printf "%s\n%!" (packetIn_to_string pk);
  send_packet_out sw 0l {
    output_payload = pk.input_payload;
    port_id = None;
    apply_actions = ... (* [FILL] *)
  }

The list of actions we want is one that will send the packet out all ports excluding the input port. This is easier than it may sound, because OpenFlow includes a single primitive that provides exactly this functionality. Find the right action in the Ox manual (it is in the [OpenFlow_Core] module) and fill it in.

Compiling your Controller

To build your controller, run the following command from the ox-tutorial-solutions directory:

$ ./ox-build Repeater1.d.byte

Assuming compilation succeeds, you will see output like this:

ocamlbuild ocamldep -package frenetic.async -package frenetic -package async -package core -modules Repeater1.ml Repeater1.d.byte
...

Testing your Controller

You can test your controller using Mininet, which is included in the tutorial VM. Mininet runs a virtual network on your computer, isolating each virtual host in a Linux container. To test the repeater, use Mininet to create a network with one switch and three hosts and have them ping each other:

Shut down the controller properly with Ctrl+C and Mininet with Ctrl+D.

Aside: For the most part, we will be using simple topologies in this tutorial. However, if you ever want to know more information about the topology mininet is currently running, you can type

mininet> net

In this example, you should see the following.

c0
s1 lo:  s1-eth1:h1-eth0 s1-eth2:h2-eth0 s1-eth3:h3-eth0 s1-eth4:h4-eth0
h1 h1-eth0:s1-eth1
h2 h2-eth0:s1-eth2
h3 h3-eth0:s1-eth3
h4 h4-eth0:s1-eth4

The first line indicates there is a controller (c0) running. The second line lists the ports on switch s1: port 1 (s1-eth1) is connected to host h1, port 2 (s1-eth2) is connected to host h2,and so on. If there was more than one switch in the network, we would see additional lines prefixed by the switch identifier, one line per switch. The remaining lines describe the hosts h1 through h4.

Exercise 2: An Efficient Repeater

Solution

Processing all packets at the controller works, in a sense, but is inefficient. Next let’s install forwarding rules in the flow table on the switch so that it processes packets itself.

For this part, we will continue building on the naive repeater from above. We will add a switch_connected handler. This function is invoked when the switch first connects to the controller. Hence, we can use it to install forwarding rules in its forwarding table. Use the following code as a template.

let switch_connected (sw : switchId) feats : unit =
  Printf.printf "Switch %Ld connected.\n%!" sw;
  send_flow_mod sw 1l (add_flow priority pattern action_list)

The function send_flow_mod adds a new rule to the flow table of the switch. Your task is to fill in priority, pattern, and action_list.

Building and Testing Your Controller

We can build and test this extended repeater in exactly the same way as before. But now, during testing, the controller should not receive any packets.

Why Keep the Controller Function?

We now have two implementations of the repeater: the packet_in function on the controller and the flow table on the switch. Since the switch is so much faster, it is natural to wonder why we would want to keep the packet_in function at all!

It turns out that there are still situations where the packet_in function is necessary. We’ll try to create such a situation artificially:

It is very likely that a few packets will get sent to the controller because when we launch the controller and the switch re-connects, the controller sends two messages:

In the intervening time between these two messages, the flow table is empty, thus some packets may get diverted to the controller. More generally, whenever the switch is configured for the first time, or re-configured to implement a policy change, we may see packets at the controller. Hence, the controller need both (redundant) definitions of the intended packet-processing functions.

API Reference

OpenFlow_Core

send_stats_request

header accessor functions

send_flow_mod

pattern

match_all

Action

PacketIn

PacketOut

OxPlatform

Match

Packet

Network module

Topology