基于Ubuntu18.04的OVS与Mininet仿真工具安装及网络测量应用案例

目录

一、Ubuntu18.04 安装

1.1镜像下载地址

1.2在VMware Workstation中安装镜像

1.3在Ubuntu18.04apt修改为国内的阿里云镜像源

1.4Ubuntu18.04的NAT8静态网络配置及连接互联网

二、OVS安装使用及应用案例

2.1OVS的构建与安装(可直接安装Mininet包括OVS)

2.2OVS使用说明

2.3应用案例

2.3.1实例介绍

2.3.2实例开发

三、Mininet安装使用及简单案例

3.1软件安装

3.2使用说明

3.3应用案例

3.3.1案例准备

3.3.2案例说明

3.3.3案例具体实现

3.4测试结果


一、Ubuntu18.04 安装

1.1镜像下载地址

http://mirrors.163.com/ubuntu-releases/18.04/

1.2在VMware Workstation中安装镜像

1)打开VMware,点击"文件"--->"新建虚拟机"--->选择"典型"

2)选择"稍后安装操作系统"--->选择"Linux",版本选择"Ubuntu 64位"--->选择用于存放虚拟机的"位置"--->指定"磁盘大小",然后选择"将虚拟磁盘存储为单个文件"--->在"已准备好创建虚拟机"窗口中,选择"完成"

3)选择"虚拟机"--->"设置",根据自己的硬件条件,适当分配一些硬件资源。必须设置的一项为"CD/DVD(STAT)",在右侧"连接"板块内选择"使用ISO镜像文件",并指定前面下载的Ubuntu镜像文件存放目录。分配好虚拟机的硬件资源后点击"确定"。

4)运行虚拟机,加载一段时间后弹出"安装"界面。语言栏倒数第三个是中文。点击"安装 Ubuntu"。 然后在键盘布局界面选择"继续"

    

5)选择"正常安装",点击"继续"

6)点击"现在安装",然后在出现的"将改动写入此盘吗",选择"继续"

    

7)你在什么地方,选择"Shanghai"--->"继续"

8)根据自身需求填写信息,点击"继续",等待一会完成安装。

以下为可选配置

1.3在Ubuntu18.04apt修改为国内的阿里云镜像源

1)查看当前系统的版本信息

命令:lsb_release -a

2)切换为root用户

命令:su root 

注:首次进入若输入密码老是验证失败则输入命令: sudo passwd root 然后输入密码,并确认密码,最后再次切换root用户输入刚才设置的密码即可。

3)备份系统默认的源(安全起见可不备份)

命令:cp /etc/apt/sources.list /etc/apt/sources.list.backups

4)配置阿里云镜像源

命令:vim /etc/apt/sources.list

镜像资源网站:https://developer.aliyun.com/mirror/ubuntu?spm=a2c6h.13651102.0.0.2c741b118CQEPe

注:将系统默认的镜像源注释掉,然后添加阿里云镜像源

5)更新镜像源

命令:sudo apt-get update

1.4Ubuntu18.04的NAT8静态网络配置及连接互联网

1)网络配置命令

sudo vim /etc/netplan/01-network-manager-all.yaml 

注:01-network-manager-all.yaml 每个ubuntu主机名字不同

2)修改内容

ens33:

    分别是ip地址,网关地址,是否动态解析地址,域名解析(DNS)地址。

3)网络重启

命令:sudo netplan apply 

4)重启系统

命令:reboot

二、OVS安装使用及应用案例

此节可先不看应用案例,直接看第三节Mininet。

2.1OVS的构建与安装(可直接安装Mininet包括OVS)

1)下载指定版本压缩包

下载地址:http://www.openvswitch.org/download/

2)将下载的.tar.gz压缩包通过软件上传到ubuntu中(此处我用的是SecureCRT)

上传命令: rz -E

3)解压压缩包

命令:sudo tar -zxvf openvswitch-2.12.1.tar.gz

注:openvswitch-2.12.1.tar.gz是我下载的版本,且一定要在对应文件目录下解压,否则要指定压缩包的目录

4)将解压完的文件放到一个对应的目录下

命令:mv openvswitch-2.12.1 /opt/modules/ 

注:/opt/modules/  是我存放的目录

5)进入openvswitch-2.12.1目录,配置。

首先如果代码是从OVS GIT树上下载的,则在代码的第一层目录执行boot.sh脚本。

命令:./boot.sh

在代码包的第一层目录,通过运行configure脚本来配置代码包的编译环境。通常可以直接调用不带任何参数命令进行默认配置。

命令: sudo ./configure

默认所有的文件都会安装在/usr/local目录下,如果想安装在指定目录下,可以添加如下参数(以安装在/usr/和/var/ 目录为例)。

命令:sudo ./configure --prefix=/usr --localstatedir=/var

默认情况下,静态库被构建和链接,如果想使用共享库的话,操作如下。

命令:sudo ./configure --enable-shared

若需要使用特定的C编译器来编译OVS用户程序,可以在configure命令行上指定编译器及版本。

命令:sudo ./configure CC=gcc-4.2

也可以在configure命令行指定clang编译。

命令:sudo ./configure CC=clang

可以将安装的软件创建指定目录存放,操作如下。

命令:sudo mkdir _gcc && (cd _gcc && ../configure CC=gcc)

或者使用如下命令。

命令: sudo mkdir _clang && (cd _clang && ../configure CC=clang)

若需要构建Linux内核模块,操作如下。

命令:sudo ./configure --with-linux=/lib/modules/'uname -r'/build

6)构建OVS,在代码包的第一层目录下运行make。

命令:sudo  make

7)切换到root用户,运行“make install”安装OVS到系统中,默认安装在/usr/local目录

命令:

su root

make install

8)安装OVS,加载Open vSwitch模块

命令:

make modules_install

/sbin/modprobe openvswitch

9)OVS安装成功之后,可使用ovsdb-tool来初始化配置数据库

ovsdb-tool create /usr/local/etc/openvswitch/conf.db vswitchd/vswitch.ovsschema

2.2OVS使用说明

OVS安装完成后,相关设备还不能被称作一台交换机,在它发挥交换机功能之前,还需要手动创建ovsdb并对其进行初始化。另外,仅创建了ovsdb的OVS还是一台没有生命的交换机,还需要开启OVS的主进程vswitchd,才能完成OVS启动。完成上述步骤后。安装OVS的设备才可以成为一台名副其实的交换机。

OVS的启动和停止可参考如下步骤。

1)在启动ovs-vswitchd之前,需要用下面的命令设置环境变量。

#export PATH=$PATH:/usr/local/share/openvswitch/scripts/

2)启动OVS 。

命令:

ovs-ctl start

3)若用户需要停止OVS,可使用如下命令。

命令:

ovs-ctl stop

详细文档使用可参考网站:https://docs.openvswitch.org/en/stable/intro

OVS常用的命令主要是ovs-vsctl和ovs-ofctl这两个工具。

(1)ovs-vsctl

该工具是一个查询和配置OVS数据库的实用工具,用于查询或者变更vswitchd的配置信息,通过它可以直接更新ovsdb。

(2)ovs-ofctl

该工具是OpenFlow交换机的命令行管理工具,用于管理OVS作为OpenFlow交换机时的各种参数,用户可以通过ovs-ofctl查询或修改OpenFlow交换机的状态、配置和流表项等信息。

