Select Page

In our previous articles, we covered pretty much everything needed to get started with SDN development using Ryu and Mininet. Now that our virtual workspace has all the development tools we need, it is now time to look into the real meat of Ryu: its API. Follow along as we discuss the detailed inner workings of Simple Switch as an existing example using the Ryu API with detailed documentation and code references in this continuation of our OpenFlow Core Tutorial Track.

Overview

Ryu’s API allows the rapid development of controller application prototypes. So much functionality is packed in the API that creating a simple L2 learning switch controller (simple_switch_13.py) requires only 119 lines of Python glue code. If we take out comments and blank lines, this boils down to 73 SLOC (Source Lines of Code). I mention glue code here because that is essentially what most of this code is. Ryu takes care of all the common details of handling connections from switches, interpreting and converting packets into easy to use objects and so on. The controller app itself just listens for packet-in messages, keeps track of a simple dictionary-based MAC table, and adds OpenFlow flows as needed to the switch.

Anatomy of a Ryu Controller Application

While browsing some of the example controller applications, you should run into a common pattern of how Ryu Controller Applications are segmented on a high level. Essentially, there are three functional parts in nearly every controller application:

  • Registration and Initialization of the Controller Application – This occurs before any switches are processed by Ryu and is used to allow the application instance to initialize data that will be shared across the entire network. For example, Simple Switch initializes a MAC-to-Port table.
  • Initialization on a Switch Connecting to Ryu – When a switch connects to Ryu during the configuration stage, the application can optionally listen to the Switch Features response event. This is usually where any static flow entries are added to the switch that the controller application expects. In Simple Switch, a table-miss flow is added so any unhandled packets are sent to the controller. Another app may use this to initiate a topology relearning event.
  • Handling Incoming Packets – The controller application may choose to listen on Packet-In events, which occurs any time the switch matches a flow entry that instructs the switch to forward the packet to the controller. Since Simple Switch instructed the switch to send any unhandled packets to the controller, Simple Switch listens to these Packet-In events and performs some logic and chooses what to do with the packet.

Although these three parts are pretty much universal, the last two are really part of a more general structure where the controller applications chooses what events to listen to. A switch connecting and a packet arriving to the controller are only two of the many events that are available for listening. Your applications could even generate their own events that other parts of the application, or other applications altogether, could choose to listen to. We will get more in depth on the event handler registration later. For now, let’s look at our current example of an L2 switch and break it down at a high level.

How Does a Traditional L2 Switch Work?

I know most of our readers already know how an L2 switch generally works, but for the sake of demonstrating the requirements of the controller application, let’s boil it down to the following essentials. In contrast to an L2 Hub that floods all ports with any frames received, an L2 Switch must efficiently route unicast ethernet frames to the specific physical port that is attached to the device with the destination data-layer (MAC) address. Of course, when a switch is first started, it doesn’t know the location of every device it is connected to, so some flooding must occur until frames are received from every device that will be addressed. The flow generally looks like this:

  • A frame is received on a physical port with source and destination MAC addresses.
  • The switch’s internal MAC-to-Port table is updated with the source address and physical port.
  • The MAC-to-Port table is searched for the destination MAC address to find the associated physical port.
    • On a miss, the frame is flooded to every physical port on the switch, except the receiving port.
    • On a hit, the frame is sent out to the physical port associated with the address.

Most non-software-defined physical switches also support other more complex functions, such as dealing with packet loops and finding the shortest path from a source to a destination across a network of switches, or even dealing with higher-level concepts such as virtual LANs. However, for a simple topology without any loops, the flow defined above is really all that is needed to support communication between hosts, even if it isn’t the most efficient.

Responsibilities of an L2 Switch Controller

