How to use OpenFlowJ-Loxigen

This tutorial is for Floodlight v1.0 and later. If you are using v0.9, v0.91, a copy of the master branch prior to December 30, 2014, or another prior version of Floodlight, this tutorial does not apply. If you are using the old openflow-1.3 branch, you should consider updating to the main repository's v1.2 (or even better master) branch to ensure you have the latest updates.

Table of Contents

Background

Floodlight v1.0 and up is backed by the new OpenFlowJ-Loxigen library. OpenFlowJ-Loxigen supports multiple OpenFlow versions, from 1.0 to 1.5. Floodlight v1.2 supports up to 1.4 and the master branch supports up to v1.5, through a single, common, version-agnostic API. Developers can take advantage of the API to write applications compatible with switches of multiple and various OpenFlow versions. All OpenFlow concepts and types are accessible from the OpenFlowJ-Loxigen library. The idea is that each OpenFlow version has a factory that can build all types and messages as they are defined for that version of OpenFlow.

OpenFlowJ-Loxigen also sports a new and very much improved way to create OpenFlow Messages, Matches, Actions, FlowMods, etc. The creation of many OpenFlow objects has been greatly simplified using builders, all accessible from a common OpenFlow factory. Simply set your fields and build. The handling of low-level details such as Message lengths wildcards are dealt with behind the scenes. Never again will you have to worry about keeping track of and (in)correctly setting Message lengths. When composing FlowMods, all Match fields are automatically wildcarded, except for those fields you specifically set in the FlowMod. Masked fields are also supported, and the mask can be specified with the Match, which will automatically set the appropriate wildcard bits if necessary. All objects produced from builders are immutable, which allows for safer code and makes your applications easier to debug.

All switches that connect to Floodlight contain a factory for the version of OpenFlow the switch speaks. There can be multiple switches, all speaking different versions of OpenFlow, where OpenFlowJ-Loxigen handles the low-level protocol differences behind the scenes. From the perspective of modules and application developers, the switch is simply exposed as an IOFSwitch, which has the function getOFFactory() to return the OpenFlowJ-Loxigen factory appropriate for the OpenFlow version the switch is speaking. Once you have the correct factory, you can create OpenFlow types and concepts through the common API OpenFlowJ-Loxi exposes.

As such, you do not need to switch APIs when composing your FlowMods and other types. Let's say you wish to build a FlowMod and send it to a switch. Each switch known to the switch manager has a reference to an OpenFlow factory of the same version negotiated in the initial handshake between the switch and the controller. Simply reference the factory from your switch, create the builder, build the FlowMod, and write it to the switch. The same API is exposed for the construction of all OpenFlow objects, regardless of the OpenFlow version. You will however need to know what you are allowed to do for each OpenFlow version; otherwise, if you e.g. tell an OF1.0 switch to perform some action such as add a Group, which is not supported for it's OpenFlow version, the OpenFlowJ-Loxi library will kindly inform you with an UnsupportedOperationException.

There are some other subtle changes introduced, for the better, with OpenFlowJ-Loxigen. For example, many common types such as switch datapath IDs, OpenFlow ports, and IP and MAC addresses are defined by the OpenFlowJ-Loxi library through the DatapathId, OFPort, IPv4Address, and MacAddress, respectively. You are encouraged to explore org.projectfloodlight.openflow.types, where you will find a wide variety of common types that are now conveniently defined in a single location. Like the objects produced from builders above, all types are immutable.

Lastly, OpenFlowJ-Loxigen is open source, has a comprehensive suite of auto-generated protocol unit tests, an automated integration-tested release process, and is in use in production by the current family of commercial BSN products. 

If you want to know more about the architecture of OpenFlowJ-Loxigen, take a look at this wiki page: https://github.com/floodlight/loxigen/wiki/OpenFlowJ-Loxi

Prerequisites and Goals

The goal of this tutorial is to introduce the most common uses of the OpenFlowJ-Loxigen library within a user module. Examples are given on how to perform everyday operations like compose a FlowMod, handle a PacketIn, and many more. These examples introduce the techniques and assume you have a module written in which you can apply them. If you would like to know how to create a module for Floodlight v1.0, please refer to this tutorial.

OpenFlow Concepts

Factories

Almost every OpenFlow concept (OFObjects such as Match, OFAction, OFMessage, etc.) is constructed by means of a builder, and all builders are exposed through the OFFactory interface. Due to differences in the OpenFlow protocol across different OpenFlow versions, there is a specific factory per OpenFlow version:

  • OFFactoryVer10
  • OFFactoryVer11
  • OFFactoryVer12
  • OFFactoryVer13
  • OFFactoryVer14
  • OFFactoryVer15

These all implement the OFFactory interface, so you can simply use the OFFactory interface in your code regardless of the specific version of the factory.

There are many ways to get a reference to the OFFactory you need. You can ask for a specific OFFactory version from OpenFlowJ-Loxigen by specifying the OFVersion enum:

OFFactory my13Factory = OFFactories.getOFFactory(OFVersion.OF_13); /* Get an OpenFlow 1.3 factory. */

Perhaps more useful, you can get a factory from an IOFSwitch (more on how to get the IOFSwitch object in How to Write a Floodlight Module for v1.0).

IOFSwitch mySwitch = switchService.getSwitch(DatapathId.of("00:00:00:00:00:00:00:01"));
OFFactory myFactory = mySwitch.getOFFactory(); /* Use the factory version appropriate for the switch in question. */

You can also obtain a specific OFFactory from an existing object generated by an OFFactory itself. Any object constructed from an OFFactory is set with the same OFVersion of the OFFactory that constructed it.

OFVersion flowModVersion = myFlowMod.getVersion(); /* We assume myFlowMod has been already constructed (keep reading for more info on building objects) */
OFFactory myFactory = OFFactories.getFactory(flowModVersion); /* Get the OFFactory version we need based on the existing object's version. */

The API for each OFFactory version is identical, which means you as the developer need to know the capabilities of the OpenFlow version for which you're using the OFFactory. For all OFFactory versions, any operation in OFFactory that is not supported by the OpenFlow version to which the OFFactory belongs will throw an UnsupportedOperationException. For example, if you try to build an OFGroupAdd from an OFFactory that is of OFVersion.OF_10, you can certainly try, but since Groups are not supported in OpenFlow 1.0, the OFFactory will throw an UnsupportedOperationExection:

