SDN实验(一):Mininet的安装问题与Fat-Tree的构建

最初因为大创项目,接触了 OpenStack 相关的知识。但重点不在网络,所以对于其中可以随便定义、删除的网络只是有点好奇,没有多想。恰逢软件定义网络课程实验之要求,上手尝试 SDN 的相关实验,试着折腾这个不怕玩坏的网络。

然而尝试之初不免遇到问题,在此简短记述以备查阅,并可作同道者参考。如有问题,欢迎斧正。

Mininet

按照官网上的描述,Mininet 是一个强大的 SDN 实验环境,可以在单机上完成各种网络拓扑的测试。然而如果把他的代码仓库 clone 下来,会发现代码量真的不是很大——不像是一个大型框架的样子。

大致阅读一下源码,或者将其在debug级别下启动,就会发现 Mininet 是通过包装 SDN 工具命令来实现构建拓扑的,所以其核心管理功能并不包含软件定义网络的数据平面;而重要的控制节点,也使用外部的ovs-testcontroller或者poxryu等等。本质上,Mininet 用于构建网络的操作,也都是可以手动完成的。不过 Mininet 用一个进程来模拟一个 host,让每个 host 可以调用宿主机上几乎所有的程序,确实带来了非常大的方便。

安装

官方文档已经给出了各种安装方案,截至目前,各种方案的版本和环境如下:

  • 虚拟机镜像:官方已迁移至 Ubuntu 20.04 LTS,在 GitHub Releases 上可以下载。
  • 包管理器:Ubuntu 18.04/20.04 LTS 下,均更新到2.2版本。
  • 源码安装:最新的稳定版本为2.3.0版本。

需要注意的是,即使是最新的 Ubuntu 发行版,软件源中也没有最新版的 Mininet 。考虑到 Mininet 是大学开发的教学实验用软件,我认为在新系统下,追最新版本是没问题的。当前 Mininet 官方已经完成了向 Python3 的迁移,也鼓励用户迁移到 Python3,不过 POX 控制器仍然只能在 Python2 下运行。

我本机的环境是 Kubuntu 20.04 LTS, Mininet 2.3.0,附带安装了 POX, Ryu 和 Wireshark。很推荐源码安装时的-w选项,安装后 Wireshark 就能分清 OpenFlow 的不同 tag,从而能单独抓取某个虚拟交换机的数据。

问题排查和解决

其实这个问题出现得挺让我哭笑不得的。

安装之初,我按照文档说明,进行最简单的测试:

1
$ sudo mn --test pingall

只见每个节点之间 ping 的结果都正常,Mininet 的统计结果却非说节点都不通,还有报错:

1
2
3
4
5
6
7
8
h1 -> h2
*** Error: could not parse ping output: PING h2 (10.0.0.2) 56(84) bytes of data.
64 比特,来自 h2 (10.0.0.2): icmp_seq=1 ttl=49 时间=35.4 毫秒

--- h2 ping 统计 ---
已发送 1 个包, 已接收 1 个包, 0% 包丢失, 耗时 0 毫秒
rtt min/avg/max/mdev = 35.388/35.388/35.388/0.000 ms
X

翻遍了文档、GitHub Issue、StackOverflow,我也没找到什么解释。不服得很,遂翻找源码。源码组织得比较整齐,于是很快找到了解析 ping 输出的部分:就在mininet/mininet/net.py当中,是Mininet类的一个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@staticmethod
def _parsePing( pingOutput ):
"Parse ping output and return packets sent, received."
# Check for downed link
if 'connect: Network is unreachable' in pingOutput:
return 1, 0
r = r'(\d+) packets transmitted, (\d+)( packets)? received'
m = re.search( r, pingOutput )
if m is None:
error( '*** Error: could not parse ping output: %s\n' %
pingOutput )
return 1, 0
sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
return sent, received

一看之下,直笑过去了。本以为是调了哪个系统级的 lib 之类,拿着返回值在解析,谁知是直接把ping命令的输出文字拿来正则匹配了,真有点儿倒拔垂杨柳式的蛮力。

问题其实已经明了:我的 Linux 是双系统,打算长期使用的,因此配置了中文环境、中文输入法种种,ping命令的输出也被本地化成了中文。这样一来,用来匹配英文输出的正则永远失败,Mininet 就认为ping都不通。

我考虑的解决方法是设置临时环境变量。根据以前对 Linux 的了解,本地化工作应该受LANG这个环境变量控制,于是改变运行 Mininet 的方式:

1
$ sudo LANG=en_US mn --test pingall

