Salta ai contenuti

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

ApplicationDescription
ryu.app.simple_switchL2 learning switch (OF1.0)
ryu.app.simple_switch_13L2 learning switch (OF1.3)
ryu.app.simple_switch_rest_13L2 switch with REST API
ryu.app.ofctl_restOpenFlow REST API
ryu.app.gui_topology.gui_topologyWeb-based topology viewer
ryu.app.simple_monitor_13Traffic monitor
ryu.app.rest_firewallREST-based firewall
ryu.app.rest_routerREST-based L3 router
ryu.app.rest_qosQoS 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

IssueSolution
Switch not connectingCheck port 6653/6633 open, verify OVS controller
Import errorsInstall missing deps: pip install eventlet
Packet-in floodInstall flows to reduce controller traffic
REST API 404Ensure ryu.app.ofctl_rest is loaded
App crashes on packet parseAdd try/except around packet parsing
Multiple apps conflictingCheck event handler priorities
OFPFlowMod errorVerify match/action compatibility with OF version
Topology not detectedLoad 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