[Openflow integration] in lighty-core

What is OpenFlow?

OpenFlow (OF) is a communications protocol, that gives access to the forwarding plane of a network switch or router over the network. OpenFlow can be applied for:

  • Quality of Service measurement by traffic filtering
  • Network monitoring (i.e., using the controller as a monitoring device)

In virtual networking context, OF can be used to program virtual switches with tenant level segregation tags (for example VLANs). In the context of NFV, OF can be used to re-direct traffic to a chain of services. It is managed by the Open Networking Foundation.

Why do we need OpenFlow?

Routers and switches can achieve various (limited) levels of user programmability. However, engineers and management need more than the limited functionality of this hardware. OpenFlow achieves consistent traffic management and engineering exactly for these needs. This is achieved by controlling the functions independently of the hardware used.

PANTHEON.tech has managed to implement the OpenFlow plugin in lighty-core. Today, we will show you how you can run the plugin yourself.

Prerequisites

In order to build and install lighty-core artifacts locally, follow the procedure below:

  1. Install JDK – make sure JDK 8 is installed
  2. Install maven – make sure you have maven 3.5.0 or later installed
  3. Setup maven – make sure you have proper settings.xml in your ~/.m2 directory
  4. Download/clone lighty-core
  5. Download/clone the OpenFlow Manager App

Build and Run Openflow plugin example

Build the lighty-core example project lighty-community-restconf-ofp-app (lighty-core/lighty-examples/lighty-community-restconf-ofp-app) with maven command:

mvn clean install

This will create a .zip archive in the target directory. Extract this archive and start start-ofp.sh script, or run .jar file using Java 8 with command:

java -jar lighty-community-restconf-ofp-app-9.2.1-SNAPSHOT.jar

Use custom config files

The previous command will run the application with default configuration. In order to run it with a custom configuration, edit (or create new) json configuration file. Example of json configuration can be found in lighty-community-restconf-ofp-app-9.2.1-SNAPSHOT folder.

With previous command to start application, pass path to configuration file as argument:

java -jar lighty-community-restconf-ofp-app-9.2.1-SNAPSHOT.jar sampleConfigSingleNode.json

Openflow plugin and RESTCONF Configuration

An important configuration, which decides what can be changed, is stored in sampleConfigSingleNode.json. For applying all changes, we need to start OFP with this configuration as a Java parameter.

ForwardingRulesManager

FLOWs can be added to Openflow Plugin (OFP) in two ways:

  • Put FLOW to config data-store and ForwardingRulesManager (FRM). This will add flow to the device via a RPC call. When FRM is enabled and FLOW is added to config data-store, only then will this FLOW persist until user will decide to delete it
  • Second way is by a RPC call, which adds FLOW directly to the target device. After a restart of this device, all flows will be deleted. RPC calls can be used with enabled FRM

When you disable FRM, the device will start OFP with an external configuration.This json configuration can be changed by setting enableForwardingRulesManager property to false.

RESTCONF

RESTCONF configuration cam be changed in the json config file enableForwardingRulesManager, mentioned above. It is possible to change the RESTCONF port, IP address or version of RESTCONF . Currently, the version of RESTCONF set to DRAFT_18, but it can be set to DRAFT_02.

"restconf": {
    "httpPort": 8888,
    "restconfServletContextPath": "/restconf",
    "inetAddress": "0.0.0.0",
    "jsonRestconfServiceType": "DRAFT_18"
  },

How to start Openflow example

Firstly. start the OpenFlow example application.

Next step is to start mininet, with at least one OpenFlow switch (use version 2.2.0 and higher):

sudo mn --controller=remote,ip=<IP_OF_RUNNING_LIGHTY> --topo=tree,1 --switch ovsk,protocols=OpenFlow13

For this explanation of OFP usage, RESTCONF is set to DRAFT_18. All RESTCONF calls used in this example can be imported from file OFP_postman_collection.json, in project resources to Postman.

We will quickly check, if the controller is the owner of the connected device. If not, then the controller is not running or  the device is not properly connected:

curl --request GET \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/entity-owners:entity-owners \
  --header 'Authorization: Basic YWRtaW46YWRtaW4='

If you followed the instructions and there is a single controller running (not cluster) and only one device connected, the result is:

