2023年12月

网络安全实训

文章版本1.1

更好的阅读地址:

项目背景

某企业购买了防火墙设备,现需要进行企业网络建设,考虑到安全性,要求实现权限最小化,所有非必要端口和业务不可以对外开放。

项目目标

  • 一、掌握AF的部署配置,理解AF作为网关出口部署的原理。
  • 二、掌握AF的终端下载防御配置方法,理解终端下载防护的应用场景。
  • 三、掌握AF的终端僵尸网络防御配置方法,理解终端僵尸网络防护的应用场景。通过AF捕获恶意链接数据包,分析外联过程 。
  • 四、掌握AF防止DDOS攻击外网的配置方法,理解防止DDOS攻击外网的应用场景。通过AF捕获DOS攻击数据包,分析SYN Flood攻击过程 。
  • 五、掌握AF的服务器漏洞攻击防护功能及策略配置。理解WAF防御Web攻击的技术原理,掌握AF的WAF功能及策略配置。通过AF捕获SQL注入攻击数据包,分析SQL注入攻击过程 。

网络拓扑

外网区域:202.96.137.2/24 -> 202.96.137.1 -> SNAT 到租户区域外,这里的IP地址是172.16开头的

网络安全实训任务要求

准备?

  1. 登录后找到拓扑

  1. 一样的就可以 (你看这里就多个机子)

需要的拓扑图:

IP相关信息

实际上172.172.0.0/16 也是公网IP段

内网机器:172.172.4.10/24
内网服务器: 172.172.3.100/24

不知道为什么分的号有所区别,不同的号有不同的AF版本,最后的章节里有老板AF的信息。目前看,四班的号似乎只有scsa19、scsa16 的号是新系统,剩下老板的AF比较多。

任务1

1.1、防火墙做出口网关使用,需要代理内网用户和服务器网段上网进行互联网访问,要求在防火墙上划分区域分别为互联网区域和内网区域和服务器区域,内网PC和服务器访问互联网方向无限制。(配置截图,互联网访问成功截图)

  1. 创建3个区域
  • 互联网区域
  • 服务器区域
  • 内网区域

    都是三层的哦,是有IP的。

    然后分配端口:

  1. 配置三个接口

端口分配eth3 ,外网出口

这个端口将会作为外网出口,这也就是外网区域。

eth3 -> 路由器,外网出口

这里允许Ping。

端口分配eth2 服务器区域

也作为服务器网关的出口IP
  • IP:172.172.3.254/24

服务器的网络将会从这里出来。

端口分配eth1 内网区域网关

这里作为PC网关、内网区域的网关。
  • IP:172.172.4.254/24

然后尝试ping一下,确保可以通

ping之前记得先把服务器IP和PC的IP配好。

服务器IP配置可以通过这个命令:

sudo ip addr add 172.172.3.100/24 dev eth0
sudo ifdown eth0 && sudo ifup eth0

界面位置:系统->分析工具

输入下面的内容即可ping通。

ping 202.96.137.1

# CTRL+C 可以停止
# ping 服务器
ping 172.172.3.100

# ping PC
ping 172.172.4.10

能通即可。

ping网关:

ping内网:

配置路由

这里配置AF的出口路由,也就是路由表,可以配置到外网:静态路由(网络,然后路由)
  • 添加默认网关即可:
0.0.0.0/0 via 202.96.137.1 

配置策略

配置防火墙策略,允许相关区域上网。

界面位置:策略->访问控制

简单全通策略

基本的全通,简单粗暴。允许所有区域相互访问。

新建地址段

服务器地址段和客户机地址段

新建地址段:

普通策略

配置不同区域不同的上网策略
  1. 内网和服务器:
  • : pc地址、服务器地址
  • 目的地址:any

    pc、服务器 区域可以访问任意区域,但是仅可以访问PC地址、服务器地址的IP段。

  1. 内网上网:
  • : pc地址
  • 目的区域:互联网区域
  • 可以让内网正常上网。

  1. 服务器上网
  • : 服务器区域
  • 目的区域:互联网区域
  • 可以让服务器区域正常上网。

配置地址转换NAT

也就是SNAT 源地址转换
  1. 地址转换1

配置NAT,转换后的数据包为出接口地址

  1. 配置策略
  • 让服务器和内网区域,允许访问外网。
  • 禁用的忽略即可。(因为一起用的人有点多)

测试是否可以正常上网

PC测试

  1. 打开控制台

