本文主要参照官方文档和查询到的博客进行记录,用于笔记学习
推荐阅读
欢迎来到 RYU 网络操作系统 (NOS) — Ryu 4.34 文档
下面是实例l3switch.py的代码
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
from ryu.lib.packet import ipv4
#继承自ryuapp的一个子类
class l3switch(app_manager.RyuApp):
#指定OpenFlow版本
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(l3switch, self).__init__(*args, **kwargs)
self.mac_to_port = {}
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# install table-miss flow entry
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst)
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)
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
in_port = msg.match['in_port']
pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
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, {})
#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
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
out_port = ofproto.OFPP_FLOOD
actions = [parser.OFPActionOutput(out_port)]
# install a flow to avoid packet_in next time
if out_port != ofproto.OFPP_FLOOD:
# check IP Protocol and create a match for IP
if eth.ethertype == ether_types.ETH_TYPE_IP:
ip = pkt.get_protocol(ipv4.ipv4)
srcip = ip.src
dstip = ip.dst
match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP,
ipv4_src=srcip,
ipv4_dst=dstip
)
self.logger.info("packet from %s to %s", srcip, dstip)
# 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)
data = None
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
data = msg.data
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=data)
datapath.send_msg(out)
重要的函数及功能
在上面的示例代码中主要有以下几个重要的函数或字段
set_ev_cls(对数据状态的处理)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
新方法“packet_in_handler”被添加到 L3Switch 类中。这是 当 Ryu 收到 OpenFlow packet_in 消息时调用。诀窍是 “set_ev_cls”装饰器。这位装饰师告诉Ryu何时装饰 应该调用函数。
装饰器的第一个参数指示此事件的类型应该调用函数。正如你所料,每次 Ryu 得到 packet_in消息中,将调用此函数。
第二个参数指示交换机的状态。你可能想在Ryu谈判之前忽略packet_in消息切换完成。使用“MAIN_DISPATCHER”作为第二个参数表示仅在协商后调用此函数完成。
交换机的状态:
HANDSHAKE_DISPATCHER | 发送和等待问候消息 |
CONFIG_DISPATCHER | 版本协商并发送功能请求消息 |
MAIN_DISPATCHER | 切换功能消息 接收和发送设置配置 消息 |
DEAD_DISPATCHER | 断开与对等方的连接。或 由于某些原因断开连接 不可恢复的错误。 |
datapath(交换机实例)
理解为一个交换机的实例;ofproto,该交换机实例的协议;parser,该交换机的分析器;match,OpenFlow的匹配器的实例(目前还不清楚);
datapath
为 switch 的一个实体,我们透过取出 ev.msg
中的 switch 实体进行操作。在这里我们可以见到OFPMatch()
里是空的,而且是在最开始 SwitchFeatures
封包到达时就立刻增加的 flow entry,所以不难猜测此为 OpenFlow 协定中的Table-miss Flow Entry
datapath具有的属性信息(在官方文档中):
Attribute | Description |
id | 64 位 OpenFlow 数据路径 ID。 仅适用于 ryu.controller.handler.MAIN_DISPATCHER 阶段。 |
ofproto | 导出OpenFlow的模块 定义,主要是常量出现 在规范中,对于 协商的OpenFlow版本。为 例如,ryu.ofproto.ofproto_v1_0 开放流 1.0. |
ofproto_parser | 导出开放流线的模块 消息编码器和解码器,用于 协商的OpenFlow版本。 例如 ryu.ofproto.ofproto_v1_0_parser 适用于 OpenFlow 1.0。 |
ofproto_parser.OFPxxxx(datapath,...) | 准备 OpenFlow 的可调用对象 给定开关的消息。它可以 稍后与Datapath.send_msg一起发送。 xxxx 是消息的名称。为 示例 OFPFlowMod for flow-mod 消息。争论取决于 消息。 |
set_xid(self, msg) | 生成一个 OpenFlow XID 并放置它 在味精中。 |
send_msg(self, msg) | Queue an OpenFlow message to send to the corresponding switch. If msg.xid is None, set_xid is automatically called on the message before queueing. |
send_packet_out | 荒废的 |
send_flow_mod | 荒废的 |
send_flow_del | 荒废的 |
send_delete_all_flows | 荒废的 |
send_barrier | 将 OpenFlow 屏障消息排队到 发送到交换机。 |
send_nxt_set_flow_format | 荒废的 |
is_reserved_port | 荒废的 |
add_flow,添加流表记录(添加匹配规则)
add_flow(self, datapath, priority, match, actions, buffer_id=None)
datapath,交换机实例
priority,该流表规则的优先级
match,匹配器
actions,满足匹配器的条件后执行的动作
buffer_id,缓冲区的id,默认为0
下面实现了一个简单的案例:
s1和s2对流量按照一定的概率进行负载,s3为备用链路。注意,s5和s4均使用1号端口连接s1,对s2、s3也一样。
对于对端口的组处理,需要定义权重(或者相对概率),监听端口和组,以及相应的动作,放在buckets中。
如何对交换机实例进行保存?在交换机与控制器进行协商阶段将交换机id和交换机实例作为键值对存储在字典中(从别的博主的博客学习到的,ryu实例---ECMP的rr(轮询)算法实现_rr轮询算法_楊木木8023的博客-CSDN博客)。
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
# h1
#
# s4
# 1 2 3 s4端口
# 1 1 1 s1、s2、s3端口
# s1 s2 s3
# 2 2 2 s1、s2、s3端口
# 1 2 3 s5端口
# s5
#
# h2
class l3switch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(l3switch, self).__init__(*args, **kwargs)
self.mac_to_port={}
self.lb_port=[1, 2]
self.datapaths = {}
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod)
def send_group_mod(self, datapath, out1, out2, choice):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
LB_WEIGHT1 = 30
LB_WEIGHT2 = 70
watch_port = ofproto_v1_3.OFPP_ANY
watch_group = ofproto_v1_3.OFPQ_ALL
actions1 = [parser.OFPActionOutput(out1)]
actions2 = [parser.OFPActionOutput(out2)]
buckets = [parser.OFPBucket(LB_WEIGHT1, watch_port, watch_group, actions=actions1),
parser.OFPBucket(LB_WEIGHT2, watch_port, watch_group, actions=actions2)]
if choice == 'add':
req = parser.OFPGroupMod(datapath, ofproto.OFPGC_ADD, ofproto.OFPGT_SELECT, 50, buckets)
else:
req = parser.OFPGroupMod(datapath, ofproto.OFPGC_MODIFY, ofproto.OFPGT_SELECT, 50, buckets)
print("change group actions ..............")
print(buckets)
datapath.send_msg(req)
@set_ev_cls(ofp_event.EventOFPStateChange, [CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER])
def change(self, ev):
datapath = ev.datapath
# 正常阶段正常传输
if ev.state == MAIN_DISPATCHER or ev.state == CONFIG_DISPATCHER:
if datapath.id not in self.datapaths:
self.datapaths[datapath.id] = datapath
parser = datapath.ofproto_parser
if datapath.id == 4 or datapath.id == 5:
group_id = 50
self.send_group_mod(datapath, self.lb_port[0], self.lb_port[1], 'add')
match = parser.OFPMatch(in_port=4)
actions = [parser.OFPActionGroup(group_id=group_id)]
self.add_flow(datapath, 10, match, actions)
for i in range(1,4):
match = parser.OFPMatch(in_port=i)
actions = [parser.OFPActionOutput(4)]
self.add_flow(datapath, 10, match, actions)
else:
match = parser.OFPMatch(in_port=1)
actions = [parser.OFPActionOutput(2)]
self.add_flow(datapath, 10, match, actions)
match = parser.OFPMatch(in_port=2)
actions = [parser.OFPActionOutput(1)]
self.add_flow(datapath, 10, match, actions)
# 链路出现故障,开始故障转移
elif ev.state == DEAD_DISPATCHER:
if datapath.id == 1 or datapath.id == 2:
parser = datapath.ofproto_parser
self.lb_port[self.lb_port.index(datapath.id)] = 3
for i in range(4,6):
datapath = self.datapaths[i]
self.send_group_mod(datapath, self.lb_port[0], self.lb_port[1], 'modify')
match = parser.OFPMatch(in_port=4)
actions = [parser.OFPActionGroup(group_id=50)]
self.add_flow(datapath, 10, match, actions)