2.3应用案例

2.3.1实例介绍

从OpenFlow协议的角度讲,控制平面的建设分为带内(In-Band)和带外(Out-of-Band)两种模式,在组建大型网络时,为例保证控制平面的稳定性,一般采用Out-of-Band模式,通常的做法是物理分离的专用网络。然而,在实际SDN部署过程中,某些交换机之间可能只有一条物理链路,在这种情况下,需要对Out-of-Band模式进行一些调整,使部分物理链路可以同时承载两条逻辑链路(数据平面、控制平面),并且相互隔离。

此案例介绍如何基于OVS部署一个共享物理链路、Out-of-Band组网的SDN,具体拓扑如下图所示,图中OVS01与OVS02之前只有一条物理链路,OVS01的控制平面端口需要通过该链路连接至控制器,主机A也需要通过该链路与主机B进行数据平面通信,因此需要OVS对流量进行VLAN(虚拟局域网)划分和QoS保障,以保证数据平面不影响控制平面。

                                                                                              

2.3.2实例开发

与传统的网络转发设备一样,OVS需要在进行配置后才能实现相应的功能。下面列举了对上述OVS01的配置,其他的OVS配置类似。

待配置的OVS安装在一台有8个以太网口的服务器上,需要将其中的eth0、eth1分配给控制平面使用,eth2~eth6分配给数据平面使用,eth7为数据平面与控制平面共享的物理链路接口。OVS通过TCP端口连接到IP地址为192.168.87.200的控制器。在完成以上基本配置后,还需基于端口设置队列和QoS,保障数据平面与控制平面的最低带宽。

如下图所示,OVS01内部划分为3个网桥,其中br0为数据平面网桥,br1为控制平面网桥,br2为普通网桥。Patch为OVS内部虚拟接口,br0与br2、br1通过Patch接口建立内部连接。

                                                                                      

在br2内通过使用VLAN实现控制平面和数据平面的数据分流和隔离的具体方法是:给Patch21端口的数据流打上VLAN标签,将控制平面数据流隔离在VLAN999内,而Patch20端口的数据流不打VLAN标签,这样通过区分数据流是否带有VLAN标签来区分处理两个平面的数据流量,实现数据平面与控制平面的隔离。按照这种方法配置OVS后,控制平面流量经br1通过Patch接口进入br2并打上VLAN999的标签通过eth7送到相邻OVS。  br2接收到数据分组如果是带VLAN ID=999的数据分组就从Patch21转发出去,如果是无VLAN标签的数据分组就从Patch20转发出去。 最终,当同样配置的两台OVS级联时,两台OVS中的控制平面数据分组不会进入数据平面中,而数据平面的数据分组也不会进入控制平面,而两台OVS间的物理链路实现了控制平面与数据平面的复用。

当OVS中控制平面与数据平面流量复用同一物理链路时,存在如何对两种数据流量进行带宽分配管理的问题。这里通过在OVS上配置QoS策略来对复用链路上的两类数据流量进行带宽分配管理。可采用的方法是:基于端口eth7创建QoS和队列(Queue),并设置各队列的优先级和最低保障带宽,为数据平面和控制平面提供最低保证带宽的QoS。

1)删除OVS配置,当用户需要删除OVS上的网桥、QoS、队列时,可以参考下面命令。

ovs-vsctl del-br0   #删除网桥

ovs-vsctl del-br1

ovs-vsctl del-br2

ovs-vsctl -- --all destroy qos  #删除QoS

ovs-vsctl -- --all destroy queue   #删除队列

注:若想要删除网桥上的端口,可使用命令ovs-vsctl del-port [br name] [port name]

2)创建网桥,并且设置网桥的DPID。可参考如下命令。

ovs-vsctl add-br br0 -- set bridge br0 other_config:Datapath-id=0000000000000001    #新建网桥br0,并为br0配置一个DPID

ovs-vsctl add-br br1  #新建网桥br1

ovs-vsctl add-br br2  #新建网桥br2

3)配置网桥,包括在网桥上添加物理端口、在网桥上设置控制器、设置网桥的IP地址、设置网桥支持的OpenFlow协议版本。可参考如下命令。

ovs-vsctl add-port br0 eth2      #为网桥br0添加物理以太网口eth2

ovs-vsctl add-port br0 eth3      #为网桥br0添加物理以太网口eth3

ovs-vsctl add-port br0 eth4      #为网桥br0添加物理以太网口eth4

ovs-vsctl add-port br0 eth5      #为网桥br0添加物理以太网口eth5

ovs-vsctl add-port br0 eth6      #为网桥br0添加物理以太网口eth6

ovs-vsctl set-controller br0 tcp:192.168.87.200:6633    #连接控制器,网桥br0与IP为192.168.87.200的控制器通过TCP的6633端口建立连接

ovs-vsctl set bridge br0 fail_mode=secure      #指定br0以secure模式连接控制器

ovs-vsctl set bridge br0 protocols=OpenFlow10,OpenFlow12,OpenFlow13     #设置支持OpenFlow版本号

ovs-vsctl add-port br1 eth0      #为网桥br1添加物理以太网口eth0

ovs-vsctl add-port br1 eth1      #为网桥br1添加物理以太网口eth1

sudo ifconfig br1 192.168.87.1         #设定网桥IP地址

ovs-vsctl add-port br2 eth7      #为网桥br2添加物理以太网口eth7

4)连接网桥,OVS支持多个网桥之间的内部连接,连接时需要创建虚拟端口Patch。一下命令实现第(2)节图的配置,其他网桥的连接可参考此命令。

sudo ovs-vsctl add-port br0 patch02 -- set interface patch02 type=patch options:peer=patch20         #为网桥br0添加patch类型的Interface:patch02,并设置对端patch interface:patch20

sudo ovs-vsctl add-port br1 patch12 -- set interface patch12 type=patch options:peer=patch21         #为网桥br1添加patch类型的Interface:patch12,并设置对端patch interface:patch21

sudo ovs-vsctl add-port br2 patch21 -- set interface patch21 type=patch options:peer=patch12         #为网桥br2添加patch类型的Interface:patch21,并设置对端patch interface:patch12

sudo ovs-vsctl add-port br2 patch20 -- set interface patch20 type=patch options:peer=patch02         #为网桥br0添加patch类型的Interface:patch02,并设置对端patch interface:patch20

5)设置基于端口的QoS,OVS支持基于端口设置QoS,下面命令是eth7处添加linux-htb(类似令牌桶算法)类型的QoS和队列,并设置最大速率和优先级。

ovs-vsctl set port eth7 qos=@newqos -- --id=@newqos create qos type=linux-htb queues=100=@q0,101=@q1 other_config:max-rate='ovs-vsctl get interface eth7 link-speed' -- --id=@q0 create queue other_config:priority=100 other_config:min-rate=10000000 -- --id=@q1 create queue other_config:priority=10 other_config:min-rate=10000000

6)设置流表,下面命令主要实现在网桥上删除和添加流表命令,并设置流表的优先级和队列号,其他类似的操作可参考此命令。

ovs-ofctl del-flows br2        #删除网桥br2下的流表

ovs-ofctl add-flow br2 in_port='ovs-vsctl get interface patch20 ofport',priority=60000,actions=enqueue:'ovs-vsctl get interface eth7 ofport':100          #给网桥br2添加流表:“流表优先级为60000,针对从端口patch20进入的数据分组,执行动作---入队,端口号eth7,队列号100”

