SDN:Lab 練習二
0x01 作業要求
- 安裝 Ryu
- 寫一個 Ryu App:
- controller 對每個 switch 初始設置兩條 flow entry
- 從 port A 進來的封包就送往 port B
- 從 port B 進來的封包就送往 port A
- A, B port 必須動態偵測,不能直接 hardcode 在程式內
- 不寫 packet_in handler,這兩條 flow entry 必須為初始化設定,而非等到 packet_in 的監聽事件發生才設置
Tips: Controller 必須去詢問 switch 有哪些 port 是 active 的,此時應該會得到三個 port,其中一個是 special port(接到 controller),埠號會大於 ofpp_max 這個常數,剩下兩個即是連接 hosts 的 port
- 修正 SDN:Lab 作業一 的 Mininet script,使它連接至 Ryu controller,failmode 設為 secure
—-
0x02 安裝 Ryu
請參閱 Ryu安裝教學
0x03 Ryu Application
- hw2_ryuapp.py
#!/usr/bin/env python # -*- coding: utf8 -*- # 2016.07.30 kshuang from ryu.base import app_manager from ryu.ofproto import ofproto_v1_5 from ryu.controller.handler import set_ev_cls from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller import ofp_event from ryu.ofproto import ofproto_v1_5_parser class MyRyu(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_5.OFP_VERSION] normal_port = [] def __init__(self, *args, **kwargs): super(MyRyu, self).__init__(*args, **kwargs) @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): datapath = ev.msg.datapath self.send_port_stats_request(datapath) def send_port_stats_request(self, datapath): ofp = datapath.ofproto ofp_parser = datapath.ofproto_parser req = ofp_parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_ANY) datapath.send_msg(req) @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) def port_stats_reply_handler(self, ev): msg = ev.msg datapath = msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser for stat in ev.msg.body: if stat.port_no < ofproto.OFPP_MAX: self.normal_port.append(stat.port_no) if len(self.normal_port) == 2: # port B to port A match = parser.OFPMatch(in_port=self.normal_port[0]) actions = [parser.OFPActionOutput(self.normal_port[1])] self.add_flow(datapath, 0, match, actions) # port A to port B match = parser.OFPMatch(in_port=self.normal_port[1]) actions = [parser.OFPActionOutput(self.normal_port[0])] self.add_flow(datapath, 0, match, actions) # clear port record after add flow entry self.normal_port = [] def add_flow(self, datapath, priority, match, actions): ofproto = datapath.ofproto parser = datapath.ofproto_parser inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)] mod = parser.OFPFlowMod(datapath=datapath, priority=priority, command=ofproto.OFPFC_ADD, match=match, instructions=inst) datapath.send_msg(mod)
定義和初始化類別
class MyRyu(app_manager.RyuApp): OFP_VERSION = [ofproto_v1_5.OFP_VERSION] normal_port = [] def __init__(self, *args, **kwargs): super(MyRyu, self).__init__(*args, **kwargs) # ...
要實作 RyuApp 必需繼承 ryu.base.app_manager.RyuApp,而在應用程式中我們使用的 OpenFlow protocl 版本為 1.5,因此將 OFP_VERSIONS 指定為 1.5 版
normal_port 是在這個 RyuApp 中我用於記錄 switch 上連接 hosts 的 ports 的變數
事件管理
對於 Ryu 來說,接受到任何一個 OpenFlow 訊息即會產生一個相對應的事件。而 Ryu 應用程式則是必須實作事件管理以處理相對應發生的事件
事件管理( Event Handler )是一個擁有事件物件( Event Object )做為參數,並且使用 ryu.controller.handler.set_ev_cls
修飾( Decorator )的函式
set_ev_cls 中的參數用來指定要監聽的事件類別與事件發生的 Switch 狀態
事件類別名稱的規則為 ryu.controller.ofp_event.EventOFP + <OpenFlow訊息名稱>, 例如:在 Packet-In 訊息的狀態下的事件名稱為 EventOFPPacketIn
switch 狀態則包含以下四種:
- ryu.controller.handler.HANDSHAKE_DISPATCHER: 交換 HELLO 訊息
- ryu.controller.handler.CONFIG_DISPATCHER: openflow 版本協商,傳送 SwitchFeatures 訊息
- ryu.controller.handler.MAIN_DISPATCHER: 接收 SwitchFeatures 訊息,並傳送 set-config message
- ryu.controller.handler.DEAD_DISPATCHER: 連線中斷
—-
OpenFlow Handshark
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): datapath = ev.msg.datapath self.send_port_stats_request(datapath) # ...
作業要求是在初始化就對 Switch 配置好 flow entries,而非等到 packet_in 事件發生才下 flow entries
所以這邊我們可以註冊一個 EventOFPSwitchFeatures 監聽事件
Ryu framework 會處理 send_features_request,所以我們的 ryuapp 中只要處理這監聽事件就好
這個事件會在 openflow handshark 結束後被觸發
細節可參閱 ryu.ofproto.ofproto_v1_5_parser.OFPSwitchFeatures
我們便可在 OpenFlow Switch 的握手協議完成之後就嘗試去取得 Switch 資訊
嘗試取得 Switch active port
def send_port_stats_request(self, datapath): ofp = datapath.ofproto ofp_parser = datapath.ofproto_parser req = ofp_parser.OFPPortStatsRequest(datapath, 0, ofp.OFPP_ANY) datapath.send_msg(req) # ...
接著這個函式中,我們嘗試向 switch 發送 port statistics request message
細節可參閱 ryu.ofproto.ofproto_v1_5_parser.OFPPortStatsRequest
ofp.OFPP_ANY 表示為所有 port
監聽回應並設置 flow entries
@set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) def port_stats_reply_handler(self, ev): msg = ev.msg datapath = msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser for stat in ev.msg.body: if stat.port_no < ofproto.OFPP_MAX: self.normal_port.append(stat.port_no) if len(self.normal_port) == 2: # port B to port A match = parser.OFPMatch(in_port=self.normal_port[0]) actions = [parser.OFPActionOutput(self.normal_port[1])] self.add_flow(datapath, 0, match, actions) # port A to port B match = parser.OFPMatch(in_port=self.normal_port[1]) actions = [parser.OFPActionOutput(self.normal_port[0])] self.add_flow(datapath, 0, match, actions) # clear port record after add flow entry self.normal_port = [] # ...
因為前面我們向 switch 發送了 port statistics request message,所以這邊我們要註冊一個 EventOFPPortStatsReply 來監聽 switch 的回應
細節可參閱 ryu.ofproto.ofproto_v1_5_parser.OFPPortStatsReply
這邊 switch 會回應他使用中的 ports 的狀態細節,不過其中有包含跟 controller 溝通的 port,這個 port number 一般會很大,在 Ryu 中我們可以用 ofproto.OFPP_MAX 這個常數來判斷,一般 port number 皆會小於此常數
將 port number 存入變數之後,判斷 switch 上使用的 normal port 若只有兩個便對 switch 下達我們要求的 flow entry: match 從 port A 進入的就送往 port B,反之亦然
最後將 normal_port 重新記錄下一台 switch active port (如果有)
新增 Flow Entry
def add_flow(self, datapath, priority, match, actions): ofproto = datapath.ofproto parser = datapath.ofproto_parser inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)] mod = parser.OFPFlowMod(datapath=datapath, priority=priority, command=ofproto.OFPFC_ADD, match=match, instructions=inst) datapath.send_msg(mod)
flowmatch 部分可以參考 flow-match-structure
actions 則可參考 action-structures
0x04 Mininet Script
- hw2_net.py
#!/usr/bin/env python2 from mininet.log import setLogLevel, info from mininet.net import Mininet from mininet.cli import CLI from mininet.node import RemoteController, OVSSwitch def MininetTopo(): net = Mininet() info("Create host nodes.\n") lefthost = net.addHost("h1") righthost = net.addHost("h2") info("Create switch node.\n") switch = net.addSwitch("s1", switch=OVSSwitch, protocols = 'OpenFlow15', failMode = 'secure') #switch = net.addSwitch("s1", switch=OVSSwitch, protocols = 'OpenFlow15', failMode = 'standalone') info("Connect to controller node.\n") net.addController(name='c1',controller=RemoteController,ip='192.168.1.22',port=6633) info("Create Links.\n") net.addLink(lefthost, switch) net.addLink(righthost, switch) info("build and start.\n") net.build() net.start() CLI(net) if __name__ == '__main__': setLogLevel('info') MininetTopo()
修改 mininet script,連接到 remote contrller,設置 Switch Openflow version 為 1.5
0x05 測試
先執行 mininet
- 取得目前 OpenvSwitch 的相關訊息,包含 controller, failmode, 使用中的 port
mininet> s1 ovs-vsctl show
- 查看目前 switch 上的 flow entries
mininet> s1 ovs-ofctl -O OpenFlow15 dump-flows "s1"
目前 ryu 還沒開啟,flow 應該是空的,pingall 應該不會通
接著啟動 ryu
此時重新執行
mininet> s1 ovs-ofctl -O OpenFlow15 dump-flows "s1"
應該可見到 flow entries
- 測試 pingall 指令應該要會通
mininet> pingall
- clean
若反覆測試中,有些 openvswitch 或其他 bridge 在 mininet 結束時並未被清除,可使用
# sudo mn -c