0%

[資料中心網路技術] Project2 實作一簡單的網路系統

資料中心網路技術 project2 實作一簡單的網路系統


交大資料中心網路技術 project 2,在這個 project 將會學到如何使用 Mininet 和 SDN controller (RYU) 來實作一簡單的網路系統。

Demo日期: 3/22 19:45

介紹


SDN Switch

SDN-switch
flow-table

  • 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

minet

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
    2
    cd mininet/util
    sudo ./install.sh -a
  • 測試 Mininet 是否安裝成功
    1
    sudo mn --test pingall
    pignall

安裝 Ryu

  • 安裝需要的套件
    1
    sudo apt-get install -y python3-pip
  • 安裝 Ryu
    1
    2
    3
    git clone git://github.com/osrg/ryu.git
    cd ryu
    sudo pip3 install .
  • 測試 Ryu
    1
    ryu-manager
    ryu-manager

執行 Mininet

  • 建立 tree topology
    1
    sudo mn --controller=remote,ip=127.0.0.1 --topo tree,depth=3
    tree_topology

執行 Ryu

  • 找到 Ryu 安裝目錄
    1
    pip show ryu
    show_ryu
  • 前往 ryu 安裝目錄
    show_ryu2
  • 執行 sample code: simple_switch_13.py
    1
    ryu-manager ryu/app/simple_switch_13.py
    simple_switch_13

在 Mininet 執行 pingall

  • 執行 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。
    • initial
  • A -> B

    • 當封包從主機 A 發送到主機 B 時,會發送 Packet-In 訊息,並通過埠 1 學習主機 A 的 MAC 位址。由於尚未找到主機 B 的埠,因此封包被 flooding 並由 主機 B 和主機 C 接收。
    • flooding
    • Packet-In
      1
      2
      3
      in-port: 1
      eth-dst: Host B
      eth-src: Host A
    • Packet-Out
      1
      action: OUTPUT:Flooding
  • B -> A

    • 當封包從主機 B -> A 時,會在 flow table 中增加一個 entry,並將封包傳送到埠 1。因此不會被主機 C 收到。
    • B->A
    • Packet-In
      1
      2
      3
      in-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。
    • again
    • Packet-In
      1
      2
      3
      in-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
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet

class ExampleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

def __init__(self, *args, **kwargs):
super(ExampleSwitch13, self).__init__(*args, **kwargs)
# initialize mac address table.
self.mac_to_port = {}

@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

# install the table-miss flow entry.
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]

self.add_flow(datapath, 0, match, actions)

def 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)]
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod)

@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

# get Datapath ID to identify OpenFlow switches.
dpid = datapath.id
self.mac_to_port.setdefault(dpid, {})

# analyse the received packets using the packet library.
pkt = packet.Packet(msg.data)
eth_pkt = pkt.get_protocol(ethernet.ethernet)
dst = eth_pkt.dst
src = eth_pkt.src

# 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

# if the destination mac address is already learned,
# decide which port to output the packet, otherwise FLOOD.
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)

# 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)
  1. 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 = {}
    # ...
  1. 事件處理器(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 交換器狀態
      @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
      # ...
    • 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
      8
      def 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() 方法在後面會解釋。
  • Packet-in Massage

    • 建立 Packet-In 事件處理器來處理未知目的地的封包事件
      1
      2
      3
      4
      5
      6
      7
      @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
      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
  • 更新 MAC Address Table

    1
    2
    3
    4
    5
    6
    7
    8
    def _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
      16
      def _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 實現每個處理。
  • Adding Processing of Flow Entry

    • Packet-In 處理程序的處理尚未完成,下面是新增 flow entry 的方法。
      1
      2
      3
      4
      5
      6
      7
      8
      def 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
      5
      def 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 Transfer

    • 回到 Packet-In 處理器,並解釋最後的處理。
    • 無論是否從 MAC address table 中找到目的 MAC 位址,最後都會發出 Packet-Out 訊息並傳送接收到的封包。
      1
      2
      3
      4
      5
      6
      7
      8
      def _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)