ovs-ofctl add-flow br2 in_port='ovs-vsctl get interface patch21 ofport',priority=61000,actions=mod_vlan_vid:999,enqueue:'ovs-vsctl get interface eth7 ofport':101          #给网桥br2添加流表:“流表优先级为61000,针对从端口patch21进入的数据分组,执行动作---改变数据分组的VLAN id=999,然后入队,端口号分别eth7,队列号101”

ovs-ofctl add-flow br2 in_port='ovs-vsctl get interface eth7 ofport',priority=60000,actions=output:'ovs-vsctl get interface patch20 ofport'          #给网桥br2添加流表:“流表优先级为60000,针对从端口eth7进入的数据分组,执行动作---经由patch20端口转发出去”

ovs-ofctl add-flow br2 in_port='ovs-vsctl get interface eth7 ofport',dl_vlan=999,priority=61000,actions=strip_vlan,output:'ovs-vsctl get interface patch21 ofport'          #给网桥br2添加流表:“流表优先级为61000,针对从端口eth7进入VLAN id=999的数据分组,执行动作---去掉数据分组的VLAN标签,然后经由patch21端口转发出去”

7)启动以太网端口

ifconfig eth0 up     #开启以太网网口eth0

ifconfig eth1 up     #开启以太网网口eth1

ifconfig eth2 up     #开启以太网网口eth2

       .

       .

      .

ifconfig eth7 up     #开启以太网网口eth7

注:若虚拟机中没有以太网口需要添加 

命令:

sudo ip tuntap add dev eth6 mode tun                  #创建tun模式虚拟网卡,名为:eth6

sudo ip addr add 192.168.87.160/24 dev eth6     #指定虚拟网卡地址

想了解具体内容(tun/tap)可查看:https://www.junmajinlong.com/virtual/network/all_about_tun_tap/

如前面所述,实现数据控制平面的隔离需要根据数据分组的VLAN标签对其进行分类,并以此为基础提供有差别的QoS保障。采用的方法是在转发端口设置两个不同优先级的队列,并为每一个队列设置最小保证带宽,针对来自不同端口的数据分组各自进行排队,保障不同数据流的带宽。这样,通过设置不同的队列优先级,保证只有在处理完优先级较高的队列后才会处理优先级较低的队列。同时,基于流表实现了对不同类型数据流差别化的流表匹配策略,优先级高的流规则先于优先级较低的流规则进行匹配,匹配后的动作也先于优先级低的流规则执行。

三、Mininet安装使用及简单案例

3.1软件安装

1)本地安装Mininet源代码

首先获取Mininet源代码:

git clone git://github.com/mininet/mininet

注:如没有git,安装命令:sudo apt-get install git

获取Mininet源代码后即可安装Mininet,安装Mininet的命令是:

mininet/util/install.sh [options]

options 包括:

①-a  :此命令将安装Mininet VM中的所有工具,包括Open vSwitch、Wireshark抓包工具和POX,默认情况下这些工具安装在用户的主目录(root目录)下。

②-nfv :此命令默认安装Mininet、User Switch和Open vSwitch

③-s mydir :此命令可以将Mininet安装在指定的目录下,而不是默认主目录。

出现Enjoy Mininet表示安装成功!!!

2)安装Mininet文件包

如果更新过Ubuntu或者Mininet,在安装前可以运行以下命令以确保删除Mininet、Open vSwitch以前版本的痕迹,否则影响新版本的安装。

