Ryu入門:learning switch

我只想看他代碼裏對一些類、方法的註釋 五:實現整體程序運行了解《重點》這裏講得很詳細~
LearningSwitch中文詳解

SDN Datapath

數據平面 是由若干網元(Network Element)組成,每個網元包含一個或多個SDN數據路徑(SDN Datapath)。
SDN Datapath是邏輯上的網絡設備,負責轉發和處理數據無控制能力,
一個SDN DataPath包含控制數據平面接口(Control Data Plane Interface,CDPI)、代理、轉發引擎(Forwarding Engine)表和處理功能(Processing Function)
SDN數據面(轉發面)的關鍵技術:對數據面進行抽象建模。

OFPActionOutput

classryu.ofproto.ofproto_v1_3_parser.OFPActionOutput(port, max_len=65509, type_=None, len_=None)

這個動作輸出包到一個交換機端口

actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)] #流表項動作表示將數據包發送給控制器

將數據包發送出去,

  • 第一個參數OFPP_CONTROLLER是接收端口,把所有不知道如何處理的封包都送到 Controller
  • 第二個是數據包在交換機上緩存buffer_id,由於我們將數據包全部傳送到控制器,所以不在交換機上緩存
# enum ofp_port_no
OFPP_MAX = 0xffffff00
OFPP_IN_PORT = 0xfffffff8       # Send the packet out the input port. This
                                # virtual port must be explicitly used
                                # in order to send back out of the input
                                # port.
OFPP_TABLE = 0xfffffff9         # Perform actions in flow table.
                                # NB: This can only be the destination
                                # port for packet-out messages.
OFPP_NORMAL = 0xfffffffa        # Process with normal L2/L3 switching.
OFPP_FLOOD = 0xfffffffb         # All physical ports except input port and
                                # those disabled by STP.
OFPP_ALL = 0xfffffffc           # All physical ports except input port.
OFPP_CONTROLLER = 0xfffffffd    # Send to controller.  這是讓交換機將數據包發送給控制器《重點》
OFPP_LOCAL = 0xfffffffe         # Local openflow "port".
OFPP_ANY = 0xffffffff             # Not associated with a physical port.

OFPInstructionActions

       classryu.ofproto.ofproto_v1_3_parser.OFPInstructionActions(type_, actions=None, len_=None)

Actions instruction
This instruction writes/applies/clears the actions.

此指令寫入/應用/清除操作。就是說通過這個類實現我們對聲明的actions行爲進行應用(應用這個動作)、還是清除(不需要這個動作了)、還是寫入操作

  inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
                                             actions)]
  mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
                                match=match, instructions=inst)                                            
Attribute Description
type

One of following values.