{
  "entity-owners": {
    "entity-type": [{
      "type": "org.opendaylight.mdsal.ServiceEntityType",
      "entity": [{
        "id": "/odl-general-entity:entity[name='openflow:1']",
        "candidate": [{
          "name": "member-1"
        }],
        "owner": "member-1"
      }]
    }, {
      "type": "org.opendaylight.mdsal.AsyncServiceCloseEntityType",
      "entity": [{
        "id": "/odl-general-entity:entity[name='openflow:1']",
        "candidate": [{
          "name": "member-1"
        }],
        "owner": "member-1"
      }]
    }]
  }
}

Let’s get information about the connected device. If you want to see all OFP inventory, use ‘get inventory‘ call from PostmanCollection.

From config:

curl -k --insecure --request GET \
  --url http:///<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A1 \
  --header 'Authorization: Basic YWRtaW46YWRtaW4='

From operational:

curl -k --insecure --request GET \
  --url http:///<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A1?content=nonconfig \
  --header 'Authorization: Basic YWRtaW46YWRtaW4='

Json result starts with:

{
  "node": [{
    "id": "openflow:1",
    "node-connector": [{
      "id": "openflow:1:LOCAL",
      "flow-node-inventory:peer-features": "",
      "flow-node-inventory:advertised-features": "",
      "flow-node-inventory:port-number": 4294967294,
      "flow-node-inventory:hardware-address": "4a:15:31:79:7f:44",
      "flow-node-inventory:supported": "",
      "flow-node-inventory:current-speed": 0,
      "flow-node-inventory:current-feature": "",
      "flow-node-inventory:state": {
        "live": false,
        "link-down": true,
        "blocked": false
      },
      "flow-node-inventory:maximum-speed": 0,
      "flow-node-inventory:name": "s1",
      "flow-node-inventory:configuration": "PORT-DOWN"
    }, {
      "id": "openflow:1:2",
      "flow-node-inventory:peer-features": "",
      "flow-node-inventory:advertised-features": "",
      "flow-node-inventory:port-number": 2,
      "flow-node-inventory:hardware-address": "fa:c3:2c:97:9e:45",
      "flow-node-inventory:supported": "",
      "flow-node-inventory:current-speed": 10000000,
      "flow-node-inventory:current-feature": "ten-gb-fd copper",
      "flow-node-inventory:state": {
        "live": false,
        "link-down": false,
        "blocked": false
      },
      .
      .
      .

Add FLOW

Now try to add table-miss flow, which will modify the switch to send all not-matched-packets to controller via packet-in messages.

To config data-store:

curl --request PUT \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A1/table=0/flow=1 \
  --header 'Authorization: Basic YWRtaW46YWRtaW4=' \
  --header 'Content-Type: application/xml' \
  --data '<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<flow xmlns="urn:opendaylight:flow:inventory">
   <barrier>false</barrier>
   <cookie>54</cookie>
   <flags>SEND_FLOW_REM</flags>
   <flow-name>FooXf54</flow-name>
   <hard-timeout>0</hard-timeout>
   <id>1</id>
   <idle-timeout>0</idle-timeout>
   <installHw>false</installHw>
   <instructions>
       <instruction>
           <apply-actions>
               <action>
                   <output-action>
                       <max-length>65535</max-length>
                       <output-node-connector>CONTROLLER</output-node-connector>
                   </output-action>
                   <order>0</order>
               </action>
           </apply-actions>
           <order>0</order>
       </instruction>
   </instructions>
   <match/>
   <priority>0</priority>
   <strict>false</strict>
   <table_id>0</table_id>
</flow>'

Directly to device via RPC call:

curl --request POST \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/operations/sal-flow:add-flow \
  --header 'Authorization: Basic YWRtaW46YWRtaW4=' \
  --header 'Content-Type: application/json' \
  --data '{
    "input": {
      "opendaylight-flow-service:node":"/opendaylight-inventory:nodes/opendaylight-inventory:node[opendaylight-inventory:id='\''openflow:1'\'']",
      "priority": 0,
      "table_id": 0,
      "instructions": {
        "instruction": [
          {
            "order": 0,
            "apply-actions": {
              "action": [
                {
                  "order": 0,
                  "output-action": {
                    "max-length": "65535",
                    "output-node-connector": "CONTROLLER"
                  }
                }
              ]
            }
          }
        ]
      },
      "match": {
      }
    }
}

Get FLOW

Check if flow is in data-store:

In config:

curl --request GET \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A1/table=0 \
  --header 'Authorization: Basic YWRtaW46YWRtaW4='

In operational:

curl --request GET \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A1/table=0?content=nonconfig \
  --header 'Authorization: Basic YWRtaW46YWRtaW4='

Result:

{
    "flow-node-inventory:table": [
        {
            "id": 0,
            "opendaylight-flow-table-statistics:flow-table-statistics": {
                "active-flows": 1,
                "packets-looked-up": 14,
                "packets-matched": 4
            },
            "flow": [
                {
                    "id": "1",
                    "priority": 0,
                    "opendaylight-flow-statistics:flow-statistics": {
                        "packet-count": 4,
                        "byte-count": 280,
                        "duration": {
                            "nanosecond": 936000000,
                            "second": 22
                        }
                    },
                    "table_id": 0,
                    "cookie_mask": 0,
                    "hard-timeout": 0,
                    "match": {},
                    "cookie": 54,
                    "flags": "SEND_FLOW_REM",
                    "instructions": {
                        "instruction": [
                            {
                                "order": 0,
                                "apply-actions": {
                                    "action": [
                                        {
                                            "order": 0,
                                            "output-action": {
                                                "max-length": 65535,
                                                "output-node-connector": "CONTROLLER"
                                            }
                                        }
                                    ]
                                }
                            }
                        ]
                    },
                    "idle-timeout": 0
                }
            ]
        }
    ]
}

Get FLOW directly from modified device s1 in command line:

sudo ovs-ofctl -O OpenFlow13 dump-flows s1

Device result:

cookie=0x36, duration=140.150s, table=0, n_packets=10, n_bytes=700, send_flow_rem priority=0 actions=CONTROLLER:65535

Update FLOW

This works the same as add flow. OFP will find openflow:1, table=0, flow=1 from url and update changes from body.

Delete FLOW

From config data-store:

curl --request DELETE \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A1/table=0/flow=1 \
  --header 'Authorization: Basic YWRtaW46YWRtaW4=' \
  --header 'Content-Type: application/xml' \
  --data ''

Via RPC calls:

curl --request POST \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/operations/sal-flow:remove-flow \
  --header 'Authorization: Basic YWRtaW46YWRtaW4=' \
  --header 'Content-Type: application/json' \
  --data '{
    "input": {
      "opendaylight-flow-service:node":"/opendaylight-inventory:nodes/opendaylight-inventory:node[opendaylight-inventory:id='\''openflow:1'\'']",
      "table_id": 0
    }
}'

Proactive flow installation and traffic monitor via Packet-In messages.

In order to create traffic, we need to setup topology behavior. There are three methods for flow table population (Reactive, Proactive, Hybrid). We encourage you to read more about their differences.

In our example we use Proactive flow installation, which means that we proactively create flows before traffic start.

  1. Start lighty OpenFlow example lighty-community-restconf-ofp-app
  2. Create mininet with linear topology and 2 open vSwitches
sudo mn --controller=remote,ip=<IP_OF_RUNNING_LIGHTY>:6633 --topo=linear,2 --switch ovsk,protocols=OpenFlow13

Now we tested that connection between devices is not established:

mininet> pingall
*** Ping: testing ping reachability
h1 -> X
h2 -> X
*** Results: 100% dropped (0/2 received)

Next step is to create flows, that create a connection between switches. This will be managed by setting Matched and Action filed in Table 0. In this example, we connect two ports eth1 and eth2 in a switch together. So everything that comes from port eth1, is redirected to port eth2 and vice versa.

Next configuration will be sending monitoring packets in network. This configuration has set the switch s1 to send all packets, which came to port eth2, to the controller. So the visualized result should looks like this:

1. Add flow to switch s1 (also named openflow:1 in OFP ), which redirects all traffic that comes from port 1 to port 2:

curl --request PUT \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A1/table=0/flow=0 \
  --header 'Authorization: Basic YWRtaW46YWRtaW4=' \
  --header 'Content-Type: application/json' \
  --data '{
      "flow": [
          {
              "table_id": "0",
              "id": "0",
              "priority": "10",
              "match": {
                  "in-port": "openflow:1:1"
              },
              "instructions": {
                  "instruction": [
                      {
                          "order": 0,
                          "apply-actions": {
                              "action": [
                                  {
                                      "order": 0,
                                      "output-action": {
                                          "output-node-connector": "2",
                                          "max-length": "65535"
                                      }
                                  }
                              ]
                          }
                      }
                  ]
              }
          }
      ]
}'

2. Add flow to switch s1, which will connect port 2 and 1 in another direction. Set switch s1 to send all packet transmitted through port 2 to controller:

curl --request PUT \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A1/table=0/flow=1 \
  --header 'Authorization: Basic YWRtaW46YWRtaW4=' \
  --header 'Content-Type: application/json' \
  --data '{
    "flow": [
        {
            "table_id": "0",
            "id": "1",
            "priority": "10",
            "match": {
                "in-port": "openflow:1:2"
            },
            "instructions": {
                "instruction": [
                    {
                        "order": 0,
                        "apply-actions": {
                            "action": [
                                {
                                    "order": 0,
                                    "output-action": {
                                        "output-node-connector": "1",
                                        "max-length": "65535"
                                    }
                                },
                                {
                                    "order": 1,
                                    "output-action": {
                                        "output-node-connector": "CONTROLLER",
                                        "max-length": "65535"
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}'

3. Check all added flow at s1 switch:

GET: http://<<IP_OF_RUNNING_LIGHTY>>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A1/table=0?content=nonconfig

4. Add flow to switch s2 to connect port 1 and 2:

curl --request PUT \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A2/table=0/flow=0 \
  --header 'Authorization: Basic YWRtaW46YWRtaW4=' \
  --header 'Content-Type: application/json' \
  --data '{
    "flow": [
        {
            "table_id": "0",
            "id": "0",
            "priority": "10",
            "match": {
                "in-port": "openflow:2:1"
            },
            "instructions": {
                "instruction": [
                    {
                        "order": 0,
                        "apply-actions": {
                            "action": [
                                {
                                    "order": 0,
                                    "output-action": {
                                        "output-node-connector": "2",
                                        "max-length": "65535"
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}'
curl --request PUT \
  --url http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A2/table=0/flow=1 \
  --header 'Authorization: Basic YWRtaW46YWRtaW4=' \
  --header 'Content-Type: application/json' \
  --data '{
    "flow": [
        {
            "table_id": "0",
            "id": "1",
            "priority": "10",
            "match": {
                "in-port": "openflow:2:2"
            },
            "instructions": {
                "instruction": [
                    {
                        "order": 0,
                        "apply-actions": {
                            "action": [
                                {
                                    "order": 0,
                                    "output-action": {
                                        "output-node-connector": "1",
                                        "max-length": "65535"
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}'

5. Check all added flow at s2 switch:

GET: http://<IP_OF_RUNNING_LIGHTY>:8888/restconf/data/opendaylight-inventory:nodes/node=openflow%3A2/table=0?content=nonconfig

Now, when we try ping all devices in mininet, we will receive a positive feedback:

mininet> pingall
*** Ping: testing ping reachability
h1 -> h2
h2 -> h1
*** Results: 0% dropped (2/2 received)

Show Packet-In messages with Wireshark

Wireshark is a popular network protocol analyzer. It lets you analyze everything that is happening in your network and is a necessity for network administrators and power-users alike.

Start Wireshark with command:

sudo wireshark

After it starts, double click on ‘any’ filter. Then, filter packets with ‘openflow_v4.type == 10‘. Now, the Wireshark environment setup will only show Packet-In messages from OpenFlow protocol.

To create traffic in mininet network, we used mininet command:

h2 ping h1

If everything is setup correctly, then we can see Packet-In messages showing up.

Show Packet-In messages with PacketInListener

In the below section for Java developers, there is an option for setting Packet-In Listener. This configuration set to log every Packet-In message as a TRACE log to console. When this is done, run mininet command ‘h2 ping h1’ again.

If everything is setup correctly, then we can see Packet-In messages in logs.

Java Developers

Some configuration can be done in Java Main class from OpenFlow Protocol example.

Packet-in listner

Packet handling is managed by adding a Packet Listener. In our example we add the PacketInListener class, which will be logging packet-in messages. For a new packet listener class, it is important to have implemented the class “PacketProcessingListener”.

First step is to create an instance of PacketInListener (1 – in the window below). Then we will add OpenflowSouthboundPluginBuilder to this part of code (2 – in the window below).

//3. start openflow SBP
     PacketProcessingListener packetInListener = new PacketInListener();           (1)
     final OpenflowSouthboundPlugin plugin;
     plugin = new OpenflowSouthboundPluginBuilder()
             .from(configuration, lightyController.getServices())
             .withPacketListener(packetInListener)                                 (2)
             .build();
     ListenableFuture<Boolean> start = plugin.start();

OpenFlow Manager (OFM) App

In the screenshot below, you can find the OFM App windows with our example running:

Statistics

Conclusion

We have managed to integrate the whole OpenFlow plugin into lighty-core. This is the same way it is implemented in OpenDaylight, version Fluorine (SR1). It is therefore possible to use lighty-controller for managing network devices via the OpenFlow protocol.

If you would like to see how our commercial version might benefit your business, please visit lighty.io for more information, or contact us here.