When working with an OpenFlow switch, you are essentially starting with a clean slate (there are exceptions with “normal” mode, and fail-safe and fail-secure modes, but that is a later article). The switch will only perform the actions listed in its tables for packets that match certain conditions listed alongside those actions, or flows. In other words, the switch starts off “dumb”. In order to make an OpenFlow switch act like a learning L2 switch with the flow defined in the previous section, we need to define how the switch will act in software (hence, software defined networking or SDN). Specifically, we must have an OpenFlow controller to act as the brains for the switch with software that follows a slightly more complicated flow than above:

  • When an OpenFlow switch registers itself to the OpenFlow controller:
    • A catch-all flow is added to the switch with a low priority that instructs the switch to send any packets received, but does not match any other flow’s match conditions, to the OpenFlow controller.
    • An internal MAC-to-Port table is constructed for this specific switch.
  • When a PacketIn message is sent from the switch (generally caused by the flow added above):
    • An entry is added to the internal MAC-to-Port table that assigns the source MAC address to the receiving port.
    • Check the internal MAC-to-Port table for the destination MAC address.
      • On a miss, set the destination port to flood.
      • On a hit,
        • Set the destination port to the port associated with the destination MAC address
        • Add a flow to the switch (with a higher priority than the catch-all flow) that matches on the current in-port and destination MAC address of a packet and instructs the switch to send the packet out the port associated with that destination address. This prevents further PacketIn messages to the controller for packets from the same physical port to the same destination MAC address, but still allows the controller to learn more MAC addresses if packets arrive on different physical ports to the same destination.
      • Send a PacketOut message to the switch instructing to send the current packet to the port found above or to flood.

There is a major distinction between this flow and flow of a traditional learning L2 switch that must be made. The controller will only see packets that the switch is instructed to send to it. This causes a few issues with learning the devices on the network in a production setting:

  • If two hosts on the same port only send data to an already-learned destination MAC address, only one of the source addresses will be learned. Packets sent to the second host on that port will end up being flooded unless that host sends other traffic to an unknown destination MAC, or a multicast MAC (which is essentially an unknown destination for this setup). Thankfully, many host operating systems will send some auto-discovery chatter on the network that by its nature must be flooded across the network and thus learned by the controller since that is the only path that may flood in this setup.
  • If the topology changes, such as a host being moved from one port to another, the existing OpenFlow flows that point to the original port will still exist. Even if the controller learns the new location of the host and adds a new flow accordingly, the existing flow may cause packets to be sent to the old location until manually removed. This can be resolved by adding code to the controller that detects the topology change and explicitly removes any flows with match conditions associated with that MAC address first. Keep in mind that this detection is not implemented in the Simple Switch example we are dissecting today.

There are some other challenges that may also arise in a production setting, but for the purpose of testing a prototype controller application, the flow above, and this Simple Switch example, is more than adequate. It can be tested with simple topologies and confirmed that data is routed appropriately as already demonstrated in previous articles.

Simple Switch Code

The Simple Switch example code can be broken down to its major elements that follow the flow above. Later in the article, we will cover nearly each command in detail with references to Ryu documentation and definition code. For now, though, let’s quickly run through the major sections as an overview. The code excerpts are from simple_switch_13.py from the v4.4 release of Ryu under the Apache License. The code license block is available at the end of this article.

Terminology and Icon Legend

Any reference to a variable in this article should be prefixed with an icon that represents its type. It may optionally be followed by a reference to the class that the variable is an instance of. Here is a list of the icons in use, with an example name:

  • ryu.ofproto.ether – A Module, which in Python is usually defined by an entire file such as ryu/ofproto/ether.py for ryu.ofproto.ether, or ryu/ofproto/__init__.py for ryu.ofproto.
  • ryu.util.round_up – A Function, a defined block of code that can be called with or without arguments and produce a return value. Generally available globally under a module, but Functions can be defined inside any code block and are subject to normal Python namespacing and scoping rules. Functions belonging as a direct ancestor to a Module are generally shared utilities, helpers, or Decorators.
  • ryu.ofproto.ofproto_v1_3_parser.OFPSwitchFeatures – A Class, a declared shared namespace object that may be Called to instantiate any number of Instance objects. Usually, Classes are used to provide a framework for a specific type of object, potentially including Methods (Class Functions) that an instance, or client of the instance, can use to provide common behavior.
  • self.mac_to_port – A public Data Attribute, a reference to another object, usually referenced as a Class or Instance Variables, but the target object may be anything. In this article, Attributes are followed by the Class that was used to instantiate the Object referenced by the Attribute, if known.
  • self.add_flow – A public Method, a Function defined in a Class and available to Instances along with the Class itself.
  • self._reserved – A private Data Attribute
  • self.__init__ – A private Method
  • int – A native type
  • ev – An argument passed into a Function or Method
  • parser – A local variable
  • True – A Python primitive value

