Application components

In a typical development cycle, we will start to develop the application after we have planned out the requirements. However, since we are in learning mode and already know of a reference application, it would be smart of us to examine the simple_switch_13.py application as a starting point. Based on what we've seen, we can also tweak or make our own code based on this reference to help us understand the Ryu framework better.

Let's make a separate folder called mastering_python_networking under /home/ubuntu/ryu and store our file in there:

$ mkdir mastering_python_networking

In the folder, you can copy and paste the following code, from chapter10_1.py:

from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3, ofproto_v1_0

class SimpleSwitch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]

def __init__(self, *args, **kwargs):
super(SimpleSwitch, self).__init__(*args, **kwargs)
self.mac_to_port = {}

@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
print("ev message: ", ev.msg)
datapath = ev.msg.datapath
print("datapath: ", datapath)
ofproto = datapath.ofproto
print("ofprotocol: ", ofproto)
parser = datapath.ofproto_parser
print("parser: ", parser)

This file is very similar to the top portion of the reference application, with a few exceptions:

  • Since Open vSwitch supports both OpenFlow 1.0 and 1.3, we purposely want to register the switch with OpenFlow 1.0
  • We are using the print function to get the output of ev, datapath, ofprotocol, and parser

We can see from the Ryu API document, http://ryu.readthedocs.io/en/latest/, that a Ryu application is a Python module that defines a subclass of ryu.base.app_manager.RyuApp. Our switch class is a subclass of this base class. We also see the decorator of ryu.controller.handler.set_ev_cls, which is a decorator for the Ryu application to declare an event handler. The decorator takes two arguments: the first one indicates which event will invoke the function, and the second argument indicates the state of the switch. In our scenario, we are calling the function when there is an OpenFlow Switch Features event; CONFI_DISPATCHER means the switch is in the negotiation phase of version is negotiated and the features-request message has been sent. If you are curious about the different switch states, you can check out the documentation at http://ryu.readthedocs.io/en/latest/ryu_app_api.html#ryu-base-app-manager-ryuapp. Although our application does not do much at this time, it is nonetheless a complete application that we can run:

$ bin/ryu-manager --verbose mastering_python_networking/chapter10_1.py

When you run the application, you will notice that the switch version is now 1.0 (0x1) instead of version 1.3 (0x4) as well as the different objects that we printed out. This is helpful because it gives us the full path to the object if we want to look up the API documentation:

 ...
switch features ev version: 0x1
...
('ev: ', OFPSwitchFeatures(actions=4095,capabilities=199,datapath_id=1,n_buffers=256,n_tables=254,ports={1: OFPPhyPort(port_no=1,hw_addr='92:27:5b:6f:97:88',name='s1-eth1',config=0,state=0,curr=192,advertised=0,supported=0,peer=0), 2: OFPPhyPort(port_no=2,hw_addr='f2:8d:a9:8d:49:be',name='s1-eth2',config=0,state=0,curr=192,advertised=0,supported=0,peer=0), 3: OFPPhyPort(port_no=3,hw_addr='ce:34:9a:7d:90:7f',name='s1-eth3',config=0,state=0,curr=192,advertised=0,supported=0,peer=0), 65534: OFPPhyPort(port_no=65534,hw_addr='ea:99:57:fc:56:4f',name='s1',config=1,state=1,curr=0,advertised=0,supported=0,peer=0)}))
('datapath: ', <ryu.controller.controller.Datapath object at 0x7feebca5e290>)
('ofprotocol: ', <module 'ryu.ofproto.ofproto_v1_0' from '/home/ubuntu/ryu/ryu/ofproto/ofproto_v1_0.pyc'>)
('parser: ', <module 'ryu.ofproto.ofproto_v1_0_parser' from '/home/ubuntu/ryu/ryu/ofproto/ofproto_v1_0_parser.pyc'>)
Refer to http://ryu.readthedocs.io/en/latest/ for more Ryu APIs.

We can now take a look at handling PacketIn events to make a simple hub. The file can be copied from chapter10_2.py to the same directory. The top portion of the file is identical to the previous version without the print statements outside of the event message:

class SimpleHub(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]
#OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

def __init__(self, *args, **kwargs):
super(SimpleHub, self).__init__(*args, **kwargs)

@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
message = ev.msg
print("message: ", message)

What is now different is the new function that is called when there is a PacketIn event:

@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
msg = ev.msg
print("ev message: ", ev.msg)
datapath = msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser

actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_FLOOD)]
out = ofp_parser.OFPPacketOut(
datapath=datapath, buffer_id=msg.buffer_id, in_port=msg.in_port,
actions=actions)
datapath.send_msg(out)