sudo rm -rf /usr/local/bin/mn /usr/local/bin/mnexec \
    /usr/local/lib/python*/*/*mininet* \
    /usr/local/bin/ovs-*  /usr/local/sbin/ovs-*

安装Mininet文件命令:

sudo apt-get install mininet

Mininet安装完成后,验证openvswitch-controller是否在运行,如果正在运行,应该将其停止,以确保Mininet在启动时可以指定自己的控制器。

sudo service openvswitch-controller stop
sudo update-rc.d openvswitch-controller disable

Mininet安装完成后,即可使用Mininet创建模拟的SDN实验网络。为检验网络搭建是否可以正常通信,一般的做法是使用ping命令在两个主机之间进行ping操作,同样,在Mininet中可以使用如下命令直接检验Mininet是否安装成功。

sudo mn --test pingall

Mininet安装成功后,只需要用如下命令即可启动Mininet。

sudo mn

执行上述命令后,会创建默认的一个小型测试网络。

经过短暂时间的等待即可进入以“mininet>”引导的命令行界面。进入“mininet>”命令行界面后,默认拓扑创建成功,即将拥有一个一台控制节点(Controller)、一台交换机(Switch)和两台主机(Host)的网络。

3.2使用说明

Mininet除了创建默认的网络拓扑之外,还提供了丰富的参数设定方式用来设定网络拓扑、交换机、控制器、MAC地址、链路属性等,以满足使用者在仿真过程中多样性的需求。

(1)设定网络拓扑

--topo 用于指定OpenFlow的网络拓扑。Mininet已经为大多数应用实现了5中类型的OpenFlow网络拓扑,分别为Tree、Single、Reversed、Linear和Minimal。缺省情况下,创建的是Minimal拓扑,该拓扑为一个交换机与两个主机相连;

--topo single ,n  则表示1个OpenFlow交换机下挂连接n个主机;Reversed与Single类型相似,区别在于Single的主机编号和相连的交换机端口编号同序,而Reversed的主机编号和相连的交换机端口编号反序;

--topo linear,n  则表示将创建n个OpenFlow交换机,且每个交换机只连接一个主机,并且所有交换机连接成直线;

--topo tree,depth=n,fanout=m  则表示创建一个树形拓扑,深度是n,扇出是m,例如,当depth=2,fanout=8时,将创建9个交换机连接64个主机(每个交换机连接8个设备,设备中包括交换机及主机)。

--custom :在上述已有拓扑的基础上,Mininet支持自定义拓扑,使用一个简单的Python API即可,例如导入自定义的mytopo。

sudo mn --custom ~/mininet/custom/topo-2sw-2host.py --topo mytopo -- test pingall

 例如:

创建8个主机,2个交换机的拓扑。

创建文件:topo-2sw-8host.py

输入命令:

sudo mn --custom  ~/mininet/custom/topo-2sw-8host.py --topo mytopo --switch ovs,protocol=OpenFlow13 --controller=remote,ip=192.168.0.103,port=6633

 打开控制器可看见创建的拓扑图如下:

(2)设置交换机

--switch :Mininet支持4类交换机,分别是UserSwitch、OVS交换机、OVSLegacyKernelSwitch和IVS交换机。其中,运行在内核空间交换机的性能和吞吐量要高于用户空间交换机,可以通过运行iperf命令测试链路的TCP带宽速率来验证。

sudo mn --switch ovsk --test iperf

 此外,在switch属性中添加protocols参数时可以指定OpenFlow协议版本,例如OpenFlow v1.0和OpenFlow v1.3的指定。

sudo mn --topo single,3 --controller remote,ip=[controller IP] -- switch ovsk,protocols=OpenFlow10
sudo mn --topo single,3 --controller remote,ip=[controller IP] -- switch ovsk,protocols=OpenFlow13

 使用以下命令查看不同OpenFlow版本的OVS交换机信息。

ovs-ofctl -O OpenFlow10 show s1
ovs-ofctl -O OpenFlow13 show s1

(3)设置控制器

--controller :通过参数设置的控制器可以是Mininet默认的控制器(NOX)或者虚拟机之外的远端控制器,如Floodlight、POX等,指定远端控制器的方法如下。

sudo mn --controller=remote,ip=[controller IP],port=[controllerlistening port]

(4)配置MAC地址

--mac :设置MAC地址的作用是增强设备MAC地址的易读性,即将交换机和主机的MAC地址设置为一个较小的、唯一的、易读的ID,以便在后续工作中减少对设备识别的难度。

sudo mn -mac

(5)设置链路属性

--link :链路属性可以是默认Link及TCLink。将链路类型指定为TC后,可以进一步指定具体参数。具体参数命令显示如下。

sudo mn --link tc,bw=[bandwidth],delay=[delay time],loss=[loss rate],max_que_size=[queue size]

bw表示链路带宽,用Mbit/s表示单位;延迟delay以字符串形式表示,如"5 ms"、"100 us"、"1 s";loss表示数据分组丢失率的百分比,用0~100的一个百分数表示;max_queue_size表示最大排队长度,使用数据分组的数量表示。

3.3应用案例

3.3.1案例准备

本案例使用Floodlight控制器。

在windows下打开idea,导入Floodlight源代码。

(可用git直接clone下Floodlight源代码,可参考官网安装:https://floodlight.atlassian.net/wiki/spaces/floodlightcontroller/pages/1343544/Installation+Guide

运行Main.class,然后在虚拟机中运行Mininet连接控制器命令如下:

sudo mn --switch ovs,protocols=OpenFlow13 --controller=remote,ip=192.168.0.102,port=6633

注:上述ip是控制器所在主机的ip地址

3.3.2案例说明

本案例实现网络测量,其中包括带宽测量、丢包率和时延。

在Floolight源码src/main/java目录下创建net.floodlightcontroller.networkmeter模块,然后在此模块下创建NetworkMeter、BandMeter、PacketLossMeter、TimeDelayMeter四个类,用来实现网络测量、带宽测量、丢包率、时延,因为我们实现对网络测量的实时监测,所以要使用多线程,从而创建NetworkMeterThread类实现。

(1)对NetworkMeter类简单描述:(测试控制器与交换机建立连接(握手成功)接收到PACKET_IN消息)

public class NetworkMeter implements IFloodlightModule, IOFMessageListener {

    protected IFloodlightProviderService floodlightProvider;  //声明Floodlight控制器


    /**
     * 接收PACKET_IN消息
     * @param sw the OpenFlow switch that sent this message
     * @param msg the message
     * @param cntx a Floodlight message context object you can use to pass
     * information between listeners
     * @return
     */
    @Override
    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {

        System.out.println("receive PACKET_IN");
        return Command.CONTINUE;  //以后调整模块接收PACKET_IN的顺序,为了防止之后的模块接收不到PACKET_IN消息。  CONTINUE:继续遍历  STOP:跳出循环终止
    }

    @Override
    public String getName() {
        return NetworkMeter.class.getSimpleName();   //返回模块名称
    }

    @Override
    public boolean isCallbackOrderingPrereq(OFType type, String name) {
        return false;
    }

    @Override
    public boolean isCallbackOrderingPostreq(OFType type, String name) {
        return false;
    }

    @Override
    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
        return null;
    }

    @Override
    public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
        return null;
    }


    /**
     * 说明模块依赖关系
     * @return
     */
    @Override
    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
        Collection<Class< ? extends IFloodlightService>> list=new ArrayList<Class< ? extends IFloodlightService>>();
        list.add(IFloodlightProviderService.class);
        return list;
    }


    //执行时先执行init,在执行startup
    @Override
    public void init(FloodlightModuleContext context) throws FloodlightModuleException {
        floodlightProvider =context.getServiceImpl(IFloodlightProviderService.class);//获取服务实例

    }

    @Override
    public void startUp(FloodlightModuleContext context) throws FloodlightModuleException {
        floodlightProvider.addOFMessageListener(OFType.PACKET_IN,this); //添加监听器

    }
}

 注:运行前需要在src\main\resources\META-INF\services\net.floodlightcontroller.core.module.IFloodlightModule中添加net.floodlightcontroller.networkmeter.NetworkMeter

在src/main/resources/floodlightdefault.properties中添加net.floodlightcontroller.networkmeter.NetworkMeter,也就是在Floodlighth中注册一下此模块

(2)此时运行控制器(Mininet在控制器开启前或后都行),然后在虚拟机上启动的Mininet中输入命令:h1 ping h2,控制器会输出结果:

在网页http://Controller-ip:8080/ui/pages/index.html中查看到有一个交换机和2个主机,如下图所示。

3.3.3案例具体实现

(1)NetworkMeter类

/**
 * 网络测量:带宽测量、丢包率、时延
 */
public class NetworkMeter implements IFloodlightModule, IOFMessageListener {


    //Floodlight中所有的管理例如链路管理和设备管理都有自己的Service,所以需要声明指定的Service
    protected IFloodlightProviderService floodlightProvider;  //声明Floodlight控制器
    protected IOFSwitchService switchService;
    protected ILinkDiscoveryService linkService;

    protected NetworkMeterThread networkMeterThread;   //声明线程变量

    //声明网络测量具体操作的对象
    protected BandMeter bandMeter;
    protected PacketLossMeter packetLossMeter;
    protected TimeDelayMeter timeDelayMeter;



    /**
     * 接收PACKET_IN消息
     * @param sw the OpenFlow switch that sent this message
     * @param msg the message
     * @param cntx a Floodlight message context object you can use to pass
     * information between listeners
     * @return
     */
    @Override
    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {

        //System.out.println("receive PACKET_IN");

        switch (msg.getType()){
            case PACKET_IN:
                Ethernet ethernet = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);//获取bcStore队列里面的消息
                if(timeDelayMeter.isDoingTimeDelayMeter(ethernet)){
                    NetworkStore networkStore=NetworkStore.getInstance();
                    System.out.println("*********处理PacketIn*********");
                    networkStore.handlePacketIn(ethernet.getPayload(),linkService);
                }
                break;
        }

