最近看到一個練習SDN的好網站,https://wiki.kshuang.xyz/doku.php/
,裡面有很多很實用的筆記,也是交大人寫的,但我不知道他是誰XD,但沒關係我還是先感謝他的筆記能讓我們快速進入狀況
怎麼沒有lab1?太簡單了先跳過哈哈,但lab2就比較有難度以及明顯感受到sdn的實做
作業要求
- 安裝 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 的監聽事件發生才設置
- 修正 SDN:Lab 作業一 的 Mininet script,使它連接至 Ryu controller,failmode 設為 secure
Tips: Controller 必須去詢問 switch 有哪些 port 是 active 的,此時應該會得到三個 port,其中一個是 special port(接到 controller),埠號會大於 ofpp_max 這個常數,剩下兩個即是連接 hosts 的 port
Mininet配置
這個要怎麼寫,首先我決定先設定好mininet產生一個拓樸如下圖,並將switch改成支援openflow1.3的ovs
而相關參考可以看mininet的api reference
但我真心覺得超不好懂的….. ,花了很多時間才大概摸出一點點
不過在設置switch上已經沒問題了,如下方顯示
from mininet.log import setLogLevel,info
from mininet.node import RemoteController
from mininet.net import Mininet
from mininet.cli import CLIdef MininetTopo():
net = Mininet()
info(“Create host nodes.\n”)
h1 = net.addHost(“h1”)
h2 = net.addHost(“h2”)info(“Create switch node.\n”)
#s1 = net.addSwitch(“s1”,failMode = ‘standalone’)
s1 = net.addSwitch(“s1”,failMode = ‘secure’,protocols = ‘OpenFlow13’)info(“Create Links. \n”)
net.addLink(h1,s1)
net.addLink(h2,s1)info(“Create controller ot switch. \n”)
net.addController(controller=RemoteController,ip=’140.113.207.84',port=6633)info(“Build and start network.\n”)
net.build()
net.start()
info(“Run the mininet CLI”)
CLI(net)if __name__ == ‘__main__’:
setLogLevel(‘info’)
MininetTopo()
比較重要的就是設置switch,failed mode 必須為secure(詳情可以看lab1),
並設置protocols為openflow1.3
s1 = net.addSwitch(“s1”,failMode = ‘secure’,protocols = ‘OpenFlow13’)
完成後,我們run一下mininet
$ sudo python sdn2_mininet.py
Create host nodes.
Create switch node.
Create Links.
Create controller ot switch.
Unable to contact the remote controller at 140.113.207.84:6633
Build and start network.
*** Configuring hosts
h1 h2
*** Starting controller
c0
*** Starting 1 switches
s1 …
Run the mininet CLI*** Starting CLI:
mininet>
這邊會注意到controller還沒有接上,不過可以先不用理它,所以正常來說h1 ping 不到h2
接著我們看一下s1的配置如何
mininet> s1 ovs-vsctl show
c87cbd64-dd8d-487f-a335–54619b3d9b05
Bridge “s1”
Controller “tcp:140.113.207.84:6633”
fail_mode: secure
Port “s1-eth2”
Interface “s1-eth2”
Port “s1-eth1”
Interface “s1-eth1”
Port “s1”
Interface “s1”
type: internal
ovs_version: “2.5.2”
mininet>
這邊說明我們的模式沒有錯,是secure
mininet> s1 ovs-ofctl -O OpenFlow13 dump-flows “s1”
OFPST_FLOW reply (OF1.3) (xid=0x2)
上面說明openflow1.3配置沒問題,但目前flow entry是空的(因為我們什麼都還沒開始做)
到這邊就能確定mininet完成了
RyuApp撰寫
再來這邊才是重頭戲,我花了一個晚上才把它給寫出來,但寫完真的蠻有感覺的,也蠻好玩的
****這邊有給提示,必須先去查一下這台ovs的port有哪幾個,所以這邊就會用到openflow的相關api了,對於入門來說我覺得是個蠻重要的練習****
先附上整段的程式碼
from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller.handler import set_ev_cls
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHERclass MySwitch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]def __init__(self, *args,**kwargs):
super(MySwitch,self).__init__(*args,**kwargs)
self.mac_to_port = {} # Mac address is defined@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
print(“send request”)
self.send_port_stats_request(datapath)# send the requestdef add_flow(self, datapath, priority, match, actions):
ofproto = datapath.ofproto
parser = datapath.ofproto_parserinst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]mod = parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,instructions=inst)
datapath.send_msg(mod)def send_port_stats_request(self, datapath):
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
req = ofp_parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY)
datapath.send_msg(req)@set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
def port_stats_reply_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
ports = []
for stat in ev.msg.body:
if stat.port_no <=ofproto.OFPP_MAX:
ports.append(stat.port_no)
print ports
for no in ports:
in_port = no
match = ofp_parser.OFPMatch(in_port = in_port)
for other_no in ports:
if other_no != in_port:
out_port = other_no
actions = [ofp_parser.OFPActionOutput(out_port)]
print in_port, out_port
self.add_flow(datapath, 1, match, actions)
我們拆開來解析一下,第一段
class MySwitch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]def __init__(self, *args,**kwargs):
super(MySwitch,self).__init__(*args,**kwargs)
self.mac_to_port = {} # Mac address is defined
這邊主要我們去繼承ryuapp來用,往後有機會會深入探討這個source code寫了什麼,這邊就先用,接著在設定openflow的版本為1.3,並接好初始化的工作,主要就是去呼叫父類別的init初始化
@set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
print(“send request”)
self.send_port_stats_request(datapath)# send the request
這邊我是根據ryubook去寫的,我們先看到@set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER)
這個為事件管理,用事件物件(Event Object)當作參數,並使用ryu.controller.handler.set_ev_cls
修飾(Decorator)的函數
至於事件有那幾種可以參考ryubook 1.3.2,這邊就不重複介紹了,有關Decorator可參考文件
ev.msg 是 用 來 儲 存 對 應 事 件 的 OpenFlow 訊 息 類 別 實 體。在 這 個 例 子 中 則 是ryu.ofproto.ofproto_v1_3_parser.OFPSwitchFeatures 。
msg.datapath 這 個 訊 息 是 用 來 儲 存 OpenFlow 交 換 器 的ryu.controller.controller.Datapath 類別所對應的實體
這樣說可能有點模糊,因為這是ryubook的解釋,我們可以把它想像為
ev.msg為對應的訊息,而datapath就是openflow的類別
之後如果解析完源瑪,有機會再跟大家分享
本來ryubook在實做傳統switch會處理Table-miss Flow Entry,但是由於我們不必處理未知的封包,所以這邊我們可以直接送一個requeset去問switch的port狀況,相關api在這邊 reference
而送出的程式我們寫在下方
def send_port_stats_request(self, datapath):
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
req = ofp_parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY)
datapath.send_msg(req)
而這邊ofproto用來定義此版本的常數模組,這邊為openflow1.3的常數模組
例如等等會用到的OFPP_ANY被定義等於0xffffffff,其餘相關的資料都可以參考source code,至於為何這樣用呢,主要是這樣可以增加程式的可讀性,而不是寫下一堆數字,要別人自己去查對應到的相關功能
而parser 就是處理函數的各個函式,用來解析
最後我們將送出msg去要求ovs給我們有關port的資訊,這邊很有趣的是,它會回傳回來,所以我們要緊接著,寫下對應的函式來接下資訊
@set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
def port_stats_reply_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
ports = []
for stat in msg.body:
if stat.port_no <=ofproto.OFPP_MAX:
ports.append(stat.port_no)
print ports
for no in ports:
in_port = no
match = ofp_parser.OFPMatch(in_port = in_port)
for other_no in ports:
if other_no != in_port:
out_port = other_no
actions = [ofp_parser.OFPActionOutput(out_port)]
print in_port, out_port
self.add_flow(datapath, 1, match, actions)
接下來後 前面的過程都一樣,就不再重複一次了
然後我們會注意到有一個port number特別大,那個port是用來連結controller,對沒錯就是你現在開的ryu,所以我們不把它加到ports的list裡
接來寫一個小function讓in_port可以對到另外一個port
利用match,設定好input_port,在用action指令要output的port,最後加到flow entry裡
def add_flow(self, datapath, priority, match, actions):
ofproto = datapath.ofproto
parser = datapath.ofproto_parserinst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]mod = parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,instructions=inst)
datapath.send_msg(mod)
這邊主要就是利用OFPFlowMod,存下mod,在送給switch告知要這麼做
到這邊就大公告成了,我們可以先開好mininet,在開ryu的app
sudo python sdn2_mininet.pyryu-manager sdn2_ryu_app.py
然後我們先試試pingall
mininet> pingall
*** Ping: testing ping reachability
h1 -> h2
h2 -> h1
*** Results: 0% dropped (2/2 received)
說明兩個都可以互相ping到
再來我們看一下ovs flow表的結果
mininet> s1 ovs-ofctl -O OpenFlow13 dump-flows “s1”
OFPST_FLOW reply (OF1.3) (xid=0x2):
cookie=0x0, duration=131.014s, table=0, n_packets=5, n_bytes=350, priority=1,in_port=1 actions=output:2
cookie=0x0, duration=131.014s, table=0, n_packets=5, n_bytes=350, priority=1,in_port=2 actions=output:1
告訴我們規則已經被寫入flow table了
以上就是sdn2 lab的過程,我覺得主要比較麻煩的就是看有哪些API可以call,然後沒有例子也不知道要怎樣去完成,所以這個lab會是一個很好入門的教成
以上程式碼皆在github
有任何疑問或是錯誤需要更正,都歡迎聯絡我,謝謝指教