Understanding Ryu OpenFlow Controller, mininet, WireShark and tcpdump
This is probably one of the longest posts I have written on Stack Overflow. I have been learning about OpenFlow, SDN and Ryu and would like to document my knowledge for a beginner here. Please correct/edit my post if needed.
This short guide assumes you already have knowledge of computer networks and major networking protocols. This guide will help you get started with OpenFlow from system setup.
1. What is OpenFlow and SDN?
Please read SDN / OpenFlow | Flowgrammable.
Further reading: The Future of Networking, and the Past of Protocols by Scott Shenker and Software-defined Networking, IEEE INFOCOM 2009.
Before you start:
The Infrastructure layer includes the routers and switches inside the network core.
The Control layer includes the PC that runs an OpenFlow controller along with the controller itself.
The Application layer includes the applications that run on top of that controller. In Ryu, these applications are written in Python.
OpenFlow is a protocol using which the Infrastructure and the Control layer interact. OpenFlow does not provide an API of itself. It is an open source protocol that is used by vendors who develop OpenFlow capable switches and by developers who write the controllers, like Ryu. The API is provided by the controller.
2. Setting up the Ryu OpenFlow controller on Debian 8
Prerequisites
You need internet access. If you are running Debian inside a Virtual Machine, issue the following command to automatically configure your Ethernet interface via NAT:
su
dhclient eth0
Enable sudo
Debian does not come with sudo by default. Some Ryu applications you'll use later on require sudo. You can install sudo and add yourself to the sudo'ers list as follows:
su
apt-get install sudo # you might need to do apt-get update first!
visudo
Find the line that says %sudo ALL=(ALL:ALL) ALL and add an entry immediately below it:
yourusername ALL=(ALL:ALL) ALL
Press CTRL+X and then press Y to save the changes to the sudoers file. Now you can logout as root to return to your own shell
exit
Enable Optimal Screen Resolution (VM Only)
If you are running Debian in Virtual Box, the default installation does not enable full screen resolution support for Virtual Box. You'll need a bigger screen later in section 3. It's a good idea to enable it now.
In the Virtual Machine's window, click Devices > Insert Guest Additions CD Image ...
Then cd to the directory containing the files
cd /media/cdrom
Debian won't let you run the script due to permission issues. Copy the files to your home directory, change the permissions, and then run it:
mkdir ~/VBOXGUEST
cp * ~/VBOXGUEST
cd ~/VBOXGUEST
chmod 755 *
sudo ./VBoxLinuxAdditions.run
Reboot
sudo shutdown -r now
Install Git
sudo apt-get install git
Install Mininet
Mininet allows you to virtually emulate various network interfaces on your laptop/PC. Install it using Git:
cd ~ # if you are in some other directory
git clone git://github.com/mininet/mininet
cd mininet
git tag # this will list available versions
git checkout -b 2.2.1 2.2.1 # replace 2.2.1 with the version you wish to install
cd ..
mininet/util/install.sh -a # default installation, includes all components, recommended
I recommend you install the OpenFlow Wireshark Dissector. You can later install Wireshark to analyze packets. The OpenFlow Wireshark Dissector helps Wireshark fetch as much information from OpenFlow packets as possible.
mininet/util/install.sh -h
Run the following command to check your mininet installation:
sudo mn --test pingall
Install Ryu OpenFlow Controller
An OpenFlow Controller communicates between the Control Layer and the Infrastructure layer using the OpenFlow protocol. Also, it's the controller that provides an API to develop SDN applications that run in the Application Layer (on top of the Control Layer). There are numerous OpenFlow controllers. Ryu OpenFlow controller is one that uses Python scripts as its applications. Again, install it using Git:
cd ~
git clone git://github.com/osrg/ryu.git
Install Wireshark
sudo apt-get install wireshark
Install the supporting Python modules
Debian 8.3 does come with both Python 2.7 and 3.4 installed by default. However, you need to install some Python modules that the Ryu applications (Python scripts) use. You can install Python modules using pip:
cd ~/ryu
sudo apt-get install python-dev python-pip python-setuptools
sudo pip install .
the above will automatically run setup.py located in this directory and fetch the missing Python modules from the Python Package Index. The script will automatically install all relevant modules. However, do run the following to make sure you aren't missing any module later:
sudo pip install webob
sudo pip install eventlet
sudo pip install paramiko
sudo pip install routes
Starting up
Start mininet to emulate 3 hosts and a switch using the following command:
sudo mn --topo single,3 --mac --switch ovsk --controller remote
You will see a mininet prompt. This prompt can be used to ping hosts, send packets between them, etc.
Open up another terminal window to run Ryu. In this example, we will run an application (simple_switch_13.py) that will emulate a simple layer 2 switch that will forward all received packets to all ports except the one received on.
cd ~/ryu
PYTHONPATH=. ./bin/ryu-manager ryu/app/simple_switch_13.py
Make sure you are in your home directory when you run this.
You are all set. To ping hosts and analyse the packet transmissions, please move onto the next section.
3. Experimenting with Wireshark and tcpdump
In this section, we will fire packets from one host to another using mininet, and analyze the resulting transmissions using tcpdump and Wireshark.
The way the packets get transmitted is exactly what we can control in Software Defined Networking. We do this by writing different applications that run on top of the controller. These applications form the Application layer of the SDN control plane.
Set up topology and run the controlling application
Note: In the earlier section, you created a topology with mininet, and started a Ryu application to control the transmissions. In case you rebooted, or exited any of them, I repeat the commands to create the topology and start the Ryu application here:
cd ~
sudo mn --topo single,3 --mac --switch ovsk --controller remote
and in a separate terminal window:
cd ~/ryu
PYTHONPATH=. ./bin/ryu-manager ryu/app/simple_switch_13.py
Play with packets
In the mininet prompt, issue the following to open a console window for each of the three hosts in the topology you created:
mininet> xterm h1 h2 h3
Stack these consoles so you can see them all simultaneously! Then in the xterms for h2 and h3, run tcpdump, a utility to print the packets seen by a host:
tcpdump -XX -n -i h2-eth0
tcpdump -XX -n -i h3-eth0
Note: If you have used Wireshark earlier, it's like capturing packets on the eth0 interface of these two hosts respectively.
When creating topology, mininet assigned the following IP addresses to the three hosts:
h1: 10.0.0.1
h2: 10.0.0.2
h3: 10.0.0.3
From host 1's shell, ping host 2 and host 3 and observe the effect on the other two consoles after each command:
ping 10.0.0.2
ping 10.0.0.3
Try pinging an unreachable (non existent host), and see the effect on the consoles:
ping 10.0.0.7
You should have observed the ICMP (ping) and ARP (who has this IP address) protocols live in this section! You can also do the above using Wireshark instead of tcpdump. That's a graphical alternative to tcpdump.
Note: The way the packets are forwarded ALL depends on the application running on top of Ryu. You could write an application to drop all packets. In that case, your pings would produce no effect on the other two consoles.
4. Understanding the basic Layer 2 Switch application
In this section, we analyze the working of a simplified version of a Layer 2 Switch application that controlled the transmissions of packets in section 3.
Working of a learning bridge (or a Layer 2 Switch)
I mentioned earlier that if you are reading this guide, I assume you already have knowledge of basic networking protocols, (which includes the working of a layer 2 switch, or a learning bridge, or an Ethernet switch!) I'll summarize it in a few lines below regardless.
A "learning" bridge stores a database of the hosts its connected to, against it's ports. The hosts are identified by the MAC address of their network card, which looks like this: ab:cd:ef:12:34:56
(it's in hexadecimal). The ports are identified simply by their number. For example, a switch with 4 ports has port 1, 2, 3 and 4.
If a switch receives a packet on its port 2, it will look at the destination MAC address (which host it's destined to) of that packet. It then looks into it's database to see if it knows which port is that host connected to. If it finds it out, it forwards that packet ONLY to that specific port. But if it doesn't have an entry in it's database yet, it floods that packet to ALL ports, and the hosts can check for themselves if the packet was destined for them.
At the same time, the switch looks at the source MAC address of that packet, and it immediately knows that host X is located at port 2. It stores that entry in that database. So now you know that if the destination host replies to the source host, the switch won't have to flood the reply packet!
Introduction to the Ryu API Python code
Instead of going directly to simple_switch_13.py, let's choose a very simple program that has no "learning" capability. For now, there is no forwarding database. The below program is just a simple layer 2 switch that transmits a received packet to all ports (floods the packet):
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
class L2Switch(app_manager.RyuApp):
def __init__(self, *args, **kwargs):
super(L2Switch, self).__init__(*args, **kwargs)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
msg = ev.msg
dp = msg.datapath
ofp = dp.ofproto
ofp_parser = dp.ofproto_parser
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
out = ofp_parser.OFPPacketOut(
datapath=dp, buffer_id=msg.buffer_id, in_port=msg.in_port,
actions=actions)
dp.send_msg(out)
The imports
I will not delve into the import statements yet. We will discuss the imports individually as we analyze the code that uses them.
The basic application skeleton
The following code is a perfectly complete Ryu application. In fact you can execute it too! It won't do anything though:
from ryu.base import app_manager
class L2Switch(app_manager.RyuApp):
def __init__(self, *args, **kwargs):
super(L2Switch, self).__init__(*args, **kwargs)
As an argument to the class, we pass ryu.base.app_manager.RyuApp
import (imported in the first line). From the Ryu API handbook, app_manager
class is the central management of Ryu applications. It loads Ryu applications, provide contexts to them and routes messages among Ryu applications.
The EventOFPPacketIn Event
A new method packet_in_handler
is added to L2Switch
class. This is called when Ryu receives an OpenFlow packet_in
message. When Ryu receives a packet_in
message, a ofp_event.EventOFPPacketIn
event is raised. The set_ev_cls
decorator tells Ryu when the associated function, packet_in_handler
should be called.
The first argument of the set_ev_cls
decorator indicates an event that makes function called. As you expect easily, every time a ofp_event.EventOFPPacketIn
event is raised, this function is called.
The second argument indicates the state of the switch when you want to allow Ryu to handle an event. Probably, you want to ignore OpenFlow packet_in
messages before the handshake between Ryu and the switch finishes. Using MAIN_DISPATCHER
as the second argument means this function is called only after the negotiation completes. MAIN_DISPATCHER
denotes the normal state of the switch. During the initialization stage, the switch is in HANDSHAKE_DISPATCHER
state!
Now let's look at the body of the function. We'll break it down into two parts.
msg = ev.msg
dp = msg.datapath
ofp = dp.ofproto
ofp_parser = dp.ofproto_parser
ev.msg
is a data structure that contains the received packet.
msg.dp
is an object inside that data structure that represents a datapath (switch).
dp.ofproto
and dp.ofproto_parser
are objects that represent the OpenFlow protocol that Ryu and the switch negotiated.
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
out = ofp_parser.OFPPacketOut(
datapath=dp, buffer_id=msg.buffer_id, in_port=msg.in_port,
actions=actions)
dp.send_msg(out)
OFPActionOutput
class is used with a packet_out
message to specify a switch port that you want to send the packet out of. Since there is no forwarding database in this simplified application, we flood the packet to all ports, so the constant OFPP_FLOOD
is used.
OFPPacketOut
class is used to build a packet_out
message.
By using datapath
class's send_msg
method, you can send an OpenFlow message object to the ports defined in the actions variable. I repeat, in this case, actions is built such that the destination includes ALL ports.
Events
You repeatedly saw the term event in the above code. In event driven programming, the flow of the program is controlled by events, which are raised by messages received by the system (e.g. EventOFPPacketIn
is raised when the packet_in
message is received by Ryu from the (OpenFlow enabled) switch). We earlier discussed that OpenFlow is a protocol using which the controller (Ryu, PC) and the infrastructure (or switch) communicate. Messages like packet_in
are exactly what the communication between the two looks like using the OpenFlow protocol!
Next steps
You might want to go ahead and build your own Ryu applications. Learning the Ryu API (or the Python language, if you are not already familiar with it) might be a good point to start. Good luck!
Something you may find useful for working with the Ryu controller is Ryuretic. Ryuretic is a modular, SDN-based, framework for network application development. It allows network operators to work directly with packet header fields at various levels of the OSI model, including L2, L3, L4, and shim layer protocols. The user simply chooses match fields and selects provided operations to update the OpenFlow switch.
The Ryuretic backend renders all events to the user as a pkt (a dictionary object), and the contents of the pkt are retrieved by providing the header field of interest (e.g, pkt['srcmac'], pkt['dstmac'], pkt['ethtype'], pkt['inport'], pkt['srcip'], etc.) Using the information from the pkt, the user can choose which fields to match and what action (fwd, drop, redirect, mirror, craft) to take when a match is found.
To install Ryuretic, simply copy the [files] (https://github.com/Ryuretic/RyureticLabs/tree/master/ryu/ryu/app/Ryuretic) to the directory /ryu/ryu/app/Ryuretic. If you installed Ryu, then you already have the /ryu/ryu/app directory. You just need to create the Ryuretic directory and copy the files there.
Ryuretic Labs provides setup instruction and some use cases for implementing security features on SDNs using Ryuretic. It also provides a Mininet testbed for testing your network applications on the VM provided by SDN-Hub.