How to Add an OpenFlow Experimenter Extension

Table of Contents

Objective

The goal of this tutorial is to provide an introduction to Loxigen and how it creates OpenFlow libraries with emphasis on adding experimenter extensions. Examples from Loxigen will be used to demonstrate syntax; however, not everything will be discussed in detail. Some reading of the Loxigen OpenFlow input files might be necessary to find examples relevant to your specific use case. Write to our email list if you have trouble or need guidance.

Note that any experimenter extensions added to Floodlight must be supported by the switch with which you intend to use them. This means you typically need to also modify switch source code with the same extensions. The method for doing so will vary from switch to switch, and note that some switches (in particular hardware switches) might not support the addition of experimenter extensions. Note that OVS has documentation here on how to add extensions.

Overview

Under the hood, Floodlight uses the OpenFlowJ-Loxi library, which defines and implements each version of the OpenFlow protocol. It also handles the serializing and deserializing of OpenFlow messages to and from the wire. In other words, OpenFlowJ-Loxi abstracts away all the low-level OpenFlow details from Floodlight developers.

OpenFlow defines a mechanism where custom types and even messages can be added to the OpenFlow protocol. These additions are referred to as experimenter extensions. Because these are extensions to the OpenFlow protocol itself, and because the OpenFlow protocol is defined by OpenFlowJ-Loxi, it reasons that OpenFlowJ-Loxi must be modified if one wishes to implement an experimenter extension.

Although modifying the OpenFlow library might sound like a challenging task, it’s actually very straightforward. OpenFlowJ-Loxi is a library generated by the Loxigen project. Loxigen takes a set of OpenFlow input files defining OpenFlow types and messages. From this single set of input files, Loxigen produces OpenFlow libraries for the language of your choice – be it Java, Python, or C. As such, in order to update OpenFlowJ-Loxi to include our new experimenter extension, we must add the experimenter extension to the OpenFlow input files within Loxigen. After this is done, we can then tell Loxigen to parse these updated OpenFlow input files and produce a new OpenFlowJ-Loxi library.

Defining the OpenFlow Protocol in Loxigen

Discovering the Syntax of OpenFlow Input Files by Example

Let’s begin by discussing the location and structure of the Loxigen OpenFlow input files. The Loxigen repository is located at http://github.com/floodlight/loxigen, and the OpenFlow input files are within the openflow_input directory. Because Loxigen is supported by Big Switch Networks, there are a number of bsn_* files within openflow_input. These are themselves experimenter extensions, however you can safely ignore them for this tutorial.

(warning)Floodlight v1.1 and v1.0 should use the "netty3" branch of Loxigen, while Floodlight v1.2 and greater (including master) should use Loxigen master. Floodlight v0.91 and earlier do not use Loxigen and are not applicable to this tutorial.

Towards the bottom of the list of files, you’ll notices a few named:

  • oxm-1.2
  • oxm-1.3
  • oxm-1.4

and:

  • standard-1.0
  • standard-1.1
  • standard-1.2
  • standard-1.3
  • standard-1.4

Files beginning with oxm-* define the OpenFlow Extensible Matches (OXMs) for each OpenFlow protocol version from when the OXM was introduced in OpenFlow 1.2 to 1.4, the highest version of OpenFlow defined in Loxigen. An OXM is how OpenFlow performs matches and set-field actions in OpenFlow 1.2+. For example, looking at oxm-1.2, we can see that the OXM for destination MAC address, both unmasked and masked is defined as:

struct of_oxm_eth_dst : of_oxm {
    uint32_t type_len == 0x80000606;
    of_mac_addr_t value;
};
 
struct of_oxm_eth_dst_masked : of_oxm {
    uint32_t type_len == 0x8000070c;
    of_mac_addr_t value;
    of_mac_addr_t value_mask;
};

Let’s explore the syntax used here. The destination MAC address OXMs are defined as a C-like struct that extends via the “:” operator the of_oxm struct, which is defined earlier at the top of the oxm-1.2 input file as:

struct of_oxm {
    uint32_t type_len == ?;
};

It is important that any parent struct, such as of_oxm, be defined before it is referenced within any other struct, like of_oxm_eth_dst or of_oxm_eth_dst_masked (else Loxigen compile errors will ensue). OpenFlow input files are parsed by Loxigen in alphabetical order and from top to bottom within each file. Keeping this convention in mind will allow us to strategically locate dependencies “above” where such dependencies are used or referenced.

An of_oxm defines a single 32-bit field called type_len. The value of this field is undefined, indicated by a wildcard “?”. The implication is that some other struct will extend the of_oxm struct and fully define the type_len field. (Think of of_oxm as an abstract class in an OO sense.) As shown above, the MAC address OXMs extend of_oxm and complete the value of the type_len field as appropriate for each OXM and as defined in the OpenFlow specification. Since the MAC address structs extend the of_oxm struct, additional fields can be defined for the OXM, extending the parent of_oxm struct. As makes sense for a MAC address OXM, the unmasked OXM contains the exact MAC address to match, while the masked OXM contains the MAC address to match plus the mask to apply.