        return Command.CONTINUE;  //以后调整模块接收PACKET_IN的顺序,为了防止之后的模块接收不到PACKET_IN消息。  CONTINUE:继续遍历  STOP:跳出循环终止
    }

    @Override
    public String getName() {
        return NetworkMeter.class.getSimpleName();   //返回模块名称
    }


    /**
     * 用来调整各个模块之间接收Packet_IN消息的顺序
     * @param type the object type to which this applies
     * @param name the name of the module
     * @return
     */
    @Override
    public boolean isCallbackOrderingPrereq(OFType type, String name) {
        return false;
    }

    /**
     * 用来调整各个模块接收Packet_IN消息的顺序
     * @param type the object type to which this applies
     * @param name the name of the module
     * @return
     */
    @Override
    public boolean isCallbackOrderingPostreq(OFType type, String name) {

        //针对PACKET_IN消息调整顺序,并且是在linkdiscovery模块之前,因为linkdiscovery模块是Floodlight下发PACKET_IN消息的第一个模块
        return (type.equals(OFType.PACKET_IN)&&(name.equals("linkdiscovery")));
    }



    @Override
    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
        return null;
    }

    @Override
    public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
        return null;
    }


    /**
     * 说明模块依赖关系
     * @return
     */
    @Override
    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
        Collection<Class< ? extends IFloodlightService>> list=new ArrayList<Class< ? extends IFloodlightService>>();
        list.add(IFloodlightProviderService.class);
        return list;
    }


    //执行时先执行init,在执行startup
    @Override
    public void init(FloodlightModuleContext context) throws FloodlightModuleException {
        floodlightProvider =context.getServiceImpl(IFloodlightProviderService.class);//获取Floodlight服务实例
        switchService=context.getServiceImpl(IOFSwitchService.class);
        linkService=context.getServiceImpl(ILinkDiscoveryService.class);

        this.bandMeter=new BandMeter();
        this.packetLossMeter=new PacketLossMeter();
        this.timeDelayMeter=new TimeDelayMeter();

        networkMeterThread=new NetworkMeterThread(this);  //构造networkMeterThread对象时把当前对象NetworkMeter传给networkMeterThread的构造函数

    }

    @Override
    public void startUp(FloodlightModuleContext context) throws FloodlightModuleException {

        //添加监听器,只能添加连接建立(握手成功)以后的数据包比如PACKET_IN,像FlowStatsReply等统计数据包是在握手阶段创建,所以不能添加监听器获取
        floodlightProvider.addOFMessageListener(OFType.PACKET_IN,this);


        networkMeterThread.start();   //启动线程 因为是继承Thread类,不是Runnable接口所以调用start()方法,而不是run()方法

    }

    //获取到所有交换机
    public IOFSwitchService getSwitchService() {

        return switchService;
    }

    public BandMeter getBandMeter() {
        return bandMeter;
    }



    public PacketLossMeter getPacketLossMeter() {

        return packetLossMeter;
    }



    public TimeDelayMeter getTimeDelayMeter() {

        return timeDelayMeter;
    }

    public ILinkDiscoveryService getLinkService() {
        return linkService;
    }

    /**
     * FlowStatsReply的一个中继
     * @param reply
     */
    public static void handleFlowStatsReply(OFFlowStatsReply reply, IOFSwitchBackend sw){
        NetworkStore networkStore =NetworkStore.getInstance();
        networkStore.handleFlowStatsReply(reply, sw);

    }

    /**
     * DescStatsReply的一个中继
     * @param reply
     */
    public static void handleDescStatsReply(OFPortDescStatsReply reply){
        NetworkStore networkStore=NetworkStore.getInstance();
        networkStore.handleDescStatsReply(reply);
    }

    /**
     * EchoReply的一个中继
     * @param reply
     */
    public static void handleEchoReply(OFEchoReply reply){
        NetworkStore networkStore=NetworkStore.getInstance();
        networkStore.handleEchoReply(reply);
    }

    /**
     * PortStatsReply的一个中继
     * @param reply
     * @param sw
     */
    public static void handPortStatsReply(OFPortStatsReply reply, IOFSwitchBackend sw) {
        NetworkStore networkStore = NetworkStore.getInstance();
        networkStore.handlePortStatsReply(reply,sw);
    }
}

(2)NetworkMeterThread类

在线程实现里面需要注意一个思路,就是控制器需要下发给数据平面数据包,所以数据平面获取到控制器中devicemanager模块(此模块负责网络设备的追踪与管理)中所有交换机,然后去遍历List拿到每一个交换机,最后对每一个交换机下发一个数据包。

具体实现:

/**
 * 添加多线程,让网络测量实现周期性/实时性监测
 */
public class NetworkMeterThread extends Thread{

    protected NetworkMeter networkMeter;

    public NetworkMeterThread(NetworkMeter nm) {
        this.networkMeter=nm;

    }

    public void run(){
        while (true) {
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //清空clear
            NetworkStore networkStore=NetworkStore.getInstance();
            networkStore.calCurrentBand();
            networkStore.nextMeterBegin();

            //获取所有交换机
            IOFSwitchService switchService=networkMeter.getSwitchService();

            //遍历所有交换机,获取交换机的DPID
            for(DatapathId datapathId: switchService.getAllSwitchDpids()){
                IOFSwitch sw= switchService.getSwitch(datapathId);  //通过DPID获取到交换机
                if(sw==null){
                    Log.error("sw is null");
                    continue;
                }

                //TODO
                networkMeter.getBandMeter().doBand(sw);  //带宽
                networkMeter.getPacketLossMeter().doPacketLoss(sw);  //丢包率

            }

            //时延
            networkMeter.getTimeDelayMeter().doTimeDelay(networkMeter);

        }
    }
}

注:此处自行创建一个Log类,打印信息。

(3)NetworkStore类

因为进行网络测量时,需要把测试的结果保存起来,所以创建NetworkStore类。

此类同时也完成在NetworkMeter类中处理接收到数据包(*****Reply)函数的具体实现,以便完成相应存储。

/**
 * 存储类,以便于进行网路预测
 */
public class NetworkStore {

    //现在信息 -----  历史信息
    protected static NetworkStore networkStore;
    protected List<LinkDataInfo>  currentLinkStatus;
    protected List<LinkDataInfo>  historyLinkStatus;
    protected List<LinkTimeInfo>  linkTimeStatus;

    protected final static int echoMessageLength=2;

    protected long maxBand;

    public long getMaxBand() {
        return maxBand;
    }

    public void setMaxBand(long maxBand) {
        this.maxBand = maxBand;
    }

    public NetworkStore() {
        currentLinkStatus=new ArrayList<LinkDataInfo>();
        historyLinkStatus=new ArrayList<LinkDataInfo>();
        linkTimeStatus = new ArrayList<LinkTimeInfo>();
    }

    /**
     * 单例模式
     * @return
     */

    public static synchronized NetworkStore getInstance(){   //关键字synchronized: 表示若有多个地方同时请求NetworkStore对象时,会按照一定策略逐一分配,防止线程阻塞
        if(networkStore==null){
            networkStore=new NetworkStore();
        }
        return networkStore;
    }

    /**
     * 处理Packet_In消息,获取传输总时延
     * @param payload
     * @param linkService
     */
    public void handlePacketIn(IPacket payload, ILinkDiscoveryService linkService){
        System.out.println("****************收到PacketIn*******************");
        IPv4 ip=(IPv4)payload; //封装时将IP数据报封装在ethernet帧中,调用时ehernet.getPayload就得到第三层的数据包
        Data data=(Data)ip.getPayload(); //获取IP包中封装的信息timeStamp
        String mess[] =new String(data.getData()).split("<>");
        if(mess.length!=5){
            Log.error("length is not 5!!!");
            return ;
        }
        SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        Date currentTime=new Date();
        long allTime=0;
        try {
            Date sendTime=df.parse(mess[0]);
            allTime=currentTime.getTime()-sendTime.getTime();  //当前时间-发送时间
        } catch (ParseException e) {
            e.printStackTrace();
        }

        System.out.println("**************所有时间allTime:"+allTime+" *****************");

        //进行存储工作
        Map<Link, LinkInfo> links=linkService.getLinks();
        for(Link l:links.keySet()){
            if(l.getSrc().equals(DatapathId.of(mess[1]) ) &&
                l.getSrcPort().getPortNumber() ==Integer.parseInt(mess[2]) &&
                l.getDst().equals(DatapathId.of(mess[3]) ) &&
                l.getDstPort().getPortNumber() ==Integer.parseInt(mess[4])){

                LinkTimeInfo linkTimeInfo=new LinkTimeInfo();
                linkTimeInfo.setLink(l);
                linkTimeInfo.setAllTime(allTime);
                linkTimeStatus.add(linkTimeInfo);
                break;

            }
        }

    }

