RYU控制規則

本文所述的是規劃轉發路徑最重要的部分:控制規則。實現方式將通過一個例子來說明。首先,說明一下這個例子需求:

  • Switch 中的主機可以在學習後,互相溝通
  • 主機脫離時,刪除有關此主機的規則

環境

設備

  • Switch:1 臺
  • Host:3 臺

連線情況

Host 1 < h1-eth0)---(s1-eth1 > Switch
Host 2 < h2-eth0)---(s1-eth2 > Switch
Host 3 < h3-eth0)---(s1-eth3 > Switch

程序說明

本程序有兩個重點,分別爲:
  • 新增、刪除規則
  • EventOFPPortStateChange 事件
新增、刪除是本文的重點,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,使用這兩個信息,我們將可以把脫離主機的規則進行刪除。

@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]

總結

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

完整代碼:

from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3

from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls

from ryu.lib.packet import packet
from ryu.lib.packet import ethernet

class control_flow (app_manager.RyuApp):
	OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
	def __init__(self, *args, **kwargs):
		super(control_flow, self).__init__(*args, **kwargs)
		self.switch_table = {}

	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)

	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)

	@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
	def switch_features_handler(self, ev):
		dp = ev.msg.datapath
		self.switch_table.setdefault(dp.id,{})

	@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)

	@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]


本文根據https://github.com/OSE-Lab/Learning-SDN/tree/master/Controller/Ryu/ControlFlow#環境進行整理,以便於自己和他人查閱,如有侵權請告知刪除!



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