Runos is an OpenFlow Controller.
It is fully userspace controller with high functionality, easy to develop your apps, relatively high performance comparing with existing controllers. It supports OpenFlow 1.3.
More info: http://arccn.github.io/runos/
Slides: http://www.slideshare.net/AlexanderShalimov/runos-openflow-controller-eng
RuNOS documentation [ru]: http://arccn.github.io/runos/doc/ru/index.html
This components should be installed in the system:
- Utilities: cmake, autoconf, libtool, pkg-config
- libfluid dependencies: libevent openssl.
- Libraries: QtCore 5, google-glog, boost::graph, boost::system, boost::thread, boost::coroutine, boost::context, uglifyjs
- UglifyJS dependencies: npm, nodejs
You can use this line on Ubuntu 17.10+ to install all required packages:
$ sudo apt-get install build-essential cmake autoconf libtool \
pkg-config libgoogle-glog-dev libevent-dev \
libssl-dev qtbase5-dev libboost-graph-dev libboost-system-dev \
libboost-thread-dev libboost-coroutine-dev libboost-context-dev \
libgoogle-perftools-dev curl nodejs npm \
You need to install the JavaScript packages:
$ sudo npm install uglify-js -g
# You maybe needed to creating symbolic link from nodejs to node
$ sudo ln -s /usr/bin/nodejs /usr/bin/node
To build project you must use g++-5.2 compiler (or above) or similar another compiler with support std::regex. We recommend to install g++-5.2 by this way:
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install g++-5.2
# Initialize third-party libraries
$ third_party/bootstrap.sh
# Create out of source build directory
$ mkdir -p build; cd build
# Configure (if you use g++)
$ CXX=g++-5.2 cmake -DCMAKE_BUILD_TYPE=Release ..
# OR configure (otherwise)
$ cmake -DCMAKE_BUILD_TYPE=Release ..
# Build third-party libraries
$ make prefix -j2
# Build RuNOS
$ make -j2
Setup environment variables (run once per shell session):
# Run it INSIDE build directory
$ source ../debug_run_env.sh
Run the controller:
$ cd .. # Go out of build dir
$ build/runos
You can use this arguments for MiniNet:
$ sudo mn --topo $YOUR_TOPO --switch ovsk,protocols=OpenFlow13 \
--controller remote,ip=$CONTROLLER_IP,port=6653
To run web UI, open the following link in your browser:
http://$CONTROLLER_IP:8000/topology.html
Be sure your MiniNet installation supports OpenFlow 1.3. See https://wiki.opendaylight.org/view/OpenDaylight_OpenFlow_Plugin::Test_Environment#Setup_CPqD_Openflow_1.3_Soft_Switch for more instructions.
- Execute Building section, but use
cmake -DCMAKE_BUILD_TYPE=Debug ..
insteadRelease
- In QtCreator
File->Open File or Project
and selectCMakeLists.txt
as project file - In build section: select build directory as
./build
- In run section:
- select working direcory as
./
(project path) - set Run Environment:
LD_LIBRARY_PATH
to$PDW/prefix/lib
for LinuxDYLD_LIBRARY_PATH
to$PWD/prefix/lib
for OS XGLOG_logtostderr
to1
GLOG_colorlogtostderr
to1
- select working direcory as
- Compile and run!
- Add project to CLion by
File->Open...
and select the project root's location. Further we will call it as./
- Mark
./third_party
directory as Library root (right click to the directory name inProject view
panel) and restart the IDE. You may also need to installlibfluid
system-wide: it will be used by CLion for making smart suggestions (not for compilation). - Configure project.
Bad news are that Clion is still not be able to build project natively (via
Build
action), but you can build it by running a bash comand (write your own or use listed bellow one). Good news are that graphical debugger and other aspects of using IDE works perfect! So,Edit configurations... -> runos
:- Executable: select
./build/runos
- Working directory:
./
- Environment variables:
LD_LIBRARY_PATH
to./prefix/lib
for LinuxGLOG_logtostderr
to1
GLOG_colorlogtostderr
to1
- Before launch:
- Remove
Build
- Add
Run external tool -> Add...
:- Program:
/bin/bash
- Parameters:
-c "cd ./build/ && make -j2 && source ../debug_run_env.sh"
- Program:
- Remove
- Mark the configuration as
Single instance only
- Executable: select
- Compile and run via
Run
action or launch debugging session viaDebug
action!
Note: look at full documentation in Russian: http://arccn.github.io/runos/doc/ru/index.html
Create MyApp.cc
and MyApp.hh
files inside src
directory and
reference them in CMakeLists.txt
.
Fill them with the following content:
/* MyApp.hh */
#pragma once
#include "Application.hh"
#include "Loader.hh"
class MyApp : public Application {
SIMPLE_APPLICATION(MyApp, "myapp")
public:
void init(Loader* loader, const Config& config) override;
};
/* MyApp.cc */
#include "MyApp.hh"
REGISTER_APPLICATION(MyApp, {""})
void MyApp::init(Loader *loader, const Config& config)
{
LOG(INFO) << "Hello, world!";
}
What we do here? We declare our application by subclassing Application
interface and named it myapp
. Now we should reference myapp
in configuration
file (defaults to network-setting.json
) at services
section to force run it.
Start RuNOS and see "Hello, world!" in the log.
Now our application was started but do nothing. To make it useful you need to communicate with other applications.
Look at this line of MyApp.cc
:
REGISTER_APPLICATION(MyApp, {""})
In the second argument here you tell RuNOS what apps you will use as dependencies. Last element should be empty string indicating end-of-list.
You can interact with other applications with method calls and Qt signals. Let's
subscribe to switchUp event of controller
application. Add required dependencies:
REGISTER_APPLICATION(MyApp, {"controller", ""})
Then include Controller.hh
in MyApp.cc
and get controller instance:
void MyApp::init(Loader *loader, const Config& config)
{
Controller* ctrl = Controller::get(loader);
...
}
Now we need a slot to process switchUp
events. Declare it in MyApp.hh
:
#include "SwitchConnection.hh"
...
public slots:
void onSwitchUp(SwitchConnectionPtr ofconn, of13::FeaturesReply fr);
And connect signal to it in MyApp::init
:
QObject::connect(ctrl, &Controller::switchUp,
this, &MyApp::onSwitchUp);
Finally, write MyApp::onSwitchUp
implementation:
void MyApp::onsSitchUp(SwitchConnectionPtr ofconn, of13::FeaturesReply fr)
{
LOG(INFO) << "Look! This is a switch " << fr.datapath_id();
}
Great! Now you will see this greeting when switch connects to RuNOS.
You learned how subscribe to other application events, but how to manage flows? Imagine you wan't to do MAC filtering, ie drop all packets from hosts not listed in the configuration file.
To do so you need to create packet-in handler. Packet-in handlers will be invoked
for each switch by controller
application and will live in a number of threads.
So, you need define not only a "handler" but a handler factory.
So, register PacketMissHandlerFactory in init
:
#include "api/PacketMissHandler.hh"
#include "api/Packet.hh"
#include "types/ethaddr.hh"
#include "api/TraceablePacket.hh"
#include "oxm/openflow_basic.hh"
void MyApp::init(Loader *loader, const Config& config)
{
...
Controller* ctrl = Controller::get(loader);
ctrl->registerHandler("mac-filtering",
[=](SwitchConnectionPtr connection){
return [=](Packet& pkt, FlowPtr, Decision decision){
if (pkt.test(oxm::eth_src() == "00:11:22:33:44:55"))
return decision.drop().return_();
else
return decision;
};
});
...
}
What it means? First, all handlers arranged into pipeline, where every handler
can stop processing, look at some packet fields and add actions. We need also
insert our handler in pipeline. In network-settings.json
controller
has his setting,
and pariculary pipeline
, which contains name of handlers in pipeline.
So, we name our handler as "mac-filtering" and need to place it before "forwarding".
Now compile RuNOS and test that all packets from 00:11:22:33:44:55
had been dropped.
The format of the RunOS REST requests:
<HTTP-method> /api/<app_name>/<list_of_params>
<HTTP-method>
is GET, POST, DELETE of PUT<app_name>
is calling name of the application<list_of_params>
is list of the parameters separated by a slash
In POST and PUT request you can pass parameters in the body of the request using JSON format.
Current version of RunOS has 6 REST services:
- switch-manager
- topology
- host-manager
- flow
- static-flow-pusher
- stats
GET /api/switch-manager/switches/all (RunOS version)
GET /wm/core/controller/switches/json (Floodlight version)
Return the list of connected switches
GET /api/topology/links (RunOS version)
GET /wm/topology/links/json (Floodlight version)
Return the list of all the inter-switch links
GET /wm/topology/external-links/json (Floodlight)
Return external links
GET /api/host-manager/hosts (RunOS)
GET /wm/device/ (Floodlight)
List of all end hosts tracked by the controller
GET /api/flow/<switch_id>
DELETE /api/flow/<switch_id>/<flow_id>
List flow entries in the switch and remove some flow entry
POST /api/static-flow-pusher/newflow/<switch_id>
body of request: JSON description of new flow
Create new flow entry in the some switch
GET /api/stats/port_info/<switch_id>/all
GET /api/stats/port_info/<switch_id>/<port_id>
Get switch port statistics
GET /apps
List of available applications with REST API.
Also you can get events in the applications that subscribed to event model. In this case you should specify list of required applications and your last registered number of events.
GET /timeout/<app_list>/<last_event>
<app_list>
isapp_1&app_2&...&app_n
<last_event>
isunsigned integer
value
For testing your and other REST application's requests and replies you can use cURL
.
You can install this component by sudo apt-get install curl
command in Ubuntu.
In mininet topology --topo tree,2
, for example:
Request: $ curl $CONTROLLER_IP:$LISTEN_PORT/api/switch-manager/switches/all
Reply: [{"DPID": "0000000000000001", "ID": "0000000000000001"}, {"DPID": "0000000000000002", "ID": "0000000000000002"}, {"DPID": "0000000000000003", "ID": "0000000000000003"}]
To make REST for your application class MyApp
:
-
add REST for your application:
#include "Rest.hh" #include "AppObject.hh" #include <string> class MyApp : public Application, RestHandler { bool eventable() override { return false; } std::string displayedName() override { return "My beautiful application"; } // or skip this std::string page() override { return "my_page.html"; } // if your application has webpage json11::Json handleGET(std::vector<std::string> params, std::string body) override; json11::Json handlePOST(std::vector<std::string> params, std::string body) override; // also handlePUT and handleDELETE }
-
in
init()
function inclass MyApp
register Rest handler:RestListener::get(loader)->registerRestHandler(this);
-
then, you should set the handled pathes and methods in MyApp::init:
// handle GET request with path /api/my-app/switches/ acceptPath(Method::GET, "switches/[0-9]+");
// handle POST request with path /api/my-app/mac/ acceptPath(Method::POST, "[A-Fa-f0-9:-]");
-
method
MyApp::handleGET
proccesses input GET-HTTP requests. This method gets the list of parameters and returns reply in JSON format. -
each REST application may have own webpage, i.e. WebUI for application To connect REST application to webpage you must specify this page in
page()
method:std::string page() override { return "my_page.html"; }
File
"my_page.html"
must be located inweb/html
directory. If your application has not WebUI, write"none"
instead webpage. -
if your application supports event model, you can enable it by setting
true
ineventable()
method:bool eventable() override { return true; }
In current version events can signal about appearance or disappearance some objects:
# some_object is object inherited class AppObject
addEvent(Event::Add, some_object);
# or
addEvent(Event::Delete, some_object);
You can use static flow pusher to set rules proactively.
At first, you can write required rules in network-settings.json
file.
For example:
"static-flow-pusher": {
"flows": [
{
"dpid": "all",
"flows": [
{
"priority": 0,
"in_port": 15,
"ip_src": "3.3.3.3",
"out_port" : 22
}
]
}
]
}
Also, you can add other match fields: in_port
, eth_src
, eth_dst
, ip_src
, ip_dst
, out_port
. To set idle and hard timeout use idle
and hard
strings. To set OFPP_TO_CONTROLLER
action, write in out_port
string value to-controller
.
Secondly, to set flows proactively, you should add to your application StaticFlowPusher application, create FlowDesc object, fill it with your match field and call &StaticFlowPusher::sendToSwitch
method.
And thirdly, use REST POST requests of StaticFlowPusher to set new flow from REST API.