OFPIT_WRITE_ACTIONS
OFPIT_APPLY_ACTIONS
OFPIT_CLEAR_ACTIONS
actions list of OpenFlow action class
learning switch 本質上就是一個存儲在controller應用程序中的全局字典,從mac地址映射到端口號
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 SimpleSwitch13(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super(SimpleSwitch13, self).__init__(*args, **kwargs)
        # 新增一個儲存 Host MAC 的資料結構,類別為 dict(字典)
        self.mac_to_port = {}

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        # 一開始 Switch 連上 Controller 時的初始設定 Function
        datapath = ev.msg.datapath          # 接收 OpenFlow 交換器實例
        ofproto = datapath.ofproto          # OpenFlow 交換器使用的 OF 協定版本
        parser = datapath.ofproto_parser    # 處理 OF 協定的 parser

        # 以下片段用於設定 Table-Miss FlowEntry
        # 首先新增一個空的 match,也就是能夠 match 任何封包的 match rule
        match = parser.OFPMatch()
        # 指定這一條 Table-Miss FlowEntry 的對應行為
        # 把所有不知道如何處理的封包都送到 Controller
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                          ofproto.OFPCML_NO_BUFFER)]
        # 把 Table-Miss FlowEntry 設定至 Switch,並指定優先權為 0 (最低)
        self.add_flow(datapath, 0, match, actions)

    def add_flow(self, datapath, priority, match, actions):
        # 取得與 Switch 使用的 IF 版本 對應的 OF 協定及 parser
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # Instruction 是定義當封包滿足 match 時,所要執行的動作
        # 因此把 action 以 OFPInstructionActions 包裝起來
        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
                                             actions)]

        # FlowMod Function 可以讓我們對 Switch 寫入由我們所定義的 Flow Entry
        mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
                                match=match, instructions=inst)
        # 把定義好的 FlowEntry 送給 Switch
        datapath.send_msg(mod)

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        # 收到來自 Switch 不知如何處理的封包(Match 到 Table-Miss FlowEntry)
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        # in_port 相當於封包從 Switch 的哪個 port 進到 Switch 中
        # 同時也代表 source Host MAC 要往 in_port 送,才能送達
        in_port = msg.match['in_port']

        pkt = packet.Packet(msg.data)
        eth = pkt.get_protocols(ethernet.ethernet)[0]

        dst = eth.dst            # 得到封包目的端 MAC address
        src = eth.src            # 得到封包來源端 MAC address

        dpid = datapath.id       # Switch 的 datapath id (獨一無二的 ID)
        
        # 如果 MAC 表內不曾儲存過這個 Switch 的 MAC,則幫他新增一個預設值
        # ex. mac_to_port = {'1': {'AA:BB:CC:DD:EE:FF': 2}}
        #     但是目前 dpid 為 2 不存在,執行後 mac_to_port 會變成
        #     mac_to_port = {'1': {'AA:BB:CC:DD:EE:FF': 2}, '2': {}}
        self.mac_to_port.setdefault(dpid, {})

        self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)

        # 我們擁有來源端 MAC 與 in_port 了,因此可以學習到 src MAC 要往 in_port 送
        self.mac_to_port[dpid][src] = in_port

        # 如果 目的端 MAC 在 mac_to_port 表中的話,就直接告訴 Switch 送到 out_port
        # 否則就請 Switch 用 Flooding 送出去
        if dst in self.mac_to_port[dpid]:
            out_port = self.mac_to_port[dpid][dst]
        else:
            out_port = ofproto.OFPP_FLOOD

        # 把剛剛的 out_port 作成這次封包的處理動作
        actions = [parser.OFPActionOutput(out_port)]

        # 如果沒有讓 switch flooding,表示目的端 mac 有學習過
        # 因此使用 add_flow 讓 Switch 新增 FlowEntry 學習此筆規則
        if out_port != ofproto.OFPP_FLOOD:
            match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
            self.add_flow(datapath, 1, match, actions)

        data = None
        if msg.buffer_id == ofproto.OFP_NO_BUFFER:
            data = msg.data

        # 把要 Switch 執行的動作包裝成 Packet_out,並讓 Switch 執行動作
        out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
                                  in_port=in_port, actions=actions, data=data)
        datapath.send_msg(out)

ControlFlow
兩個重點

  • 新增、刪除規則
  • EventOFPPortStateChange 事件

新增

要對 Switch 新增規則,是需要由 Controller(Ryu)透過傳送OFPFlowMod給 Switch,Switch 收到此訊息後,則將對應的規則加入。在此建立一個 Function(add_flow),讓我們在加入規則時方便一些:

def add_flow(self, dp, match=None, inst=[], table=0, priority=32768):
	ofp = dp.ofproto
	ofp_parser = dp.ofproto_parser

	buffer_id = ofp.OFP_NO_BUFFER

	mod = ofp_parser.OFPFlowMod(
		datapath=dp, cookie=0, cookie_mask=0, table_id=table,
		command=ofp.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
		priority=priority, buffer_id=buffer_id,
		out_port=ofp.OFPP_ANY, out_group=ofp.OFPG_ANY,
		flags=0, match=match, instructions=inst)

	dp.send_msg(mod)

可以發現 OFPFlowMod 可以使用參數很多,但常需要更動的不多,也因此將常需要更動的參數當作add_flow的參數。分別為:
dp:指定的 Switch
match:此規則的 Match 條件
inst:Match 規則後,將執行的動作
table:規則要放在哪一個 table 中
priority:此規則的優先權

刪除

刪除跟新增的方式其實是很像的,最大的差別就在於command此參數的給定。在刪除中,給定的是ofp.OFPFC_DELETE,新增則是ofp.OFPFC_ADD。也因為只是刪除,所以需要的參數也會比較少。在此我們可以透過指定 Match 條件及所在的 Table 進行刪除:

def del_flow(self, dp, match, table):
	ofp = dp.ofproto
	ofp_parser = dp.ofproto_parser

	mod = ofp_parser.OFPFlowMod(datapath=dp,
		command=ofp.OFPFC_DELETE,
		out_port=ofp.OFPP_ANY,
		out_group=ofp.OFPG_ANY,
		match=match,
		table_id=table)

	dp.send_msg(mod)