We use the same set_ev_cls, but to specify by calling the function for a PacketIn event. We also specified the state of the switch after the feature negotiation is completed via MAIN_DISPATCHER. As we can see from the previous example, the event message typically contains the information that we are interested in; therefore, we are printing it out in this function as well. What we added is an OpenFlow version 1.0 parser, OFPActionOutput (http://ryu.readthedocs.io/en/latest/ofproto_v1_0_ref.html#action-structures), for flooding all the ports as well as a PacketOut specifying the flooding action.

If we launch the application, we will see the same switch feature message. We can also ping from h1 to h2:

 mininet> h1 ping -c 2 h2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=4.89 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=3.84 ms

--- 10.0.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 3.842/4.366/4.891/0.528 ms
mininet>

In this case, we will see the broadcast packet coming in on port 1:

EVENT ofp_event->SimpleHub EventOFPPacketIn
('ev message: ', OFPPacketIn(buffer_id=256,data='xffxffxffxffxffxffx00x00x00x00x00x01x08x06x00x01x08x00x06x04x00x01x00x00x00x00x00x01nx00x00x01x00x00x00x00x00x00nx00x00x02',in_port=1,reason=0,total_len=42))

The broadcast packet will reach h2, and h2 will respond to the broadcast from port 2:

EVENT ofp_event->SimpleHub EventOFPPacketIn
('ev message: ', OFPPacketIn(buffer_id=257,data='x00x00x00x00x00x01x00x00x00x00x00x02x08x06x00x01x08x00x06x04x00x02x00x00x00x00x00x02nx00x00x02x00x00x00x00x00x01nx00x00x01',in_port=2,reason=0,total_len=42))

Note that unlike the simple switch application, we did not install any flows into the switch, so all the packets are flooded out to all ports, just like a hub. You may have also noticed a difference between our application and the simple_switch_13.py reference module. In the reference module, upon feature negotiation, the application installed a flow-miss flow to send all packets to the controller:

match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)

However, we did not have to do that in our module in order to see future PacketIn events. This is due to the default behavior change between OpenFlow 1.0 to OpenFlow 1.3. In 1.0, the default table-miss action is to send the packet to the controller, whereas in 1.3, the default action is to drop the packet. Feel free to uncomment the OFP_VERSIONS variable form 1.0 to 1.3 and relaunch the application; you will see that the PacketIn event will not be sent to the controller:

class SimpleHub(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]
#OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

Let's look at the add_flow() function of the reference module:

def add_flow(self, datapath, priority, match, actions, buffer_id=None):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod)

We have seen most of these fields; the only thing we have not seen is the buffer_id. This is part of the OpenFlow standard, in which the switch has the option of sending the whole packet including payload or choosing to park the packet at a buffer and only send a fraction of the packet header to the controller. In this case, the controller will send the flow insertion, including the buffer ID.

The reference module contains a more sophisticated PacketIn handler than our hub in order to make it a switch. There were two extra imports in order to decode the packet:

from ryu.lib.packet import packet
from ryu.lib.packet import ethernet

Within the _packet_in_hander(), we took stock of the in_port, and populated the mac_to_port dictionary by using the dpid of the switch and the MAC address:

in_port = msg.match['in_port']

pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0]

dst = eth.dst
src = eth.src

The rest of the code takes care of the PacketOut event as well as inserting the flow using the add_flow() function.

# learn a mac address to avoid FLOOD next time.
self.mac_to_port[dpid][src] = in_port

if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
out_port = ofproto.OFPP_FLOOD

actions = [parser.OFPActionOutput(out_port)]

# install a flow to avoid packet_in next time
if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
# verify if we have a valid buffer_id, if yes avoid to send both
# flow_mod & packet_out
if msg.buffer_id != ofproto.OFP_NO_BUFFER:
self.add_flow(datapath, 1, match, actions, msg.buffer_id)
return
else:
self.add_flow(datapath, 1, match, actions)
data = None
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
data = msg.data

out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=data)
datapath.send_msg(out)

You might be thinking at this point, "Wow, that is a lot of work just to get a simple switch to work!" and you would be correct in that regard. If you just want a switch, using OpenFlow and Ryu is absolutely overkill. You are better off spending a few bucks at the local electronics store than studying all the API documents and code. However, keep in mind that the modules and design patterns we have learned in this section are reused in almost any Ryu application. By learning these techniques, you are setting yourself up for bigger and better OpenFlow applications using the Ryu controller to enable rapid development and prototyping. I would love to hear about your next million-dollar network feature using OpenFlow and Ryu!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.81.43