OFGroupAdd not supported in version 1.0

A good practice is to first determine the OpenFlow version (OFVersion) you are working with, then handle each case with regard to the capabilities of each OpenFlow version.

/*
 * We don't know what version the factory is, 
 * but we can detect and account for each scenario.
 * Maybe you want to take advantage of the extended
 * feature-set of OF1.3 if you can, or fall-back to
 * your OF1.0-compatible algorithm if you cannot.
 */
OFVersion detectedVersion = myFactory.getVersion();
switch (detectedVersion) {
	case OF_10:
		/* Begin 1.0 algorithm */
		break;
	...
	...
	...
	case OF_13:
		/* Begin 1.3 algorithm */
		break;
	default:
		/* 
		 * Perhaps report an error if
		 * you don't want to support
		 * a specific OFVersion.
		 */
		...
		break;
}

Matches

Matches are used in OpenFlow to describe or define the characteristics of packet header fields. They are most commonly used when composing FlowMods, and OpenFlowJ-Loxigen exploits the builder pattern to make constructing Matches rather straightforward.

As mentioned above in Factories, almost all OpenFlow concepts are accessible from an OFFactory. So, a prerequisite to constructing a Match is first obtaining a reference to an OFFactory, which we will assume you already have. The following is an example of how to construct a Match:

Match myMatch = myFactory.buildMatch()
	.setExact(MatchField.IN_PORT, OFPort.of(1))
	.setExact(MatchField.ETH_TYPE, EthType.IPv4)
	.setMasked(MatchField.IPV4_SRC, IPv4AddressWithMask.of("192.168.0.1/24"))
	.setExact(MatchField.IP_PROTO, IpProtocol.TCP)
	.setExact(MatchField.TCP_DST, TransportPort.of(80))
	.build();

As you can see, the OFFactory myFactory contains a builder (Match.Builder) that can be used to construct the Match. You can either specify an exact field to match on or a masked field by using either setExact() or setMasked(), respectively. The first parameter of either setExact() or setMasked() is the MatchField, which specifies the header field you desire to match. MatchField contains a variety of OFValueTypes on which you can match. The second parameter is the the specific header field contents you want to match. See Value Types below for information on how to use OFPort, EthType, IPv4AddressWithMask, and other OFValueTypes.

Just as it is true when writing FlowMods manually using ovs-ofctl or using the Static Flow Pusher, you must specify all prerequisite matches for each MatchField you use. For example, in the above code, the match on EthType.IPv4 is required in order to match on an IPv4Address. (It is not possible to have a header field of either a source or destination IPv4Address without first having an IPv4 packet.) Fortunately, OpenFlowJ-Loxigen makes the prerequisite-checking process easy. You can supply a Match object completed with the exact and masked MatchFields you set as described above to the arePrerequisitesOK() function. Each MatchField OFValueType can invoke the arePrerequisitesOK() function on a Match object, which will check and verify that all prerequisites to the MatchField type are specified. For example, if you wish to check if a Match object contains all prerequisite matches necessary to match on MatchField.IPV4_SRC, you can do the following:

MatchField.IPV4_SRC.arePrerequisitesOK(myMatch);

This will return true if a match on IPv4's ethertype is included in the Match object; otherwise, it will return false if the prerequisite match on ethertype=IPv4 is not present. (In the example code above where we create the myMatch Match object, we specify a match of ethertype=IPv4, so the prerequisite check would return true in this case.)

One nice feature of OpenFlowJ-Loxigen is that the MatchField's OFValueType you specify forces a check on the second parameter to make sure it is of the same type. For example, the first field matched in the above code is:

.setExact(MatchField.IN_PORT, OFPort.of(1))

MatchField.IN_PORT has an OFValueType of OFPort. If you do not also specify an OFValueType of OFPort for the second parameter, the Java compiler will report an error. If you are using an IDE such as Eclipse, you should see the compile error immediately.

Another useful feature of Match's builder is that you can check whether or not your OFFactory's OpenFlow version supports exact or masked matching. This is done by calling the builder's supports() and supportsMasked() functions, respectively. If both are false, the OFFactory's OFVersion (and thus the OpenFlow protocol version the switch is speaking) does not support matching on the header field at all.

Match myMatch = myFactory.buildMatch()
	...
	...
	.supports(MatchField.IPv6_SRC)
	.supportsMasked(MatchField.ETH_SRC)
	.build();

It's also worth noting that duplication of Match objects and all OpenFlowJ-Loxigen-built objects is very simple. Every built-object, such as a Match, contains a function called createBuilder(). This will create a new builder of the object type that is pre-set with all properties of the object from which the builder is invoked. Consider an example:

Match myMatch = myFactory.buildMatch()
	.setExact(MatchField.IN_PORT, OFPort.of(1))
	.setExact(MatchField.ETH_TYPE, EthType.IPv4)
	.setMasked(MatchField.IPV4_SRC, IPv4AddressWithMask.of("192.168.0.1/24"))
	.setExact(MatchField.IP_PROTO, IpProtocol.TCP)
	.setExact(MatchField.TCP_DST, TransportPort.of(80))
	.build();

Match anotherMatch = myMatch.createBuilder().build();
if ( anotherMatch.equals(myMatch) ) { ... } /* true */
if ( anotherMatch == myMatch ) { ... } /* false */

Notice that anotherMatch is created by means of myMatch. The builder is created from the myMatch object, and this builder contains all properties/fields pre-set that were set in myMatch. This builder is then immediately built, which produces the anotherMatch Match object. anotherMatch and myMatch are deeply equal; however, they are separate objects. All OpenFlowJ-Loxigen-built objects can be duplicated in this manner.

Actions

OFAction's, like Matches, are also exposed by an OFFactory. Note: There is a class called OFAction and another called OFActions (with an s). To denote the plural of OFAction, I will use the possessive OFAction's, which isn't grammatically correct, but oh well. This convention will be used throughout this document for all similar cases.

OFActions allActions = myFactory.actions();

