Mininet
Mininet 是一种网络仿真工具,可以创建包含虚拟主机、交换机、控制器和链路的网络。Mininet 主机运行标准的 Linux 网络软件,其交换机支持 OpenFlow,实现高度灵活的定制路由和软件定义网络(SDN)。
安装
准备好 Ubuntu 或者 Debian 操作系统,然后获取 Mininet 的源码:
git clone https://github.com/mininet/mininet
上述 git
命令将获取最新版本的 Mininet。如果想切换其他特定版本,可以明确检出该版本:
cd mininet
git tag # 列出可用版本
git checkout -b mininet-2.3.0 2.3.0 # 希望安装的版本
cd ..
安装命令为:
mininet/util/install.sh [选项]
install.sh
脚本的常用选项包括:
-a
:安装所有内容,包括 Open vSwitch 等依赖项,以及 OpenFlow Wireshark 解码器和 POX 等附加工具。默认情况下,这些工具将安装在主目录中创建的文件夹内。-nfv
:安装 Mininet、OpenFlow 参考交换机和 Open vSwitch。-s mydir
:在其他选项之前使用此选项,将源码放置在指定目录 mydir 中,而不是放在主目录中。
可以通过以下命令了解其他有用的选项(例如安装 OpenFlow Wireshark 解码器,如果安装的 Wireshark 版本中尚未包含该解码器):
install.sh -h
安装完成后,测试 Mininet 的基本功能:
sudo mn --switch ovsbr --test pingall
入门
1 Mininet 日常使用
1.1 查看帮助信息
sudo mn -h
1.2 启动 Wireshark
sudo wireshark &
1.2.1 如果未安装 Wireshark(显示“找不到命令”错误)
如果没有安装 Wireshark 和 OpenFlow 插件,可以尝试使用 Mininet 的 install.sh
脚本来安装它们,如下所示:
cd ~
git clone https://github.com/mininet/mininet # 如果之前没有克隆
mininet/util/install.sh -w
1.3 与主机和交换机交互
启动一个最小拓扑并进入 Mininet 客户端:
sudo mn
默认拓扑是 minimal
拓扑,包括一个连接两个主机的 OpenFlow 内核交换机和一个 OpenFlow 参考控制器。此拓扑也可以在命令行中使用 --topo=minimal
来指定。Mininet 还提供其他现成的拓扑,可以查看 mn -h
输出中的 --topo
部分。
此时,虚拟机中运行着四个实体:2 个主机进程、1 个交换机进程、1 个基本控制器。
同时,会进入 Mininet 命令行。
在 Wireshark 窗口中,可以看到内核交换机连接到参考控制器。
显示 Mininet CLI 命令:
mininet> help
显示节点:
mininet> nodes
显示链接:
mininet> net
转储所有节点的信息:
mininet> dump
此时应该会看到列出的交换机和两个主机。
如果在 Mininet CLI 中输入的第一个字符串是主机、交换机或控制器的名称,则该命令将在该节点上执行。在主机进程上运行命令:
mininet> h1 ifconfig -a
此时应该会看到主机的 h1-eth0
和回环(lo
)接口。请注意,当在主系统中运行 ifconfig
时,无法看到此接口(h1-eth0
),因为它是特定于主机进程的网络命名空间。
相反,交换机默认运行在根网络命名空间中,因此在“交换机”上运行命令与在常规终端中运行命令相同:
mininet> s1 ifconfig -a
这将显示交换机接口以及虚拟机的外部连接(eth0
)。
1.4 测试主机之间的连通性
验证是否可以从主机 h1
ping 到主机 h2
:
mininet> h1 ping -c 1 h2
另一种更简便的测试方法是使用 Mininet CLI 内置的 pingall
命令,它会对所有节点成对 ping
测试:
mininet> pingall
1.5 运行一个简单的 Web 服务器和客户端
ping
并不是可以在主机上运行的唯一命令。Mininet 主机可以运行任何在底层 Linux 系统(或虚拟机)及其文件系统中可用的命令或应用程序,还可以输入 bash
命令,包括作业控制(&
、jobs
、kill
等)。
接下来,尝试在 h1
上启动一个简单的 HTTP 服务器,从 h2
发起请求,然后关闭该 Web 服务器:
mininet> h1 python -m http.server 80 &
mininet> h2 wget -O - h1
...
mininet> h1 kill %python
注意
对于 Python 3,HTTP 服务器命令是 http.server
;对于 Python 2,它是 SimpleHTTPServer
。请确保使用与运行的 Mininet 版本对应的正确命令。要确定 Mininet 使用的 Python 版本,可以输入以下命令:
mininet> py sys.version
'3.12.3 (main, Sep 11 2024, 14:17:37) [GCC 13.2.0]'
退出 CLI:
mininet> exit
1.6 清理环境
如果 Mininet 因某种原因崩溃,执行清理操作可能会解决问题:
sudo mn -c
2 高级启动选项
2.1 回归测试
例如:
sudo mn --test pingpair
此命令创建了一个最小拓扑,启动了 OpenFlow 参考控制器,运行了全对 ping 测试,然后销毁了拓扑和控制器。
另一个有用的测试是 iperf
(大约需要 10 秒完成):
sudo mn --test iperf
此命令创建了相同的 Mininet,分别在一台主机上运行 iperf 服务器,在另一台主机上运行 iperf 客户端。
2.2 拓扑大小和类型
默认拓扑是一个连接两个主机的单交换机。可以使用 --topo
更改为不同的拓扑,并为该拓扑的创建传递参数。例如,要验证一个交换机和三个主机之间的全对 ping 连通性:
运行回归测试:
sudo mn --test pingall --topo single,3
另一个示例是线性拓扑(每个交换机有一个主机,所有交换机连接成一条直线):
sudo mn --test pingall --topo linear,4
2.3 链路参数
Mininet 2.0 允许设置链路参数,这些参数甚至可以通过命令行自动设置:
sudo mn --link tc,bw=10,delay=10ms
mininet> iperf
...
mininet> h1 ping -c10 h2
如果每条链路的延迟为 10 毫秒,那么往返时间 (RTT) 应大约为 40 毫秒,因为 ICMP 请求经过两条链路(一个到交换机,一个到目标),ICMP 回复则经过返回的两条链路。
可以使用 Mininet 的 Python API 自定义每条链路。
2.4 详细信息
默认的详细程度是 info
,它会在启动和清理过程中打印 Mininet 的操作。使用 -v
参数可以查看完整的 debug
输出:
sudo mn -v debug
...
mininet> exit
这会打印出大量的详细信息。尝试使用 output
级别,它仅打印 CLI 输出,几乎没有其他信息:
sudo mn -v output
mininet> exit
在 CLI 之外,还可以使用其他详细程度级别,例如 warning,该级别用于回归测试中以隐藏不必要的功能输出。
2.5 自定义拓扑
使用简单的 Python API 也可以轻松定义自定义拓扑。在如下示例中,两个交换机直接连接,每个交换机连接一个主机:
"""自定义拓扑示例
两个直接连接的交换机,每个交换机连接一个主机:
主机 --- 交换机 --- 交换机 --- 主机
添加一个包含键值对的 'topos' 字典来生成新定义的拓扑,使得可以从命令行传入 '--topo=mytopo' 来调用该拓扑。
"""
from mininet.topo import Topo
class MyTopo( Topo ):
def build( self ):
# 新增主机和交换机
leftHost = self.addHost( 'h1' )
rightHost = self.addHost( 'h2' )
leftSwitch = self.addSwitch( 's3' )
rightSwitch = self.addSwitch( 's4' )
# 增加链路
self.addLink( leftHost, leftSwitch )
self.addLink( leftSwitch, rightSwitch )
self.addLink( rightSwitch, rightHost )
topos = { 'mytopo': ( lambda: MyTopo() ) }
提供自定义 Mininet 文件时,可以向命令行添加新的拓扑、交换机类型和测试。例如:
sudo mn --custom ./custom.py --topo mytopo --test pingall
2.6 ID = MAC
默认情况下,主机启动时会被随机分配 MAC 地址。这会增加调试难度,因为每次创建 Mininet 时,MAC 地址都会更改。
--mac
选项可以将主机的 MAC 和 IP 地址设置为简短、唯一且易于读取的 ID。
不使用 --mac
:
sudo mn
...
mininet> h1 ifconfig
"h1-eth0 Link encap:Ethernet HWaddr f6:9d:5a:7f:41:42
inet addr:10.0.0.1 Bcast:10.255.255.255 Mask:255.0.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:6 errors:0 dropped:0 overruns:0 frame:0
TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:392 (392.0 B) TX bytes:392 (392.0 B)"
mininet> exit
使用 --mac
:
sudo mn --mac
...
mininet> h1 ifconfig
"h1-eth0 Link encap:Ethernet HWaddr 00:00:00:00:00:01
inet addr:10.0.0.1 Bcast:10.255.255.255 Mask:255.0.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)"
mininet> exit
2.7 其他交换机类型
可以使用其他类型的交换机。例如,要运行用户空间交换机:
sudo mn --switch user --test iperf
注意
与之前使用内核交换机相比,此时 iperf 报告的 TCP 带宽要低得多。
如果进行 ping 测试,延迟明显增大,因为现在数据包必须经历内核到用户空间的转换。ping 时间也会更加不稳定,因为主机的用户空间进程可能会被操作系统调度进出。
另一种交换机类型是 Open vSwitch(OVS),它在 Mininet 虚拟机中已预装。iperf 报告的 TCP 带宽与 OpenFlow 内核模块相似,甚至可能更快:
sudo mn --switch ovsk --test iperf
3 Mininet CLI 命令
3.1 显示命令列表
要查看命令行界面(CLI)选项列表,先启动一个最小拓扑并保持其运行。构建 Mininet:
sudo mn
显示命令列表:
mininet> help
3.2 Python 解释器
如果在 Mininet 命令行中第一个词是 py
,则该命令将使用 Python 执行。每个主机、交换机和控制器都有一个关联的 Node 对象。
在 Mininet CLI 中运行:
mininet> py 'hello ' + 'world'
打印可访问的局部变量:
mininet> py locals()
接下来,使用 dir()
函数查看节点的可用方法和属性:
mininet> py dir(s1)
可以使用 help()
函数查看节点上可用方法的在线文档:
mininet> py help(h1) (按 "q" 退出阅读文档)
还可以执行变量的方法:
mininet> py h1.IP()
3.3 链路启用/禁用
禁用虚拟以太网对的两个部分:
mininet> link s1 h1 down
此时,会看到一个 OpenFlow 端口状态更改通知被生成。要重新启用链路:
mininet> link s1 h1 up
3.4 启动 XTerm
为 h1 和 h2 启动一个 xterm 终端窗口:
mininet> xterm h1 h2
Mininet自定义命令拓展实现
- 修改
mininet/net.py
,编写自定义的功能代码; - 修改
mininet/cli.py
,注册命令; - 修改
bin/mn
,加入到可执行文件中; - 重新安装 MiniNet 核心文件:
mininet/util/install.sh -n
增加网络性能测试命令
1 mininet/net.py
中定义 iperfSingle
函数
在两个主机间进行 iperf udp
测试,并在 server 端记录,实现该功能的 iperfSingle
函数写在 Mininet
类下,代码如下:
class Mininet(object):
... # 原始代码
def iperf_single(self, hosts=None, udp_bw='10M', period=60):
if not hosts:
return
assert len(hosts) == 2
client, server = hosts
filename = client.name[1:] + '.out'
output(f'*** iperf: testing bandwidth between {client.name} and {server.name}\n')
iperf_args = 'iperf -u'
bw_args = f'-b {udp_bw}'
print('*** Start server')
server.cmd(f'{iperf_args} -s -i 1 > ~/mn_log/{filename}&')
print('*** Start client')
client.cmd(f'{iperf_args} -t {period} -c {server.IP()} {bw_args} > ~/mn_log/client{filename}&')
2 mininet/net.py
中定义 iperfMulti
函数
依次为每台主机随机选择另一台主机作为 iperf
的服务端,通过调用 iperfSingle
,自身以客户端按照指定参数发送 UDP 流。
代码编写在 iperfSingle
之后:
import random
class Mininet(object):
... # 原始代码
def iperfSingle(self, hosts=None, udp_bw='10M', period=60):
... # 前面实现的方法
def iperfMulti(self, bw, period=60):
for client in self.hosts:
server = client
while server == client:
server = random.choice(self.hosts)
self.iperfSingle(hosts=[client, server], udp_bw=bw, period=period)
sleep(0.05)
sleep(period)
print('test has done')
3 mininet/cli.py
中注册 iperfmulti
命令
解析用户输入命令,net.py
定义的 iperf_multi
函数需要在 CLI
类中注册成命令:
class CLI(Cmd):
... # 原始代码
def do_iperfmulti(self, line):
"""Multi iperf UDP test between nodes"""
args = line.split()
if len(args) == 1:
self.mn.iperfMulti(args[0])
elif len(args) == 2:
self.mn.iperfMulti(args[0], float(args[1]))
else:
error('invalid number of args: iperfMulti udpBw period\n' +
'udpBw examples:1M 120\n')
4 bin/mn
中加入 iperf-multi
命令
修改后的 bin/mn
部分代码如下:
TESTS = {
name: True
for name in (
'pingall',
'pingpair',
'iperf',
'iperfudp',
'iperfmulti' # new
)
}
ALTSPELLING = {
'pingall': 'pingAll',
'pingpair': 'pingPair',
'iperfudp': 'iperfUdp',
'iperfmulti': 'iperfMulti' # new
}
5 重新编译 mininet
重新编译安装 mininet
:
mininet/util/install.sh -n
TIP
mininet
的安装默认使用 Python2,可以修改 util/install.sh
文件,指定 Python 版本。
重新创建网络,输入 iperf-m
,可用 Tab
键补全 iperfmulti
,从而可使用 iperfmulti
进行随机流量测试。