When writing controller applications, it is often useful to determine when a port status changes. For example, when a port’s physical link is removed, flows and internal tables may need to be modified as part of a network layout learning algorithm. Thankfully, subscribing to these events is quite simple.
To get started quickly, download our portwatch.py
example application (full source listing and download below) and put it in the apps/
folder of your working Simple Switch Quick Start for Ryu environment and run ryu-manager
with the new example application alongside the existing simple_switch_13.py
application. If you are using the local Ryu installation (see the link above), the following command will run it:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ usr/bin/ryu-manager apps/portwatch.py apps/simple_switch_13.py loading app apps/portwatch.py loading app apps/simple_switch_13.py loading app ryu.controller.ofp_handler loading app ryu.app.ofctl.service loading app ryu.controller.ofp_handler instantiating app ryu.app.ofctl.service of OfctlService instantiating app apps/portwatch.py of PortWatch PortWatch init instantiating app ryu.controller.ofp_handler of OFPHandler instantiating app apps/simple_switch_13.py of SimpleSwitch13 SimpleSwitch13 initialization... |
Once the switch connects, you should see a full list of ports listed:
1 2 3 4 5 6 7 8 9 |
Received port list: [...] Port 103 (zre102, hw_addr:e8:8d:f5:5b:09:01) Configuration: State: No physical link present (OFPPS_LINK_DOWN) Current Speed: 1000000kbps Max Speed: 10000000kbps [...] |
And when ports are configured, you should see update messages:
1 2 3 4 5 6 |
Received port status update: Port was modified Port 103 (zre102, hw_addr:e8:8d:f5:5b:09:01) Configuration: State: Current Speed: 10000000kbps Max Speed: 10000000kbps |
In this case, the State
changed with the OFPPS_LINK_DOWN
flag removed.
How It Works
These events are triggered by asynchronous messages sent by the switch. There are several async messages used in any controller application, especially error and packet-in messages. The messages we want to receive for updates on port status are the OFPT_PORT_STATUS
messages. These will provide a reason (added, deleted, modified) and the current port information. In order to make sure our controller receives these updates, the switch configuration for async messages may need to be set at the beginning of the switch’s connection to the controller.
All async message configuration (with the exception of error messages) can be configured with the OFPT_SET_ASYNC
message. See the OpenFlow 1.3.4 specification under section 7.3.10 Set Asynchronous Configuration Message for more details. As of this writing, the OF-DPA 2.0 agent application does not handle the OFPT_SET_ASYNC
message for configuration, but OFPT_PORT_STATUS
messages are enabled by default. Our example application will still include the appropriate configuration message to ensure future versions of OF-DPA 2.0 agents are configured properly.
To process the messages, just add a method to your RyuApp
that is called on the ofp_event.EventOFPPortStatus
event on the MAIN_DISPATCHER
. A small example is given in the Ryu documentation for ryu.ofproto.ofproto_v1_3_parser.OFPPortStatus
. Here is our example in portwatch.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) def port_status_handler(self, ev): """Handles async Port Status messages""" msg = ev.msg ofp = msg.datapath.ofproto # In a production application, you would trigger your group and flow # modifications as needed for your network logic. # # if msg.reason == ofp.OFPPR_MODIFY: # trigger_some_network_logic_update(msg.datapath, msg.desc) # # For this example, however, we just print out the event to the console reason = { ofp.OFPPR_ADD: "Port was added", ofp.OFPPR_DELETE: "Port was deleted", ofp.OFPPR_MODIFY: "Port was modified" }.get(msg.reason, "Unknown reason (%d)" % msg.reason) print("Received port status update: %s" % reason) print(self.port_to_string(msg.datapath, msg.desc)) |
We also have a simple port_to_string
method to pretty print some port information, especially the actual flags for the state
and config
values in the provided port desc
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
def port_to_string(self, dp, port): ofp = dp.ofproto out = " Port %d (%s, hw_addr:%s)\n" % (port.port_no, port.name, port.hw_addr) out += " Configuration:\n" if port.config & ofp.OFPPC_PORT_DOWN: out += " Port is administratively down (OFPPC_PORT_DOWN)\n" if port.config & ofp.OFPPC_NO_RECV: out += " Drop all packets received by port (OFPPC_NO_RECV)\n" if port.config & ofp.OFPPC_NO_FWD: out += " Drop packets forwarded to port (OFPPC_NO_FWD)\n" if port.config & ofp.OFPPC_NO_PACKET_IN: out += " Do not send packet-in msgs for port (OFPPC_NO_PACKET_IN)\n" out += " State:\n" if port.state & ofp.OFPPS_LINK_DOWN: out += " No physical link present (OFPPS_LINK_DOWN)\n" if port.state & ofp.OFPPS_BLOCKED: out += " Port is blocked (OFPPS_BLOCKED)\n" if port.state & ofp.OFPPS_LIVE: out += " Live for Fast Failover Group (OFPPS_LIVE)\n" out += " Current Speed: %dkbps\n" % port.curr_speed out += " Max Speed: %dkbps\n" % port.max_speed return out |
Feel free to use this application while debugging your own Ryu applications.
Happy Coding!
-Leo
Full Listing
Download from the Marketplace: portwatch-0.0.2.tar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# Copyright 2015 SQI, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. 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 class PortWatch(app_manager.RyuApp): """Watch port status and print port events on the console.""" OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] def __init__(self, *a, **kw): super(PortWatch, self).__init__(*a, **kw) print "PortWatch init" @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): """Called when a switch connects. Asks for port information and notifies the switch that we want to watch for port status changes. """ dp = ev.msg.datapath ofp = dp.ofproto parser = dp.ofproto_parser # Send off the request to get current port descriptions req = parser.OFPPortDescStatsRequest(dp, 0) dp.send_msg(req) # Configure the switch to send port status changes # (OF 1.3.4 section 7.3.10, p.104) # # NOTE: As of this writing, OF-DPA 2.0 does not support Set Async, but # we send it anyways because it is the right thing to do. # # NOTE: This is normally set by the master application as this overrides # the current configuration for this connection. There may be other # components that need async messages of certain types to be enabled. # Some sane defaults specified here from the Ryu documentation. packet_in_mask = ofp.OFPR_ACTION | ofp.OFPR_INVALID_TTL port_status_mask = ofp.OFPPR_ADD | ofp.OFPPR_DELETE | ofp.OFPPR_MODIFY flow_removed_mask = 0 req = parser.OFPSetAsync(dp, [packet_in_mask, 0], [port_status_mask, 0], [flow_removed_mask, 0]) dp.send_msg(req) @set_ev_cls(ofp_event.EventOFPPortDescStatsReply, MAIN_DISPATCHER) def port_desc_stats_reply_handler(self, ev): """Handles response to the Port Desc Stats request""" dp = ev.msg.datapath ofp = dp.ofproto parser = dp.ofproto_parser print("Received port list:") for port in ev.msg.body: print(self.port_to_string(dp, port)) @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) def port_status_handler(self, ev): """Handles async Port Status messages""" msg = ev.msg ofp = msg.datapath.ofproto # In a production application, you would trigger your group and flow # modifications as needed for your network logic. # # if msg.reason == ofp.OFPPR_MODIFY: # trigger_some_network_logic_update(msg.datapath, msg.desc) # # For this example, however, we just print out the event to the console reason = { ofp.OFPPR_ADD: "Port was added", ofp.OFPPR_DELETE: "Port was deleted", ofp.OFPPR_MODIFY: "Port was modified" }.get(msg.reason, "Unknown reason (%d)" % msg.reason) print("Received port status update: %s" % reason) print(self.port_to_string(msg.datapath, msg.desc)) def port_to_string(self, dp, port): ofp = dp.ofproto out = " Port %d (%s, hw_addr:%s)\n" % (port.port_no, port.name, port.hw_addr) out += " Configuration:\n" if port.config & ofp.OFPPC_PORT_DOWN: out += " Port is administratively down (OFPPC_PORT_DOWN)\n" if port.config & ofp.OFPPC_NO_RECV: out += " Drop all packets received by port (OFPPC_NO_RECV)\n" if port.config & ofp.OFPPC_NO_FWD: out += " Drop packets forwarded to port (OFPPC_NO_FWD)\n" if port.config & ofp.OFPPC_NO_PACKET_IN: out += " Do not send packet-in msgs for port (OFPPC_NO_PACKET_IN)\n" out += " State:\n" if port.state & ofp.OFPPS_LINK_DOWN: out += " No physical link present (OFPPS_LINK_DOWN)\n" if port.state & ofp.OFPPS_BLOCKED: out += " Port is blocked (OFPPS_BLOCKED)\n" if port.state & ofp.OFPPS_LIVE: out += " Live for Fast Failover Group (OFPPS_LIVE)\n" out += " Current Speed: %dkbps\n" % port.curr_speed out += " Max Speed: %dkbps\n" % port.max_speed return out |