控制台确实挺难进入的。

  1. 配置IP

172.172.4.10/24

  1. 尝试上网

可以正常上网。

  1. 访问服务器

服务器测试

1. 配置IP

为了配置服务器的IP地址,请按照以下步骤操作:

sudo ip addr add 172.16.3.100/24 dev eth0
sudo ifdown eth0 && sudo ifup eth0
2. 访问互联网

确保服务器能够正常访问互联网。

1.2、互联网测试:互联网测试PC机可以通过访问防火墙的映射端口8080,访问到内网WEB服务器的80端口服务。(配置截图、访问成功截图)

PC机可以通过访问防火墙的映射端口8080,访问到内网WEB服务器的80端口服务。需要配置防火墙和进行一些测试。

AF配置目的地址转换

也叫端口映射和虚拟服务器
  • 界面位置:在地址转换配置界面
  • 配置内容202.96.137.2:8080 转换到 172.172.3.100:80

以下是相关配置和说明:

  1. 配置截图:

  2. 地址组配置:

    这里不需要配置地址组,服务器的单IP已新建一个地址组。

    注意是目的地址转换。

尝试访问

配置完毕后,应能够通过公网IP的8080端口访问内网的HTTP服务,对应到80端口的服务器。

  • 访问截图:

问题诊断

目前,8080端口无法打开。

这个机子的IP不像是云服务器的IP,更像是家庭宽带,不过访问的话可以在外网区域进行添加机器访问。

113.120.111.90

任务2

2.1 为保障内网终端安全,需要开启针对内网PC网段的对应的僵尸网络防护、防止下载病毒文件的安全策略,确保终端的安全性。(配置截图)

为保障内网终端安全,需开启以下安全策略:

  1. 添加僵尸网络策略:此策略有助于防止内网PC网段加入僵尸网络。

  2. 添加非法网站检测策略:此策略用于防止内网用户访问非法网站,避免下载病毒文件。

安全策略配置

新建安全策略,以确保终端的安全性。

  • 服务器IP段配置:针对服务器IP段进行安全设置。

  • 防护策略配置:设置具体的安全策略以保护内网。

  • 暂时禁用其他人策略:在配置期间,可以暂时禁用其他人的策略,以避免干扰。配置完成后,记得将其改回。

2.2 分别在AF内网PC口和外网口抓包,分析查看恶意域名访问拦截过程

在AF内网PC口和外网口进行抓包操作,分析查看恶意域名访问拦截过程。

1. 抓包过程

  • 进行抓包时,动作需迅速以捕捉相关数据。

2. Windows尝试访问

  • Windows终端尝试访问恶意域名,发现无法访问,显示访问已被拦截。

3. 抓包结果分析

  • 抓包结果显示恶意访问被成功拦截。

4. 数据包分析

  • 数据包 eth1

  • 数据包 eth3

5. 日志查看

  • 日志显示相关恶意访问已经被AF系统过滤掉。

这里已经被成功过滤啦!

任务3

3.1.为保障内网PC的DOS安全,防止服务器区域和互联网区域攻击PC,需要开启针对内网PC网段的对应的DOS安全策略。(配置截图,攻击拦截截图)

  1. 新增策略组
  2. 开启

3.2. 分别在AF内网PC口和服务器口抓包,分析查看SYN Flood攻击访问拦截过程

为模拟SYN Flood攻击,使用服务器对PC机发起攻击。

准备命令

  1. 用服务器打PC机
# 打SMB端口
ab -n 50 -c 10 http://172.172.4.10:445/

#先不要回车

开打

回车,执行命令,开始SYN Flood攻击。

过一会就好啦,过一会攻击完成,查看捕获的包。

eth2 的包

抓包结果显示,eth2接口捕捉到大量数据包。

eth1的包

会发现少了一些包,被防火墙拦截了一些包。

平台显示

平台显示记录了SYN Flood攻击的拦截过程。

拦截过程:

  1. 防火墙检测到超过阈值的SYN TCP握手包。
  2. 过滤并且对相关主机进行控制。

可以看出AF系统有效地识别并拦截了大规模的SYN Flood攻击,保护了内网环境不受该类型攻击的影响。

任务4

4.1.为保障内网WEB应用安全,防止互联网区域和PC区域攻击WEB应用,需要开启针对服务器网段的对应的安全策略,确保WEB应用的安全性。(配置截图,攻击拦截截图)