    /**
     *处理EchoReply的数据包,计算链路时延
     * @param reply
     */
    public void handleEchoReply(OFEchoReply reply){
        if (reply.getData().length<=0){  //判断EchoReply是否是需要的Reply
            return ;
        }
        String[] data=new String(reply.getData()).split("<>");
        if(data.length!=echoMessageLength){
            Log.error("*********length is not 2!!!*********");
            return;
        }
        SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        Date currentTime=new Date();
        long Time=0;
        try {
            Date sendTime=df.parse(data[0]);  //封装时data[0]放的是时间戳
            Time=currentTime.getTime() -sendTime.getTime();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        //更新
        for(LinkTimeInfo lti:linkTimeStatus){  //linkTimeStatus为之前存储过数据的数据结构
            if(lti.getLink().getSrc().equals(DatapathId.of(data[1]))){
                lti.setControllerToSrcSwTime(Time/2);
            }
            if(lti.getLink().getDst().equals(DatapathId.of(data[1]))){
                lti.setControllerToDesSWTime(Time/2);
            }
            if(lti.getControllerToDesSWTime() !=-1 && lti.getControllerToSrcSwTime() !=-1){
                long delay=lti.getAllTime()-lti.getControllerToDesSWTime()-lti.getControllerToSrcSwTime();
                lti.setDelay(delay>=0?delay : 0);
                Log.info("***********时延:"+lti.getDelay()+"***********");

            }

        }


    }


    /**
     * 处理DescStatsReply,获取当前带宽
     * @param reply
     */
    public void handleDescStatsReply(OFPortDescStatsReply reply){
        long speed=0;
        Log.info("***********收到DescStatsReply**************");
        List<OFPortDesc> entries=reply.getEntries();
        for(OFPortDesc e:entries){
            speed=e.getCurrSpeed();
            if(speed>0)
                break;
        }
        NetworkStore.getInstance().setMaxBand(speed);



    }

    /**
     * 处理PortStatsReply数据包
     * @param reply
     * @param sw
     */
    public void handlePortStatsReply(OFPortStatsReply reply, IOFSwitchBackend sw) {

        List<OFPortStatsEntry> entries=reply.getEntries();
        for(OFPortStatsEntry e:entries){
            System.out.println("*************接收的丢包数:"+e.getRxDropped()+"************");
        }
    }


    /**
     * 处理FlowStatsReply
     * @param reply
     * @param sw
     */
    public void handleFlowStatsReply(OFFlowStatsReply reply, IOFSwitchBackend sw){
        OFSwitch fromSW=null;  //源交换机
        OFSwitch toSW=null;    //目的交换机
        OFPort in_Port=null;    //接入端口
        OFPort out_Port=null;   //输出端口
        long byteCount=0;   //匹配字节数
        long maxBand=0;     //最大带宽
        long currentBand=0; //当前带宽
        Log.info("*******************收到FlowStatsReply******************");

        fromSW=toSW=(OFSwitch)sw;
        List<OFFlowStatsEntry> entries= reply.getEntries();
        for(OFFlowStatsEntry e:entries){
            byteCount=e.getByteCount().getValue();
            in_Port=e.getMatch().get(MatchField.IN_PORT);
            if(in_Port==null){  //匹配域中并没有指定入端口,所以此流表项要发给控制器
                in_Port=OFPort.ALL;
            }

            //得到out_port
            List<OFInstruction> instruction=e.getInstructions();  //得到流表项的指令集
            for(OFInstruction i:instruction){
                if(i instanceof OFInstructionApplyActions){ //如果当前的instruction是OFInstructionApplyActions类/接口的一个实例
                    List<OFAction> actions=((OFInstructionApplyActions) i).getActions();
                    for(OFAction a:actions){
                        if(a.getType()== OFActionType.OUTPUT){
                            out_Port=((OFActionOutput)a).getPort();
                            break;
                        }
                    }

                }else
                    continue;
            }

            //默认的流表项不需要存储   默认流表项:进/出端口的值小于1
            if(in_Port==OFPort.ALL || out_Port.getPortNumber()<1 ){
                continue;
            }
            //流表项非默认流表项,则需要构造链路信息对象

           //  maxBand=calculateMaxBand(fromSW,toSW,in_Port,out_Port); 基于物理交换机获取的最大带宽
            maxBand=NetworkStore.getInstance().getMaxBand();

            LinkDataInfo linkData=new LinkDataInfo();
            linkData.setIn_Port(in_Port);
            linkData.setOut_Port(out_Port);
            linkData.setByteCount(byteCount);
            linkData.setFromSW(fromSW);
            linkData.setToSW(toSW);
            linkData.setMaxBand(maxBand);

           // Log.info("对象构造完毕");

            //存储
            storeLinkStatus(linkData);

        }
    }

    //存储
    public void storeLinkStatus(LinkDataInfo linkDataInfo){
        /**
         * 流表项种类:
         * 入端口 :1   出端口 :2   匹配 :IP               100kb
         * 入端口 :1   出端口 :2   匹配 :   TCP           100kb
         * 入端口 :1   出端口 :2   匹配 :       HTTP      200kb
         *
         * 所以需要合并流表项
         */

        if(currentLinkStatus.size()==0) //第一次
            currentLinkStatus.add(linkDataInfo);
        else{
            for(LinkDataInfo l:currentLinkStatus){

                //判断是否是同一链路信息,若是更新链路信息的相关字段,如不是则将新的链路信息添加进来       源和目的交换机及源和目的端口相同就是同一链路信息
                if(l.getFromSW().getId() ==linkDataInfo.getFromSW().getId() &&
                l.getToSW().getId() == linkDataInfo.getToSW().getId()  &&
                l.getIn_Port().getPortNumber() ==linkDataInfo.getIn_Port().getPortNumber() &&
                l.getOut_Port().getPortNumber() ==linkDataInfo.getOut_Port().getPortNumber())
                {
                 l.setByteCount(l.getByteCount()+linkDataInfo.getByteCount());
                }
            }

            //当前存储的链路信息linkDataInfo,在所有存储中不存在,则添加此链路信息
            currentLinkStatus.add(linkDataInfo);

        }


    }

    public void nextMeterBegin(){ //下一次测量的开始
        historyLinkStatus.clear();

        for(LinkDataInfo l:currentLinkStatus){
            historyLinkStatus.add(l);
        }

        currentLinkStatus.clear();//清空LinkDataInfo保存之前的链路信息
        linkTimeStatus.clear();  //清空LinkTimeInfo保存之前链路上的时延信息

    }

    //计算链路的最大带宽
    public long calculateMaxBand(OFSwitch fromSW, OFSwitch toSW, OFPort inPort,OFPort outPort){
        long fromBand=0,toBand=0;

        //inport入口带宽
        OFPortDesc inPortDesc= fromSW.getPort(inPort);
        Set<OFPortFeatures> inFeatures=inPortDesc.getAdvertised(); //物理特性的声明
        //
        for(OFPortFeatures f:inFeatures){

            fromBand=f.getPortSpeed().getSpeedBps();   //单位bps
            System.out.println("******fromBand**********:"+fromBand);
            if(fromBand>0)
                break;   //inFeatures表示的属性有三个,只有第一个不是0,剩下全是0
        }

        //outPort 出口带宽
        OFPortDesc outPortDesc=toSW.getPort(outPort);
        Set<OFPortFeatures> outFeatures=outPortDesc.getAdvertised();
        for(OFPortFeatures f:outFeatures){
            toBand=f.getPortSpeed().getSpeedBps();
            if(toBand > 0)
                break;
        }

        System.out.println("-----------"+fromBand +"    "+toBand+"-----------");
        return (fromBand>=toBand?toBand:fromBand);
    }

    //计算当前带宽,输出
    public void calCurrentBand(){
        for(LinkDataInfo h:historyLinkStatus)
            for(LinkDataInfo c:currentLinkStatus){
                if(h.getFromSW().getId()==c.getFromSW().getId() &&
                       h.getToSW().getId()==c.getToSW().getId() &&
                       h.getIn_Port().getPortNumber()==c.getIn_Port().getPortNumber() &&
                       h.getOut_Port().getPortNumber()==c.getOut_Port().getPortNumber()){
                    long speed=(c.getByteCount()-h.getByteCount())/1;  //1秒钟之内增加的流量
                    float band=(float)(speed*1.0/c.maxBand);
                    System.out.println("currentSpeed: "+speed+"Bps"+"---------"+"currentBand: "+band*100+"%");
                }
            }
    }


}

//
class LinkDataInfo{  //交换机之间链路数据信息
    protected OFSwitch fromSW;  //源交换机
    protected OFSwitch toSW;    //目的交换机
    protected OFPort in_Port;    //接入端口
    protected OFPort out_Port;   //输出端口
    protected long byteCount;   //匹配字节数
    protected long maxBand;     //最大带宽
    protected long currentBand; //当前带宽