There are a number of predefined types for use in Loxigen structs:

  • uint8_t
  • uint16_t
  • uint32_t
  • uint64_t
  • uint128_t
  • of_octets_t
  • enum <defined-enum>
  • list(<defined-type>)
  • pad(<num-bytes>)
  • of_port_no_t
  • of_mac_addr_t
  • of_ipv4_addr_t
  • of_ipv6_addr_t

of_mac_addr_t is used in the destination MAC address example OXMs we're discussing.

Scope of OpenFlow Input Files

The OpenFlow version scope of any OpenFlow input file is defined at the top of the file by:

#version <of-version-raw>

where <of-version-raw> is the integer version of OpenFlow as defined in the specification of that OpenFlow version:

  • OpenFlow 1.0 = 1
  • OpenFlow 1.1 = 2
  • OpenFlow 1.2 = 3
  • OpenFlow 1.3 = 4
  • OpenFlow 1.4 = 5

As an example, the top of the oxm-1.2 file lists the applicable versions as:

#version 3
#version 4
#version 5

since OpenFlow 1.2 OXMs are defined the same in OpenFlow 1.2, 1.3, and 1.4. With regard to our destination MAC address example, this makes sense, since OpenFlow 1.2, 1.3, and 1.4 defines the same OXMs for matching on destination MAC addresses (unmasked and masked). The same is true for any OXM in the oxm-1.2 input file. The oxm-1.3 file shortens the applicable versions to:

#version 4
#version 5

or OpenFlow 1.3 and 1.4. Thus, from what we’ve discussed so far, we can see that OpenFlow 1.3 OXMs are defined in the oxm-1.2 and the oxm-1.3 file due to the inclusion of the #version 4 line at the top of each of these input files.

If an input file is to apply to all OpenFlow versions, the keyword “any” can be used in place of an actual integer version, for example:

#version any

Adding an Experimenter Extension

Warning for Adding any Experimenter Extension

Although it is rather straightforward to add an extension to Loxigen, the switch the experimenter extension is going to be used with must also be equipped with the same experimenter extension. For example, if another OXM is defined to match on some exotic header field, the controller will then be able to define a flow that matches on this field, but unless the switch "knows" how to interpret the newly-defined OXM, the flow will be rejected.

This tutorial covers how to add experimenter extensions to Floodlight via Loxigen. It does not cover adding experimenter extensions to the switch, such as your hardware OpenFlow switch or OVS. Note that adding an experimenter extension to a hardware switch is likely difficult, since most firmwares are proprietary and do not expose the ability to plug-in your own extensions. However, OVS and other software switches readily accept experimenter extensions. The OSS Indigo virtual switch is also based on Loxigen, so in theory, adding an experimenter extension to Loxigen should allow you to add it to both Floodlight and Indigo at the same time. The Loxigen-generated libraries should simply be swapped out for each – Floodlight and Indigo. Both Floodlight and Indigo source will need to be changed though as necessary in order to utilize the newly added extension.

Unfortunately, I am not aware of any documentation on how to add experimenter extensions to OVS. If anyone is, please send an email to floodlight-dev@openflow.org so that we can link to it.

Adding an Experimenter OXM

Oftentimes, developers want to match on a header field not defined in the OpenFlow specification. For example, one might wish to match on HTTP methods like GET, POST, etc, which isn't defined in any OpenFlow protocol version. An experimenter extension can be used to define a custom OXM or match field.

Note that matching on HTTP on the switch is going to be more complex than this, since (1) there isn't a way in TCP to specify the payload's protocol like there is between Ethernet and IP and between IP and transport and (2) a TCP segment containing HTTP data will only contain the HTTP header in the first TCP packet. The other packets in the segment will only have data. Thus, the switch would need to implement some sort of stateful packet matching if matching on HTTP methods, but the controller could specify such matching through an OXM as we will implement.

Each oxm-* file includes the OXMs for that OpenFlow version. Recall that subsequent oxm-* files include the previous oxm-* files. Thus, if we want to add an OXM for use by OpenFlow 1.2 and above, it could go in the oxm-1.2 file, which is included by oxm-1.3 and oxm-1.4. If we want to add an OXM for use by OpenFlow 1.3 and above, it could go in the oxm-1.3 file, which is included in oxm-1.4 but not in oxm-1.2. The simplest solution to adding an experimenter OXM is to add it to an oxm-* file. However, to distinguish between those OXMs that are a part of the OpenFlow specification and those experimenter OXMs, a new file can also be used as long as it come alphabetically after oxm-1.2, where the of_oxm struct is defined. We will do the latter in this example to demonstrate the most complex case.

Let's say we want to add an experimenter OXM to match on the HTTP methods as discussed above. The first thing to do is create a new file in the openflow_input directory. Since it must come after oxm-1.2, let's call it oxm-http.

Within oxm-http, let's first define the OpenFlow versions we want this OXM to apply to:

/* 
 * oxm-http:
 * Match on HTTP method
 */
 
#version 4
#version 5

Then, we'll define the name of our new OXM and the parent it extends:

/* 
 * oxm-http:
 * Match on HTTP method
 */
 
#version 4
#version 5
 
