How to Write a Module

This tutorial is for releases v1.0 and later. If you are using v0.91 or earlier, please see the old documentation.

It is highly advised that you learn a little bit of Java and Object Oriented Programming skills if you are not familiar with them.

Table of Contents


Prerequisites

We are going to create a bundle that will watch for new MAC addresses that have not been seen before, and log the MAC and switch they were seen on.

  • Successfully completed the Getting Started tutorial, including setting up Eclipse
  • Mininet installed and running, or a physical OpenFlow switch

Creating The Listener

Add Class In Eclipse

  1. Expand the "floodlight" item in the Package Explorer and find the "src/main/java" folder.
  2. Right-click on the "src/main/java" folder and choose "New/Class".
  3. Enter "net.floodlightcontroller.mactracker" in the "Package" box.
  4. Enter "MACTracker" in the "Name" box.
  5. Next to the "Interfaces" box, choose "Add...".
  6. Add the "IOFMessageListener" and the "IFloodlightModule", click "OK".
  7. Click "Finish" in the dialog.

You should end up with something like this:

package net.floodlightcontroller.mactracker;

import java.util.Collection;
import java.util.Map;

import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.types.MacAddress;

import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.module.IFloodlightModule;
import net.floodlightcontroller.core.module.IFloodlightService;

public class MACTracker implements IOFMessageListener, IFloodlightModule {

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isCallbackOrderingPrereq(OFType type, String name) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean isCallbackOrderingPostreq(OFType type, String name) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public Collection<Class<? extends IFloodlightService>> getModuleServices() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void init(FloodlightModuleContext context)
			throws FloodlightModuleException {
		// TODO Auto-generated method stub

	}

	@Override
	public void startUp(FloodlightModuleContext context) {
		// TODO Auto-generated method stub

	}

	@Override
	public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
		// TODO Auto-generated method stub
		return null;
	}

}

Setting Up Module Dependencies And Initialization

Before we get started, we are going to need a number of dependencies for the code to work. A tool like Eclipse should make it easy to add them. However, if you aren't using eclipse, you may just want to add them up front:

import net.floodlightcontroller.core.IFloodlightProviderService;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.Set;
import net.floodlightcontroller.packet.Ethernet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Now that we have our skeleton class, we have to implement the correct functions to make the module loadable. Lets start by registering some member variables we'll need into the class. Since we are listening to OpenFlow messages we need to register with the FloodlightProvider (IFloodlightProviderService class). We also need a set to store macAddresses we've seen. Finally, we need a logger to output what we've seen.

protected IFloodlightProviderService floodlightProvider;
protected Set<Long> macAddresses;
protected static Logger logger;

Now we need to wire it up to the module loading system. We tell the module loader we depend on it by modifying the getModuleDependencies() function.

@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
    Collection<Class<? extends IFloodlightService>> l =
        new ArrayList<Class<? extends IFloodlightService>>();
    l.add(IFloodlightProviderService.class);
    return l;
}

Now its time to create our Init method. Init is called early in the controller startup process — it primarily is run to load dependencies and initialize datastructures.

@Override
public void init(FloodlightModuleContext context) throws FloodlightModuleException {
    floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
    macAddresses = new ConcurrentSkipListSet<Long>();
    logger = LoggerFactory.getLogger(MACTracker.class);
}

Handling The Packet-In Message

Now it's time to implement the basic listener. We'll register for PACKET_IN messages in our startUp method. Here we are assured other modules we depend on are already initialized.

@Override
public void startUp(FloodlightModuleContext context) {
    floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
}

We also have to put in an ID for our OFMessage listener. This is done in the getName() call.

@Override
public String getName() {
    return MACTracker.class.getSimpleName();
}

Now we have to define the behavior we want for PACKET_IN messages. Note that we return Command.CONTINUE to allow this message to continue to be handled by other PACKET_IN handlers as well.