先把PC从黑名单里拉出来

移除PC机从黑名单
首先将PC机从黑名单中移除,以确保其能正常访问网络。

添加策略

添加安全策略
配置针对服务器网段的安全策略,确保WEB应用的安全性。

只开WEB防护

只记录日志

策略信息:

4.2. 分别在AF内网PC口和服务器口抓包,分析查看SQL注入攻击访问拦截过程

准备抓包

准备抓包
准备抓包过程,以监测网络活动。

先准备着:

登录平台

使用用户名gordonb和密码abc123登录平台,执行SQL注入攻击测试。

用户名gordonb
密码abc123

到sql注入

输入-1 union select 1,2,3,观察反应。

输入-1 union select 1,2,3

会卡住

PHP远程执行木马

PHP远程执行木马测试

也会出现卡住的情况。

也会卡住

停止抓包分析
抓包结束后,分析数据包。

停止抓包

Eth1数据包

Eth2 数据包

会发现数据包没有出现,不管是php木马的还是文件上传的。

已经被AF拦截。

未发现攻击相关的数据包,被AF拦截。

平台信息

平台显示了拦截过程和相关信息。

没检测到木马,直接显示的php意外哈哈哈哈,也可以。


okay

5、老版AF配网

有一些版本是老板的。

这里的配网:

内网:172.16.4.10/24 via 172.16.4.1
服务器:172.16.3.100/24 via 172.16.3.100

后来发现可能会有点小冲突(跟租户出口)

登录后这个样子:

老版AF配置接口

外网:

内网:

服务器:

PC ip

172.16.4.10/24

SERVER IP

配置地址转换

这样就差不多啦,剩下跟新版AF的大同小异,都是一些功能(感觉老板AF好用)。

(才不是服务器关了上不去了。)

SDN 3 作业2-2

  1. https://type.dayiyi.top/index.php/archives/306/
  2. https://cmd.dayi.ink/btoyilxmRNq9VEuZa8_0_Q?view

虚拟机

【测试0.1】Ubuntu_20.04_sdn_ovs_2.17.8-LTS-fix1 ,建议虚拟机内存多分2G,不然容易KILLED

找个IDEA构建(IF U NEED 最新的?)

下一个这个:

https://www.jetbrains.com/zh-cn/idea/download/?section=linux