This call to actions() above returns an implementation of the OFActions interface. The implementation returned to you depends entirely on the OFVersion of the OFFactory. Each OpenFlow version has a separate implementation of the OFActions interface specific to the actions supported in that version of OpenFlow. That being said, because the interface is exposed all possible OFAction's across all OpenFlow protocol versions are exposed in every OFFactory version. As such, you should take care to only use OFAction's that your switch's OpenFlow version supports. If you attempt to use an OFAction not supported by your switch's OpenFlow protocol version (which you can detect by checking the OFVersion), the OpenFlowJ-Loxigen library will kindly give you an UnsupportedOperationException as you attempt to acquire a builder for or construct the invalid OFAction. Consider the following: 

OFMeterMod.Builder meterModBldr = myFactory.actions().buildMeterMod();

If your OFFactory myFactory is of OFVersion.OF_10 (meaning your switch is speaking OpenFlow 1.0), and you try to build an OFMeterMod message, an UnsupportedOperationExecption will be thrown, since meters do not exist in OpenFlow 1.0.

"OFMeterMod not supported in version 1.0"

Now, let's discuss the details on how to use OFAction's. As hinted at above, there are builders for most OFAction's, all exposed as functions within the OFActions interface. OFAction's can also be obtained without the use of builders. In fact, some OFAction's that do not require any application data as input do not even have builders (e.g. stripping a VLAN).

OpenFlow 1.0

Let's consider an OpenFlow 1.0 example:

ArrayList<OFAction> actionList = new ArrayList<OFAction>();
OFActions actions = myOF10Factory.actions();

