Handling packets on the OpenDaylight controller

One of the actions that an OpenFlow switch can take is to punt a packet to the controller.  This example will take a look at how we can see those packets, and do something with them.  I hope to follow this up with another post that does something more exciting, but for now I’ll just try to print out what type of packet it is.  This is one of the things that (as far as I know)  you would only be able to do with an OSGi module, and is not available via the REST API.

First we create our Maven pom.xml with the required imports. In this case we’ll need some parts of the SAL and switchmanager:

 
Now we can create our activator. The key ingredient here is to register for callbacks from the Data Packet Service via OSGi in our public void configureInstance method:

            c.add(createContainerServiceDependency(containerName).setService(
                    IDataPacketService.class).setCallbacks(
                    "setDataPacketService", "unsetDataPacketService")
                    .setRequired(true));

This ties into methods that we implement in our GetPackets class:

    void setDataPacketService(IDataPacketService s) {
        this.dataPacketService = s;
    }

    void unsetDataPacketService(IDataPacketService s) {
        if (this.dataPacketService == s) {
            this.dataPacketService = null;
        }
    }

We make the class implement the IListenDataPacket interface to get notified of packets received on the controller:

public class GetPackets implements IListenDataPacket

And we override the public PacketResult receiveDataPacket(RawPacket inPkt) method:

    @Override
    public PacketResult receiveDataPacket(RawPacket inPkt) {
        if (inPkt == null) {
            return PacketResult.IGNORED;
        }
        log.trace("Received a frame of size: {}",
                        inPkt.getPacketData().length);
        Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
        System.out.println("packet");
        System.out.println(formattedPak);        
        if (formattedPak instanceof Ethernet) {
            System.out.println(formattedPak);
            Object nextPak = formattedPak.getPayload();
            if (nextPak instanceof IPv4) {
                IPv4 ipPak = (IPv4)nextPak;
                System.out.println("IP");
                log.trace("Handled IP packet");
                int sipAddr = ipPak.getSourceAddress();
                InetAddress sip = NetUtils.getInetAddress(sipAddr);
                int dipAddr = ipPak.getDestinationAddress();
                InetAddress dip = NetUtils.getInetAddress(dipAddr);
                System.out.println("SRC IP:");
                System.out.println(sip);
                System.out.println("DST IP:");
                System.out.println(dip);

                Object frame = ipPak.getPayload();
                if (frame instanceof ICMP) {
                    System.out.println("ICMP from instance");
                }
                String protocol = IPProtocols.getProtocolName(ipPak.getProtocol());
                if (protocol == IPProtocols.ICMP.toString()) {
                    ICMP icmpPak = (ICMP)ipPak.getPayload();
                    System.out.println("ICMP from checking protocol");
                    handleICMPPacket((Ethernet) formattedPak, icmpPak, inPkt.getIncomingNodeConnector());
                }
            }
        }
        return PacketResult.IGNORED;
    }

You’ll notice that we can keep going into the different payloads of the frame/packet to get to the next network layer. However, using instanceof can be slow, so an alternative is to pull out the protocol field, and do a comparison. In my example I’ve specifically handled ICMP packets, and used both methods for determining if the IP packet is ICMP.

ODL + Maven + OSGi

Maven is a project/build management tool that is used in OpenDaylight to handle the builds and dependencies. I decided to try and build an OSGi bundle from scratch that interacts with ODL, that is similar to the python stats script I wrote earlier.

To get started you can run mvn from the command prompt to have Maven build your project directory structure. Maven dictates a general structure for where your source, test, and binary files will go. I used the following command for to setup my project which I’m calling mystats (with a group ID of example.com):

mvn archetype:generate -DgroupId=com.example -DartifactId=mystats -DarchetypeArtifactId=maven-archetype-quickstart -Dpackage=com.example.mystats -DinteractiveMode=false

This creates a directory mystats with the following structure:

   |-src
   |---main
   |-----java
   |-------com
   |---------example
   |-----------mystats

It also creates a pom.xml file which is key to the maven build process.

