SDN作业5(我也不知道是多少了) RYU分析脚本

分析两个脚本

初始环境

已经安好ryu。

su
find / | grep simple_switch_13.py #找到你的脚本的位置
# 我这里在/opt/ryu/ryu 这里(在最后一个/ryu/app/simple_switch_13.py的前面)
cd /opt/ryu/ryu
python3 bin/ryu run --verbose --observe-links ryu/app/gui_topology/gui_topology.py ryu/app/simple_switch_13.py

找文件:

然后尝试运行一下即可。

# 另外一个终端
ovs-ctl start 
mn --controller remote --topo tree,depth=3

上面这些能用就行了

Simple Switch 13

启动

# 在ryu/ryu/app启动app

# 找到类似这个样的安装目录(也可以找到你复制到虚拟机里的文件)
cd /opt/ryu/ryu/ryu/app/

ryu-manager simple_switch_13.py --verbose

# 另外一个终端
mn -c && mn --controller=remote --topo tree,depth=3

你可能能用到的代码:

来源:https://www.cnblogs.com/wangxiaotao/p/8645451.html
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys

from ryu.cmd import manager


def main():
    #用要调试的脚本的完整路径取代/home/tao/workspace/python/ryu_test/app/simple_switch_lacp_13.py就可以了
    sys.argv.append('/home/sdn/simple_switch_13.py')
    sys.argv.append('--verbose')
    sys.argv.append('--enable-debugger')
    manager.main()

if __name__ == '__main__':
    main()

然后改一下,就可以启用调试了。(记得在另外一个文件打上断点)

写注释:

from ryu.base import app_manager  #导入 Ryu 应用程序管理器,用于管理 SDN 应用程序。
from ryu.controller import ofp_event  #导入 OpenFlow 事件模块,用于处理 OpenFlow 事件。
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER  #处理器类型,用于指定事件处理阶段。
from ryu.controller.handler import set_ev_cls  #事件装饰器,用于将事件处理函数与特定事件相关联。
from ryu.ofproto import ofproto_v1_3  # OpenFlow 1.3 协议模块,这是本程序使用的协议版本。
from ryu.lib.packet import packet  #数据包模块,用于解析和构造数据包。
from ryu.lib.packet import ethernet  # 以太网协议模块,用于处理以太网帧。
from ryu.lib.packet import ether_types  #以太类型模块,包含以太网类型字段的定义。

