RYU-代码简单讲解

本文介绍了如何使用RYU网络操作系统创建一个简易的Switch,通过`packet_in`事件处理数据包,并添加流表规则。`l3switch`类实现了OpenFlow1.3版本的交换机功能,包括添加`Table-Miss`流表条目和处理入站数据包。当链路出现故障时,通过修改组播组进行故障转移,实现负载均衡。
摘要由CSDN通过智能技术生成

本文主要参照官方文档和查询到的博客进行记录,用于笔记学习

推荐阅读

使用 RYU 实现简易 Switch - 知乎

欢迎来到 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具有的属性信息(在官方文档中):

AttributeDescription
id64 位 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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值