As you may recall from my previous post on OSGi, an OSGi bundle has a manifest file that declares the various packages that are imported and exported. By using a plugin, maven can generate this for you while doing the build and insert it into your bundle with the jar file. To make all this work I had to make the following changes to the pom file:

  • Added a parent that references the ODL project. Note the relativePath field which needs to point to your ODL pom.xml. (lines 4-9)
  • Changed the packaging from jar to osgi. (line 15)
  • Added the felix plugin for OSGi, and declared the packages that will be imported an what this bundle will export. (lines 18-48)

Here we also define an Activator class for the bundle

  • I am importing the following:
    • statisticsmanager – to get the stats
    • switchmanager – to find all the switches
    • sal – service abstraction layer
    • slf4j – logging
    • apache.felix.dm – dependency management
  • Declare our dependencies, in this case statisticamanager and sal.  (lines 49-66)
    • The dependencies are transitive and should grab what is needed, but I ended up putting the sal in there manually to get the correct version.
    • Remember when you did a mvn clean install when building the controller? That installs a bunch of the stuff you’ll be accessing in your ~/.m2/repositories directory. Go have a look there to see where some of this stuff is coming from.

Next we need to create the Activator.java and MyStats.java files.  The Activator helps manage the lifecycle of the bundle, allowing you to specify what gets run when the bundle gets initialized, starts, stops, etc.  I probably don’t even need one for this simple example, but I put one in anyway.  The MyStats file is the actual program that uses the IStatisticsManager interface to pull the stats for all the switches.

Once everything is coded up we can build the package with Maven by executing a mvn package which builds the bundle/package.

$ mvn package
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building mystats 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- properties-maven-plugin:1.0-alpha-2:set-system-properties (default) @ mystats ---
[INFO] Set 1 system property
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ mystats ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/fhsu/code/odl/mystats/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ mystats ---
[INFO] Compiling 2 source files to /home/fhsu/code/odl/mystats/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ mystats ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/fhsu/code/odl/mystats/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ mystats ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.14.1:test (default-test) @ mystats ---
[INFO] Surefire report directory: /home/fhsu/code/odl/mystats/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.example.mystats.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.006 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- maven-bundle-plugin:2.3.6:bundle (default-bundle) @ mystats ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.872s
[INFO] Finished at: Mon May 13 16:27:20 PDT 2013
[INFO] Final Memory: 23M/340M
[INFO] ------------------------------------------------------------------------

If you’re running Eclipse, you may need to update the packages using the m2e tool. Now we can install it in the OSGi runtime that is running the controller:

osgi> install file:/Users/fhsu/dev/eclipse/odlworkspace/mystats/target/mystats-0.0.1-SNAPSHOT.jar
Bundle id is 117
RegisteredServices   null
ServicesInUse        null
Fragments            null
ClassLoader          null
LoaderProxy          com.example.mystats; bundle-version="0.0.1.SNAPSHOT"
Headers               Bnd-LastModified = 1368335490782
 Build-Jdk = 1.7.0_17
 Built-By = fhsu
 Bundle-Activator = com.example.mystats.Activator
 Bundle-ManifestVersion = 2
 Bundle-Name = mystats
 Bundle-SymbolicName = com.example.mystats
 Bundle-Version = 0.0.1.SNAPSHOT
 Created-By = Apache Maven Bundle Plugin
 Export-Package = com.example.mystats;uses:="org.opendaylight.controller.sal.core,org.apache.felix.dm,org.slf4j,org.opendaylight.controller.sal.flowprogrammer,org.opendaylight.controller.statisticsmanager,org.opendaylight.controller.switchmanager,org.opendaylight.controller.sal.reader,org.opendaylight.controller.sal.match,org.opendaylight.controller.sal.utils";version="0.0.1.SNAPSHOT"
 Import-Package = org.apache.felix.dm;version="[3.0,4)",org.opendaylight.controller.sal.core;version="[0.4,1)",org.opendaylight.controller.sal.flowprogrammer;version="[0.4,1)",org.opendaylight.controller.sal.match;version="[0.4,1)",org.opendaylight.controller.sal.reader;version="[0.4,1)",org.opendaylight.controller.sal.utils;version="[0.4,1)",org.opendaylight.controller.statisticsmanager;version="[0.4,1)",org.opendaylight.controller.switchmanager;version="[0.4,1)",org.slf4j;version="[1.7,2)"
 Manifest-Version = 1.0
 Tool = Bnd-1.50.0

