本文所述的是规划转发路径最重要的部分:控制规则。实现方式将通过一个例子来说明。首先,说明一下这个例子需求:
- 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 事件
新增
要对 Switch 新增规则,是需要由 Controller(Ryu)透过传送OFPFlowMod给 Switch,Switch 收到此消息后,则将对应的规则加入。在此建立一个 Function(add_flow),让我们在加入规则时方便一些:
要对 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:此规则的优先权
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:
首先,取得对应的 Datapath 及 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)
删除脱离主机(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#環境进行整理,以便于自己和他人查阅,如有侵权请告知删除!