以期 Linux 按照en_US来本地化输出,避免输出中文。结果是成功的,不过总这样有点麻烦,可以设置别名:

1
2
alias sudo='sudo '
alias mn='LANG=en_US mn'

第一条让sudo命令也查找别名(默认是不查找的),第二条就是设置语言环境。这两个别名要全局生效,可以放在/etc/profile或者/etc/bash.bashrc当中。

装系统只为用来实验、语言环境是英文的同学,完全不会有这个问题——这就是各种资料上找不到的原因。

Fat-Tree 拓扑的搭建

关于 Fat-Tree,我参考的主要是这篇文章: Fat-tree:A Scalable, Commodity Data Center Network Architecture 解读

在构建 Fat-Tree 时,我是用自底向上的顺序连接节点,将每个 Pod 都存放在一个 Object 里。具体代码就不贴了,想来思路上大同小异,不会有太多的差别。

在实现了一个FatTreeTopo类之后,我用一个小函数来测试:

1
2
3
4
5
6
7
def doTest():
topo = FatTreeTopo(k=4)
net = Mininet(topo)
net.start()
net.waitConnected()
CLI(net)
net.stop()

然而在解决了上面的一切问题后,还是所有的ping都不通,且这次是真的不通了,h1 ping -c1 h4这样的命令显示完全是访问不到的。于是先尝试着建一个 k = 2 的 Fat-Tree,每个 Pod 只有两台交换机,方便调试。奇怪的是,在 k = 2 的情况下,一切都工作正常。

在没有头绪的情况下,我只好重新建起 k = 4 的拓扑,再打开 Wireshark 抓包。这里让h1连续尝试ping另一个 Pod 的主机h6,让 Wireshark 抓取连接了h1的边缘交换机上通过的数据包,过滤规则是icmp。当我在h1上发起ping时,发现根本抓不到想要的 ICMPv4 数据包,只有疯狂刷屏的 ICMPv6 包,h1发出的 ARP 包完全淹没在其中,h6连 ARP 应答都没有作出。

又是一阵好找,找着找着摸到一点线索:k = 4 和 k = 2 情况最大的不同,应该是拓扑中出现了环路。这时,用来探测网络连接、更新路由等信息的数据包可能形成广播风暴,让网络处于极度拥塞的状态下。最终搜出了官方 Wiki 上的一条解答:

Why does my controller, which implements an Ethernet bridge or learning switch, not work with my network which has loops in it? I can't ping anything!

其中提到,在这种情况下,应该为网络开启生成树协议(STP),这样可以明确转发路径,进而避免数据包在一个环上来回转发。对应到实现上,需要修改的是addSwitch部分,为其增加一些参数:

1
2
switchOpts = {'cls': OVSBridge,'stp': 1}
addSwitch('sxx', **switchOpts)

这里指定使用OVSBridge类型的交换机,stp=1开启 STP 协议。另外,开启 STP 后需要等待交换机连接,所以脚本中

1
net.waitConnected()

是必不可少的,在连接上之前,网络都是不通的。或者也可以在 CLI 下调用这个方法:(启动时创建的Mininet实例一般都叫做net

1
mininet> py net.waitConnected()

再启动拓扑进行测试,一切就都符合预期了。

一些其他用途

Mininet 用来当网络测试工具也不错。在启动时,加上 NAT 选项,就可以让 host 接入外部网络:

1
$ sudo mn --nat

这个网络环境非常干净、简单(没防火墙、没拥塞,甚至没多少流量),还比用多台虚拟机组网轻量许多。通过 Xterm 或者 SSH 连接到 host 之后,网络实验中的 ARP 攻击、DNS 攻击等等都可以在这里尝试,并且完全没有后遗症sudo mn -c之后,一切灰飞烟灭不留痕迹,纵然南朝四百八十寺,再无楼台烟雨中。

一点小问题:host 的 DNS 解析跟随宿主机设置,所以如果系统中有systemd-resolved这个服务,需要暂时将/etc/resolv.conf这个文件中的nameserver项修改成一个可用的 DNS 服务器地址。 原先的nameserver一般是127.0.0.53,这是systemd-resolved维护的一个本地 DNS,经过它指向真正的 DNS 服务器,但是 Mininet 并不能使用这个东西。 注意玩完之后,还是把这一项该回来为好,虽说哪怕不改重启后也会恢复。

另外,在addLink时,Mininet 提供了限制带宽的选项。如果想要观察拥塞之类的现象,完全可以在添加连接时就把带宽限得很低,本机上的网络环境总是比复杂的外网要好下手许多。