struct of_oxm_http_method : of_oxm {
    uint32_t type_len == 0xffff0205;
	uint32_t experimenter_id == 0x0000; # should be set to your own experimenter ID
    uint8_t value;
}

As defined in the OpenFlow specification, an OXM header consists of a 32-bit type-length where bits 31-16 are the class, bits 15-9 are the field within the class, bit 8 defines whether or not the OXM has a mask, and bits 7-0 are the length in bytes of the value following and not including this 4-byte OXM header. An experimenter class is 0xffff. Thus, the upper 2 bytes of our type_len field are 0xffff. We'll define this HTTP method OXM to be field number 1, or 0x02. The OXM will not sport a mask, so bit 8 is a 0. The least significant byte is a 0x05, since the fields defined within our OXM are four bytes for the experimenter ID and only a single byte for the HTTP command (i.e. the uint32_t is four bytes and the uint8_t is a single byte).

An HTTP method has an ID associated with it that can be parsed from the HTTP header. For example, an HTTP GET is method ID 1, an HTTP POST is method ID 3, and an HTTP DELETE is method ID 5. The uint8_t value field will be used to specify the HTTP method on which we wish to match. The switch will be responsible for decoding this method in the same manner and implementing the match.

Additional Steps for Java

For C and Python, that is the only change required to add the experimenter extension. However, for Java, there are a couple extra steps required.

  1. The OXM needs to be added to a prewritten enum defining all possible OXMs.
  2. The OXM needs to be defined with its prerequisites.
  3. Any types used in your OXM need to be defined.

First, to add the OXM to the enum, simply append the name of the OXM in all capital letters to the end of the enum definition. The name should be the same as defined by the OpenFlow input struct minus the beginning of_oxm_. So, for our HTTP method OXM, we'll add HTTP_METHOD to the enum.

public enum MatchFields {
    IN_PORT,
	IN_PHY_PORT,
    ...,
	TUNNEL_IPV4_SRC,
    TUNNEL_IPV4_DST,
	HTTP_METHOD,
	...
}

Next, we'll add our OXM and the prerequisite matches that must also take place in order to use our OXM in a flow. Since our OXM matches on HTTP methods, we'll want to make sure the packet's we're matching are in fact HTTP packets. A prerequisite to HTTP is typically TCP, so we will include it as our prerequisite. Note that TCP itself has a prerequisite of IP, which will automatically be included as a prerequisite of HTTP.

public final static MatchField<U8> HTTP_METHOD =
    new MatchField<U8>("http_method", MatchFields.HTTP_METHOD,
        new Prerequisite<IpProtocol>(MatchField.IP_PROTO, IpProtocol.TCP));

Add this anywhere within the class:

public class MatchField<F extends OFValueType<F>>

And lastly, we'll add an entry into java_gen/java_type.py's exception list to explicitly tell Loxigen what Java types it should map to our OXM:

exceptions = {
	...,
	...,
	'of_oxm_http_method': { 'value' : u8obj },
	...,
	...
}

Adding an Experimenter Action

TODO

Adding an Experimenter Message

TODO

Generating an OpenFlow Library with Loxigen

Once you've made the changes necessary to add your new extension to Loxigen, it's time to generate a new OpenFlow library – OpenFlowJ-Loxi for Java, Loci for C, and PyLoxi for Python – to replace the one where you're wanting to add your extension. To generate the OpenFlow bindings for the language you want, start by following the instructions for installing Loxigen prerequisites, if you haven't do so already. Then follow the instructions below for the language of your choice. These instructions are also given, in less detail, in the Loxigen README.

Generating OpenFlowJ-Loxi

If you'd like to produce an OpenFlowJ-Loxi jar for inclusion in a Java project, such as Floodlight, run:

make package-java

This will create three jars in <loxigen-folder>/loxi_output/openflowj/target:

openflowj-X.Y.Z-SNAPSHOT.jar
openflowj-X.Y.Z-SNAPSHOT-sources.jar
openflowj-X.Y.Z-SNAPSHOT-javadoc.jar
openflowj-X.Y.Z-SNAPSHOT-tests.jar

The first jar is the library itself, the second is the source code (helpful when reading the code in Eclipse), the third is the Javadoc, and the fourth and last includes the unit tests.

To run the tests, you can run:

make check-java

Generating Loci

If you'd like to produce Loci source, headers, and objects for inclusion in a C project, such as Indigo Virtual Switch (IVS), run:

make c

This will create an inc and src directory in <loxigen-folder>/loxi_output/loci. Copy these and replace the ones included in your project.

To run the tests, you can run:

make check-c

Generating PyLoxi

If you'd like to produce PyLoxi source and byte code, run:

make python

This will create the Python source and byte code in <loxigen-folder>/loxi_output/pyloxi/loxi. Copy these and replace the ones included in your project.

To run the tests, you can run:

make check-python

Generating OpenFlowJ-Loxi, Loci, and PyLoxi

If you'd like to generate for all three languages, you can run:

make all

This will perform the above operations, with the exception of Java, which will include the source but not the jars. (Run make package-java again to get those.)

To test all three, run:

make check-all

Generating and testing for all is recommended if you are considering contributing your extension to the Loxigen project.