目錄表

SDN:Lab 練習二

0x01 作業要求

Tips: Controller 必須去詢問 switch 有哪些 port 是 active 的,此時應該會得到三個 port,其中一個是 special port(接到 controller),埠號會大於 ofpp_max 這個常數,剩下兩個即是連接 hosts 的 port

—-

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 狀態則包含以下四種:

—-

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

mininet> s1 ovs-vsctl show
mininet> s1 ovs-ofctl -O OpenFlow15 dump-flows "s1"

目前 ryu 還沒開啟,flow 應該是空的,pingall 應該不會通

接著啟動 ryu

此時重新執行

mininet> s1 ovs-ofctl -O OpenFlow15 dump-flows "s1"

應該可見到 flow entries

mininet> pingall

若反覆測試中,有些 openvswitch 或其他 bridge 在 mininet 結束時並未被清除,可使用

# sudo mn -c 

0x06 參考資料