Controller Application Registration and Initialization

This first section creates the Ryu Application, specifies which versions of the OpenFlow protocol that the application is compatible with, and initializes the internal MAC-to-Port table.

Event Handler for New Switches

A method is defined and registered as a listener for any ryu.controller.ofp_event.EventOFPSwitchFeaturessrc, doc events (more on that later). The main purpose for this code is to have it run any time a switch is added to the controller and install a catch-all (or table-miss) flow entry in the switch, which allows the switch to send packets to the controller.

Helper Method for Adding Flow Entries

Since most of the flow entries that will be added are going to have similar structure, a helper method is defined to construct and send the final flow entry.

Packet-In Handler and Packet Dissection

Now we get to the main logic in the controller application. A handler is defined for ryu.controller.ofp_event.EventOFPPacketInsrc, doc (again, more on that later) which is called any time the switch sends a packet to the controller. This only occurs if the switch doesn’t already know where to send the packet and the table-miss flow entry is matched. The first part of the handler extracts vital information about the message and the packet sent to the controller.

Learning the MAC Address and Associated Port

Once the essentials are extracted, the MAC-to-Port table for the DPID of the current switch is created if it does not already exist. The packet information is logged and the MAC-to-Port table is finally updated with the source address of the packet associated with the port it arrived on.

MAC-to-Port Lookup and Packet Destination

The MAC-to-Port table for this switch is checked to see if the destination MAC address has already been learned. If so, it sets the output port to the learned port, otherwise it is set to flood. The actions for the later parser.OFPPacketOutsrc, doc message and potentially a new flow entry are defined.

Adding a Flow Entry for a Learned Destination

If the destination MAC address was found in the MAC-to-Port table, a flow entry is added to the switch to ensure future packets from the same port to the same address are forwarded without bothering the controller.

Forwarding the Packet Sent to the Controller

Finally, now that the destination set to flood or a learned port, the switch is instructed to send out the packet that was received by the controller so the packet is not lost during the learning process.

As demonstrated, this example controller application is, for the most part, very easy to understand how it works by reading just the code. The classes and object properties referenced have descriptive names and like any good prototype code, the logic applied is easy to follow. However, there might confusion as to why the code is written this particular way. What if you wanted to modify the logic and needed access to more information about the packets, switches, and other entities available in Ryu? There is not enough information in the code here to really give a good idea as to how to extend it. This brings us to our next section.

Simple Switch In Detail

Now we try to attempt to explain the hows and whys of the development of Simple Switch by digging through the class hierarchy and explaining what each piece of code really does. The readability of the code is a testament to Ryu’s ability to allow a developer to express the intended logic of the controller, but like any other application platform, the development magic happens when the coder understands what libraries are available and their place in the overall system. Let’s go through the code again, except this time we will dig deep in what each statement means, what it does and, more importantly, why the coder may choose to write the the code this specific way. It’s one thing to know what a library does, and there is plenty of documentation on Ryu libraries, but it’s another thing to know how to use it.

Imports and Registration

Before any controller code can be written, the required libraries must be imported and the application itself must be defined and registered.

Imports

For this breakdown, the details of the code will be below the listing. Let’s start with the basics: importing the required libraries.

The libraries in use by Simple Switch consist of the following:

Registration of the Simple Switch Controller Application