/* Use builder to create OFAction. */
OFActionSetDlDst setDlDst = actions.buildSetDlDst()
	.setDlAddr(MacAddress.of("ff:ff:ff:ff:ff:ff")
	.build();
actionList.add(setDlDst);
/* Create OFAction directly w/o use of builder. */
OFActionSetNwDst setNwDst = actions.setNwDst(IPv4Address.of("255.255.255.255"));
actionList.add(setNwDst);

/* OFActionStripVlan requires no data, thus is does not have a builder at all. */
OFActionStripVlan stripVlan = actions.stripVlan();
actionList.add(stripVlan);

/* Use builder again. */
OFActionOutput output = actions.buildOutput()
	.setMaxLen(0xFFffFFff)
	.setPort(OFPort.of(1))
	.build();
actionList.add(output);

The above example creates a list of OFAction's. OFAction's that require data from the application can be composed both with or without the use of a builder; the two methods are provided as a convenience and can be used interchangeably. OFAction's like OFActionStripVlan that do not take any data as input do not have builders at all. Note that all specific OFAction's extend OFAction, which is how they can be stored in a single ArrayList of type OFAction. As you will see in subsequent topics, you can supply this list of OFAction's to other types (such as OFFlowMods).

OpenFlow 1.3

OpenFlow 1.2 introduced the OXM or OpenFlow Extensible Match. With OXMs, all actions that result in the modification of an existing header field are specified by a new set-field action, where the set-field action consists of an OXM specifying the header field and the new value to write into that header field. OpenFlowJ-Loxigen has the OFAction OFActionSetField for this purpose. OFActionSetField takes an OFOxm defining the new value and field in which to write it. Like OFAction's, OFOxm's are accessible from an OFFactory but through the oxms() function instead. This call returns the Oxms interface backed by an implementation specific to the OFactory's OFVersion. Note the distinction between Oxm's and Oxms, similar to OFAction's and OFActions. Oxms is the interface containing all individual Oxm's. Let's complete the same OpenFlow 1.0 example above for OpenFlow 1.3.

ArrayList<OFAction> actionList = new ArrayList<OFAction>();
OFActions actions = myOF13Factory.actions();
OFOxms oxms = myOF13Factory.oxms();

/* Use OXM to modify data layer dest field. */
OFActionSetField setDlDst = actions.buildSetField()
	.setField(
		oxms.buildEthDst()
		.setValue(MacAddress.of("ff:ff:ff:ff:ff:ff"))
		.build()
	)
	.build();
actionList.add(setDlDst);

/* Use OXM to modify network layer dest field. */
OFActionSetField setNwDst = actions.buildSetField()
	.setField(
		oxms.buildIpv4Dst()
		.setValue(IPv4Address.of("255.255.255.255"))
		.build()
	)
	.build();
actionList.add(setNwDst);

/* Popping the VLAN tag is not an OXM but an OFAction. */
OFActionPopVlan popVlan = actions.popVlan();
actionList.add(popVlan);

/* Output to a port is also an OFAction, not an OXM. */
OFActionOutput output = actions.buildOutput()
	.setMaxLen(0xFFffFFff)
	.setPort(OFPort.of(1))
	.build();
actionList.add(output);

Comparing the two examples, you can see that in OpenFlow 1.3, OFOxm's are used in conjunction with OFActionSetFields to specify the rewrite of the destination MAC and IP addresses, since they are field modifications. On the other hand, removing a VLAN tag and sending the packet out a switch port are not defined as OFOxm's, but OFAction's instead.

Instructions

The advent of OpenFlow 1.1 introduced OFInstruction's, which was extended slightly in OpenFlow 1.3. An OFInstruction can be any of the following:

  • OFInstructionApplyActions:  Immediately modify a packet based on a supplied list of OFActions
  • OFInstructionWriteActions:  Associate a list of actions with a packet but wait to execute them
  • OFInstructionClearActions:  Clear a pending action list associated with a packet
  • OFInstructionGotoTable:  Send a packet to a particular flow table
  • OFInstructionWriteMetadata:  Save some metadata with the packet as it progresses through the processing pipeline
  • OFInstructionExperimenter:  Allow extensions to the OFInstruction set
  • OFInstructionMeter:  Send a packet to a meter

OFInstruction's are one layer above OFAction's. An OFFlowMod for OpenFlow 1.1+ consists of a list of OFInstruction's and no explicit list of OFAction's. Any OFAction's to be applied to the packets matching the flow are specified within either OFInstructionApplyActions or OFInstructionWriteActions. The most common case is OFInstructionApplyActions, which immediately modifies the packet according to the list of OFActions.

All OFInstruction's are accessible from an OFFactory by means of the instructions() function. This function call returns an OFInstructions interface. The implementation of this interface will be specific to the OpenFlow version of the OFFactory.

OFInstructions instructions = myFactory.instructions();

Like the OFActions interface implementations, OFInstructions will throw an UnsupportedOperationException in the case any OFInstruction is attempted to be constructed that is not supported for that particular OFVersion. For example, if myFactory is of OFVersion.OF_10, any attempt to create a specific instruction will result in an UnsupportedOperationException.

myOF10Factory.instructions().buildApplyActions();

This results in:

"OFInstructionApplyActions not supported in version 1.0"

The same will occur if buildMeter() is invoked for an OFFactory of OFVersion less than OFVersion.OF_13. Meters were not introduced until OpenFlow 1.3.

As stated above, a list of OFAction's can be included within an OFInstructionApplyActions or OFInstructionWriteActions object. Consider the following example based on the OpenFlow 1.3 example in Actions:

OFInstructions instructions = myOF13Factory.instructions();
ArrayList<OFAction> actionList = new ArrayList<OFAction>();
OFActions actions = myOF13Factory.actions();
OFOxms oxms = myOF13Factory.oxms();

/* Use OXM to modify data layer dest field. */
OFActionSetField setDlDst = actions.buildSetField()
	.setField(
		oxms.buildEthDst()
		.setValue(MacAddress.of("ff:ff:ff:ff:ff:ff"))
		.build()
	)
	.build();
actionList.add(setDlDst);

/* Use OXM to modify network layer dest field. */
OFActionSetField setNwDst = actions.buildSetField()
	.setField(
		oxms.buildIpv4Dst()
		.setValue(IPv4Address.of("255.255.255.255"))
		.build()
	)
	.build();
actionList.add(setNwDst);

/* Popping the VLAN tag is not an OXM but an OFAction. */
OFActionPopVlan popVlan = actions.popVlan();
actionList.add(popVlan);

/* Output to a port is also an OFAction, not an OXM. */
OFActionOutput output = actions.buildOutput()
	.setMaxLen(0xFFffFFff)
	.setPort(OFPort.of(1))
	.build();
actionList.add(output);

/* Supply the OFAction list to the OFInstructionApplyActions. */
OFInstructionApplyActions applyActions = instructions.buildApplyActions()
	.setActions(actionList)
	.build();

Any OFInstruction applicable to your OFVersion can be included in the list of OFInstructions. This entire list can then be supplied to an OFFlowMod, as we will discuss below.

FlowMods

The OpenFlowJ-Loxigen library exposes a version-agnostic OFFlowMod interface, which has several subinterfaces that allow you to write a specific type of OFFlowMod corresponding to a specific OFFlowModCommand:

  • OFFlowAdd
  • OFFlowModify
  • OFFlowModifyStrict
  • OFFlowDelete
  • OFFlowDeleteStrict

Like other OpenFlow concepts and types we've discussed already, the OFFlowMod interfaces are implemented separately for each OpenFlow version. In this manner, any OFFlowMod operation not supported for your OFFlowMod's OpenFlow version will throw an UnsupportedOperationException. (For example, setting the OFInstruction list in an OFFlowAdd of OFVersion.OF_10 will throw an UnsupportedOperationException. The OFInstruction was not introduced until OpenFlow 1.1.)

An important note about OFFlowMods: OpenFlow 1.3 removes the default table-miss behavior of forwarding the packet to the controller. Instead, OpenFlow 1.3 specifies the controller should insert a special table-miss flow of zero priority, all MatchFields wildcarded, and with an action list defined by any OFAction's. To support processing of unmatched, table-miss packets in the controller, Floodlight will insert a flow of zero priority, no MatchFields set, and an single output OFAction to send the packet to the controller. This flow will serve as a "catch-all" flow and send the packet as a packet-in message to the controller in the case all other flows in the table do not match the packet. Unlike OpenFlow 1.0 - 1.2 where this behavior is embedded within the flow table itself, in OpenFlow 1.3, it is clearly implemented using an actual flow, visible and modifiable by all models. As such, you should be very careful when writing your OpenFlow 1.3 OFFlowMods. You should ensure they do not replace or remove the default table-miss flow. Otherwise, the controller and thus other modules may not be able to receive any packet-in messages from the switch.

A side note about "strict": according to OpenFlow specification, the flow modification message and flow deletion message support a "strict" version and a "without strict" version. A "strict" means any flow that exactly looks like the match you specify, then the flow(s) will be modified or deleted. While if you choose a version "without strict", it means any flow that look like the match you specify (i.e. the flow contains at least the match you specify, but could contains more match fields) will be modified or deleted. 

Furthermore, in a similar manner to the previous OpenFlow concepts and types covered, an OFFactory can be used to produce each type of OFFlowMod.

OFFlowAdd flowAdd = myFactory.buildFlowAdd()

The OFFactory will return a builder for the OFFlowMod you specify. There are certain situations where you may find it useful to be able to convert your OFFlodMod from one OFFlowModCommand type to another. This is not possible at present within OpenFlowJ-Loxigen; however, the FlowModUtils class in net.floodlightcontroller.util.FlowModUtils.java provides several helper functions that convert any OFFlowMod into any of the specific OFFlowMod's listed above:

OFFlowAdd flowAdd = myFactory.buildFlowAdd().build();

/* Convert from any extension of OFFlowMod to another. */
OFFlowModify flowModify = FlowModUtils.toFlowModify(flowAdd);
OFFlowModifyStrict flowModifyStrict = FlowModUtils.toFlowModifyStrict(flowAdd);
OFFlowDelete flowDelete = FlowModUtils.toFlowDelete(flowAdd);
OFFlowDeleteStrict flowDelStrict = FlowModUtils.toFlowDeleteStrict(flowAdd);
OFFlowAdd flowAdd2 = FlowModUtils.toFlowAdd(flowModify);

Now, let's briefly discuss how to compose an OFFlowMod. All OFFlowMods can be composed/built in the same manner as all other OFObjects we've discussed. The builder the OFFactory returns contains many setter functions to set all possible fields of the OFFlowMod. Take this OpenFlow 1.3 example:

OFFlowAdd flowAdd = my13Factory.buildFlowAdd()
	.setBufferId(OFBufferId.NO_BUFFER)
	.setHardTimeout(3600)
	.setIdleTimeout(10)
	.setPriority(32768)
	.setMatch(myMatch)
	.setInstructions(myInstructionList)
	.setTableId(TableId.of(1))
	.build();

There are other setters available to do things such as specify an output OFGroup, flags, etc.

Once you are done composing your OFFlowMod, you may want to send it to a switch. All OFFlowMods are OFMessages, and as such, they can be written directly to a switch:

/* Continuing with the previous OFFlowAdd example... */
...
IOFSwitch mySwitch = switchService.getSwitch(DatapathId.of(1));
mySwitch.write(flowAdd);

What if you want to insert similar OFFlowMods? If you do, then there's no need to write them all from scratch. Simply create another builder from the existing OFFlowMod, change the properties you need, and build the new object.

OFFlowAdd flowAdd = my13Factory.buildFlowAdd()
	.setBufferId(OFBufferId.NO_BUFFER)
	.setHardTimeout(3600)
	.setIdleTimeout(10)
	.setPriority(32768)
	.setMatch(myMatch)
	.setInstructions(myInstructionList)
	.setTableId(TableId.of(3))
	.build();

/* Create copy and modify slightly. */
OFFlowAdd flowAdd2 = flowAdd.createBuilder()
	.setInstructions(newInstructionList)
	.build();

In the example above, flowAdd is used as a starting point when writing flowAdd2. All properties from flowAdd are preserved, except those that are explicitly set in flowAdd2's builder. All properties will be the same for both OFFlowAdds with the exception of the OFInstruction list and the output port.

One final, important thing worth noting: Although OpenFlow versions 1.1 and higher wrap OFAction's within the OFInstructionApplyActions and OFInstructionWriteActions OFInstructions, OpenFlowJ-Loxigen does allow you to specify a list of actions to an OFFlowMod directly. The library will automatically insert any list of OFActions provided as an OFInstructionApplyActions in the list of OFInstructions. Thus, the following two implementations are produce an equivalent OFFlowAdd object.

(1) Allowing OpenFlowJ-Loxigen to create the OFInstructionApplyActions and insert it into the OFInstruction list for you:

/*
 * Upon build, the builder will automatically insert the
 * supplied OFAction list as an OFInstructionApplyActions
 * in the list of OFInstructions.
 */
OFFlowAdd flowAdd13 = myFactory.buildFlowAdd()
	...
	.setActions(myActionList)
	.build();

(2) Explicitly supplying the OFInstructionApplyActions within the OFInstruction list:

OFInstructionApplyActions applyActions = my13Factory.instructions().buildApplyActions()
	.setActions(myActionList)
	.build();
ArrayList<OFInstruction> instructionList = new ArrayList<OFInstruction>();
instructionList.add(applyActions);
OFFlowAdd flowAdd13 = myFactory.buildFlowAdd()
	...
	.setInstructions(instructionList)
	.build();

Implementation (1) is arguably more difficult to decipher, since it is not evident what OpenFlow version for which your code is written. On the other hand, implementation (2) is more cumbersome and requires more code. Take your pick when using OpenFlow 1.1 or greater and need to have OFActions applied immediately. Note, this shortcut only works for OFInstructionApplyActions, which applies the OFActions to the packet immediately. OFInstructionWriteActions and all others must be supplied explicitly within a list of OFInstruction's.

Groups

OFGroups were first introduced in OpenFlow 1.1 with minor extension in OpenFlow 1.3. An OFGroup is designed to simplify and make possible more complex operations in an OpenFlow switch, such as duplicating packets, applying different sets of OFAction's to a single packet, load-balancing, and detecting and handling link failures. To accomplish this, an OFGroup is structured as a list of lists of OFAction's, or a 2D list of OFAction's if you will. The outer list is called the bucket list (which I find humorous =), which consists of OFBuckets. Each OFBucket in the bucket list contains a list of OFAction's. The type of OFGroup that is assigned will dictate the manner in which the OFBuckets are used. There are four different types of OFGroups:

  • OFGroupType.ALL:  Provide each OFBucket with a copy of the packet, and apply the list of OFAction's within each OFBucket to the OFBucket's copy of the packet.
  • OFGroupType.SELECT:  Use a switch-determined (typically round-robin) approach to load-balance the packet between all OFBuckets. Weights can be assigned for a weighted round-robin distribution of packets.
  • OFGroupType.INDIRECT:  Only a single OFBucket is allowed, and all OFAction's are applied. This allows for more efficient forwarding when many flows contain the same action set. Identical to ALL with a single OFBucket.
  • OFGroupType.FF:  Fast-Failover. Use a single OFBucket and change automatically to the next OFBucket in the OFGroup if a specific link or a link in the specified OFGroup fails for the active OFBucket. 

In order to use OFGroups on an OpenFlow switch, you need to configure your switch with the quantity and type of OFGroups you desire. All OFGroup messages are accessible from an OFFactory corresponding the OpenFlow protocol version your switch is speaking. Like other types we've discussed already, if your OFFactory's OFVersion does not support OFGroups or the operation you wish to perform, an UnsupportedOperationException will be thrown.

Like OFFlowMod, OpenFlowJ-Loxigen exposes a version-agnostic OFGroupMod, which has several subinterfaces that allow you to write a specific type of OFGroupMod that corresponds to a specific OFGroupModCommand:

  • OFGroupAdd
  • OFGroupModify
  • OFGroupDelete

These OFGroupMods are OFMessages that can be composed and then written to a switch, just like OFFlowMods.

ArrayList<OFGroupMod> groupMods = new ArrayList<OFGroupMod>();

OFGroupAdd addGroup = myFactory.buildGroupAdd()
	.setGroup(OFGroup.of(1))
	.setGroupType(OFGroupType.ALL)
	.build();

groupMods.add(addGroup);

OFGroupModify modifyGroup = myFactory.buildGroupModify()
	.setGroup(OFGroup.of(2))
	.setGroupType(OFGroupType.SELECT)
	.build();
groupMods.add(modifyGroup);

OFGroupDelete deleteGroup = myFactory.buildGroupDelete()
	.setGroup(OFGroup.of(3))
	.setGroupType(OFGroupType.INDIRECT)
	.build();
groupMods.add(deleteGroup);

IOFSwitch mySwitch = switchService.getSwitch(DatapathId.of(1));
mySwitch.write(groupMods);

When you add an OFGroup, you can also specify a list of OFBuckets to include with the OFGroup. An OFGroupMod will, by definition, replace the entire OFGroup's OFBucket list with the specified OFBucket list. Providing an OFBucket list to an OFGroupDelete has no meaning; the entire OFGroup will be deleted as defined in the OpenFlow specification. And of course, the list of OFBuckets will be included with the new OFGroup if provided to an OFGroupAdd OFMessage.

ArrayList<OFBucket> bucketList = new ArrayList<OFBucket>();
ArrayList<OFBucket> singleBucket = new ArrayList<OFBucket>();
...
...
/* Assume for the time being the bucketList has been magically populated with OFBuckets. */
OFGroupAdd addGroup = myFactory.buildGroupAdd()
	.setGroup(OFGroup.of(1))
	.setGroupType(OFGroupType.ALL)	.setBuckets(bucketList) /* Will be included with the new OFGroup. */
	.build();

OFGroupModify modifyGroup = myFactory.buildGroupModify()
	.setGroup(OFGroup.of(2))
	.setGroupType(OFGroupType.SELECT)
	.setBuckets(bucketList) /* Will replace the OFBucket list defined in the existing OFGroup. */
	.build();

OFGroupModify modifyGroup2 = myFactory.buildGroupModify()
	.setGroup(OFGroup.of(3))
	.setGroupType(OFGroupType.INDIRECT)
	.setBuckets(singleBucket) /* INDIRECT only supports a single OFBucket; will replace an OFBucket or assign one if not present in the OFGroup already. */
	.build();

OFGroupAdd addGroup2 = myFactory.buildGroupAdd()
	.setGroup(OFGroup.of(4))
	.setGroupType(OFGroupType.FF)
	.setBuckets(bucketList) /* Will be included with the new OFGroup. */
	.build();

The tricky part about OFGroups is that you need to specify each OFBucket's properties keeping in mind the OFGroupType. Each OFGroupType is designed to behave differently, and as such the OFBucket properties need to be set appropriately. Like all other types we've discussed, OFBuckets can be built from an OFFactory.

OFBucket myBucket = myFactory.buildBucket()
	.setActions(myActionList)
	.setWatchGroup(OFGroup.of(1))
	.setWatchPort(OFPort.of(48))
	.setWeight(100)
	.build();

As shown above, OFBuckets contain a list of OFAction's, an OFGroup to watch, an OFPort to watch, and a weight. The following is an example on how to compose an OFBucket for an OFGroupType.ALL and an OFGroupType.INDIRECT:

OFBucket myBucket = myFactory.buildBucket()
	.setActions(myActionList)
	.setWatchGroup(OFGroup.ANY) /* ANY --> don't care / wildcarded. */
	.setWatchPort(OFPort.ANY) /* ANY --> don't care / wildcarded. */
	.build();

Although watch OFGroups and OFPorts are not defined for OFGroupType.ALL, OpenFlowJ-Loxigen requires you explicitly specify the ANY OFGroup and OFPort types. Specifying a weight is not defined and is not required.

Now let's consider how an OFBucket is composed for an OFGroup of OFGroupType.SELECT:

OFBucket myBucket = myFactory.buildBucket()
	.setActions(myActionList)
	.setWatchGroup(OFGroup.ANY) /* ANY --> don't care / wildcarded. */
	.setWatchPort(OFPort.ANY) /* ANY --> don't care / wildcarded. */
	.setWeight(89) /* Relative weight used to govern packet distribution to OFBuckets in the OFGroup. */
	.build();

Again, the watch OFGroup and OFPort are wildcarded via ANY; however, this time we assign a weight to the OFBucket to tell the switch's OFBucket selection mechanism the relative priority of this particular OFBucket. If all OFBuckets are assigned an equal weight, then the packet distribution amongst all OFBuckets in the OFGroup's OFBucket list be equal. The mechanism by which the switch distributes packets entering the OFGroup to the OFBuckets is outside the scope of the OpenFlow protocol and depends on the particular switch implementation. A weighted round-robin approach is hinted/recommended in the OpenFlow specification for the OFGroupType.SELECT OFGroup.

Finally, let's consider the OFGroupType.FF (Fast-Failover):

OFBucket myBucket = myFactory.buildBucket()
	.setActions(myActionList)
	.setWatchGroup(OFGroup.of(23)) /* Monitor liveness of OFGroup 23. */
	.setWatchPort(OFPort.of(7)) /* Monitor liveness of OFPort 7. */
	.build();

OFGroupType.FF is designed to allow fast-failover to the next OFBucket if the current OFBucket experiences a link failure at the specified watch port and/or if the watched OFGroup experiences a link failure. As such, this particular OFBucket needs to have its watch OFGroup and/or watch OFPort set to some meaningful value. If only one of either the watch OFGroup or the watch OFPort is needed, then the other should be set to OFGroup.ANY or OFPort.ANY.

Finally, just like any other type we've discussed, the builder pattern provides an easy duplication mechanism for OFGroupMods and OFBuckets.

OFGroupAdd addGroup = myFactory.buildGroupAdd()
	.setGroupType(OFGroupType.ALL)
	.setGroup(OFGroup.of(50))
	.setBuckets(myBucketList)
	.build();
OFGroupAdd addGroup2 = addGroup.createBuilder() /* Builder contains all fields as set in addGroup. */
	.setGroup(OFGroup.of(51))
	.setBuckets(myOtherBucketList)
	.build(); /* Will create addGroup2, only changing the OFGroup number and the OFBucket list. */

OFBucket myBucket = myFactory.buildBucket()
	.setActions(myActionList)
	.setWatchGroup(OFGroup.ANY)
	.setWatchPort(OFPort.ANY)
	.build();
OFBucket myBucket2 = myBucket.createBuilder() /* Builder contains all fields as set in myBucket. */
	.setActions(myOtherActionList)
	.build(); /* Will create myBucket2 the same as myBucket, only changing the OFAction list. */

Meters

A tutorial on meters can be found here.

PacketIns

Any module can register as an IOFMessageListener. As such, the module will be notified by the controller whenever an OFMessage is received from a connected switch. The most common scenario is a module that wishes to react to an unmatched packet sent from a switch, also known as an PacketIn message. OpenFlowJ-Loxigen presents the PacketIn message as an OFPacketIn instance to your listening module. OFPacketIns are just like any other OpenFlow object in that they are immutable and are constructed from a builder. The construction of the OFPacketIn message is done for your module by the controller, so we won't touch on that here. (But, if you wanted to build an OFPacketIn, you could do so by getting an OFFactory and constructing it as you would any other OpenFlow concept.) There are some useful things you can get from an OFPacketIn message though, including the Match corresponding to the packet within.

/**
  * The controller will invoke the receive()
  * function automatically when an OFMessage is
  * received from a switch if the module
  * implements the IOFMessageListener interface
  * and is registered with the controller as an
  * IOFMessageListener.
  *
Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
	switch (msg.getType()) {
        	case PACKET_IN:
        		OFPacketIn myPacketIn = (OFPacketIn) msg;
			OFPort myInPort = 
				(myPacketIn.getVersion().compareTo(OFVersion.OF_12) < 0) 
				? myPacketIn.getInPort() 
				: myPacketIn.getMatch().get(MatchField.IN_PORT);
			...
			...
			break;
            	default:
                	break;
        }
	return Command.CONTINUE;
}

Notice first that the OFMessage is delivered by the IOFMessageListener interface's receive() function. Because an OFPacketIn extends OFMessage, we can cast the OFMessage msg to an OFPacketIn after we detect that it is indeed an OFPacketIn via msg.getType().

Now, here's the tricky part, which you can see demonstrated in almost every IOFMessageListener-implementing module in the controller -- How do you detect the OFPort on which the packet arrived to the switch? If the OpenFlow protocol version is OpenFlow 1.0 or 1.1, the ingress port is defined as a distinct field within the OFPacketIn message, accessible via the getInPort() function. On the other hand, if the OpenFlow version is 1.2 or greater, the ingress port is included as an OXM within the Match object of the OFPacketIn. Thus, you need to be careful and check the OpenFlow version in order to know the correct way to get the ingress OFPort. The ternary operator above is how the check is implemented throughout the controller and is a nice and compact way to do so.

PacketOuts

It is oftentimes beneficial to send a packet from the controller, which is to be injected into the data plane of a particular switch. An OFPacketOut is how this task can be accomplished. Like all OpenFlow concepts, an OFPacketOut can be constructed from an OFFactory.

By definition, an OFPacketOut is an OFMessage and should be received by the switch from the controller, at which time the switch will take the payload of the OFPacketOut and send it out whichever port(s) the OFPacketOut specifies. Thus, an OFPacketOut should contain some data representing a valid packet. The following is the typical workflow for constructing and working with an OFPacketOut message.

include net.floodlightcontroller.packet.*; /* All network packets are defined in the packet package. */
...
...

/* Compose L2 packet. */
Ethernet eth = new Ethernet();
eth.setSourceMACAddress(MacAddress.of(1));
eth.setDestinationMACAddress(MacAddress.of(2));
...
...

/* Compose L3 packet. */
IPv4 ipv4 = new IPv4();
ipv4.setSourceAddress(IPv4Address.of("192.168.1.1"));
ipv4.setDestinationAddress(IPv4Address.of(0xffFFffFF));
...
...

/* Set L2's payload as the L3 packet. */
eth.setPayload(ipv4);
...
...

/* Specify the switch port(s) which the packet should be sent out. */
OFActionOutput output = myFactory.actions().buildOutput()
	.setPort(OFPort.FLOOD)
	.build();

/* 
 * Compose the OFPacketOut with the above Ethernet packet as the 
 * payload/data, and the specified output port(s) as actions.
 */
OFPacketOut myPacketOut = factory.buildPacketOut()
	.setData(eth.serialize())
	.setBufferId(OFBufferId.NO_BUFFER)
 	.setActions(Collections.singletonList((OFAction) output))
	.build();
...
...

/* Write the packet to the switch via an IOFSwitch instance. */
mySwitch.write(myPacketOut);

As shown above, the net.floodlightcontroller.packet package contains many useful classes for abstracting away the details of composing packets from the transport layer down to the data/link layer. Here, we create an Ethernet instance and place an IPv4 instance inside it as its payload. We then construct the OFPacketOut object from an OFFactory and set its payload as the Ethernet packet (which has the IPv4 packet within it). When eth.serialize() is invoked, the Ethernet packet and its payload are serialized to a byte[], which is the data of the OFPacketOut. The actions specify which port(s) the Ethernet packet will be sent out -- in this case it's a FLOOD. When the switch receives the OFPacketOut OFMessage, it will remove the data from the OFPacketOut and perform all OFActions in its OFAction list; most commonly, the OFAction list will specify the packet be sent out the port(s) specified by an OFActionOutput.

Value Types

In OpenFlowJ-Loxigen, Value Types are types that remain constant in-between OpenFlow versions. These include but are not limited to IP addresses, MAC addresses, switch ports, datapath IDs, and so forth. All Value Types are immutable, but what distinguishes them from other types described above is that these do not employ the use of builders in order to construct them. Instead, they exhibit they are created by the SomeValueType.of() mechanism. Furthermore, Value Types are for the most part self-contained and include most all utilities for working with the particular Value Type. For example, IPv4Addresses include functions to perform bitwise operations, subnet masking, CIDR, conversion to and from the IPv4Address type to a String, integer, and byte array, and the list goes on.

The following are some limited examples on how to use select Value Types. For all possible Value Types, take a look at org.projectfloodlight.openflow.types, which contains all Value Types in OpenFlowJ-Loxigen. Please refer to the OpenFlowJ-Loxigen documentation for the full feature-set of each Value Type. Although they may not be explicitly stated below, most all Value Types can be created from and converted to String, hexadecimal, and numerical (long, int, etc.) form, as makes sense for the particular Value Type.

DatapathId

DatapathId dpid1 = DatapathId.of(1); /* Create a DatapathId from a long. */
DatapathId dpid2 = DatapathId.of("00:00:00:00:00:00:00:02"); /* ...from a String. */
DatapathId dpid3 = DatapathId.of(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}); /* ...from a byte array. */

