RYU入門教程

1 前言

輾轉了POX, NOX, OpenDaylight等多個控制器之後,我終於意識到我只喜歡python語言的控制器。但是我依然記得OpenDaylight的Nullpointer的Exception,還記得YANG文件的深奧,但是OpenDaylight讓我對控制器開發的興趣減少了,這不是我想要的事情。最後,我下決定轉向RYU。我突然發現,生活突然變得很美好。我用着我熟悉的,喜歡的,優美的python,寫着充滿美感的語句,猶如寫詩一般的愜意。

本篇主要介紹如何安裝RYU,和如何在RYU上開發APP。

2 RYU的安裝

安裝RYU,需要安裝一些python的套件:

  • python-eventlet
  • python-routes
  • python-webob
  • python-paramiko

安裝RYU主要有兩種方式:

  • pip安裝
pip install ryu
  • 下載源文件安裝

git clone git://github.com/osrg/ryu.git
cd ryu
sudo python setup.py install

若還有更多問題,可參考@linton小夥伴的博客

3 RYU使用

安裝RYU之後,進入ryu目錄,輸入:

ryu-manager yourapp.py
運行對應的APP,如

ryu-manager simple_switch.py

4 RYU源碼分析

當我安裝好了RYU之後,第一件事就是迫不及待地去看它的源碼,其可讀性之高,超出我的想象。
下面介紹ryu/ryu目錄下的主要目錄內容。

  • base

base中有一個非常重要的文件:app_manager.py,其作用是RYU應用的管理中心。用於加載RYU應用程序,接受從APP發送過來的信息,同時也完成消息的路由。

其主要的函數有app註冊、註銷、查找、並定義了RYUAPP基類,定義了RYUAPP的基本屬性。包含name, threads, events, event_handlers和observers等成員,以及對應的許多基本函數。如:start(), stop()等。

這個文件中還定義了AppManager基類,用於管理APP。定義了加載APP等函數。不過如果僅僅是開發APP的話,這個類可以不必關心。

  • controller

controller文件夾中許多非常重要的文件,如events.py, ofp_handler.py, controller.py等。其中controller.py中定義了OpenFlowController基類。用於定義OpenFlow的控制器,用於處理交換機和控制器的連接等事件,同時還可以產生事件和路由事件。其事件系統的定義,可以查看events.py和ofp_events.py。

在ofp_handler.py中定義了基本的handler(應該怎麼稱呼呢?句柄?處理函數?),完成了基本的如:握手,錯誤信息處理和keep alive 等功能。更多的如packet_in_handler應該在app中定義。

在dpset.py文件中,定義了交換機端的一些消息,如端口狀態信息等,用於描述和操作交換機。如添加端口,刪除端口等操作。

其他的文件不再贅述。

  • lib

lib中定義了我們需要使用到的基本的數據結構,如dpid, mac和ip等數據結構。在lib/packet目錄下,還定義了許多網絡協議,如ICMP, DHCP, MPLS和IGMP等協議內容。而每一個數據包的類中都有parser和serialize兩個函數。用於解析和序列化數據包。

lib目錄下,還有ovs, netconf目錄,對應的目錄下有一些定義好的數據類型,不再贅述。

  • ofproto

在這個目錄下,基本分爲兩類文件,一類是協議的數據結構定義,另一類是協議解析,也即數據包處理函數文件。如ofproto_v1_0.py是1.0版本的OpenFlow協議數據結構的定義,而ofproto_v1_0_parser.py則定義了1.0版本的協議編碼和解碼。具體內容不贅述,實現功能與協議相同。

  • topology

包含了switches.py等文件,基本定義了一套交換機的數據結構。event.py定義了交換上的事件。dumper.py定義了獲取網絡拓撲的內容。最後api.py向上提供了一套調用topology目錄中定義函數的接口。

  • contrib

這個文件夾主要存放的是開源社區貢獻者的代碼。我沒看過。

  • cmd

定義了RYU的命令系統,具體不贅述。

  • services

完成了BGP和vrrp的實現。具體我還沒有使用這個模塊。

  • tests

tests目錄下存放了單元測試以及整合測試的代碼,有興趣的讀者可以自行研究。

5 開發你自己的RYU應用程序

大概瀏覽了一下RYU的源代碼,相信看過OpenDaylight的同學會發現,太輕鬆了!哈哈,我想我真的不喜歡maven, osgi, xml, yang以及java,但是不能不承認OpenDaylight還是很牛逼的,在學習的讀者要堅持啊!

開發RYU的APP,真的再簡單不過了。先來最簡單的:

from ryu.base import app_manager
 
class L2Switch(app_manager.RyuApp):
    def __init__(self, *args, **kwargs):
        super(L2Switch, self).__init__(*args, **kwargs)
如果你覺得非常熟悉,不要懷疑,我確實是在拿官網的例子再講。

首先,我們從ryu.base import app_manager,在前面我們也提到過這個文件中定義了RyuApp基類。我們在開發APP的時候只需要繼承這個基類,就獲得你想要的一個APP的一切了。於是,我們就不用去註冊了?!是的,不需要了!

保存文件,可以取一個名字爲L2Switch.py。

現在你可以運行你的APP了。快得有點不敢相信吧!但是目前什麼都沒有,運行之後,馬上就會結束,但起碼我們的代碼沒有報錯。

運行:

ryu-manager L2Switch.py
繼續往裏面添加內容:

from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
 
class L2Switch(app_manager.RyuApp):
    def __init__(self, *args, **kwargs):
        super(L2Switch, self).__init__(*args, **kwargs)
 
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packet_in_handler(self, ev):
        msg = ev.msg
        datapath = msg.datapath
        ofp = datapath.ofproto
        ofp_parser = datapath.ofproto_parser
 
        actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
        out = ofp_parser.OFPPacketOut(
            datapath=datapath, buffer_id=msg.buffer_id, in_port=msg.in_port,
            actions=actions)
        datapath.send_msg(out)