Creates a new class, subclassing ryu.base.app_manager.RyuAppsrc, doc. This registers the application to be instantiated by ryu-manager. Any Ryu controller application must have at least one subclass of RyuAppthough a more complex controller may be specified by many individual, but cooperating, RyuApps. When passed to ryu-manager, all RyuApp subclasses imported or defined on the specified module will also be started. Since this Simple Switch controller exists under the Ryu code base at ryu/app/simple_switch_13.py, it may be referred to as ryu.app.simple_switch_13src as the Ryu code  base is available in the PYTHONPATH. If you wish to start your controller app via a module name rather than by file name, make sure your module is available in the PYTHONPATH. See the Python documentation on the module search path for more information.

As explained in the Ryu API Reference, OFP_VERSIONSsrc, doc define a list of supported versions of OpenFlow for the controller application. If this was not specified, the application would be marked as compatible with all versions of OpenFlow that Ryu supports. Since this version of Simple Switch is designed for OpenFlow v1.3, the OFP_VERSIONsrc constant from ofproto_v1_3src is provided as the only value in the list. It is important to note that when multiple applications are loaded, the compatible versions from all applications are considered and only the intersection of all of the applications are used to determine which version of OpenFlow the controller should use to communicate with the switches, assuming the switch supports that version of the OpenFlow Protocol. This is why it is a good idea for your applications to support a wide range of OpenFlow protocol versions if at all possible. It may not be all that important when designing applications for internal production use, but is important when releasing your code for others to use.

Initializing the Controller Application

The self.__init__ method follows the standard convention for overriding the initialization of a superclass. Specifically, self.__init__ will accept any number of arguments or keyword arguments, then pass them to the superclass RyuApp.__init__ method to ensure the superclass is initialized properly. Finally, the application-wide MAC-to-Port table (self.mac_to_port) is initialized as an instance variable defined as a dict. This will later be referenced as a two-dimensional dictionary with the first key set to the DPID of the switch in question and the second key set to a MAC address to find or set the associated learned port (dict<dict<int>>).

Listening for New Switches

Ryu Controller Applications function by listening to events fired by Ryu, which handles the actual communication and tracking of the attached switches.

Registering an Event Handler

These two simple lines cause a lot of background work to be performed. Thankfully, one of the main purposes of having object-oriented programming is to allow the separation of areas of concern. Technically, you don’t need to know exactly how event handlers are registered and organized internally to Ryu in order to use them, so I’ll start with what these commands do that matter to your application. Line #33 is decorator statement. Again, how decorators work is not important, but what it does in this case is. The decorator@set_ev_cls(...)src, doc registers the attached function as an event handler for the specified event type, within the specified dispatcher. In this case, the instance method switch_features_handler(self, ev) will be called any time Ryu processes an event of type EventOFPSwitchFeaturessrc, doc during the CONFIG_DISPATCHERsrc, doc negotiation phase.

While a switch connects to the Ryu controller, the switch connection goes through different negotiation phases. During the CONFIG_DISPATCHER negotiation phase, Ryu asks the switch for its features (via OFPFeaturesRequestsrc, doc). The switch will respond with a features reply message, which is interpreted as an OFPSwitchFeaturessrc, doc message. This message is handled by Ryu for its own purposes and also emits an EventOFPSwitchFeatures event. In general, any message received by the switch will result in an event that is similarly named. Line #34 starts the instance method that will be called with the event as ev when the event is triggered.