BundleContext        null
BundleId             117
StartLevel           1
SymbolicName         com.example.mystats
Location             file:/Users/fhsu/dev/eclipse/odlworkspace/mystats/target/mystats-0.0.1-SNAPSHOT.jar
State                2
Bundle                 117|Installed  |    1|com.example.mystats(0.0.1.SNAPSHOT)
Version              0.0.1.SNAPSHOT
BundleData           com.example.mystats.0.1.SNAPSHOT
KeyHashCode          117
StateChanging        null
BundleDescription    com.example.mystats.0.1.SNAPSHOT
Framework            org.eclipse.osgi.framework.internal.core.Framework@10695754
ResolutionFailureException org.osgi.framework.BundleException: The bundle "com.example.mystats.0.1.SNAPSHOT [117]" could not be resolved
Revisions            [com.example.mystats.0.1.SNAPSHOT]
ProtectionDomain     null
Key                  117
LastModified         1368335508452

Now we can start our bundle and see the results:

osgi> start 117
Node: OF|00:00:00:00:00:00:00:07
 DST: NW_DST(10.0.0.3,null) Bytes: 784
 DST: NW_DST(10.0.0.6,null) Bytes: 784
 DST: NW_DST(10.0.0.8,null) Bytes: 2646
 DST: NW_DST(10.0.0.2,null) Bytes: 686
 DST: NW_DST(10.0.0.4,null) Bytes: 784
 DST: NW_DST(10.0.0.7,null) Bytes: 2548
 DST: NW_DST(10.0.0.5,null) Bytes: 784
 DST: NW_DST(10.0.0.1,null) Bytes: 588
Node: OF|00:00:00:00:00:00:00:06
 DST: NW_DST(10.0.0.3,null) Bytes: 784
 DST: NW_DST(10.0.0.6,null) Bytes: 2548
 DST: NW_DST(10.0.0.8,null) Bytes: 784
 DST: NW_DST(10.0.0.2,null) Bytes: 588
 DST: NW_DST(10.0.0.4,null) Bytes: 784
 DST: NW_DST(10.0.0.7,null) Bytes: 784
 DST: NW_DST(10.0.0.5,null) Bytes: 2548
 DST: NW_DST(10.0.0.1,null) Bytes: 588
Node: OF|00:00:00:00:00:00:00:05
 DST: NW_DST(10.0.0.3,null) Bytes: 1568
 DST: NW_DST(10.0.0.6,null) Bytes: 2156
 DST: NW_DST(10.0.0.8,null) Bytes: 2254
 DST: NW_DST(10.0.0.2,null) Bytes: 1274
 DST: NW_DST(10.0.0.4,null) Bytes: 1568
 DST: NW_DST(10.0.0.7,null) Bytes: 2156
 DST: NW_DST(10.0.0.5,null) Bytes: 2156
 DST: NW_DST(10.0.0.1,null) Bytes: 1176
Node: OF|00:00:00:00:00:00:00:04
 DST: NW_DST(10.0.0.3,null) Bytes: 2548
 DST: NW_DST(10.0.0.6,null) Bytes: 784
 DST: NW_DST(10.0.0.8,null) Bytes: 784
 DST: NW_DST(10.0.0.2,null) Bytes: 588
 DST: NW_DST(10.0.0.4,null) Bytes: 2548
 DST: NW_DST(10.0.0.7,null) Bytes: 784
 DST: NW_DST(10.0.0.5,null) Bytes: 784
 DST: NW_DST(10.0.0.1,null) Bytes: 588
Node: OF|00:00:00:00:00:00:00:03
 DST: NW_DST(10.0.0.3,null) Bytes: 588
 DST: NW_DST(10.0.0.6,null) Bytes: 588
 DST: NW_DST(10.0.0.8,null) Bytes: 686
 DST: NW_DST(10.0.0.2,null) Bytes: 2058
 DST: NW_DST(10.0.0.4,null) Bytes: 588
 DST: NW_DST(10.0.0.7,null) Bytes: 588
 DST: NW_DST(10.0.0.5,null) Bytes: 588
 DST: NW_DST(10.0.0.1,null) Bytes: 1960
