資料中心網路技術 project3 在 Mininet 中建立拓樸並使用 SDN 控制器監視系統
交大資料中心網路技術 project 3,在這個 project 將會學到如何使用 Mininet 建立拓樸並使用 SDN controller 監視系統。
Demo日期 : 4/21 16:00
Mininet 介紹
基本指令
$
在 shell 中執行 Linux 指令
mininet>
在 Mininet’s CLI 中執行 Mininet 指令
#
在 root shell 中執行 Linux 指令
顯示 Mininet 參數選項
使用最小拓樸進入 Mininet CLI
mininet 預設拓樸 mininal
,包含一個 switch、一個 controller 和 兩個主機
顯示 Mininet CLI commands
顯示 nodes
顯示鏈結
印出所有 nodes 的資訊
對 host1 下 ifonfig -a 指令
測試兩個hosts的連線
測試所有連線
執行一簡單的 web server and client
1 2 3 4 mininet> h1 python -m SimpleHTTPServer 80 & mininet> h2 wget -O - h1 ... mininet> h1 kill %python
離開 CLI
CleanUp,若有 carsh 則執行
進階選項
--custom
: 使用 custom topology
--topo
: 使用在腳本中的字典 “topos” 的 “mytopo”
--link=tc
: 使用 traffic control link
--controller
: 使用外部 controller 控制1 sudo mn --topo mytopo --custom ~/mininet/custom/xxxscript.py --controller remote --switch default,protocols=OpenFlow13 --link =tc
執行測試
在 shell 執行 pingall1 $ sudo mn --test pingair
測試 h1/h2 間流量效能
改變拓樸大小和型態
使用 --topo
這個參數
single 是單個 switch 連接多台 host
linear 是一台 host 接一台 switch,switch 再串成線狀1 2 $ sudo mn --test pingall --topo single,3 $ sudo mn --test pingall --topo linear,4
Link variations
Mininet 2.0 後允許對 link 下參數
假設將頻寬設為 10 Mps,每條 link 有 delay 10 ms,在測試中可見 RTT 約為 40 ms1 2 3 $ sudo mn --link tc,bw=10,delay=10ms mininet> iperf mininet> h1 ping -c10 h2
步驟
大綱:
在 mininet 中建立一個特定的拓樸網路系統
修改 SDN controller simple_switch_13.py
程式碼
使控制器可以監控交換器的流量
並顯示交換器的 L2 address table
在 mininet 中建立拓樸 參考 mininet 中一簡單 script: ~/mininet/custom/topo-2sw-2host.py
。
建立拓樸指令 1 sudo mn --topo mytopo --custom ~/mininet/custom/yourscript.py --controller remote --switch default,protocols=OpenFlow13 -- link =tc
--custom
: 使用 custom topology
--topo
: 使用在腳本中的字典 “topos” 的 “mytopo”
--link=tc
: 使用 traffic control link
--controller
: 使用外部 controller 控制
~/mininet/custom/topo-2sw-2host.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from mininet.topo import Topoclass MyTopo ( Topo ): "Simple topology example." def build ( self ): "Create custom topo." leftHost = self.addHost( 'h1' ) rightHost = self.addHost( 'h2' ) leftSwitch = self.addSwitch( 's3' ) rightSwitch = self.addSwitch( 's4' ) self.addLink( leftHost, leftSwitch ) self.addLink( leftSwitch, rightSwitch ) self.addLink( rightSwitch, rightHost ) topos = { 'mytopo' : ( lambda : MyTopo() ) }
建立一拓樸
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class MyTopo ( Topo ): "Simple topology example." def build ( self ): "Create custom topo." myHost = [] for h in range (6 ): myHost.append(self.addHost('h%s' % (h+1 ))) mySwitch = [] for h in range (4 ): mySwitch.append(self.addSwitch('s%s' % (h+1 ))) self.addLink(myHost[0 ], mySwitch[0 ], bw=100 ) self.addLink(myHost[1 ], mySwitch[0 ], bw=100 ) self.addLink(myHost[2 ], mySwitch[1 ], bw=100 ) self.addLink(myHost[3 ], mySwitch[2 ], bw=100 ) self.addLink(myHost[4 ], mySwitch[3 ], bw=100 ) self.addLink(myHost[5 ], mySwitch[3 ], bw=100 ) self.addLink(mySwitch[0 ], mySwitch[1 ], bw=1000 , loss=5 ) self.addLink(mySwitch[1 ], mySwitch[2 ], bw=1000 , loss=5 ) self.addLink(mySwitch[2 ], mySwitch[3 ], bw=1000 , loss=5 ) topos = { 'mytopo' : ( lambda : MyTopo() ) }
修改 SDN controller code
建立一 thread 來每五秒監視交換器流量 參考 Chap.3 of Ryubook
使用 OFPPortStatsRequest()
、OFPPortStatsReply()
來取得交換器埠的資訊 參考 Chap.3 of Ryubook or the link below
目標
使用 OFPPortStatsRequest()、OFPPortStatsReply() 得到交換器 port 資訊
監控資訊
Switch ID
TX 和 RX 封包資訊
交換器 MAC address table
每五秒將結果印出來
使用上面所規定的拓樸(4sw_6hosts)
實作一流量監視器 網路是許多服務和業務的基礎設施,因此必須保持正常穩定運行。當網路發生錯誤時,必須查明原因並迅速恢復運行。為了檢測錯誤並找出原因,有必要定期了解網路狀態。
例如,假設某個網路設備的某個 port 的流量指示一個非常高的值,如果沒有連續測量該 port 的流量,則無法確定是異常狀態還是通常是這種狀態以及何時變為這種狀態,出於這個原因,持續監控網路的健康狀況對於使用該網次的服務或企業的持續和安全運行至關重要。當然,簡單地監控流量資訊並不能提供完美的保證,但本節將介紹如何使用 OpenFlow 獲取交換器的統計信息。
Fixed-Cycle Processing 在交換集線器處理的同時,創建一個 thread 定期向 OpenFlow 交換器發出請求以得到統計資訊。
1 2 3 4 5 6 class SimpleMonitor13 (simple_switch_13.SimpleSwitch13): def __init__ (self, *args, **kwargs ): super (SimpleMonitor13, self).__init__(*args, **kwargs) self.datapaths = {} self.monitor_thread = hub.spawn(self._monitor)
ryu.lib.hub
中有一些 eventlet wrappers 和基本類別實現。在此使用 hub.spawn()
,用於建立 thread。實際上建立的 thread 是一個 eventlet green thread。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @set_ev_cls(ofp_event.EventOFPStateChange, [MAIN_DISPATCHER, DEAD_DISPATCHER] ) def _state_change_handler (self, ev ): datapath = ev.datapath if ev.state == MAIN_DISPATCHER: if datapath.id not in self.datapaths: self.logger.debug('register datapath: %016x' , datapath.id ) self.datapaths[datapath.id ] = datapath elif ev.state == DEAD_DISPATCHER: if datapath.id in self.datapaths: self.logger.debug('unregister datapath: %016x' , datapath.id ) del self.datapaths[datapath.id ] def _monitor (self ): while True : for dp in self.datapaths.values(): self._request_stats(dp) hub.sleep(10 )
為了確保連接的交換器被監控,EventOFPStateChange
事件用於檢測連接和斷開連接。此事件由 Ryu 框架發出,在 Datapath 狀態更改時發出。 因此當 Datapath 狀態變為 MAIN_DISPATCHER
時,該交換器被註冊為監視目標,當它變為 DEAD_DISPATCHER
時,該註冊被刪除。
1 2 3 4 5 6 7 8 9 10 def _request_stats (self, datapath ): self.logger.debug('send stats request: %016x' , datapath.id ) ofproto = datapath.ofproto parser = datapath.ofproto_parser req = parser.OFPFlowStatsRequest(datapath) datapath.send_msg(req) req = parser.OFPPortStatsRequest(datapath, 0 , ofproto.OFPP_ANY) datapath.send_msg(req)
透過定期呼叫 _request_stats()
,向交換器發出 OFPFlowStatsRequest
和 OFPPortStatsRequest
。
OFPFlowStatsRequest
請求交換器提供與 flow entry 相關的統計資訊。請求的目標 flow entry 可以通過 table ID、output port、cookie 值和 match 等條件來縮小範圍,但這裡所有 entry 都受請求的約束。
OFPPortStatsRequest
請求交換器提供 port 相關的統計資訊。可以指定所需的 port number 以從中獲取資訊。在這裡,OFPP_ANY
被指定為從所有 port 請求資訊。
FlowStats 為了從交換器接收回應,要建立一個接收 FlowStatsReply
消息的事件處理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER ) def _flow_stats_reply_handler (self, ev ): body = ev.msg.body self.logger.info('datapath ' 'in-port eth-dst ' 'out-port packets bytes' ) self.logger.info('---------------- ' '-------- ----------------- ' '-------- -------- --------' ) for stat in sorted ([flow for flow in body if flow.priority == 1 ], key=lambda flow: (flow.match['in_port' ], flow.match['eth_dst' ])): self.logger.info('%016x %8x %17s %8x %8d %8d' , ev.msg.datapath.id , stat.match['in_port' ], stat.match['eth_dst' ], stat.instructions[0 ].actions[0 ].port, stat.packet_count, stat.byte_count)
OPFFlowStatsReply
類別的屬性 body
是 OFPFlowStats
的列表,存儲了每個 flow entry 的統計資訊,受 FlowStatsRequest
的約束。
除了優先權為 0 的 Table-miss flow 外,所有 flow entry 都被選中。按照接收到的 port 和目的 MAC 位址排序,輸出與各個 flow entry match 的封包數和位元組數。
此處,只將部分值輸出到日誌中,但為了持續收集和分析資訊,可能需要與外部程序連動。在這種情況下,可以將 OFPFlowStatsReply
的內容轉換為 JSON 格式。
如下:
1 2 3 4 import jsonself.logger.info('%s' , json.dumps(ev.msg.to_jsondict(), ensure_ascii=True , indent=3 , sort_keys=True ))
PortStats 為了從交換器接收回應,要建立一個接收 PortStatsReply
消息的事件處理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER ) def _port_stats_reply_handler (self, ev ): body = ev.msg.body self.logger.info('datapath port ' 'rx-pkts rx-bytes rx-error ' 'tx-pkts tx-bytes tx-error' ) self.logger.info('---------------- -------- ' '-------- -------- -------- ' '-------- -------- --------' ) for stat in sorted (body, key=attrgetter('port_no' )): self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d' , ev.msg.datapath.id , stat.port_no, stat.rx_packets, stat.rx_bytes, stat.rx_errors, stat.tx_packets, stat.tx_bytes, stat.tx_errors)
OPFPortStatsReply
類別的屬性 body 是 OFPPortStats
的列表。
OFPPortStats
分別儲存 port number、發送/接收封包 count、byte count、drop count、error count、frame error count、overrun count、CRC error count 和 collision count 等統計資訊。
這裡,按照 port number 排序,輸出接收封包計數、接收字元組計數、接收錯誤計數、發送封包計數、發送字元組計數和發送錯誤計數。
實作監視器,修改 simple_monitor_13.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 class SwitchMonitor13 (simple_switch_13.SimpleSwitch13): def __init__ (self, *args, **kwargs ): super (SwitchMonitor13, self).__init__(*args, **kwargs) self.datapaths = {} self.monitor_thread = hub.spawn(self._monitor) @set_ev_cls(ofp_event.EventOFPStateChange, [MAIN_DISPATCHER, DEAD_DISPATCHER] ) def _state_change_handler (self, ev ): datapath = ev.datapath if ev.state == MAIN_DISPATCHER: if datapath.id not in self.datapaths: self.logger.debug('register datapath: %016x' , datapath.id ) self.datapaths[datapath.id ] = datapath elif ev.state == DEAD_DISPATCHER: if datapath.id in self.datapaths: self.logger.debug('unregister datapath: %016x' , datapath.id ) del self.datapaths[datapath.id ] def _monitor (self ): count = 0 while True : for dp in self.datapaths.values(): self._request_stats(dp) self.logger.info(f'Print the address table of all switches {count} times' ) count = count + 1 hub.sleep(5 ) def _request_stats (self, datapath ): self.logger.debug('send stats request: %016x' , datapath.id ) ofproto = datapath.ofproto parser = datapath.ofproto_parser req_port = parser.OFPPortStatsRequest(datapath, 0 , ofproto.OFPP_ANY) datapath.send_msg(req_port) req_flow = parser.OFPFlowStatsRequest(datapath) datapath.send_msg(req_flow) @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER ) def _port_stats_reply_handler (self, ev ): body = ev.msg.body self.logger.info('Switch ID: %d' , ev.msg.datapath.id ) self.logger.info('Port No. Tx-Bytes Rx-Bytes' ) self.logger.info('-------- -------- --------' ) for stat in sorted (body, key=attrgetter('port_no' )): self.logger.info('%8x %8d %8d' , stat.port_no, stat.tx_bytes, stat.rx_bytes) @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER ) def _flow_stats_reply_handler (self, ev ): body = ev.msg.body self.logger.info('MAC Address Table Port No.' ) self.logger.info('----------------- --------' ) for stat in sorted ([flow for flow in body if flow.priority == 1 ], key=lambda flow: (flow.match['in_port' ], flow.match['eth_dst' ])): self.logger.info('%17s %8x' , stat.match['eth_dst' ], stat.match['in_port' ]) self.logger.info('***************************\n' )
結果
為了方便執行,使用 makefile 更加快速,相關教學可以參考連結
1 2 3 all: xterm -e "ryu-manager switchMonitor13.py" & sudo mn --topo mytopo --custom ./topo-4sw-6host.py --link tc
利用 mininet 開啟一拓樸
1 $ sudo mn --topo mytopo --custom ./topo-4sw-6host.py --link tc
利用 ryu 進行控制
1 $ ryu-manager switchMonitor13.py