其中ofp_event完成了事件的定義,從而我們可以在函數中註冊handler,監聽事件,並作出迴應。

packet_in_handler方法用於處理packet_in事件。@set_ev_cls修飾符用於告知RYU,被修飾的函數應該被調用。(翻譯得有點爛這句)

set_ev_cls第一個參數表示事件發生時應該調用的函數,第二個參數告訴交換機只有在交換機握手完成之後,纔可以被調用。

下面分析具體的數據操作:

  • ev.msg:每一個事件類ev中都有msg成員,用於攜帶觸發事件的數據包。
  • msg.datapath:已經格式化的msg其實就是一個packet_in報文,msg.datapath直接可以獲得packet_in報文的datapath結構。datapath用於描述一個交換網橋。也是和控制器通信的實體單元。datapath.send_msg()函數用於發送數據到指定datapath。通過datapath.id可獲得dpid數據,在後續的教程中會有使用。
  • datapath.ofproto對象是一個OpenFlow協議數據結構的對象,成員包含OpenFlow協議的數據結構,如動作類型OFPP_FLOOD。
  • datapath.ofp_parser則是一個按照OpenFlow解析的數據結構。
  • actions是一個列表,用於存放action list,可在其中添加動作。
  • 通過ofp_parser類,可以構造構造packet_out數據結構。括弧中填寫對應字段的賦值即可。

如果datapath.send_msg()函數發送的是一個OpenFlow的數據結構,RYU將把這個數據發送到對應的datapath。

至此,一個簡單的HUB已經完成。

6 RYU進階——二層交換機

在以上的基礎之上,繼續修改就可以完成二層交換機的功能。具體代碼如下:

import struct
import logging
 
from ryu.base import app_manager
from ryu.controller import mac_to_port
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_0
from ryu.lib.mac import haddr_to_bin
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
 
class L2Switch(app_manager.RyuApp):
 
    OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]#define the version of OpenFlow
 
    def __init__(self, *args, **kwargs):
        super(L2Switch, self).__init__(*args, **kwargs)
        self.mac_to_port = {}
 
    def add_flow(self, datapath, in_port, dst, actions):
        ofproto = datapath.ofproto
 
        match = datapath.ofproto_parser.OFPMatch(
            in_port = in_port, dl_dst = haddr_to_bin(dst))
 
        mod = datapath.ofproto_parser.OFPFlowMod(
            datapath = datapath, match = match, cookie = 0,
            command = ofproto.OFPFC_ADD, idle_timeout = 10,hard_timeout = 30,
            priority = ofproto.OFP_DEFAULT_PRIORITY,
            flags =ofproto.OFPFF_SEND_FLOW_REM, actions = actions)
 
        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
 
        pkt = packet.Packet(msg.data)
        eth = pkt.get_protocol(ethernet.ethernet)
 
        dst = eth.dst
        src = eth.src
 
        dpid = datapath.id    #get the dpid
        self.mac_to_port.setdefault(dpid, {})
 
        self.logger.info("packet in %s %s %s %s", dpid, src, dst , msg.in_port)
        #To learn a mac address to avoid FLOOD next time.
 
        self.mac_to_port[dpid][src] = msg.in_port
 
 
        out_port = ofproto.OFPP_FLOOD
 
        #Look up the out_port 
        if dst in self.mac_to_port[dpid]:
            out_port = self.mac_to_port[dpid][dst]
 
        ofp_parser = datapath.ofproto_parser
 
        actions = [ofp_parser.OFPActionOutput(out_port)]
 
        if out_port != ofproto.OFPP_FLOOD:
            self.add_flow(datapath, msg.in_port, dst, actions)
 
 
        #We always send the packet_out to handle the first packet.
        packet_out = ofp_parser.OFPPacketOut(datapath = datapath, buffer_id = msg.buffer_id,
            in_port = msg.in_port, actions = actions)
        datapath.send_msg(packet_out)
    #To show the message of ports' status.
    @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
    def _port_status_handler(self, ev):
        msg = ev.msg
        reason = msg.reason
        port_no = msg.desc.port_no
 
        ofproto = msg.datapath.ofproto
 
        if reason == ofproto.OFPPR_ADD:
            self.logger.info("port added %s", port_no)
        elif reason == ofproto.OFPPR_DELETE:
            self.logger.info("port deleted %s", port_no)
        elif reason == ofproto.OFPPR_MODIFY:
            self.logger.info("port modified %s", port_no)
        else:
            self.logger.info("Illeagal port state %s %s", port_no, reason)

相信代碼中的註釋已經足以讓讀者理解這個程序。完成之後,運行:

ryu-manager L2Switch.py
然後可以使用Mininet進行pingall測試,成功!

7 後語

習慣性還是寫一寫總結。RYU的方便簡潔大大超出我的預料,比我使用過的任何一個控制器都要易於使用和開發。這些是我學RYU一兩天的收穫,希望在後續的學習中還能有所收穫,寫出更好的博文。如果你有什麼意見或建議可以評論,相互學習,共同進步。

最後提供一些有用的鏈接:
(1)http://osrg.github.io/ryu/resources.html
我比較喜歡裏裏面的 http://ryu.readthedocs.org/en/latest/
當然裏面的電子書也是相當好的:http://osrg.github.io/ryu-book/en/html/
(2)推薦一個小夥伴的博客:linton.tw
他在RYU上有更多的學習和研究。歡迎訪問!


本文轉載自:李呈博客@李呈,http://www.muzixing.com/pages/2014/09/20/ryuru-men-jiao-cheng.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章