String dpid1String = dpid1.toString(); /* Return the DPID in proper String format, "00:00:00:00:00:00:00:01". */
long dpid2Long = dpid2.getLong(); /* Return the DPID as a long, 2. */U64 dpid3ULong = dpid3.getUnsignedLong(); /* Return the DPID as an unsigned long, 3. */
byte[] dpid3Bytes = dpid3.getBytes(); /* Return the DPID as a byte array, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}. */

IPv4Address (also for IPv6Address)

IPv4Address ip1 = IPv4Address.of(1); /* ...from integer. */
IPv4Address ip2 = IPv4Address.of("0.0.0.2"); /* ...from String. */
IPv4Address mask = IPv4Address.of(new byte[] {0xFF, 0xFF, 0xFF, 0x00}); /* ...from byte array. */

IPv4Address ipMasked = ip1.applyMask(mask); /* Get IP with mask. */
IPv4Address ipFromCIDR = IPv4Address.ofCidrMaskLength(24); /* Same as "mask" above. */
if ( mask.isBroadcast() ) { ... } /* Is the IP 255.255.255.255? (no) */

if ( mask.equals(ipFromCIDR) ) { ... } /* Deep equality check. */
IPVersion version = ip2.getVersion(); /* Returns enum IPVersion.IPv4. */
IPv4Address ip3 = ip1.or(ip2); /* Bitwise OR: 0.0.0.1 | 0.0.0.2 --> 0.0.0.3. */

