資料中心網路技術 project2 實作一簡單的網路系統
交大資料中心網路技術 project 2,在這個 project 將會學到如何使用 Mininet 和 SDN controller (RYU) 來實作一簡單的網路系統。
Demo日期: 3/22 19:45
介紹
SDN Switch
- Server1 要送封包給 Server2
- 首先,SDN switch 會檢查 flow table
- 接著,如果沒有符合的資訊在 flow table,則 switch 會將封包轉送給 controller,稱為 packet-in
- 最後,Controller 會決定 action,並送封包回去給 switch (packet-out)後在 flow table 新增項目。
Mininet
- Mininet 建立一真實的「虛擬網路」,執行真實的 kernel、switch和 application code。
- 它在 linux kernel 上運行一終端主機、交換器、路由器和鏈結的 collection。
- 交換器是 OpenFlow-enabled
Ryu
- 為 SDN controller
- Ryu 支援 OpenFlow 1.0~1.5
- Ryu 可以和 OpenStack 協同工作以進行雲端計算
- 由 Python 撰寫
- 參考書
如何執行
- 建立 VM,如何建立就不再贅述,這邊使用的是 Ubuntu 20.04
- 安裝 Mininet
- 安裝 Ryu
- 執行 Mininet 和 Ryu 共同協作一簡單的 SDN 網路系統
- 研讀 SDN controller 的 sample code (參考上面的參考書第二章)
安裝 Mininet
- 安裝 git需先將
1
sudo apt-get install -y git
https
改為git
1
git config --global url."https://".insteadOf git://
- Clone Mininet repo
1
git clone git://github.com/mininet/mininet
- 安裝 Mininet (約3~7分鐘)
1
2cd mininet/util
sudo ./install.sh -a - 測試 Mininet 是否安裝成功
1
sudo mn --test pingall
安裝 Ryu
- 安裝需要的套件
1
sudo apt-get install -y python3-pip
- 安裝 Ryu
1
2
3git clone git://github.com/osrg/ryu.git
cd ryu
sudo pip3 install . - 測試 Ryu
1
ryu-manager
執行 Mininet
- 建立 tree topology
1
sudo mn --controller=remote,ip=127.0.0.1 --topo tree,depth=3
執行 Ryu
- 找到 Ryu 安裝目錄
1
pip show ryu
- 前往 ryu 安裝目錄
- 執行 sample code: simple_switch_13.py
1
ryu-manager ryu/app/simple_switch_13.py
在 Mininet 執行 pingall
- 執行
pingall
simple_switch_13.py 如何運作
Switching Hub
Switching Hub 有很多功能,這邊測試的交換器只具有以下簡單功能:
- 知道連接到埠的主機 MAC 位址並將其保留在
MAC address table
中。 - 當接收到發送已知主機的封包時,將它們傳送到連接到主機的埠。
- 當接收到發送未知主機的封包時,執行
flooding
。
以下利用 Ryu 實作一 switch。
Switching Hub by OpenFlow
OpenFlow switches 可以透過接收來自 Ryu 等 OpenFlow controller 的指令來執行以下操作:
- 重寫接收封包的位址或從指定埠傳輸封包。
- 將接收到的封包傳送到控制器 (
Packet-In
)。 - 轉送控制器從指定埠轉送的封包 (
Packet-Out
)。
首先,需要使用 Packet-In
功能來學習 MAC 位址。 控制器可以使用 Packet-In
功能從交換機接收封包。 交換器分析接收到的封包以了解主機的MAC位址和連接埠的資訊。
學習後,交換器將接收到的封包轉送出去。 交換器會檢查封包的目的 MAC 地址是否屬於學習到的主機。根據檢查結果,交換器會執行以下處理。
- 如果是已經學習過的主機: 使用 Packet-Out 功能從連接埠傳送封包
- 如果是未知主機: 使用 Packet-Out 功能執行
flooding
。
實際操作前的觀念
初始化
- flow table 為空的初始狀態。
- 假設主機 A 連接到埠 1,主機 B 連接到埠 4,主機 C 連接到埠 3。
A -> B
- 當封包從主機 A 發送到主機 B 時,會發送 Packet-In 訊息,並通過埠 1 學習主機 A 的 MAC 位址。由於尚未找到主機 B 的埠,因此封包被
flooding
並由 主機 B 和主機 C 接收。 - Packet-In
1
2
3in-port: 1
eth-dst: Host B
eth-src: Host A - Packet-Out
1
action: OUTPUT:Flooding
- 當封包從主機 A 發送到主機 B 時,會發送 Packet-In 訊息,並通過埠 1 學習主機 A 的 MAC 位址。由於尚未找到主機 B 的埠,因此封包被
B -> A
- 當封包從主機 B -> A 時,會在 flow table 中增加一個 entry,並將封包傳送到埠 1。因此不會被主機 C 收到。
- Packet-In
1
2
3in-port: 4
eth-dst: Host A
eth-src: Host B - Packet-Out
1
action: OUTPUT:Port 1
A -> B
- 再一次 A-> B,會在 flow table 中增加一個 entry,並且封包也會傳送到埠 4。
- Packet-In
1
2
3in-port: 1
eth-dst: Host B
eth-src: Host A - Packet-Out
1
action: OUTPUT:Port 4
實際操作
利用 simple_switch_13.py 實作,尚有 simple_switch.py (OpenFlow 1.0) 和 simple_switch_12.py (OpenFlow 1.2),依據 OpenFlow 的版本決定,所以這邊是以 OpenFlow 1.3 實現。
1 | from ryu.base import app_manager |
- Class 定義和初始化
為了實作 Ryu
- 要繼承 ryu.base.app_manager.RyuApp。
- 設定
OFP_VERSIONS
指定為 OpenFlow 1.3。 - MAC address table mac_to_port 也要定義。
在 OpenFlow 中,定義 OpenFlow 交換器與控制器間通訊所需的握手過程。但由於 Ryu 的框架負責處理這些程序,因此無需了解。1
2
3
4
5
6
7
8
9
10
11# 繼承於 app_manager.RyuApp
class ExampleSwitch13(app_manager.RyuApp):
# 使用 OpenFlow 1.3
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
# 初始化設定
def __init__(self, *args, **kwargs):
super(ExampleSwitch13, self).__init__(*args, **kwargs)
# 定義 MAC address table
self.mac_to_port = {}
# ...
- 事件處理器(Event Handler)
- 使用 Ryu 時,當接收到 OpenFlow 訊息時,會產生與該訊息相對應的事件。Ryu App. 實現了一個與希望接收的訊息相對應的事件處理器。
- 事件處理器定義一個具有事件對象(event object)的函數,並使用
ryu.controller.handler.set_ev_cls
decorator 進行 decorate。(decorate 是什麼有待補充,我也不太了解細節) set_ev_cls
為指定事件類別和 OpenFlow 交換器的狀態。- 事件類別為
ryu.controller.ofp_event.EventOFP
+ <OpenFlow Message name>。 例如,在Packet-In
訊息的情況下,會變成EventOFPPacketIn
。Definition Explanation ryu.controller.handler.HANDSHAKE_DISPATCHER 交換 HELLO 訊息 ryu.controller.handler.CONFIG_DISPATCHER 等待接收 SwitchFeatures 訊息 ryu.controller.handler.MAIN_DISPATCHER 正常狀態 ryu.controller.handler.DEAD_DISPATCHER 取消連接
新增表格遺失(Table-miss)的 Flow entry
- 與 OpenFlow 交換器握手後,將表格遺失的 flow entry 新增到 flow table 中,以準備接收
Packet-In
訊息。 - 在接收到 Switch Features(Features Reply)訊息後,新增表格遺失的 flow entry。
1
2
3
4
5
6
7# 指定事件類別和 OpenFlow 交換器狀態
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# ... - 在
ev.msg
中,存儲了事件對應的 OpenFlow 訊息類別的實例。在這種情況下他是ryu.ofproto.ofproto_v1_3_parser.OFPSwitchFeatures
。 - 在
msg.datapath
中,存儲發出此訊息 OpenFlow 交換器對應的ryu.controller.controller.Datapath
類別的實例。 - Datapath 類執行重要的處理,例如與 OpenFlow 交換器的實際通訊以及與接收到的訊息對應的事件發布。
- Ryu 應用程序使用的主要屬性如下:
屬性名 解釋 id 連接的 OpenFlow switch ID (data path ID)。 ofproto 表示支持正在使用的 OpenFlow 版本的 ofproto 模組。 在 OpenFlow 1.3 形式的情況下,將遵守模組 ryu.ofproto.ofproto_v1_3
。ofproto_parser 同 ofproto,表示 ofproto_parser 模組。 在 OpenFlow 1.3 形式的情況下,將遵守模組 ryu.ofproto.ofproto_v1_3_parser
。 - Ryu 中使用 Datapath class 的主要方法如下:
send_msg(msg)
發送 OpenFlow 消息。 msg 是ryu.ofproto.ofproto_parser.MsgBase
的子類別,對應於發送 OpenFlow 訊息。
- 交換集線器本身並不特別使用接收到的 Switch Features 訊息。而是作為一個事件處理,以獲得新增 Table-miss flow entry 的時間。
1
2
3
4
5
6
7
8def switch_features_handler(self, ev):
# ...
# install the table-miss flow entry.
# 產生一空匹配以匹配封包,並用 OFPMatch 表示
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions) - Table-miss flow entry 有最低 (0) 優先權,並且該 entry 匹配所有封包。 在該 entry 的指令中,通過指定輸出到控制器埠的動作,如果接收到的封包與任何正常 flow entry 不匹配,則發出
Packet-In
。 - 產生一個空匹配(match)以匹配所有封包。匹配用
OFPMatch
類別表示。 - 接下來,產生 OUTPUT action class (
OFPActionOutput
) 的實例以傳送到控制器埠。控制器被指定為輸出目的地,並且OFPCML_NO_BUFFER
被指定為 max_len 以便將所有封包發送到控制器。 - 最後,為優先權指定為 0(最低)並執行
add_flow()
方法以發送 Flow Mod 訊息。add_flow()
方法在後面會解釋。
- 與 OpenFlow 交換器握手後,將表格遺失的 flow entry 新增到 flow table 中,以準備接收
Packet-in Massage
- 建立 Packet-In 事件處理器來處理未知目的地的封包事件
1
2
3
4
5
6
7
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# ... - 常用的 OFPPacketIn class 屬性:
屬性名 解釋 match ryu.ofproto.ofproto_v1_3_parser.OFPMatch
class 實例,其中設置了接收封包的 meta 訊息。data 代表接收到封包的二元資料 total_len 封包資料長度 buffer_id 當接收封包在 OpenFlow switch 中緩衝時,代表為其 ID; 若沒有緩衝,則設置 ryu.ofproto.ofproto_v1_3.OFP_NO_BUFFER
- 建立 Packet-In 事件處理器來處理未知目的地的封包事件
更新 MAC Address Table
1
2
3
4
5
6
7
8def _packet_in_handler(self, ev):
# ...
# get the received port number from packet_in message.
in_port = msg.match['in_port']
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
# learn a mac address to avoid FLOOD next time.
self.mac_to_port[dpid][src] = in_port
# ...- 從 OFPPacketIn match 取得接收埠(in_port),目的端 MAC 位址和發送端 MAC 位址是使用 Ryu 的封包庫從接收到的封包中的以太 header 中獲得。
- 依獲取的發送端 MAC 位址和接收埠,更新MAC address table。
- 為了支持多個 OpenFlow switches 的連線,MAC address table 用來管理每個 OpenFlow switch,並且用 data path ID 代表他們的身分。
Judging the Transfer Destination Port
- 當 MAC address table 中存在目的 MAC 位址時,使用對應的埠。如果未找到,則生成為輸出埠特定泛洪 (
OFPP_FLOOD
) 的 OUTPUT action class的實例。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def _packet_in_handler(self, ev):
# ...
# if the destination mac address is already learned,
# decide which port to output the packet, otherwise FLOOD.
# 如果 MAC address table 有就傳,沒有就 flooding。
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
out_port = ofproto.OFPP_FLOOD
# construct action list.
actions = [parser.OFPActionOutput(out_port)]
# install a flow to avoid packet_in next time.
if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
self.add_flow(datapath, 1, match, actions)
# ... - 如果找到目的 MAC 位址,則在 OpenFlow switch 的 flow table 中新增一個entry。
- 與新增 Table-miss flow entry 一樣,指定 match 和 action,並執行 add_flow() 以新增 flow entry。
- 與 Table-miss flow entry 不同,這次設置 match 的條件。這次交換集線器的實現,已經指定了接收埠 (in_port) 和目的 MAC 位址 (eth_dst)。例如,埠1 接收到發往主機 B 的封包是目標。
- 本次 flow entry 的優先權指定為 1,值越大優先權越高,因此此處新增的 flow entry 將在Table-miss flow entry 之前進行評估。
- 根據包含上述 action 的總結,將以下 flow entry 加到flow table 中。
- 封包 -> B (目的地 MAC 位址是 B) 接收從 埠4 到埠1。
- Hint: 在 OpenFlow 中,選項中規定了一個稱為 NORMAL 的邏輯輸出埠,當為輸出埠指定 NORMAL 時,交換器的 L2/L3 功能用於處理封包。代表通過指示將所有封包輸出到 NORMAL 埠,可以使交換器作為交換集線器運行。並用 OpenFlow 實現每個處理。
- 當 MAC address table 中存在目的 MAC 位址時,使用對應的埠。如果未找到,則生成為輸出埠特定泛洪 (
Adding Processing of Flow Entry
- Packet-In 處理程序的處理尚未完成,下面是新增 flow entry 的方法。
1
2
3
4
5
6
7
8def add_flow(self, datapath, priority, match, actions):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# construct flow_mod message and send it.
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
# ... - 對於 flow entry,設置表示目標封包條件的 match,以及表示對封包的操作、entry 優先級和有效時間的指令。
- 在交換集線器實現中,Apply Actions 用於指令設置,以便立即使用指定的 action。
- 最後,通過發出 Flow Mod 訊息將 entry 新增到 flow table 中。
1
2
3
4
5def add_flow(self, datapath, priority, match, actions):
# ...
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod) - Flow Mod 訊息對應的 class 是
OFPFlowMod
class。生成 OFPFlowMod class 的實例,並使用 Datapath.send_msg() 方法將消息發送到 OpenFlow 交換器。 - OFPFlowMod class 的建構子有很多參數。通常可以直接用預設值,括號內是預設值。
- datapath
- Packet-In 處理程序的處理尚未完成,下面是新增 flow entry 的方法。
Packet Transfer
- 回到 Packet-In 處理器,並解釋最後的處理。
- 無論是否從 MAC address table 中找到目的 MAC 位址,最後都會發出 Packet-Out 訊息並傳送接收到的封包。
1
2
3
4
5
6
7
8def _packet_in_handler(self, ev):
# ...
# construct packet_out message and send it.
out = parser.OFPPacketOut(datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=in_port, actions=actions,
data=msg.data)
datapath.send_msg(out)