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#环境进行整理,以便于自己和他人查阅,如有侵权请告知删除!



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