Node: OF|00:00:00:00:00:00:00:02
 DST: NW_DST(10.0.0.3,null) Bytes: 2156
 DST: NW_DST(10.0.0.6,null) Bytes: 1372
 DST: NW_DST(10.0.0.8,null) Bytes: 1470
 DST: NW_DST(10.0.0.2,null) Bytes: 1862
 DST: NW_DST(10.0.0.4,null) Bytes: 2156
 DST: NW_DST(10.0.0.7,null) Bytes: 1372
 DST: NW_DST(10.0.0.5,null) Bytes: 1372
 DST: NW_DST(10.0.0.1,null) Bytes: 1764
Node: OF|00:00:00:00:00:00:00:01
 DST: NW_DST(10.0.0.3,null) Bytes: 1568
 DST: NW_DST(10.0.0.6,null) Bytes: 1372
 DST: NW_DST(10.0.0.8,null) Bytes: 1470
 DST: NW_DST(10.0.0.2,null) Bytes: 1274
 DST: NW_DST(10.0.0.4,null) Bytes: 1568
 DST: NW_DST(10.0.0.7,null) Bytes: 1372
 DST: NW_DST(10.0.0.5,null) Bytes: 1372
 DST: NW_DST(10.0.0.1,null) Bytes: 1176
osgi>

This is a pretty simple example, but I found it useful to start understanding how all these things work together. Let me know if I’ve misinterpreted anything! You can find all the code on github:
https://github.com/fredhsu/odl-apps

OpenDaylight and OSGI basics

I am starting to learn about OSGI as it pertains to ODL.  OSGI allows for modules to be dynamically loaded and unloaded from a system, and provides encapsulation between those modules.  The modules are called bundles, and they provide services to one another via the execution environment.  The bundles are basically JAR files with a manifest file that indicates what is to be exported to other bundles, and what needs to be imported from other bundles. Here’s a diagram of how I currently think of it:
osgi-1
After executing the run.sh script in ODL, you will load the OSGI environment, and get an osgi> prompt which allows you to interact with the system.  We can use the command ss to get a list of all the bundles currently running:

osgi> ss
"Framework is launched."

id	State       Bundle
0	ACTIVE      org.eclipse.osgi_3.8.1.v20120830-144521
1	ACTIVE      org.apache.felix.fileinstall_3.1.6
2	ACTIVE      org.eclipse.jdt.core.compiler.batch_3.8.0.I20120518-2145
3	ACTIVE      org.eclipse.equinox.ds_1.4.0.v20120522-1841
4	ACTIVE      org.eclipse.equinox.util_1.0.400.v20120522-2049
5	ACTIVE      org.eclipse.osgi.services_3.3.100.v20120522-1822
6	ACTIVE      org.eclipse.equinox.console_1.0.0.v20120522-1841
7	ACTIVE      slf4j.api_1.7.2
8	ACTIVE      ch.qos.logback.classic_1.0.9
9	ACTIVE      ch.qos.logback.core_1.0.9
10	ACTIVE      org.opendaylight.controller.logging.bridge_0.4.0.SNAPSHOT
11	ACTIVE      com.sun.jersey.core_1.17.0
12	ACTIVE      com.sun.jersey.json_1.17.0
13	ACTIVE      com.sun.jersey.jersey-server_1.17.0
14	ACTIVE      org.opendaylight.controller.clustering.test_0.4.0.SNAPSHOT
15	ACTIVE      org.apache.felix.gogo.shell_0.8.0.v201110170705
... snip ...100	ACTIVE      org.opendaylight.controller.flowprogrammer.northbound_0.4.0.SNAPSHOT
101	RESOLVED    org.opendaylight.controller.troubleshoot.web_0.4.0.SNAPSHOT
102	ACTIVE      javax.servlet.jsp.jstl_1.2.0.v201105211821
103	ACTIVE      org.springframework.beans_3.1.3.RELEASE
104	ACTIVE      org.opendaylight.controller.configuration.implementation_0.4.0.SNAPSHOT
105	ACTIVE      org.apache.felix.gogo.command_0.8.0.v201108120515
106	ACTIVE      org.springframework.transaction_3.1.3.RELEASE
107	ACTIVE      org.opendaylight.controller.forwarding.staticrouting_0.4.0.SNAPSHOT
108	ACTIVE      org.opendaylight.controller.forwardingrulesmanager_0.4.0.SNAPSHOT
109	ACTIVE      org.eclipse.gemini.web.core_2.2.0.RELEASE
110	RESOLVED    org.apache.catalina.ha_7.0.32.v201211201952
	            Master=31