IPv4AddressWithMask (also for IPv6AddressWithMask)

/* Continuing from IPv4Address above... */

IPv4AddressWithMask ipAndMask = IPv4AddressWithMask.of(IPv4Address.of("192.168.0.1"), mask); /* Contains 192.168.0.1 w/mask 255.255.255.0. */
IPv4AddressWithMask ipAndMask2 = IPv4AddressWithMask.of("10.0.0.1/16"); /* Accepts CIDR notation for mask. */
if ( ipAndMask.contains(ip2) ) { ... } /* Check if ip2 is in ipAndMask's subnet. (false) */

MacAddress

MacAddress mac1 = MacAddress.of("FF:ff:FF:ff:FF:ff"); /* ...from String. */
MacAddress mac2 = (MacAddress.of("11:22:33:44:55:66")).applyMask(mac1); /* Apply a mask. */
String macStr = mac2.toString(); /* Returns String "11:22:33:44:55:66". */

if ( mac1.isBroadcast() ) { ... } /* Is the MAC all 1's? (yes) */
if ( mac2.isMulticast() ) { ... } /* Does MAC have a 1 in LSB of 1st octet? XXXXXXX1:XX:XX:XX:XX:XX (yes) */
if ( mac2.isLLDPAddress() ) { ... } /* Does the MAC look like 01:80:C2:00:00:0X, ignoring the LS-Byte? (no) */

