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