Ryu SDN Cheat Sheet
Overview
Ryu is an open-source, component-based Software-Defined Networking (SDN) framework written entirely in Python. It provides software components with well-defined APIs that make it easy for developers to create new network management and control applications. Ryu supports various southbound protocols including OpenFlow 1.0 through 1.5, Netconf, OF-config, and sFlow, with OpenFlow being the primary focus. Its event-driven architecture allows developers to write applications as Python classes that respond to network events.
Ryu is widely used in academic research, network prototyping, and production SDN deployments. Its Python-based approach makes it accessible for rapid development and experimentation. The framework includes a comprehensive library of sample applications including L2/L3 switches, firewalls, traffic monitors, REST API routers, and topology viewers. Ryu integrates well with Open vSwitch and Mininet, forming the backbone of many SDN testbed environments. The project is maintained by the Ryu SDN Framework Community and is part of the Linux Foundation Networking ecosystem.
Installation
Via pip
# Install Ryu
pip install ryu
# Install with optional dependencies
pip install ryu eventlet msgpack
# Verify installation
ryu-manager --version
# Install from source for latest features
git clone https://github.com/faucetsdn/ryu.git
cd ryu
pip install .
Dependencies
# Ubuntu/Debian prerequisites
sudo apt update
sudo apt install python3-pip python3-dev gcc libffi-dev libssl-dev libxml2-dev libxslt-dev
# Install additional useful packages
pip install networkx webob tinyrpc
Running Ryu
Basic Execution
# Run a single application
ryu-manager my_app.py
# Run multiple applications
ryu-manager app1.py app2.py
# Run built-in simple switch
ryu-manager ryu.app.simple_switch_13
# Run with REST API
ryu-manager ryu.app.simple_switch_13 ryu.app.ofctl_rest
# Run with GUI topology viewer
ryu-manager ryu.app.simple_switch_13 ryu.app.gui_topology.gui_topology
# Specify OpenFlow version
ryu-manager --ofp-tcp-listen-port 6653 my_app.py
# Verbose output
ryu-manager --verbose my_app.py
# Custom listen address and port
ryu-manager --ofp-listen-host 0.0.0.0 --ofp-tcp-listen-port 6653 my_app.py
Built-in Applications
| Application | Description |
|---|---|
ryu.app.simple_switch | L2 learning switch (OF1.0) |
ryu.app.simple_switch_13 | L2 learning switch (OF1.3) |
ryu.app.simple_switch_rest_13 | L2 switch with REST API |
ryu.app.ofctl_rest | OpenFlow REST API |
ryu.app.gui_topology.gui_topology | Web-based topology viewer |
ryu.app.simple_monitor_13 | Traffic monitor |
ryu.app.rest_firewall | REST-based firewall |
ryu.app.rest_router | REST-based L3 router |
ryu.app.rest_qos | QoS management |
Application Development
Minimal L2 Switch
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
from ryu.lib.packet import packet, ethernet
class SimpleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(SimpleSwitch13, self).__init__(*args, **kwargs)
self.mac_to_port = {}
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# Install table-miss flow entry
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)
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)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
in_port = msg.match['in_port']
pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
dst = eth.dst
src = eth.src
dpid = datapath.id
self.mac_to_port.setdefault(dpid, {})
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 flow to avoid future packet_in
if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src)
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)
Traffic Monitor
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib import hub
class TrafficMonitor(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(TrafficMonitor, self).__init__(*args, **kwargs)
self.datapaths = {}
self.monitor_thread = hub.spawn(self._monitor)
@set_ev_cls(ofp_event.EventOFPStateChange, [MAIN_DISPATCHER, DEAD_DISPATCHER])
def state_change_handler(self, ev):
datapath = ev.datapath
if ev.state == MAIN_DISPATCHER:
self.datapaths[datapath.id] = datapath
elif ev.state == DEAD_DISPATCHER:
if datapath.id in self.datapaths:
del self.datapaths[datapath.id]
def _monitor(self):
while True:
for dp in self.datapaths.values():
self._request_stats(dp)
hub.sleep(10)
def _request_stats(self, datapath):
parser = datapath.ofproto_parser
req = parser.OFPFlowStatsRequest(datapath)
datapath.send_msg(req)
req = parser.OFPPortStatsRequest(datapath, 0, datapath.ofproto.OFPP_ANY)
datapath.send_msg(req)
@set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER)
def flow_stats_reply_handler(self, ev):
for stat in ev.msg.body:
self.logger.info('flow: match=%s packets=%d bytes=%d',
stat.match, stat.packet_count, stat.byte_count)
@set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
def port_stats_reply_handler(self, ev):
for stat in ev.msg.body:
self.logger.info('port %d: rx_packets=%d tx_packets=%d rx_bytes=%d tx_bytes=%d',
stat.port_no, stat.rx_packets, stat.tx_packets,
stat.rx_bytes, stat.tx_bytes)
REST API Application
from ryu.app.wsgi import ControllerBase, WSGIApplication, route
from webob import Response
import json
class RestApi(app_manager.RyuApp):
_CONTEXTS = {'wsgi': WSGIApplication}
def __init__(self, *args, **kwargs):
super(RestApi, self).__init__(*args, **kwargs)
wsgi = kwargs['wsgi']
wsgi.register(RestController, {'app': self})
self.switches = {}
class RestController(ControllerBase):
def __init__(self, req, link, data, **config):
super(RestController, self).__init__(req, link, data, **config)
self.app = data['app']
@route('switches', '/api/switches', methods=['GET'])
def list_switches(self, req, **kwargs):
body = json.dumps(list(self.app.switches.keys()))
return Response(content_type='application/json', body=body)
@route('flows', '/api/flows/{dpid}', methods=['GET'])
def get_flows(self, req, dpid, **kwargs):
# Return flow stats for switch
return Response(content_type='application/json', body='{}')
REST API (ofctl_rest)
# Run with REST API
ryu-manager ryu.app.simple_switch_13 ryu.app.ofctl_rest
# Get all switches
curl http://localhost:8080/stats/switches
# Get flow stats
curl http://localhost:8080/stats/flow/1
# Get port stats
curl http://localhost:8080/stats/port/1
# Get port description
curl http://localhost:8080/stats/portdesc/1
# Add flow entry
curl -X POST -d '{
"dpid": 1,
"priority": 100,
"match": {
"in_port": 1,
"dl_type": 2048,
"nw_dst": "10.0.0.2"
},
"actions": [
{"type": "OUTPUT", "port": 2}
]
}' http://localhost:8080/stats/flowentry/add
# Delete flow entry
curl -X POST -d '{
"dpid": 1,
"match": {"in_port": 1}
}' http://localhost:8080/stats/flowentry/delete
# Delete all flows
curl -X DELETE http://localhost:8080/stats/flowentry/clear/1
Configuration
Ryu Configuration File
# /etc/ryu/ryu.conf
[DEFAULT]
ofp_listen_host = 0.0.0.0
ofp_tcp_listen_port = 6653
wsapi_host = 0.0.0.0
wsapi_port = 8080
observe_links = True
explicit_drop = True
Using with Mininet
# Start Ryu controller
ryu-manager ryu.app.simple_switch_13 &
# Start Mininet with remote controller
sudo mn --controller=remote,ip=127.0.0.1,port=6653 \
--switch ovsk,protocols=OpenFlow13 \
--topo tree,depth=2,fanout=3
Advanced Usage
Packet Parsing Library
from ryu.lib.packet import packet, ethernet, arp, ipv4, tcp, udp, icmp
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
pkt = packet.Packet(ev.msg.data)
eth_pkt = pkt.get_protocol(ethernet.ethernet)
arp_pkt = pkt.get_protocol(arp.arp)
ip_pkt = pkt.get_protocol(ipv4.ipv4)
tcp_pkt = pkt.get_protocol(tcp.tcp)
if arp_pkt:
self.logger.info("ARP: %s -> %s", arp_pkt.src_ip, arp_pkt.dst_ip)
if ip_pkt:
self.logger.info("IP: %s -> %s proto=%d", ip_pkt.src, ip_pkt.dst, ip_pkt.proto)
if tcp_pkt:
self.logger.info("TCP: port %d -> %d", tcp_pkt.src_port, tcp_pkt.dst_port)
Multi-Table Processing
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
parser = datapath.ofproto_parser
ofproto = datapath.ofproto
# Table 0: Classification
match = parser.OFPMatch(eth_type=0x0800)
inst = [parser.OFPInstructionGotoTable(1)]
mod = parser.OFPFlowMod(datapath=datapath, table_id=0,
priority=100, match=match, instructions=inst)
datapath.send_msg(mod)
# Table 1: Forwarding
match = parser.OFPMatch(ipv4_dst='10.0.0.2')
actions = [parser.OFPActionOutput(2)]
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]
mod = parser.OFPFlowMod(datapath=datapath, table_id=1,
priority=100, match=match, instructions=inst)
datapath.send_msg(mod)
Group Tables
def install_group(self, datapath):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# Select group for load balancing
buckets = [
parser.OFPBucket(actions=[parser.OFPActionOutput(1)]),
parser.OFPBucket(actions=[parser.OFPActionOutput(2)]),
parser.OFPBucket(actions=[parser.OFPActionOutput(3)])
]
req = parser.OFPGroupMod(datapath, ofproto.OFPGC_ADD,
ofproto.OFPGT_SELECT, 1, buckets)
datapath.send_msg(req)
Troubleshooting
| Issue | Solution |
|---|---|
| Switch not connecting | Check port 6653/6633 open, verify OVS controller |
| Import errors | Install missing deps: pip install eventlet |
| Packet-in flood | Install flows to reduce controller traffic |
| REST API 404 | Ensure ryu.app.ofctl_rest is loaded |
| App crashes on packet parse | Add try/except around packet parsing |
| Multiple apps conflicting | Check event handler priorities |
| OFPFlowMod error | Verify match/action compatibility with OF version |
| Topology not detected | Load ryu.app.rest_topology with observe_links |
Debugging
# Run with debug logging
ryu-manager --verbose --log-file /tmp/ryu.log my_app.py
# Monitor OpenFlow messages
sudo ovs-ofctl monitor s1
# Check controller connection
sudo ovs-vsctl show | grep -A2 Controller
# Trace packet path
sudo ovs-appctl ofproto/trace s1 in_port=1,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02