111	ACTIVE      jackson-jaxrs_1.9.8
112	ACTIVE      javax.annotation_1.1.0.v201209060031
113	ACTIVE      org.opendaylight.controller.statisticsmanager_0.4.0.SNAPSHOT
114	ACTIVE      javax.activation_1.1.0.v201211130549
osgi>

An example I was shown on bundle lifecycle is to stop the troubleshooting bundle, and see the results on the ODL page. Here’s how the page looks initially:
odl-plain
Now lets stop the troubleshooting bundle:

osgi> ss | grep troubleshoot
101	ACTIVE      org.opendaylight.controller.troubleshoot.web_0.4.0.SNAPSHOT
true
osgi> stop 101
May 03, 2013 10:58:25 AM org.apache.catalina.core.ApplicationContext log
INFO: Destroying Spring FrameworkServlet 'Troubleshoot'

Now if we reload the page, notice the troubleshooting tab is gone:
odl-notopo
So OSGI let us unload this piece of the controller without stopping anything. We can just as easily start it back up again:

osgi> start 101
May 03, 2013 11:01:19 AM org.apache.catalina.core.ApplicationContext log
INFO: No Spring WebApplicationInitializer types detected on classpath
May 03, 2013 11:01:19 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'Troubleshoot'

And now we get our troubleshooting tab back. I’ve just started learning about OSGI, but hopefully this is a good start. Please let me know if I’m misunderstanding anything.

Programming Langauge choices for SDN

Inspired by a recent post by @etherealmind, I’ve been thinking about the pros and cons of learning different programming languages, and what would be best suited for SDN.

C

Pros:  The standard when it comes to sockets and “hard core” network programming.  

Cons:  Pointers, memory allocation, and a tendency (for me at least) to build monolithic code.

APIs/Frameworks of interest:  onePK

Java

Pros:  Widespread use, and lots of libraries for doing things.  Cross-platform.  OpenDaylight uses it.

Cons:  Requires a lot of “cruft” to do simple things.  

APIs/Frameworks of interest:  onePK, Spring, OSGI, OpenDaylight

Python

Pros:  Great high level language, lots of libraries, used by many projects like POX

Cons:  Less useful for OpenDaylight stuff

APIs/Frameworks of interest:  onePK, Django, OpenStack (added 2013-25-04)

Ruby

Pros:  Another nice high level language, Rails, used in Chef/Puppet, seems to work well for building DSLs

Cons:  Less useful for OpenDaylight and onePK

APIs/Frameworks of interest:  Chef, Puppet, Rails

Javascript/Coffeescript/Node.js

Pros:  Widespread use in web programming.  Node.js allows for server side stuff to be build and is fast due to V8.  Node.js encourages event-driven programming which may map well to network stuff.  

Cons:  “Different” syntax (which gets somewhat fixed by Coffeescript)

APIs/Frameworks of interest:  Express

Scala

Pros:  A functional language that runs on JVM, interop with Java, “cleaner” than Java

Cons:  Not as widespread use.  

APIs/Frameworks of interest:  Lift

Clojure/Clojurescript

Pros:  Like Scala, is a functional language for the JVM.  Can use Java libraries.  It’s a LISP (this can be good or bad depending on your point of view).  Clojurescript allows you to also write Javascript code in Clojure.

Cons:  Doesn’t seem to work well with OSGI.  Smaller user base.

APIs/Frameworks of interest:  Compojure

Its tough to find one language that does everything I want, with minimal fuss.  At first glance it seems like Java can do everything, with Python as a close second.  I’m interested in the JVM functional languages such as Clojure and Scala, but it may be tough to get help with problems when using those languages.  What I’ll probably end up doing is use Python when I can, then drop to Java when necessary.  Am I missing anything?  Feedback is welcome!