    public OFSwitch getFromSW() {
        return fromSW;
    }

    public void setFromSW(OFSwitch fromSW) {
        this.fromSW = fromSW;
    }

    public OFSwitch getToSW() {
        return toSW;
    }

    public void setToSW(OFSwitch toSW) {
        this.toSW = toSW;
    }

    public OFPort getIn_Port() {
        return in_Port;
    }

    public void setIn_Port(OFPort in_Port) {
        this.in_Port = in_Port;
    }

    public OFPort getOut_Port() {
        return out_Port;
    }

    public void setOut_Port(OFPort out_Port) {
        this.out_Port = out_Port;
    }

    public long getByteCount() {
        return byteCount;
    }

    public void setByteCount(long byteCount) {
        this.byteCount = byteCount;
    }

    public long getMaxBand() {
        return maxBand;
    }

    public void setMaxBand(long maxBand) {
        this.maxBand = maxBand;
    }

    public long getCurrentBand() {
        return currentBand;
    }

    public void setCurrentBand(long currentBand) {
        this.currentBand = currentBand;
    }
}

class LinkTimeInfo{
    protected Link link;  //链路信息
    protected long allTime=-1;
    protected long controllerToSrcSwTime=-1;  //控制器到源交换机的时延
    protected long controllerToDesSWTime=-1;  //控制器到目的交换机的时延
    protected long delay=-1;      //链路时延

    public Link getLink() {
        return link;
    }

    public void setLink(Link link) {
        this.link = link;
    }

    public long getAllTime() {
        return allTime;
    }

    public void setAllTime(long allTime) {
        this.allTime = allTime;
    }

    public long getControllerToSrcSwTime() {
        return controllerToSrcSwTime;
    }

    public void setControllerToSrcSwTime(long controllerToSrcSwTime) {
        this.controllerToSrcSwTime = controllerToSrcSwTime;
    }

    public long getControllerToDesSWTime() {
        return controllerToDesSWTime;
    }

    public void setControllerToDesSWTime(long controllerToDesSWTime) {
        this.controllerToDesSWTime = controllerToDesSWTime;
    }

    public long getDelay() {
        return delay;
    }

    public void setDelay(long delay) {
        this.delay = delay;
    }
}

(4)BandMeter类

/**
 * 带宽测量
 *    --主动测量  : 控制器下发消息
 *    --被动测量Flowremove  :流过期时会自动将流的相关信息上交给控制器
 *
 * 带宽预测
 *     --小波分析
 *     --深度学习
 *     --神经网络
 */
public class BandMeter {

    public BandMeter(){

    }

    /**
     * 下发指定报文
     *    OpenFlow1.3中有两种类型的统计
     *           --OFFlowStatsRequest  : 针对每一条流的信息
     *           --OFAggregateStatsRequest  : 针对所有流的信息
     * @param sw
     */
    public void doBand(IOFSwitch sw){
        //交换机下发statsrequest报文
        OFFlowStatsRequest.Builder statsRequest=sw.getOFFactory().buildFlowStatsRequest();  //根据交换机得到OpenFlow工厂,然后创建出statsrequest报文
        statsRequest.setTableId(TableId.ALL);
        statsRequest.setOutPort(OFPort.ANY);
        statsRequest.setOutGroup(OFGroup.ANY);
        statsRequest.setCookie(AppCookie.makeCookie(2,0));
        Log.info("************send statsRequest************");

        //交换机下发
        OFPortDescStatsRequest.Builder descRequest = sw.getOFFactory().buildPortDescStatsRequest();
        Log.info("************send descRequest*************");



        sw.write(descRequest.build());
        sw.write(statsRequest.build()); //把构造出来的request对象进行序列化,变成一个字节数组,通过交换机下发


    }
}

(5)PacketLossMeter类

/**
 * 丢包率
 */
public class PacketLossMeter {

    public PacketLossMeter() {

    }

    public void doPacketLoss(IOFSwitch sw){
        Collection<OFPortDesc> ports=sw.getEnabledPorts(); //获取交换机启用的接口(插网线或有连接亮灯的接口)
        for(OFPortDesc pd :ports){
            OFPortStatsRequest.Builder portStatsRequestBuild = sw.getOFFactory().buildPortStatsRequest();
            portStatsRequestBuild.setPortNo(pd.getPortNo());

            //发送OFPortStatsRequest包
            sw.write(portStatsRequestBuild.build());
        }

    }
}

(6)TimeDelayMeter类

/**
 * 时延
 */
public class TimeDelayMeter {


    public static byte[] destinationMACAddress={0x00,0x00,0x00,0x00,0x00,0x00}; //8C-C6-81-CE-53-33  --->-116,-58,-127,0-50,0x53,0x33
    public static byte[] sourceinationMACAddress={0x00,0x00,0x00,0x00,0x00,0x01};
    public final static int MacAddressLength=6;

    public TimeDelayMeter(){

    }