# 简单交换机13,类名,从RyuApp继承的类。
class SimpleSwitch13(app_manager.RyuApp):
    # 版本1.3
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    
    # 先调用父class的初始化,然后自己定义一个mac to port的字典。
    def __init__(self, *args, **kwargs):
        super(SimpleSwitch13, self).__init__(*args, **kwargs)
        self.mac_to_port = {}

    # 修饰器拦截请求,这个地方把CONFIG_DISPATCHER给拦截下来了?然后交换机事件。

    # 当一个交换机与控制器连接并发送其功能(如支持的流表数量、功能等)时,这个事件(ofp_event.EventOFPSwitchFeatures)

    # CONFIG_DISPATCHER  是当控制器与交换机刚建立连接并进行初始配置时的状态。在这个状态下,控制器处理来自交换机的功能信息,并可以设置一些初始的流表项。
    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        #这里的ev是事件
        datapath = ev.msg.datapath # 从事件中获取datapath对象,代表一个OpenFlow交换机,也通过datapath来联络交换机,交换信息。
        ofproto = datapath.ofproto # 获取该datapath使用的OpenFlow协议版本的定义
        parser = datapath.ofproto_parser # 获取该datapath使用的OpenFlow协议的处理器

        # 这里说之前可能有BUG,但是现在修了。
        # install table-miss flow entry
        #
        # We specify NO BUFFER to max_len of the output action due to
        # OVS bug. At this moment, if we specify a lesser number, e.g.,
        # 128, OVS will send Packet-In with invalid buffer_id and
        # truncated packet data. In that case, we cannot output packets
        # correctly.  The bug has been fixed in OVS v2.1.0.

        match = parser.OFPMatch() #创建一个空的匹配条件,匹配所有的流量
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)] #创建一个动作,将匹配到的包发送到控制器,不缓存数据包
        self.add_flow(datapath, 0, match, actions) # 调用add_flow函数添加流表项

    # 添加流表
    def add_flow(self, datapath, priority, match, actions, buffer_id=None):
        ofproto = datapath.ofproto #拿协议
        parser = datapath.ofproto_parser #拿协议parse

        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)] # 创建指令列表,包含actions,然后actions是刚才传过来的,上一个函数生成的那个。
        if buffer_id: #这里我觉得不可能会不是None,至少从handler不会
            mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
                                    priority=priority, match=match,
                                    instructions=inst) #如果指定了buffer_id,创建一个包含它的流表项
        else:
            mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
                                    match=match, instructions=inst) # 创建一个普通的流表项
        datapath.send_msg(mod) # 然后把流表发送给交换机。

    # 另外一个装饰器,这里把Packet-In拿一下。
    # 当交换机接收到一个数据包,并且没有匹配的流表项来告诉它如何处理这个包时,它会将这个数据包发送到控制器。这个事件就是通知控制器有一个这样的数据包到达。
    # MAIN_DISPATCHER:通常在与交换机的初始配置完成后进入这个状态。在这个状态下,控制器处理大部分的数据流,如数据包的进入、状态变化等
    @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

        # 如果接收到的数据包被截断了,也就是不完整?,输出下debug级别的 log。
        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 # 从事件中拿msg
        datapath = msg.datapath # 拿datapath
        ofproto = datapath.ofproto # 拿协议
        parser = datapath.ofproto_parser # 拿协议处理器


        in_port = msg.match['in_port'] # 从消息中获取进入端口号
        pkt = packet.Packet(msg.data) # 将接收到的数据转换为Packet对象
        eth = pkt.get_protocols(ethernet.ethernet)[0] #从Packet对象中获取以太网协议的信息

        if eth.ethertype == ether_types.ETH_TYPE_LLDP: #如果是LLDP包,直接忽略。链路层发现协议,用于网络设备之间的相互发现。
            # ignore lldp packet
            return
        dst = eth.dst # 以太帧的源mac
        src = eth.src # 以太帧目的mac

        

        dpid = format(datapath.id, "d").zfill(16) #获取交换机的ID,并转换为16位的字符串
        self.logger.info(f"[{dpid}]当前包的,源MAC:{src} 目的MAC:{dst}")
        self.mac_to_port.setdefault(dpid, {}) # 如果交换机的 MAC 到端口映射不存在(dpid变量),则把dpid初始化为一个空字典。也就是dpid键值如果不存在,则添加 dpid={},如果有了就不弄了。

        self.logger.info("[%s]当前包:packet in dpid:%s src:%s dst:%s in_port:%s",dpid, dpid, src, dst, in_port)

        # learn a mac address to avoid FLOOD next time.
        # 学习 MAC 地址,避免下次洪泛。
        self.mac_to_port[dpid][src] = in_port # 【源MAC地址】=进入端口号 存储

        if dst in self.mac_to_port[dpid]: # 如果dst(目的地址)在【dpid】里面
            out_port = self.mac_to_port[dpid][dst] # 目的地址= dpid[dst] 也就是已经学习了的端口
            self.logger.info("[%s] src:%s , dst:%s 已经学习到",dpid,src,dst)
        else:
            out_port = ofproto.OFPP_FLOOD # 否则,目的地址,出口端口为洪泛端口
            self.logger.info("[%s] src:%s , dst:%s 泛洪:%s",dpid,src,dst,out_port)

        actions = [parser.OFPActionOutput(out_port)] #创建一个动作,将数据包发送到指定端口

        self.logger.info(f"[控制器MAC数据库]{self.mac_to_port}")
        # install a flow to avoid packet_in next time # 安装一个流表项,以避免下次产生Packet-In事件
        if out_port != ofproto.OFPP_FLOOD: # 如果出口不是泛洪口。
            match = parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src) #创建匹配条件,匹配源和目的 MAC 地址以及端口。
            # verify if we have a valid buffer_id, if yes avoid to send both # 检查是否有有效的buffer_id,如果有,则避免同时发送flow_mod和packet_out
            # flow_mod & packet_out
            if msg.buffer_id != ofproto.OFP_NO_BUFFER:  # 如果交换机暂时缓存了这个数据包,而没有立即发送整个包的内容到控制器,它会使用一个非负数的buffer_id来标识这个缓存的包。这里如果不相同的话(OFP_NO_BUFFER一般为-1)当交换机没有缓存这个包,而是直接将整个包发送给控制器时。也就是如果交换机暂时缓存了这个包。
                self.add_flow(datapath, 1, match, actions, msg.buffer_id) # 调用 add_flow 函数安装流表项。在交换机上添加一个新的流表项,这个流表项具有特定的匹配条件和一系列动作,并且可能会立即应用于一个已经被交换机缓存的数据包。(带buffer_id的流表)
                return
            else:
                self.add_flow(datapath, 1, match, actions) # 否则,添加一个普通的流表项
        data = None
        if msg.buffer_id == ofproto.OFP_NO_BUFFER: #如果没有缓存这个包。也就是没有发送到控制器
            data = msg.data # 如果没有缓冲区 ID,则设置数据字段。
        out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,in_port=in_port, actions=actions, data=data) #创建数据包输出消息。
        datapath.send_msg(out)#发送数据包输出消息到交换机。

wow:

部分信息

方便理解,也许..
  • datapath

  • ofproto

这个看上去是个定义。

  • parser

  • wow

  • 数据库欸:

HUB

这个就简单一些了。