@Override
   public net.floodlightcontroller.core.IListener.Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
        Ethernet eth =
                IFloodlightProviderService.bcStore.get(cntx,
                                            IFloodlightProviderService.CONTEXT_PI_PAYLOAD);

        Long sourceMACHash = eth.getSourceMACAddress().getLong();
        if (!macAddresses.contains(sourceMACHash)) {
            macAddresses.add(sourceMACHash);
            logger.info("MAC Address: {} seen on switch: {}",
                    eth.getSourceMACAddress().toString(),
                    sw.getId().toString());
        }
        return Command.CONTINUE;
    }

If you would like more information on how to inspect higher-layer packet headers like IP, TCP, etc., please refer to this tutorial.

Ordering Modules when Processing OpenFlow Messages

Although it is not necessary in this tutorial, it is oftentimes necessary to define the order in which IOFMessageListeners process OpenFlow messages. IOFMessageListener's isCallbackOrderingPrereq(OFType type, String name) and isCallbackOrderingPostreq(OFType type, String name) functions define the order of modules that process packets received from a switch. Each OpenFlow message received, e.g. a PACKET_IN, is processed by one module at a time so that modules can pass metadata from one to another about the packet or terminate the processing chain entirely.

isCallbackOrderingPrereq() defines modules that should run before our module when processing a particular type of OpenFlow message. isCallbackOrderingPostreq() defines modules that should run after our module when processing a particular type of OpenFlow message.

A common use is to tell the Forwarding module, which implements layer-2 reactive packet forwarding, to run after our module, since Forwarding will insert flows and modify the network state. If we want to insert our own flows defined by our own algorithm, then we need to tell Forwarding to run after our module, thus we should use isCallbackOrderingPostreq().

As an example, the Device Manager reacts to PACKET_IN messages to learn where hosts are located in the network. Forwarding relies on devices learned by the Device Manager to insert flows from point A to point B in the network. Thus, Forwarding needs to tell the Device Manager to process PACKET_IN messages before itself. Taking a look at Forwarding's source code reveals how Forwarding accomplishes this.

Note specifically that the name of the other module is passed as an argument to either of the two callback ordering functions. This is the module we should make a decision about for the OpenFlow message type also passed in as an argument. Our decision is reflected by returning true for yes or false for no. The name of each module is defined by IOFMessageListener's getName(), as shown here and as discussed above.

Register The Module

We're almost done, now we just need to tell Floodlight to load the module on startup. First we have to tell the loader that the module exists. This is done by adding the fully qualified module name on it's own line in src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule. We open that file and append this line:

net.floodlightcontroller.mactracker.MACTracker

Then we tell the module to be loaded. We modify the Floodlight module configuration file to append the MACTracker. The default one is src/main/resources/floodlightdefault.properties. The key is floodlight.modules and the value is a comma separated list of fully qualified module names.

floodlight.modules = <leave the default list of modules in place>, net.floodlightcontroller.mactracker.MACTracker

Finally, let's run the controller by right clicking on Main.java and choose "Run As.../Java Application".

How To Connect Mininet Software OpenFlow Switches To Floodlight

This assumes you are running Mininet inside a VM on your host, and you are running Floodlight from Eclipse on the host.

Determine your host's IP address relative to Mininet, in the below example it is set as the Gateway (192.168.110.2)

mininet@mininet:~$ sudo route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.110.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
0.0.0.0 192.168.110.2 0.0.0.0 UG 0 0 0 eth0
mininet@mininet:~$ sudo mn --controller=remote,ip=192.168.110.2,port=6653 --switch=ovsk,protocols=OpenFlow13
*** Loading ofdatapath
*** Adding controller
*** Creating network
*** Adding hosts:
h2 h3
*** Adding switches:
s1
*** Adding edges:
(s1, h2) (s1, h3)
*** Configuring hosts
h2 h3
*** Starting controller
*** Starting 1 switches
s1
*** Starting CLI:
mininet>pingall

The Pingall command should generate debug output from your MACTracker on the console.

Have You Gotten This Far?

If you have gotten this far, its time to move over to the Floodlight wiki to learn more. We also have a more advanced tutorial available.