然后把这个文件塞虚拟机里
推荐Filezilla (查看ip:ip addr

基本环境

# 把俩文件复制到桌面上
su
mv ./Desktop/ideaIC-2023.3.tar.gz ./
mv ./Desktop/floodlight_github_after_git_submodule_update.zip ./
tar -zxvf ideaIC-2023.3.tar.gz
unzip floodlight_github_after_git_submodule_update.zip
chmod -R 777 floodlight
chmod -R 777 floodlight/*
#装点基本的
apt update
apt install git build-essential ant maven python-dev openjfx -y
apt install maven ant -y
apt install openjdk-8-jdk -y

MVN CLEAN:

cd floodlight
mvn clean

开idea

进入idea-C-xxx/bin

# 普通用户即可
./idea.sh

选择文件:

等待同步

选择JDK

  1. 先点构建
  2. 下载JDK

  1. 然后再构建

  1. 如果出错:

进入

  1. 再build

构建JAR包

  1. 点这个

  1. 点这个

  1. 点这个

  1. 点这个

  1. 点这个

先APPLY

  1. 构建:

Rebuild module

  1. BUILD

  1. build

  1. build

  1. 然后这样就成功了(如果失败了尝试多给虚拟机开3G内存)

启动

cd floodlight/
java -jar ./out/artifacts/floodlight_jar/floodlight.jar

然后去网页:

http://localhost:8080/ui/pages/index.html

Docker image

su
apt install docker-compose -y
docker pull latarc/floodlight
docker run -it --rm latarc/floodlight

其实我觉得爽的事情,就是,不用动脑子,挂网.(这个镜像)

这里就Docker image成功了(很遗憾) 就用着这个方法

docker run -it -p 8080:8080 -p 6653:6653 --rm latarc/floodlight

# 8080:8080
# 主机: 容器
#如果需要更多端口或者换端口

OVO


# 另外一个终端
sudo mn --topo single,4 --controller remote,ip=127.0.0.1,port=6653
http://localhost:8080/ui/pages/index.html

安装floodlight

失败了

#失败了
sudo apt-get autoremove openjdk-11-jre-headless -y
sudo apt-get install openjdk-8-jdk -y
mkdir -pv ~/sdn/floodlight
cd ~/sdn/floodlight
git clone https://github.com/floodlight/floodlight.git
cd ~/sdn/floodlight/floodlight
git pull origin master 
git submodule init 
git submodule update 
sudo apt-get install maven python-dev -y
sudo apt-get install build-essential -y
sudo apt install ant -y
ant
sudo mkdir /var/lib/floodlight
sudo chmod 777 /var/lib/floodlight
sudo apt-get install openjfx

Docker build

失败了
docker run -it -v ./floodlight_docker:/app ubuntu:16.04 bash
sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list
apt update 
apt install sudo -y
cd /app
sudo apt-get install build-essential ant maven python-dev openjfx -y

apt install openjdk-8-jdk git 
git clone https://github.com/floodlight/floodlight.git
cd floodlight
git submodule init
git submodule update

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,流表项是静态的,简单地匹配所有进入的数据包并执行相同的洪泛操作。没有基于流量内容的动态决策过程。

SDN ryu的安装

虚拟机

Ubuntu_20.04_sdn_ovs-2.17.8-LTS-fix1
注意这里用了fix1也就是【测试0.1】

安装:

su
mkdir -pv /opt/ryu
cd /opt/ryu && apt install python3-pip -y 
git clone https://github.com/osrg/ryu.git
cd ryu && apt install python3-eventlet -y && apt install python3-routes -y && apt install python3-webob -y  && apt install python3-paramiko -y
pip3 install -i https://mirrors.ustc.edu.cn/pypi/web/simple -r tools/pip-requires
python3 setup.py install
ryu-manager# 看看这里有没有报错,我这里蛮正常的

测试1

su
cd /opt/ryu/ryu
ovs-ctl start
python3 bin/ryu run --verbose --observe-links ryu/app/gui_topology/gui_topology.py ryu/app/simple_switch_13.py


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

记得刷新下网页

测试2

nano hub.py

from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls


class Hub(app_manager.RyuApp):
    # openflow version
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super(Hub, self).__init__(*args, **kwargs)

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        datapath = ev.msg.datapath
        ofproto = datapath.ofproto
        ofp_parser = datapath.ofproto_parser

        # install the table-miss flow entry.
        match = ofp_parser.OFPMatch()
        actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                              ofproto.OFPCML_NO_BUFFER)]
        self.add_flow(datapath, 0, match, actions)

    def add_flow(self, datapath, priority, match, actions):
        # 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)

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packet_in_handler(self, ev):
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        ofp_parser = datapath.ofproto_parser
        in_port = msg.match['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)

        out = ofp_parser.OFPPacketOut(
            datapath=datapath, buffer_id=msg.buffer_id, in_port=in_port,
            actions=actions)
        datapath.send_msg(out)

启动:

ryu-manager hub.py --verbose

# 另外一个终端
mn --controller=remote --topo tree,depth=3
mininet> pingall

测试3

nano simple_switch_13.py (nano ctrl+s保存)

# 引入包
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
 
 
class SimpleSwitch13(app_manager.RyuApp):
    # 定义openflow版本
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
 
    def __init__(self, *args, **kwargs):
        super(SimpleSwitch13, self).__init__(*args, **kwargs)
        # 定义保存mac地址到端口的一个映射
        self.mac_to_port = {}
 
    # 处理EventOFPSwitchFeatures事件
    @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
        #
        # 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)
 
    # 添加流表函数
    def add_flow(self, datapath, priority, match, actions, buffer_id=None):
        # 获取交换机信息
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
 
        # 对action进行包装
        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
                                             actions)]
        # 判断是否有buffer_id,生成mod对象
        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)
        # 发送mod
        datapath.send_msg(mod)
 
    # 处理 packet in 事件
    @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]
 
        # 忽略LLDP类型
        if eth.ethertype == ether_types.ETH_TYPE_LLDP:
            # ignore lldp packet
            return
 
        # 获取源端口,目的端口
        dst = eth.dst
        src = eth.src
 
        dpid = format(datapath.id, "d").zfill(16)
        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
 
        # 查看是否已经学习过该目的mac地址
        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)]
 
        # 下发流表处理后续包,不再触发 packet in 事件
        # 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)
        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)
  • 终端1
ryu-manager simple_switch_13.py --verbose
  • 终端2
mn --controller=remote --topo tree,depth=3
> pingall

WOW