學習(Packet In)

學習也就是開始新增規則的部分。

@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
	msg = ev.msg
	dp = msg.datapath
	ofp = dp.ofproto
	ofp_parser = dp.ofproto_parser

	port = msg.match['in_port']

	## Get the packet and parses it
	pkt = packet.Packet(data=msg.data)
	# ethernet
	pkt_ethernet = pkt.get_protocol(ethernet.ethernet)

	if not pkt_ethernet:
		return

	# Filters LLDP packet
	if pkt_ethernet.ethertype == 35020:
		return		
	
	match = ofp_parser.OFPMatch(eth_dst=pkt_ethernet.src)
	intstruction_action =
		ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS,
			[ofp_parser.OFPActionOutput(port)])
	inst = [intstruction_action]
	self.add_flow(dp, match=match, inst=inst, table=0)
	self.switch_table[dp.id][pkt_ethernet.src] = port

	actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
	out =ofp_parser.OFPPacketOut(datapath=dp, buffer_id=msg.buffer_id, in_port=port, actions=actions)
		
	dp.send_msg(out)

在此第一步就是把封包的資訊(msg.data)解析出來,才能知道該怎麼下規則。

pkt = packet.Packet(data=msg.data)
pkt_ethernet = pkt.get_protocol(ethernet.ethernet)

建立規則的 Match 條件及 Action。所建立的規則為:

  • Match:封包的 MAC Destination 為現在接收到的封包的 MAC Source
  • Action:傳往此封包的來源 port 上
match = ofp_parser.OFPMatch(eth_dst=pkt_ethernet.src)
	intstruction_action =
		ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS,
			[ofp_parser.OFPActionOutput(port)])
	inst = [intstruction_action]

此目的在於,讓我們對現有的主機狀況進行學習,往後如果有要傳往此主機的封包,會有規則可以直接對應。

接下來透過我們寫好的add_flow將規則加入,並將主機資訊記錄在 Controller 的self.switch_table,供之後規劃規則使用:

self.add_flow(dp, match=match, inst=inst, table=0)
self.switch_table[dp.id][pkt_ethernet.src] = port

也因為會引起 Packet In 事件的封包,都是沒有對應到規則的封包,所以我們用最傳統的方式去處理,也就是 Flood:

actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
out =ofp_parser.OFPPacketOut(datapath=dp, buffer_id=msg.buffer_id, in_port=port, actions=actions)
	
dp.send_msg(out)

刪除脫離主機(Port State Change)

如果主機已經脫離 Switch 了,相關的規則勢必要進行刪除,要偵測主機是否脫離,可以透過EventOFPPortStateChange此事件進行。它將會回傳對應的 Datapath(Switch)及狀態改變的 port,使用這兩個資訊,我們將可以把脫離主機的規則進行刪除。
通過EventOFPPortStateChange進行檢測主機是否脫離

@set_ev_cls(ofp_event.EventOFPPortStateChange, MAIN_DISPATCHER)
def port_state_change_handler(self, ev):
	dp = ev.datapath
	ofp = dp.ofproto
	ofp_parser = dp.ofproto_parser
	change_port = ev.port_no

	del_mac = None

	for host in self.switch_table[dp.id]:
		if self.switch_table[dp.id][host] == change_port:
			del_match = ofp_parser.OFPMatch(eth_dst=host)
			self.del_flow(dp=dp, match=del_match, table=0)
			break

	if del_mac != None:
		del self.switch_table[dp.id][del_mac]

首先,取得對應的 Datapath 及 port:

dp = ev.datapath
	ofp = dp.ofproto
	ofp_parser = dp.ofproto_parser
	change_port = ev.port_no

接下來,搜尋在學習時記錄下來的 Switch 資訊(self.switch_table),查看此 port 上是否有對應的主機,如果有則使用del_flow將對應的規則刪除:

del_mac = None

for host in self.switch_table[dp.id]:
	if self.switch_table[dp.id][host] == change_port:
		del_match = ofp_parser.OFPMatch(eth_dst=host)
		self.del_flow(dp=dp, match=del_match, table=0)
		break

if del_mac != None:
	del self.switch_table[dp.id][del_mac]

總結
瞭解以上運作模式後,就可以知道如何新增、刪除。但要控制規則,另一個關鍵其實在於事件的搭配,如「在什麼情況下,要做什麼事」。有了以上的基本知識後,就可以試著實作自己的轉送邏輯囉!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章