目录
1.3在Ubuntu18.04apt修改为国内的阿里云镜像源
1.4Ubuntu18.04的NAT8静态网络配置及连接互联网
2.1OVS的构建与安装(可直接安装Mininet包括OVS)
一、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测试