我只想看他代碼裏對一些類、方法的註釋 五:實現整體程序運行了解《重點》這裏講得很詳細~
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 |
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]
總結
瞭解以上運作模式後,就可以知道如何新增、刪除。但要控制規則,另一個關鍵其實在於事件的搭配,如「在什麼情況下,要做什麼事」。有了以上的基本知識後,就可以試著實作自己的轉送邏輯囉!