ryu实例---自学习交换机

32 篇文章 61 订阅
31 篇文章 2 订阅

前面的几篇博客介绍了hub、流表的操作、数据包的解析等知识(以下若有不明白之处,建议先把前几篇博客看完)。接下来,根据这些知识就可以编写自学习交换机的实例了。

第一部分:相关知识

转发表、路由表、ARP表之间的关系需要先行了解(https://cloud.tencent.com/developer/article/1173761

参考ryubook(https://github.com/Yang-Jianlin/ryu/tree/master/spec_book

OpenFlow 交换器会接受来自于 controller 的指令并达到以下功能:

  • 对于接收到的封包进行修改或针对指定的端口进行转送
  • 对于接收到的封包进行转送到 Controller 的动作( Packet-In )
  • 对于接收到来自 Controller 的封包转送到指定的端口( Packet-Out )。

上述的功能所组合起来的就是一台交换器的实现。

首先,利用 Packet-In 的功能来达到 MAC 地址的学习。 Controller 使用 Packet-In 接收来自交换器的封包之后进行分析,得到端口相关数据以及所连接的 host的 MAC 地址。

在学习之后,对所收到的封包进行转送。将封包的目的地址,在已经学习的 host 数据中进行检索,根据检索的结果会进行下列处理。

  • 如果是已经存在记录中的 host:使用 Packet-Out 功能转送至先前所对应的端口
  • 如果是尚未存在记录中的 host:使用 Packet-Out 功能来达到 Flooding 

具体步骤如下图所示。

1. 初始状态

此时的Flow table 为空白。将 host A 接到端口 1, host B 接到端口 4, host C 接到端口 3。

2. hostA —> hostB

当 host A 向 host B 发送封包。这时后会触发 Packet-In 讯息。 host A 的 MAC地址会被端口 1 给记录下来。由于 host B 的 MAC 地址尚未被学习,因此会进行 Flooding 并将封包往 host B 和 host C 发送。
 

3. host B → host A

封包从 host B 向 host A 返回时,在 Flow table 中新增一笔 Flow Entry,并将封包转送到端口 1。因此该封包并不会被 host C收到。

4. host A → host B

再一次, host A 向 host B 发送封包,在 Flow table 中新增一个 Flow Entry 接着转送封包到端口 4。
 

第二部分:代码

接下来,重点介绍以下代码的编写。

新建一个类Switch,内容如下:

from ryu.base import app_manager

class Switch(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.mac_table = {}  # mac表,即转发表,初始化为空

这里我使用v1.3版本的openflow,并且初始化一个字典,用来当作转发表。当然,由于现在尚未添加任何处理代码,所以这段程序什么也做不了。

接下来,接需要继续向类中添加代码以完成添加流表功能的开发。

定义一个发送流表的方法,内容如下:

# 流表的操作函数
# 详细参见:https://blog.csdn.net/weixin_40042248/article/details/115832995?spm=1001.2014.3001.5501
    def doflow(self, datapath, command, priority, match, actions):
        ofp = datapath.ofproto
        ofp_parser = datapath.ofproto_parser
        inst = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
        req = ofp_parser.OFPFlowMod(datapath=datapath, command=command,
                                    priority=priority, match=match, instructions=inst)
        datapath.send_msg(req)

这段代码就是发送流表的方法,写完之后,我们只需要在想要修改流表的时候调用就可以了。

控制器和交换机在最开始的时候进行握手,也就是features信息的请求和返回,当控制器收到features消息之后,我们希望controller会向交换机下发一条默认流表项(table-miss),用来处理没有流表匹配时,交换机将信息发送到控制器。

 # 当控制器和交换机开始的握手动作完成后,进行table-miss(默认流表)的添加
    # 关于这一段代码的详细解析,参见:https://blog.csdn.net/weixin_40042248/article/details/115749340
    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        msg = ev.msg
        datapath = msg.datapath
        ofp = datapath.ofproto
        ofp_parser = datapath.ofproto_parser

        # add table-miss
        command = ofp.OFPFC_ADD
        match = ofp_parser.OFPMatch()
        actions = [ofp_parser.OFPActionOutput(ofp.OFPP_CONTROLLER, ofp.OFPCML_NO_BUFFER)]
        self.doflow(datapath, command, 0, match, actions)

接下来,就是需要对packet_in消息进行处理,控制器根据收到的交换机的消息进行转发表的学习和流表的下发等操作。具体的解释,看代码注释。

# 关键部分,转发表的学习,流表的下发,控制器的指令等
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packet_in_handler(self, ev):
        global src, dst
        msg = ev.msg
        datapath = msg.datapath
        ofp = datapath.ofproto
        ofp_parser = datapath.ofproto_parser
        dpid = datapath.id
        # msg实际上是json格式的数据,通过解析,找出in_port
        # 可用print(msg)查看详细数据
        in_port = msg.match['in_port']
        # 接下来,主要是解析出源mac地址和目的mac地址
        pkt = packet.Packet(msg.data)
        for p in pkt.protocols:
            if p.protocol_name == 'ethernet':
                src = p.src
                dst = p.dst
                print('src:{0}  dst:{1}'.format(src, dst))

        # 字典的样式如下
        # {'dpid':{'src':in_port, 'dst':out_port}}
        self.mac_table.setdefault(dpid, {})
        # 转发表的每一项就是mac地址和端口,所以在这里不需要额外的加上dst,port的对应关系,其实返回的时候目的就是源
        self.mac_table[dpid][src] = in_port
        
        # 若转发表存在对应关系,就按照转发表进行;没有就需要广播得到目的ip对应的mac地址
        if dst in self.mac_table[dpid]:
            out_port = self.mac_table[dpid][dst]
        else:
            out_port = ofp.OFPP_FLOOD
        actions = [ofp_parser.OFPActionOutput(out_port)]

        # 如果执行的动作不是flood,那么此时应该依据流表项进行转发操作,所以需要添加流表到交换机
        if out_port != ofp.OFPP_FLOOD:
            match = ofp_parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src)
            command = ofp.OFPFC_ADD
            self.doflow(datapath=datapath, command=command, priority=1,
                        match=match, actions=actions)

        data = None
        if msg.buffer_id == ofp.OFP_NO_BUFFER:
            data = msg.data
        # 控制器指导执行的命令
        out = ofp_parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
                                      in_port=in_port, actions=actions, data=data)
        datapath.send_msg(out)

第三部分:实验

首先,利用mininet构建网络拓扑,如下所示。

拓扑设置完成,运行拓扑。然后在Ubuntu命令行运行ryu程序,命令如下。

root@yang-VirtualBox:/home/yang/ryu/ryu/app# ryu-manager switch_yjl.py

ryu程序运行后,可用查看s1和s2的流表,可用发现s1s2中已经添加了默认的流表项。

接下来,在mininet命令行输入h1 ping h2,h2 ping h3,h1 ping h3,结果如下。

结果表明,所有主机之间都可以相互ping通,接下来,查看流表项。

下面附上全部代码

from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller.handler import set_ev_cls
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import CONFIG_DISPATCHER
from ryu.controller import ofp_event
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
'''
自学习交换机的实现
结合了握手数据解析、流表下发、转发表学习等操作
'''


class Switch(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.mac_table = {}  # mac表,即转发表,初始化为空

    # 流表的操作函数
    # 详细参见:https://blog.csdn.net/weixin_40042248/article/details/115832995?spm=1001.2014.3001.5501
    def doflow(self, datapath, command, priority, match, actions):
        ofp = datapath.ofproto
        ofp_parser = datapath.ofproto_parser
        inst = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
        req = ofp_parser.OFPFlowMod(datapath=datapath, command=command,
                                    priority=priority, match=match, instructions=inst)
        datapath.send_msg(req)

    # 当控制器和交换机开始的握手动作完成后,进行table-miss(默认流表)的添加
    # 关于这一段代码的详细解析,参见:https://blog.csdn.net/weixin_40042248/article/details/115749340
    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        msg = ev.msg
        datapath = msg.datapath
        ofp = datapath.ofproto
        ofp_parser = datapath.ofproto_parser

        # add table-miss
        command = ofp.OFPFC_ADD
        match = ofp_parser.OFPMatch()
        actions = [ofp_parser.OFPActionOutput(ofp.OFPP_CONTROLLER, ofp.OFPCML_NO_BUFFER)]
        self.doflow(datapath, command, 0, match, actions)

    # 关键部分,转发表的学习,流表的下发,控制器的指令等
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packet_in_handler(self, ev):
        global src, dst
        msg = ev.msg
        datapath = msg.datapath
        ofp = datapath.ofproto
        ofp_parser = datapath.ofproto_parser
        dpid = datapath.id
        # msg实际上是json格式的数据,通过解析,找出in_port
        # 可用print(msg)查看详细数据
        in_port = msg.match['in_port']
        # 接下来,主要是解析出源mac地址和目的mac地址
        pkt = packet.Packet(msg.data)
        for p in pkt.protocols:
            if p.protocol_name == 'ethernet':
                src = p.src
                dst = p.dst
                print('src:{0}  dst:{1}'.format(src, dst))

        # 字典的样式如下
        # {'dpid':{'src':in_port, 'dst':out_port}}
        self.mac_table.setdefault(dpid, {})
        # 转发表的每一项就是mac地址和端口,所以在这里不需要额外的加上dst,port的对应关系,其实返回的时候目的就是源
        self.mac_table[dpid][src] = in_port

        # 若转发表存在对应关系,就按照转发表进行;没有就需要广播得到目的ip对应的mac地址
        if dst in self.mac_table[dpid]:
            out_port = self.mac_table[dpid][dst]
        else:
            out_port = ofp.OFPP_FLOOD
        actions = [ofp_parser.OFPActionOutput(out_port)]

        # 如果执行的动作不是flood,那么此时应该依据流表项进行转发操作,所以需要添加流表到交换机
        if out_port != ofp.OFPP_FLOOD:
            match = ofp_parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src)
            command = ofp.OFPFC_ADD
            self.doflow(datapath=datapath, command=command, priority=1,
                        match=match, actions=actions)

        data = None
        if msg.buffer_id == ofp.OFP_NO_BUFFER:
            data = msg.data
        # 控制器指导执行的命令
        out = ofp_parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
                                      in_port=in_port, actions=actions, data=data)
        datapath.send_msg(out)

github地址:https://github.com/Yang-Jianlin/ryu/blob/master/ryu/app/switch_yjl.py

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

楊木木8023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值