Now the event is passed in as ev, which is of type EventOFPSwitchFeatures, itself a subclass of EventOFPMsgBase, and references extracted from it. Here is an object property tree for the properties used in this method:

  • ev – Instance of ryu.controller.ofp_event.EventOFPSwitchFeaturessrc, doc (EventOFPMsgBasesrc, doc) – The event itself.
    • msg – Instance of ryu.ofproto.ofproto_v1_3_parser.OFPSwitchFeaturessrc, doc – The message parsed by Ryu, which triggered the event.
      • datapath – Instance of ryu.controller.controller.Datapathsrc, doc – Datapath object instance representing the switch that sent the message. Referenced as local variable datapath.
        • ofprotoryu.ofproto.ofproto_v1_3src – A reference to the definitions library for the version of the OpenFlow protocol used in communicating between the controller and this datapath. In this case, ryu.ofproto.ofproto_v1_3 since the supported protocols for this controller application only included OpenFlow 1.3. In an application that supports multiple versions of OpenFlow, this may be a reference to the definitions library of any supported protocol, usually the highest protocol version supported across all loaded apps and the current datapath (switch). Locally referenced as ofproto
        • ofproto_parserryu.ofproto.ofproto_v1_3_parsersrc, doc – The message parsing library for the version of OpenFlow protocol used here. Locally referenced as parser

Building the Flow Entry

Now that Simple Switch is notified of the attached switch, it will install a table-miss flow. This allows any traffic not handled by the current flow entries in the switch to be sent to the controller. The controller will then use these packets to learn which MAC addresses belong to each port. To create the flow entry, Simple Switch must first create a match condition and a set of actions that will be followed if those match conditions are met. In this case, the match is an empty parser.OFPMatchsrc, doc instance, which means that the flow entry will match any packet.

Now the actions are defined as a list containing a parser.OFPActionOutputsrc, doc element, which is used to output the matched packet out a specific port, in this case ofproto.OFPP_CONTROLLERsrc. This instructs the switch to send the match packet to the controller via a Packet-In message that will be handled as ofp_event.EventOFPPacketInsrc, doc later in the controller code. The ofproto.OFPCML_NO_BUFFERsrc flag instructs the switch to not buffer the packet, but instead always send the entire packet to the controller. There are other modes of operation that might be more efficient if the switch supports it. In this case, this was added due to a bug in Open vSwitch that was later fixed.

Finally, this event handler asks for a flow entry to be added to the switch through the helper instance method self.add_flow. The helper function will be explained in greater detail below. In this case, it instructs a flow entry to be added to the datapath (switch) with a priority of 0. The flow entry will also include the match and actions elements.

Event Handlers and Idempotence

It is important to note that event handlers will, by default, trigger any time the specified event is triggered, even if it is triggered by the actions of another controller application. In this case, Simple Switch depends on Ryu to send a features request and listens to the event returned. However, for any event, there is no guarantee that an event handler will be triggered only once for a specific event type. Some other code might also request switch features again and this handler would be called on a switch that already had the table-miss flow added.

Because of this, any event handler should be idempotent, meaning that the result of running the event handler many times with the same input will result in the same end result as only calling the event handler once. This can be accomplished a couple ways: either keep track of events to ensure the handler does nothing if it was already run with the same parameters, or make sure all the actions in the event handler itself is idempotent or nilpotent (has no effect, the same as running it zero times).

In the case of OpenFlow, adding the same flow entry many times is essentially the same as adding it once, so this event handler is already idempotent. Ryu and OpenFlow do allow ensuring that a specific event handler is only called with the response from a specific controller-to-switch message using an xid, which we will get to later in the series.

Add Flow Helper Function

We saw previously that the self.switch_features_handler called a helper instance method self.add_flow. This method adds flow entries with predefined options to reduce the code required elsewhere in the controller application.

self.add_flow is defined and takes a few options:

  • datapath – The ryu.controller.datapath.Datapathsrc, doc instance to add the flow entry to.
  • priority – An int representing the priority that the flow entry should be added with.
  • match – An parser.OFPMatchsrc, doc instance containing the match conditions for the flow entry.
  • actions – A list of parser.OFPActionsrc, doc subclassed entities that will be sent along with the flow mod.
  • buffer_id – An optional int that points to a buffered packet on the switch to immediately apply the flow entry to.

Additionally, the ofproto and ofproto_parser are extracted from the datapath and assigned to ofproto and parser respectively. As before, these are extracted from the datapath so the appropriate version of OpenFlow is used while constructing the OFPFlowModsrc, doc message.

Constructing the Instructions

