本文所述的是規劃轉發路徑最重要的部分:控制規則。實現方式將通過一個例子來說明。首先,說明一下這個例子需求:
- 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),讓我們在加入規則時方便一些:
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)
dp:指定的 Switch
match:此規則的 Match 條件
inst:Match 規則後,將執行的動作
table:規則要放在哪一個 table 中
priority:此規則的優先權
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)
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
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#環境進行整理,以便於自己和他人查閱,如有侵權請告知刪除!