什么是Mininet?
Mininet是一个网络仿真器,或者更准确地说是一个网络仿真编排系统。它在单个Linux内核上运行一组终端主机,交换机,路由器和链接。它使用轻量级虚拟化使单个系统看起来像一个完整的网络,运行相同的内核,系统和用户代码。 Mininet主机的行为就像真机一样;你可以ssh
到它(如果你启动sshd
并将网络桥接到你的主机)并运行任意程序(包括安装在底层Linux系统上的任何东西。)你运行的程序可以通过看似真正的以太网发送数据包接口,具有给定的链接速度和延迟。数据包通过具有给定数量的排队的真实以太网交换机,路由器或中间盒进行处理。当两个程序(如iperf
客户端和服务器)通过Mininet进行通信时,测量的性能应该与两个(较慢的)本机的性能相匹配。
简而言之,Mininet的虚拟主机,交换机,链接和控制器是真实的 – 它们只是使用软件而不是硬件创建的 – 并且大多数情况下它们的行为类似于分立的硬件元素。通常可以创建类似于硬件网络的Mininet网络,或类似于Mininet网络的硬件网络,并在任一平台上运行相同的二进制代码和应用程序。
为什么Mininet很酷?
它很快 – 启动一个简单的网络只需几秒钟。这意味着您的run-edit-debug循环可以非常快。
您可以创建自定义拓扑:单个交换机,更大的类Internet拓扑,斯坦福主干网,数据中心或其他任何东西。
您可以运行真正的程序:在Linux上运行的任何东西都可以运行,从Web服务器到TCP窗口监视工具再到Wireshark。
您可以自定义数据包转发:Mininet的交换机可以使用OpenFlow协议进行编程。在Mininet中运行的定制软件定义网络设计可以轻松转移到硬件OpenFlow交换机,以进行线速数据包转发。
您可以在笔记本电脑,服务器,虚拟机,本机Linux机器上运行Mininet(Mininet包含在Ubuntu 12.10+!中),或者运行在云端(例如Amazon EC2。)
您可以共享和复制结果:拥有计算机的任何人都可以在打包后运行您的代码。
您可以轻松使用它:您可以通过编写简单(或必要时复杂的)Python脚本来创建和运行Mininet实验。
Mininet是一个开源项目,因此我们鼓励您在https://github.com/mininet上检查其源代码,修改它,修复错误,文件问题/功能请求以及提交补丁/拉取请求。您还可以编辑此文档以修复任何错误或添加说明或其他信息。
Mininet正在积极开发中。所以,如果它很糟糕,没有意义,或者由于某种原因不起作用,请告知我们mininet-discuss
和Mininet用户和开发人员社区可以尝试解释,修复它或帮助您修复它。 :-)如果您发现错误,建议您提交补丁以修复它们,或者至少在github上提交问题,包括可重现的测试用例。
Mininet的局限是什么?
虽然我们认为Mininet很棒,但它确实有一些局限性。例如,
在单个系统上运行很方便,但它会产生资源限制:如果您的服务器具有3 GHz的CPU并且可以切换大约10 Gbps的模拟流量,则需要在虚拟主机和交换机之间平衡和共享这些资源。
Mininet为所有虚拟主机使用单个Linux内核;这意味着您无法运行依赖于BSD,Windows或其他操作系统内核的软件。 (虽然您可以将VM附加到Mininet。)
Mininet不会为您编写OpenFlow控制器;如果您需要自定义路由或切换行为,则需要查找或开发具有所需功能的控制器。
默认情况下,您的Mininet网络与您的LAN和互联网隔离 – 这通常是一件好事!但是,您可以使用NAT
对象和/或--nat
选项通过网络地址转换将Mininet网络连接到LAN。您还可以将实际(或虚拟)硬件接口连接到Mininet网络(有关详细信息,请参阅examples/ hwintf.py
.)
默认情况下,所有Mininet主机共享主机文件系统和PID空间;这意味着如果您正在运行需要在/ etc中进行配置的守护进程,您可能必须要小心,并且您需要注意不要错误地杀死错误的进程。 (注意bind.py
示例演示了如何拥有每个主机的私有目录。)
与模拟器不同,Mininet没有强大的虚拟时间概念;这意味着定时测量将基于实时,并且不能容易地模拟比实时更快的结果(例如100Gbps网络)。
暂且不谈性能:对于网络限制性实验,您必须记住的主要事项是,您可能需要使用较慢的链接,例如10或100 Mb / sec而不是10 Gb / sec,因为数据包由一组软件交换机(例如Open vSwitch)转发,这些交换机共享CPU和内存资源,并且通常具有比专用交换硬件更低的性能。对于CPU限制性实验,您还需要确保小心限制Mininet主机的CPU带宽。如果您主要关心功能正确性,则可以在没有特定带宽限制的情况下运行Mininet – 这是运行Mininet的快速简便方法,它还以负载下的计时准确性为代价提供最高性能。
除了少数例外,您可能遇到的许多限制对于Mininet来说并不是固有的;消除它们可能只是代码问题,我们鼓励您贡献您可能开发的任何增强功能!
与Mininet合作
以下部分描述了您可能会觉得有用的Mininet(及其Python API)的几个功能。
创建拓扑
Mininet支持参数化拓扑。使用几行Python代码,您可以创建一个灵活的拓扑,可以根据您传入的参数进行配置,并重复用于多个实验。
例如,这是一个简单的网络拓扑(基于mininet / topo.py:SingleSwitchTopo
),它由连接到单个交换机(s1
)的指定数量的主机(h1
到hN
)组成:
请注意,这是Mininet 2.2中引入的推荐(简化)拓扑语法:
#!/usr/bin/python from mininet.topo import Topo
from mininet.net import Mininet
from mininet.util import dumpNodeConnections
from mininet.log import setLogLevelclass SingleSwitchTopoTopo):"Single switch connected to n hosts."def buildself, n=2):switch = self.addSwitch's1')# Python's rangeN) generates 0..N-1for h in rangen):host = self.addHost'h%s' % h + 1))self.addLinkhost, switch)def simpleTest):"Create and test a simple network"topo = SingleSwitchTopon=4)net = Mininettopo)net.start)print "Dumping host connections"dumpNodeConnectionsnet.hosts)print "Testing network connectivity"net.pingAll)net.stop)if __name__ == '__main__':# Tell mininet to print useful informationsetLogLevel'info')simpleTest)
上述代码中的重要类,方法,函数和变量包括:
Topo
:Mininet拓扑的基类
build()
:在拓扑类中重写的方法。 构造函数参数(n)将由Topo .__ init __()自动传递给它。
addSwitch()
:将交换机添加到拓扑并返回交换机名称
addHost()
:将主机添加到拓扑并返回主机名
addLink()
:向拓扑添加双向链接(并返回链接密钥,但这并不重要)。 Mininet中的链接是双向的,除非另有说明。
Mininet
:创建和管理网络的主要类
start()
:启动你的网络
pingAll()
:尝试让所有节点互相ping通来测试连接性
stop()
:停止你的网络
net.hosts
:网络中的所有主机
dumpNodeConnections()
:转储与一组节点的连接。
setLogLevel('info'|'debug'|'output')
:设置Mininet的默认输出级别; 建议使用’info’,因为它提供了有用的信息。
可以在mininet /examples
中找到附加示例代码。
早期Mininet版本的注意事项不同版本的Mininet中的拓扑API略有变化。
在1.0中,addSwitch
和addHost
等方法称为add_switch
和add_host
。 另外,在1.0和2.0中,覆盖的首选方法是__init__
而不是build
:
class SingleSwitchTopoTopo):"Single switch connected to n hosts."def __init__self, n=2, **opts):# Initialize topology and default optionsTopo.__init__self, **opts)switch = self.addSwitch's1')# Python's rangeN) generates 0..N-1for h in rangen):host = self.addHost'h%s' % h + 1))self.addLinkhost, switch)
脚本输出
运行此脚本应该会产生如下内容:
$ sudo python simpletest.py*** Creating network*** Adding controller*** Adding hosts:h1 h2 h3 h4 *** Adding switches:s1 *** Adding links:h1, s1) h2, s1) h3, s1) h4, s1) *** Configuring hostsh1 h2 h3 h4 *** Starting controllerc0 *** Starting 1 switchess1 ...Dumping host connectionsh1 h1-eth0:s1-eth1h2 h2-eth0:s1-eth2h3 h3-eth0:s1-eth3h4 h4-eth0:s1-eth4Testing network connectivity*** Ping: testing ping reachabilityh1 -> h2 h3 h4 h2 -> h1 h3 h4 h3 -> h1 h2 h4 h4 -> h1 h2 h3 *** Results: 0% dropped 12/12 received)*** Stopping 1 controllersc0 *** Stopping 4 links....*** Stopping 1 switchess1 *** Stopping 4 hostsh1 h2 h3 h4 *** Done
设置性能参数
除了基本的行为网络,Mininet还通过CPULimitedHost
和TCLink
类提供性能限制和隔离功能。
有多种方法可以使用这些类,但一种简单的方法是将它们指定为默认主机,并将类/构造函数链接到Mininet()
,然后在拓扑中指定适当的参数。 (您还可以在拓扑本身中指定自定义类,或者创建自定义节点和链接构造函数和/或子类。)
#!/usr/bin/pythonfrom mininet.topo import Topofrom mininet.net import Mininetfrom mininet.node import CPULimitedHostfrom mininet.link import TCLinkfrom mininet.util import dumpNodeConnectionsfrom mininet.log import setLogLevelclass SingleSwitchTopo Topo ):"Single switch connected to n hosts."def build self, n=2 ):switch = self.addSwitch 's1' )for h in rangen):# Each host gets 50%/n of system CPUhost = self.addHost 'h%s' % h + 1), cpu=.5/n )# 10 Mbps, 5ms delay, 2% loss, 1000 packet queueself.addLink host, switch, bw=10, delay='5ms', loss=2,max_queue_size=1000, use_htb=True )def perfTest):"Create network and run simple performance test"topo = SingleSwitchTopo n=4 )net = Mininet topo=topo, host=CPULimitedHost, link=TCLink )net.start)print "Dumping host connections"dumpNodeConnections net.hosts )print "Testing network connectivity"net.pingAll)print "Testing bandwidth between h1 and h4"h1, h4 = net.get 'h1', 'h4' )net.iperf h1, h4) )net.stop)if __name__ == '__main__':setLogLevel 'info' )perfTest)
重要的方法和参数:
self.addHost(name,cpu = f):
这允许您指定将分配给虚拟主机的整个系统CPU资源的一小部分。
self.addLink(node1,node2,bw = 10,delay ='5ms',max_queue_size = 1000,loss = 10,use_htb = True)
:
添加带宽,延迟和丢失特性的双向链路,最大队列大小为1000 使用Hierarchical Token Bucket速率限制器和netem延迟/丢失仿真器的数据包。 参数bw
表示为以Mbit为单位的数字; delay
表示为具有单位的字符串(例如’5ms’,‘100us’,‘1s’); loss
以百分比表示(介于0和100之间); 和max_queue_size
以包表示。
您可能会发现创建Python字典很有用,可以很容易地将相同的参数传递给多个方法调用,例如:
linkopts = dictbw=10, delay='5ms', loss=10, max_queue_size=1000, use_htb=True)# or you can use brace syntax: linkopts = {'bw':10, 'delay':'5ms', ... } )self.addLinknode1, node2, **linkopts)
这种技术(** dict)
对于将选项传递给Matplotlib和其他库非常有用。
net.get()
:按名称检索节点(主机或交换机)对象。 如果要将命令发送到主机(例如,使用host.cmd()
)并获取其输出,这一点很重要。
注意:在Mininet的当前主分支中,您可以简单地使用大括号(例如net ['h1']
)按名称检索给定节点。
在主机中运行程序
在实验中您需要做的最重要的事情之一是在主机中运行程序,这样您可以运行比Mininet本身提供的简单pingAll()
和iperf()
测试更多的测试。
每个Mininet主机本质上都是一个连接到一个或多个网络接口的bash shell进程,因此与它交互的最简单方法是使用cmd()方法将输入发送到shell。
要在主机中运行命令并获取输出,请使用cmd()
方法。
h1 = net.get'h1') result = h1.cmd'ifconfig')print result
在许多情况下,您希望在后台运行命令一段时间,停止命令并将其输出保存到文件中:
from time import sleep...print "Starting test..."h1.cmd'while true; do date; sleep 1; done > /tmp/date.out &')sleep10)print "Stopping test"h1.cmd'kill %while')print "Reading output"f = open'/tmp/date.out')lineno = 1for line in f.readlines):print "%d: %s" % lineno, line.strip) )lineno += 1f.close)
请注意,我们使用shell的输出重定向功能将输出发送到/tmp/date.out
,后台执行shell的&
特性以在后台运行命令,并且作业控制kill%while
关闭正在运行的程序在后台。不幸的是,如果你让作业在后台运行,如果Mininet退出(无论是故意还是由于错误),它们都不能保证停止,所以你需要确保你干净地停止所有的工作。您可能希望定期使用ps命令以确保没有僵尸作业无意中前进并减慢EC2实例的速度。
(注意:Python字符串可以使用单引号或双引号分隔。上面的示例在print语句中使用双引号,在函数参数中使用单引号,但您可以执行任何您喜欢的操作.Python的print语句是为了方便而存在,可能有助于BASIC程序员在家里感觉更多。)
拥有一个shell进程可以让您轻松执行其他任务。例如,您可以使用查找后台命令的PID
pid = int h1.cmd'echo $!') )
然后你可以等待一个特定的进程来使用wait来完成执行,例如:
h1.cmd'wait', pid)
请注意,这仅适用于UNIX命令,而不适用于内置于bash shell本身的命令(例如while
,cd
)(并且没有单独的pid!)另请注意,这些命令中的每一个都在前台执行( 没有&
)而不是后台(&
),因为我们想得到输出。
[编辑:UNIX的(和bash
的)缩写和特殊字符($
,!
,&
)可以追溯到每秒快速300比特“网络”和Teletype™终端的日子,这些终端在你打字时打印在纸上。 Linux坚持使用兼容性,因为人类仍然输入缓慢。 如果你想使用它们,bash
有时会提供更详细的等价物。
除了使用shell的等待机制之外,Mininet本身还允许您使用sendCmd()
启动前台命令,然后等待稍后使用waitOutput()
完成:
for h in hosts:h.sendCmd'sleep 20')…results = {}for h in hosts:results[h.name] = h.waitOutput)
如果要将输出发送到文件,您可能希望在测试运行时以交互方式监视该文件的内容。 examples / multipoll.py
示例提供了一个函数monitorFiles()
,它实现了一种监视多个输出文件的可能机制。 这简化了测试的实现,该测试以交互方式监视来自多个主机的输出:
def monitorTest N=3, seconds=3 ):"Run pings and monitor multiple hosts"topo = SingleSwitchTopo N )net = Mininet topo )net.start)hosts = net.hostsprint "Starting test..."server = hosts[ 0 ]outfiles, errfiles = {}, {}for h in hosts:# Create and/or erase output filesoutfiles[ h ] = '/tmp/%s.out' % h.nameerrfiles[ h ] = '/tmp/%s.err' % h.nameh.cmd 'echo >', outfiles[ h ] )h.cmd 'echo >', errfiles[ h ] )# Start pingsh.cmdPrint'ping', server.IP),'>', outfiles[ h ],'2>', errfiles[ h ],'&' )print "Monitoring output for", seconds, "seconds"for h, line in monitorFiles outfiles, seconds, timeoutms=500 ):if h:print '%s: %s' % h.name, line )for h in hosts:h.cmd'kill %ping')net.stop)
您可能希望运行multipoll.py
并查看其输出。
另一个例子,examples / multiping.py
演示了使用Node.monitor()
方法监视来自主机的标准输出的不同(可能更简单但不太灵活)的方法,因此您可能也希望查看它。
新:popen()
/ pexec()
接口
除了基于shell的cmd()
/ sendCmd()
机制之外,Mininet现在还支持基于管道的接口,该接口返回标准的Python Popen()
对象(有关详细信息,请参阅Python的子进程模块。)此机制更新,而不是 经过充分测试的cmd()
机制,但您可能会发现在后台运行多个进程并监视其输出很方便。 提供了一个pmonitor()
函数,可以更轻松地监视多个Popen()
对象。
警告:在Mininet 2.0.0中,
pmonitor()
可能会在收到EOF后返回一些空行。 现在应该在主分支中修复此问题。
examples / popenpoll.py
中的代码使用popen()
接口和pmonitor()
辅助函数实现类似于上面描述的功能:
def pmonitorTest N=3, seconds=10 ):"Run pings and monitor multiple hosts using pmonitor"topo = SingleSwitchTopo N )net = Mininet topo )net.start)hosts = net.hostsprint "Starting test..."server = hosts[ 0 ]popens = {}for h in hosts:popens[ h ] = h.popen'ping', server.IP) )print "Monitoring output for", seconds, "seconds"endTime = time) + secondsfor h, line in pmonitor popens, timeoutms=500 ):if h:print '%s: %s' % h.name, line ),if time) >= endTime:for p in popens.values):p.send_signal SIGINT )net.stop)
请注意,这种实现略有不同,因为它将时间管理从助手函数中拉出来,但这使得pmonitor()
能够在中断后捕获pin
g的输出。
当然,您不必使用pmonitor()
– 您可以使用Popen.communicate()
(只要您没有太多文件描述符)或select.poll()
或任何其他有效的机制。
如果您在popen()
界面中发现错误,请告诉我们。
重要:共享文件系统!
要记住的一件事是,Mininet主机默认共享底层服务器的根文件系统。通常这是一件非常好的事情,因为为每个Mininet主机创建一个单独的文件系统是一个巨大的痛苦(并且很慢)(如果你愿意,你可以这样做,然后chroot到它!)
共享根文件系统也意味着您几乎不需要在Mininet主机之间复制数据,因为它已经存在。
然而,其中一个副作用是主机共享Mininet服务器的/ etc
目录。这意味着如果您需要程序的特定配置(例如httpd
),则可能需要为每个Mininet主机创建不同的配置文件,并将它们指定为您正在运行的程序的启动选项。
另一个副作用是,如果您尝试在多个主机上的同一目录中创建相同的文件,则可能会发生文件冲突。
如果需要每个主机的私有目录,可以将它们指定为Host
的选项,例如:
h = Host 'h1', privateDirs=[ '/some/directory' ] )
有关更多信息,请参阅examples / bind.py
。
主机配置方法
Mininet主机为网络配置提供了许多便利方法:
IP()
:返回主机或特定接口的IP地址。
MAC()
:返回主机或特定接口的MAC地址。
setARP()
:将静态ARP条目添加到主机的ARP缓存中。
setIP()
:设置主机或特定接口的IP地址。
setMAC()
:设置主机或特定接口的MAC地址
例如:
print "Host", h1.name, "has IP address", h1.IP), "and MAC address", h1.MAC)
在每种情况下,如果您不提供特定接口(例如h1-eth0
或接口对象),则该方法将使用主机的默认接口。上述函数在mininet / node.py
中定义。
在Mininet中命名
为了有效地使用Mininet,了解其主机,交换机和接口的命名方案非常重要。通常,主机称为h1..hN
,交换机称为s1..sN
。我们建议您遵循此约定或类似约定。为清楚起见,属于节点的接口以节点名称开头命名,例如h1-eth0
是主机h1
的默认接口,s1-eth1
是交换机s1
的第一个数据端口。主机接口只能在主机内部看到,但是交换机数据端口在“根”命名空间中可见(您可以通过在Mininet运行时在另一个窗口中键入ip link show
来查看它们。)因此,很容易检查切换接口,但检查主机接口有点棘手,因为您必须告诉主机这样做(通常使用host.cmd()
。)
CLI
Mininet包括一个可在网络上调用的命令行界面(CLI),并提供各种有用的命令,以及显示xterm窗口和在网络中的各个节点上运行命令的功能。 您可以通过将网络对象传递到CLI()
构造函数来调用网络上的CLI:
from mininet.topo import SingleSwitchTopofrom mininet.net import Mininetfrom mininet.cli import CLInet = MininetSingleSwitchTopo2))net.start)CLInet)net.stop)
启动CLI对于调试网络非常有用,因为它允许您查看网络拓扑(使用net
命令),测试连接(使用pingall
命令),以及向各个主机发送命令。
*** Starting CLI:mininet> netc0s1 lo: s1-eth1:h1-eth0 s1-eth2:h2-eth0h1 h1-eth0:s1-eth1h2 h2-eth0:s1-eth2mininet> pingall*** Ping: testing ping reachabilityh1 -> h2h2 -> h1*** Results: 0% dropped 0/2 lost)mininet> h1 ip link show746: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWNlink/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00749: h1-eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000link/ether d6:13:2d:6f:98:95 brd ff:ff:ff:ff:ff:ff
使用--custom
文件自定义mn
除了用Python编写完整的Mininet脚本之外,您还可以使用--custom
选项扩展mn
命令行工具。 这允许您使用mn
来调用您自己的自定义拓扑,交换机,主机,控制器或链接类。 您还可以定义和调用自己的系统测试,并添加新的Mininet CLI命令。
要添加可以使用mn
命令调用的新功能,您需要根据选项类型在--custom
文件中定义一个dict。 dict的键是传递给相应选项的短名称,值是相应的子类,构造函数或函数:
例如:
class MyTopo Topo ):def build self, ...):
def myTest net ):
...
topos = { 'mytopo': MyTopo }
tests = { 'mytest': myTest }
这将MyTopo类(或构造函数)添加到topos字典,允许它与--topo
选项一起使用,以及新测试mytest
。 请注意,使用顶级Mininet
对象调用测试函数,您可以将字符串或数字参数传递给MyTopo
:
sudo mn --custom mytopo.py --topo mytopo,3
您也可以指定多个自定义文件:
sudo mn --custom mytopo.py,mytest.py --topo mytopo,3 --test mytest
这将使用mytopo
作为默认拓扑并调用mytes
t测试。
这可以是一个非常方便的单行命令来启动Mininet,运行端到端系统测试(或多个测试),并关闭Mininet。 如果发生异常,将调用标准Mininet清理代码,因为在这种情况下通常会执行mn
。