simple_switch_13.py实现传统L2-switch功能
传统的简单二层交换机实现的功能
- 連接到連接埠的 host 之 MAC 位址,並記錄在 MAC 位址表當中。
- 對於已經記錄下來的 MAC 位址,若是收到送往該 MAC 位址的封包,則轉送該封包到相對應的連接埠。
- 對於未指定目標位址的封包,則執行 Flooding
进阶到SDN的OpenFlow交换机实现功能
- 對於接收到的封包進行修改或針對指定的連接埠進行轉送
- 對於接收到的封包進行轉送到 Controller 的動作(Packet-In)
- 對於接收到來自 Controller 的封包轉送到指定的連接埠(Packet-Out)
这里其实就是涉及到之前学的之前学的交换机接收到数据报有匹配和无匹配流表
的情况下的两种处理数据包形式,不过这里就是简单的mac_to_port的映射表。
- 如果是已經存在記錄中的 host:使用 Packet-Out 功能轉送至先前所對應的連接埠
- 如果是尚未存在記錄中的 host:使用Packet-Out 功能來達到 Flooding
图片理解
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
from ryu.lib.packet import ether_types
# 继承ryu.base.app_manager.RyuApp
# 基类ryu.base.app_manager.RyuAPP是所有开发APP必备继承的类,
# 可以理解成开发APP的环境,而且有了它都不用注册,非常方便
class SimpleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] # 指定OpenFlow 1.3版本
def __init__(self, *args, **kwargs):
super(SimpleSwitch13, self).__init__(*args, **kwargs)
self.mac_to_port = {}
# 定义MAC地址列表,这里的mac_to_port表就是对应的交换机二层通信查询表
# set_ev_cls指定事件类别得以接受事件消息和交换机状态作为参数
# set_ev_cls第一个参数表示事件发生时应该调用的函数,第二个参数告诉交换机只有在交换机握手完成之后,才可以被调用。
# ofp_event完成了事件的定义,从而我们可以在函数中注册handler,监听事件,并作出回应
# packet_in_handler方法用于处理packet_in事件。
# @set_ev_cls修饰符用于告知RYU,被修饰的函数应该被调用。
其中事件类别名称为ryu.controller.ofp_event.EventOFP+<OpenFlow消息名称>
例如:在 Packet-In 消息的状态下的事件名称为EventOFPPacketIn
对于交换机的状态来说,可指定以下中的一项
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
# ev.msg 是用来存储对应事件的 OpenFlow 消息类别实体
# msg.datapath是用来存储OpenFlow交换机ryu.controller.controller.Datapath 类别所对应的实体
datapath = ev.msg.datapath
ofproto = datapath.ofproto # ofproto表示使用的OpenFlow版本所对应的ryu.ofproto.ofproto_v1_3
parser = datapath.ofproto_parser # 和ofproto一样,有对应版本ryu.ofproto.ofproto_v1_3_parser,解析协议才能使用
OpenFlow 交換器的握手協議完成之後,新增 Table-miss Flow Entry 到 Flow table 中為接收 Packet-In 訊息做準備。
具體來說,接收到 Switch features( Features reply )訊息後就會新增 Table-miss Flow Entry。
openflow1.3版本开始就有table-miss flow entry,主要是为了第一次未能匹配流表的流(packet-in)发送给controller
# 下发table-miss流表项,让交换机对于不会处理的数据包通过packet-in消息上交给Ryu控制器!!!
# 匹配数据包
# 若数据包没有 match 任何一个普通 Flow Entry 时,则触发 Packet-In
match = parser.OFPMatch()
# 通过预留端口ofproto.OFPP_CONTROLLER,将packet-in消息发送给controller,并通过ofproto.OFPCML_NO_BUFFE指明Racket-in消息的原因是table miss
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
# 仔细看发送的调用函数的参数,第一个是端口,第二是bufferid,若不为空,则去指定缓存区去查找流表
# 执行 add_flow() 方法以发送 Flow Mod 消息
self.add_flow(datapath, 0, match, actions)
# priority = 0 ,优先级最低,为了所有流表都匹配不到的时候,才会发送到controller
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
# 新增流表项
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# Apply Actions 是用来设定那些必须立即执行的 action 所使用
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
# 通过 Flow Mod 消息将 Flow Entry 新增到 Flow table 中
if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst)
# 这里作者添加的if语句,表示流表下发缓存区id有无来区分下发,有缓存区id,到对应缓存区下发,无则自动分配,不传参buffer_id
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
# If you hit this you might want to increase
# the "miss_send_length" of your switch
if ev.msg.msg_len < ev.msg.total_len:
self.logger.debug("packet truncated: only %s of %s bytes",
ev.msg.msg_len, ev.msg.total_len)
# 送往 Controller 的封包可以僅只傳送 header 部分( Ethernet header ),剩下的則存在緩衝區間中以增加效率。
#但目前( 2014年1月 )Open vSwitch 存在臭蟲的關係,會將所有的封包都傳送,並不會只傳送 header。
#这条logger.debug日志是为了提示送往controller的packet_in包是截取header 部分,这里我是这么理解的,有错误请大家告知
# 为了接收处理未知目的地的数据包,需要执行Packet-In 事件管理
msg = ev.msg # 每一个事件类ev中都有msg成员,用于携带触发事件的数据包
datapath = msg.datapath # 已经格式化的msg其实就是一个packet_in报文,msg.datapath直接可以获得packet_in报文的datapath结构
# datapath用于描述一个交换网桥,也是和控制器通信的实体单元。
# datapath.send_msg()函数用于发送数据到指定datapath。
# 通过datapath.id可获得dpid数据。
ofproto = datapath.ofproto # datapath.ofproto对象是一个OpenFlow协议数据结构的对象,成员包含OpenFlow协议的数据结构,如动作类型OFPP_FLOOD
parser = datapath.ofproto_parser # datapath.ofp_parser则是一个按照OpenFlow解析的数据结构。
# 更新Mac地址表
in_port = msg.match['in_port']
pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
# pkt.get_protocols 传入的是 协议类 参数
# 得到协议的ethernet.ethernet类实例的协议列表,看源码知道了lib.packet.packet/ethernet
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
# ignore lldp packet
return
dst = eth.dst
src = eth.src
dpid = datapath.id
self.mac_to_port.setdefault(dpid, {})
# 指定交换机dpid,默认mac_to_port表为空
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
# 日志记录信息
# learn a mac address to avoid FLOOD next time.
self.mac_to_port[dpid][src] = in_port
# 判断转发的数据包的连接端口
# 目的 MAC 位址若存在于 MAC 地址表,则判断该连接端口号码为输出。
# 反之若不存在于 MAC 地址表则 OUTPUT action 类别的实体并生成 flooding( OFPP_FLOOD )给目的连接端口使用。
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
# 有目的地址寻找端口号,否则泛洪
else:
out_port = ofproto.OFPP_FLOOD
# 准备泛洪的packet_out指令给后面的if判断语句最后的else
actions = [parser.OFPActionOutput(out_port)]
# install a flow to avoid packet_in next time
if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src)
# verify if we have a valid buffer_id, if yes avoid to send both
# flow_mod & packet_out
if msg.buffer_id != ofproto.OFP_NO_BUFFER:
self.add_flow(datapath, 1, match, actions, msg.buffer_id)
return # 不用泛洪退出函数
else:
self.add_flow(datapath, 1, match, actions)
# 在 MAC 地址表中找寻目的 MAC 地址,若是有找到则发送 Packet-Out 讯息,并且转送数据包。
data = None
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
# 表示要泛洪发送Packet-Out,把本身的二进制数据取出来
data = msg.data
#需要Flooding 的actions和msg。buffer_id已经准备好了
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=data)
datapath.send_msg(out)
参考资料:
RYU入门教程
Ryubook 1.0 說明文件
https://blog.csdn.net/weixin_42094589/article/details/104160571