OFPort

OFPort port1 = OFPort.of(1); /* Port numbers 1 through 48 are precached for efficiency. */
OFPort port2 = OFPort.ofShort((short) 2);

OFPort pLocal = OFPort.LOCAL; /* 66534 */
OFPort pZero = OFPort.ZERO; /* Wildcarded default for OF1.0; not used in OF1.1+. */
OFPort pAny = OFPort.ANY; /* a.k.a. "NONE" in OF1.0; used in wildcarding. */
OFPort pAll = OFPort.ALL; /* All physical ports except input port. */
OFPort pCont = OFPort.CONTROLLER;
OFPort pFlood = OFPort.FLOOD; /* All physical ports in VLAN, except input and those that are down/blocked. */
OFPort pTable = OFPort.TABLE; /* In PacketOuts, send to first flow table. */
OFPort pIn = OFPort.IN_PORT;
OFPort pNorm = OFPort.NORMAL; /* Process as L2/L3 learning switch. */

int raw = port1.getPortNumber();
short raw2 = port1.getShortPortNumber();

EthType

EthType et1 = EthType.of(2048); /* Decimal IPv4 ethertype. */
EthType et2 = EthType.of(0x806); /* Hexadecimal ARP ethertype. */
EthType et3 = EthType.IPv4; /* Almost every common ethertype is precached/predefined. */