As this is working with OpenFlow above v1.2, a flow-mod must include a set of instructions rather than just actions. This is due to the additional abilities added such as writing metadata or, more commonly, instructing the switch to continue processing the packet on a different table. OpenFlow v1.3 adds an additional instruction for applying a meter.

Here, the inst variable is set to a list with a single parser.OFPInstructionActionssrc, doc instance set to ofproto.OFPIT_APPLY_ACTIONSsrc provided in actions. We will get in to the difference between Apply Actions and Write Actions in a later article.

Constructing the Flow Mod

Now the actual construction of the flow-mod message is constructed. Depending on if buffer_id was provided, a parser.OFPFlowModsrc, doc is instantiated with or without that buffer_id. In either case, it is passed the same datapath passed to the self.add_flow method and the same with priority and match arguments. The only customized argument is for the flow instructions passed in as inst, which was created above. The result is the final parser.OFPFlowModsrc, doc message.

Sending the Flow Mod

Once the message is created, sending it is as easy as passing it to the datapath.send_msgsrc, doc method. Ryu will take care of the rest to ensure the message is properly encoded and sent to the switch.

Packet In Handler

Now we get to the heart of the Simple Switch controller application: the self._packet_in_handler method.

As with the self.switch_features_handler, the self._packet_in_handler is registered to be called any time a certain event is fired. In this case, the @set_ev_clssrc, doc decorator is applied to register this method to be called on any ofp_event.EventOFPPacketInsrc, doc event is fired in the MAIN_DISPATCHER.

As before, this method is called with the current event as ev. The following attributes of ev are used directly in this method:

  • ev – Instance of ryu.controller.ofp_event.EventOFPPacketInsrc, doc – The event fired.
    • msg – Instance of ryu.ofproto.ofproto_v1_3_parser.OFPPacketInsrc, doc – The message that triggered the event.
      • buffer_id – An int identifying the buffer on the switch that holds the packet, if the packet was buffered.
      • data – Raw bytearray containing the data portion of the message.
      • datapath – Instance of ryu.controller.controller.Datapathsrc, doc that represents the switch.
        • id – 64-bit int of the datapath ID.
        • ofprotoryu.ofproto.ofproto_v1_3src, reference to the currently used OpenFlow protocol definition module.
          • OFP_NO_BUFFERsrcint, special buffer ID to indicate “no buffer”.
          • OFPP_FLOODsrcint, special port number to flood using non-OpenFlow pipeline.
        • ofproto_parserryu.ofproto.ofproto_v1_3_parsersrc, doc, reference to the currently used OpenFlow protocol parser module.
          • OFPActionOutputsrc, doc – Creates an action message segment to indicate a packet should be output on a specific port.
          • OFPMatchsrc, doc – Creates a match message segment to indicate a packet should be matched on the specified parameters.
        • send_msgsrc, doc – Sends the specified message to this datapath.
      • matchdict, dictionary of key, values provided in the match message portion.
      • msg_lenint, length of the message received by the switch.
      • total_lenint, total length of the message, may be larger than msg_len if the message was truncated.

Sanity Checking for Message Length

The method starts of by checking the message length to ensure that the entire message was received by the switch, otherwise a log message is written explaining that the packet was truncated, which may cause issues routing the package.

Pulling Important Data

Several attributes are assigned to local variables for more compact code later on. Of interest is the in_port variable, which is set to the value retrieved from msg.match, a dict, with the key of in_port. This provides the physical port number where the packet was received on the switch that sent the packet-in message. It is used later to learn the location of devices on the network.

Parsing the Submitted Packet