    /**
     * 判断当前处理的数据包是不是时延需要的数据包
     * @return
     */
    public boolean isDoingTimeDelayMeter(Ethernet ethernet){
        byte[] desmac= ethernet.getDestinationMACAddress().getBytes();
        byte[] sourcemac=ethernet.getSourceMACAddress().getBytes();
        if(desmac.length!=MacAddressLength || sourcemac.length!=MacAddressLength){
            return false;
        }
        for(int i=0;i<MacAddressLength;i++){
            if(desmac[i] !=destinationMACAddress[i] ||
              sourcemac[i] != sourceinationMACAddress[i]){
                System.out.println("****************isDoingTimeDelayMeter:false*****************");
                return false;
            }

        }
        System.out.println("****************isDoingTimeDelayMeter:True*****************");
        return true;
    }

    public void doTimeDelay(NetworkMeter networkMeter){
        //1.获取链路
        ILinkDiscoveryService linkService = networkMeter.getLinkService();
        Map<Link, LinkInfo> links = linkService.getLinks();  //links中存放所有路径的双向链路
        System.out.println("***************链路长度:"+links.size()+"****************");
        for(Link l:links.keySet()){
            //2.得到链路两端的交换机
            IOFSwitch fromSW = networkMeter.getSwitchService().getSwitch(l.getSrc());
            IOFSwitch toSW = networkMeter.getSwitchService().getSwitch(l.getDst());
            OFPort inPort = l.getSrcPort();
            OFPort outPort = l.getDstPort();

            //3.向交换机发送消息
            sendPacketOut(fromSW,inPort,toSW, outPort);   //发送PacketOut包后,可以得到从控制器到交换机-交换机再到控制器的总时延
            sendEchoRequest(fromSW);     //控制器向交换机发送EchoRequest后,可以得到控制器到交换机的时延
            sendEchoRequest(toSW);
            System.out.println("***********发送PacketOut,EchoRequest***************");
        }

        


    }

    /**
     * 控制器向交换机发送PacketOut包
     * @param fromSW
     * @param inPort
     * @param toSW
     * @param outPort
     */
    public void sendPacketOut(IOFSwitch fromSW, OFPort inPort, IOFSwitch toSW, OFPort outPort){
        OFPacketOut.Builder packetOutBuilder = fromSW.getOFFactory().buildPacketOut();

        //指定Action
        List<OFAction> actions =new ArrayList<OFAction>();
        actions.add(fromSW.getOFFactory().actions().output(inPort,Integer.MAX_VALUE)); //注意参数是inPort,因为报文从源交换机fromW发出,经过链路上的inPort才能进入目的交换机toSW。
        packetOutBuilder.setActions(actions);  //PacketOut包中存在一些转发指令

        //发送的消息:Ethernet  (以太帧格式包括:目的地址、源地址、类型(用来标志上一层使用的是什么协议)、数据、FCS)
        Ethernet eth=new Ethernet();
        eth.setDestinationMACAddress(destinationMACAddress);
        eth.setSourceMACAddress(sourceinationMACAddress);
        eth.setEtherType(EthType.IPv4);

        //发送的消息:IP
        IPv4 ip=new IPv4();
        ip.setSourceAddress(0);
        ip.setDestinationAddress(0);
        ip.setProtocol(IpProtocol.NONE);
        //时间戳timestamp
        StringBuilder sb=new StringBuilder(getCurruentTime());
        sb.append("<>").append(fromSW.getId())
            .append("<>").append(inPort.getPortNumber())
            .append("<>").append(toSW.getId())
            .append("<>").append(outPort.getPortNumber());
        String mess=new String(sb);
        Data data = new Data();  //  Data extend iPacket
        data.setData(mess.getBytes());
        ip.setPayload(data);
        eth.setPayload(ip);
        packetOutBuilder.setData(eth.serialize());

        fromSW.write(packetOutBuilder.build());
        System.out.println("********send PacketOut**********");
    }

    /**
     * 控制器向交换机发送EchoRequest
     * @param sw
     */
    public void sendEchoRequest(IOFSwitch sw){
        OFEchoRequest.Builder buildEchoRequest = sw.getOFFactory().buildEchoRequest();

        //时间戳timestamp
        StringBuilder sb=new StringBuilder(getCurruentTime());
        sb.append("<>").append(sw.getId());
        String mess=new String(sb);
        Data data=new Data();
        data.setData(mess.getBytes());
        buildEchoRequest.setData(data.serialize());

        System.out.println("********send EchoRequest**********");
        sw.write(buildEchoRequest.build());
    }

    /**
     * 得到当前的系统时间
     * @return
     */
    public String getCurruentTime(){
        SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        return df.format(new Date());
    }
}

 (7)修改Floodlight源代码

1)修改src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java代码,主要是处理StatsReply(统计数据包)时进行分类,是PORT_StatsReply还是FLOW_StatsReply,r如果是FLOW_StatsReply就是我们自行处理。

主要修改的内容如下:

//添加握手之前的信息
		void processOFStatsReply(OFStatsReply m) {
			switch(m.getStatsType()) {
			case PORT_DESC:

				//处理descStatsReply
				NetworkMeter.handleDescStatsReply((OFPortDescStatsReply) m);

				processPortDescStatsReply((OFPortDescStatsReply) m);
				break;

			//添加
			case FLOW: //networkmeter更改
				NetworkMeter.handleFlowStatsReply((OFFlowStatsReply)m,sw);
				break;

			case PORT: //networkMeter更改
				NetworkMeter.handPortStatsReply((OFPortStatsReply)m,sw);
				break;

			default:
				unhandledMessageReceived(m);
			}
		}

2)修改src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java

		void processOFMessage(OFMessage m)
				throws IOException {
			// Handle Channel Handshake
			if (!state.channelHandshakeComplete) {
				switch(m.getType()) {
				case HELLO:
					processOFHello((OFHello)m);
					break;
				case ERROR:
					processOFError((OFErrorMsg)m);
					break;
				case FEATURES_REPLY:
					processOFFeaturesReply((OFFeaturesReply)m);
					break;
				case EXPERIMENTER:
					processOFExperimenter((OFExperimenter)m);
					break;
					/* echos can be sent at any time */
				case ECHO_REPLY:
					processOFEchoReply((OFEchoReply)m);
					break;
				case ECHO_REQUEST:
					processOFEchoRequest((OFEchoRequest)m);
					break;
				case PORT_STATUS:
					processOFPortStatus((OFPortStatus)m);
					break;
				default:
					illegalMessageReceived(m);
					break;
				}
			}
			else{
				switch(m.getType()){
				case ECHO_REPLY:
					//添加EchoReply处理函数
					NetworkMeter.handleEchoReply((OFEchoReply)m);

					processOFEchoReply((OFEchoReply)m);


					break;
				case ECHO_REQUEST:
					processOFEchoRequest((OFEchoRequest)m);
					break;
					// Send to SwitchManager and thus higher orders of control
				default:
					sendMessageToConnection(m);
					break;
				}
			}
		}

修改内容:

				//添加EchoReply处理函数
					NetworkMeter.handleEchoReply((OFEchoReply)m);

当链路建立完成时,我们在计算链路之间的时延时,需要发送EchoRequest数据包获取控制器到交换机之间的时间,而接收EchoReply数据包在Floodlight控制器的OFChannelHandler.java类中。

3.4测试结果

注:此处我没搞清Floodlight中获取到速度的单位,所以带宽计算出现问题。

注:由于在minnet仿真工具上测试,且拓扑简单所以不存在丢包情况

此时拓扑图:

控制器信息

mininet开启:

通过h1 ping h4测试

猜你喜欢

转载自blog.csdn.net/weixin_42070473/article/details/113256478