if ( et1.compareTo(et3) == 0 ) { ... } /* et1 is the same ethertype as et3. */if ( et1.equals(et3) ) { ... } /* et1 does equal et3. */
if ( et1 == et3 ) { ... } /* et1 and et3 are the SAME OBJECT, since all common, predefined ethertypes are precached. */

IpProtocol

IpProtocol ipp1 = IpProtocol.of(6); /* Decimal TCP IP protocol. */
IpProtocol ipp2 = IpProtocol.of(0x11); /* Hexadecimal UDP IP protocol. */
IpProtocol ipp3 = IpProtocol.TCP; /* Like EthType, almost every common IP protocol is precached/predefined. */

if ( ipp1.compareTo(ipp3) == 0 ) { ... } /* ipp1 is the same IP protocol as ipp3. */
if ( ipp1.equals(ipp3) ) { ... } /* ipp1 does equal ipp2. */
if ( ipp1 == ipp3 ) { ... } /* ipp1 and ipp3 are the SAME OBJECT, since all common, predefined IP protocol numbers are precached. */

TransportPort

TransportPort tp1 = TransportPort.of(80); /* Decimal transport port number; not associated with any specific protocol. */
TransportPort tp2 = TransportPort.of(-1); /* Throws IllegalArgumentException; not possible to get a TransportPort outside MAX_PORT and MIN_PORT. */
TransportPort tp3 = TransportPort.MAX_PORT;
TransportPort tp4 = TransportPort.MIN_PORT;

int raw = tp1.getPort(); /* Get integer port value. */

Others include VlanVid, OFGroup, TableId, U8, U16, U32, U64, U128, and many more... please check out org.projectfloodlight.openflow.types for the complete list and API. Javadoc for OpenFlowJ-Loxi is available here.