Since this is an L2 learning switch, the devices on the network will be referred to by their L2 addresses. The pkt variable is set to an instance of packet.Packetsrc, which is passed the msg.data portion of the OpenFlow message. This class instantiation automatically parses the first header in the data as an ethernet frame. The next line sets eth to the first ethernet frame by first calling pkt.get_protocols with ethernet.ethernetsrc, doc as the argument (note: the protocol classes do break the normal Python convention of having classes always start with a capital letter). The return value of that call is a list of ethernet headers in that data. Since the L2 learning code is only interested in the outermost ethernet frame, the first value of the returned list is referenced when setting eth. The following is a list of attributes used from the decoded packet:

  • eth – Instance of ryu.lib.packet.ethernet.ethernetdoc – The decoded ethernet frame
    • dststr – String representation of the destination MAC, like 'ff:ff:ff:ff:ff:ff'
    • ethertypeint – The 16-bit ethertype of the packet
    • srcstr – String representation of the source MAC, like '00:00:00:00:00:01'

Ignoring LLDP Packets

Simple Switch ignores packets using the Link Layer Discovery Protocol (LLDP) and this code effectively stops the processing of the packet if the eth.ethertype matches ether_types.ETH_TYPE_LLDPsrc. This prevents the forwarding of LLDP traffic as only the controller is allowed to flood packets and processing is stopped before that action can be given.

Learning the Source MAC

The dst, src, and dpid variables are set to their appropriate values and the application’s MAC-to-Port table is prepared to learn on the current switch, if it was not already set up before.

The vital information for the packet being processed is logged to help with debugging both the controller application and the topology.

Finally, the self.mac_to_port (dict <dict<int>>) is updated by using the dpid and src variables as keys and setting it to the in_port.

Destination Lookup

The same table is then used to look up the physical port assigned to the destination MAC address. This check is performed by asking if the dst is in the self.mac_to_port[dpid] keys. If there is a hit, the out_port is set to the physical port at that location in the table. Otherwise, the out_port is set to ofproto.OFPP_FLOOD.

The actions are now prepared by setting actions to a list with an instance of parser.OFPActionOutput given out_port as the port to send the packet to. ofproto.OFPP_FLOODsrc is considered a special port number, much like ofproto.OFPP_CONTROLLERsrc used in self.switch_features_handler. If used in the output action, the switch will flood the packet to all ports except the port that the packet was received on.

Adding a Flow Entry

If the destination MAC address is known, the controller application needs to add a new flow entry to the switch so the packet can be directly forwarded rather than depending on the controller application to forward it, which is a much slower path.

A check is performed to ensure that the out_port is not set to ofproto.OFPP_FLOODsrc, which means the destination physical port is known. The match conditions for a new flow are specified by setting match to an instance of parser.OFPMatchsrc, doc given the arguments in_port set to in_port and eth_dst to dst.

If the packet-in message contained a buffer ID, then the switch has buffered the packet and is waiting for the controller to tell it where to send it. This means that the flow entry must be added while referencing that buffer, causing the switch to immediately forward the buffered packet while the flow entry is added to the switch. A priority is set to a higher number than the table-miss flow entry so this new learned-path flow entry will be processed before the table-miss flow entry. Since the packet was buffered and the switch was told to forward the buffered packet using the flow added, the method returns immediately.

If the packet was not buffered, the flow entry is still added, but does not reference a buffer. This simply adds the flow entry to the switch and new matching packets will be forwarded directly. The original packet must still be sent, however.

Forwarding the Packet Received by the Controller

If the code execution has gotten this far, that means that either the destination port is unknown or a buffer was not specified in the packet-in message. In either case, the original packet must be sent somewhere.

The data argument for the later parser.OFPPacketOutsrc, doc call is prepared by setting data to None. If the message did not reference a buffer, then the switch did not buffer the packet so the data in the packet-in message sent to the controller must be supplied in the packet-out message.

The parser.OFPPacketOutsrc, doc message is prepared with the variables prepared so far. The in_port argument is provided so the switch knows what port the packet should not be forwarded to, if the out_port is set to OFPP_FLOODsrc. The actions prepared are used both for this packet-out message and for the added flow entry, if one was added.

Summary

We would like to thank the Ryu developers for their example switch controller. The full source code can be viewed on GitHub at Ryu’s repository. The excerpts above are from simple_switch_13.py and are under an Apache License:

The rest of this article is covered under Inside OpenFlow’s terms of use and copyright.

Share This