from ryu.base import app_manager # APP管理器
from ryu.ofproto import ofproto_v1_3 # 协议模块1.3
from ryu.controller import ofp_event # 导入 OpenFlow 事件模块,用于处理 OpenFlow 事件。
from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER # 两个处理时期,一个普通,一个配置
from ryu.controller.handler import set_ev_cls # 事件装饰器

# wa 这是一个HUB
class Hub(app_manager.RyuApp):
    # openflow version # OPENFLOW 协议版本1.3
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, *args, **kwargs): #构造函数
        super(Hub, self).__init__(*args, **kwargs) #还是调用父class的构造。
        # 注意这里就没有了那个mac表的定义。


    # 这里同样的还是拿,控制器与交换机刚建立连接并进行初始配置时的状态。
    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        datapath = ev.msg.datapath # 从事件里拿datapath,也是跟交换机进行交互的路径,一样。
        ofproto = datapath.ofproto # 然后拿协议定义,一样
        ofp_parser = datapath.ofproto_parser #拿协议解释器,一样。

        # install the table-miss flow entry. # 感觉也可以说是添加一个流表,以避免下次产生Packet-In事件
        match = ofp_parser.OFPMatch()  # 创建一个空的匹配条件,表示匹配所有包。
        actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)] #  创建一个动作列表,包含将数据包发送到控制器的动作。
        # OFPP_CONTROLLER 匹配该流表项的所有数据包将被发送到控制器。
        # OFPCML_NO_BUFFER 控制器的数据包的最大长度,OFPCML_NO_BUFFER一般为-1,没有限制

        self.add_flow(datapath, 0, match, actions) # 添加流表

    def add_flow(self, datapath, priority, match, actions): #添加流表,安装到交换机(datapath)上
        # add a flow entry, and install it into datapath.
        ofproto = datapath.ofproto # 拿协议
        ofp_parser = datapath.ofproto_parser # 拿协议解释器

        # contruct a flow_mod msg and sent it. # 构建流修改消息并发送。
        inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)] # 创建指令列表,包含应用动作的指令。
        mod = ofp_parser.OFPFlowMod(datapath=datapath, priority=priority,match=match, instructions=inst) #创建流修改消息。
        datapath.send_msg(mod) # 发送流修改消息到交换机。

    # 同样,MAIN_DISPATCHER:通常在与交换机的初始配置完成后进入这个状态,并且处理PacketIn
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packet_in_handler(self, ev):
        msg = ev.msg #获得事件msg 一样
        datapath = msg.datapath # 一样,拿datapath
        ofproto = datapath.ofproto # 一样,拿协议
        ofp_parser = datapath.ofproto_parser #一样,拿协议处理器
        in_port = msg.match['in_port'] # 获得数据包端口号
        self.logger.info(f"in port:{in_port}")


        # contruct a flow entry.# 构建流表项。
        match = ofp_parser.OFPMatch() # 创建一个空的匹配条件。
        actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_FLOOD)] # 创建一个动作列表,包含洪泛到所有端口的动作。一样

        # install flow mod to avoid packet_in next time. #安装流表项,避免下次接收到相同数据包。一样
        self.add_flow(datapath, 1, match, actions) # 调用 add_flow 函数安装流表项。一样

        out = ofp_parser.OFPPacketOut(
            datapath=datapath, buffer_id=msg.buffer_id, in_port=in_port,
            actions=actions) #创建数据包输出消息。一样
        datapath.send_msg(out) #发送数据包输出消息到交换机。一样

对比

1. 简单学习交换机(第一个代码)

  • MAC 地址学习:维护一个 MAC 地址到端口号的映射表(mac_to_port) 来学习网络中的设备。收到新的数据包时,会检查这个映射表来决定如何转发数据包。
  • 避免洪泛:如果目的 MAC 地址已经学习过(即在 mac_to_port 表中),交换机会将数据包直接转发到特定的端口,而不是广播到所有端口。
  • 流表生成:当收到一个未知的目的 MAC 地址的数据包时,程序会先洪泛这个包,然后添加一条新的流表项来处理未来从这个源地址到这个目的地址的数据包。为了下次能够直接转发而不是再次洪泛。

2. Hub(第二个代码)

  • 无 MAC 地址学习:不维护任何 MAC 地址到端口的映射。对网络中的设备一无所知,也不学习它们的地址。
  • 总是洪泛:每当 Hub 收到一个数据包,无论它的源或目的地址是什么,它总是将数据包洪泛到所有端口。
  • 流表生成:Hub 的流表项简单地匹配所有进入的数据包并执行洪泛操作。即使它安装了这样的流表项,每个匹配的包都会被发送到所有端口。

流表生成的区别

  • 简单学习交换机中,流表项的生成是动态的,基于网络流量中观察到的源和目的 MAC 地址。
  • Hub,流表项是静态的,简单地匹配所有进入的数据包并执行相同的洪泛操作。没有基于流量内容的动态决策过程。
最后修改:2023 年 12 月 07 日
如果觉得我的文章对你有用,请随意赞赏