生动的SDN基础内容介绍(四)--Mininet和OVS,以及Ryu的使用

背景

Mininet是一个轻量级的网络模拟环境,一般用来搭配控制器来实现SDN。

Mininet有很多功能,网上也有很多的教程,这里就不细说了。

这里主要说一下Mininet配合Ryu实现对SDN的模拟,以及碰到的一些坑。

OVS

首先得说一下OVS,OVS是Open VSwitch虚拟交换机的简称,是一种具有产品级质量的多层虚拟交换机,利用软件的方式实现了硬件交换机的功能。即OVS是比硬件高一层的。

OVS的目的是让网络可以编程扩展,并且仍然支持标准的管理接口和协议。

OVS
优点有两个:
1、配置很灵活:
因为是软件实现的,可以在软件层面上去修改一些功能,而不需要去在硬件层面上修改。
可以用一台普通的服务器配置出数十台甚至上百台的虚拟交换机。
2、成本很低廉

SDN的出现离不开OpenFlow和OVS。

也就是说我们在模拟网络环境的时候可以用OVS作为我们的交换机。

Mininet自带了OVS,虽然版本有点低吧,不过不用我们自己下载了。

我对OVS也没有太深的了解,如果想要了解OVS的可以搜搜资料读读源码。

重编译

在做毕设的时候,有些现有的网络测量框架需要修改OVS源码添加新的动作,所以我就尝试着重编译一次(我没有添加动作只是重新编译了一次),结果发现好折腾,整整弄了一个下午。

参考的博客
https://www.cnblogs.com/ssyfj/p/11901922.html
https://blog.csdn.net/daihanglai7622/article/details/90047214
https://blog.csdn.net/popsuper1982/article/details/52663956

我需要找到mininet中OVS源码的位置,但是找了半天没找到。。因此去下载OVS源码,然后编译。

我选择的是2.11.0版本(要注意的是2.11只支持3.10-4.18的内核),而我的版本是5.8.0。因此需要调整内核,详情见https://blog.csdn.net/father_is_/article/details/105618180
但是我发现我的内核只有5.4.0和5.8.0,要是重装内核过于麻烦,我就选择了2.14.1版本,这个版本支持3.10-5.5内核。

但是每次开机后都需运行
ovsdb-server --remote=punix:/usr/local/var/run/openvswitch/db.sock --remote=db:Open_vSwitch,Open_vSwitch,manager_options --pidfile --detach
ovs-vsctl --no-wait init
ovs-vswitchd --pidfile --detach --log-file

原因是:ovs 需要 the ovsdb, ovs-vswitchd, ovs-vsctl, 但是关机后它们会默认关闭

除此之外还需要在运行mininet的时候运行sh ovs-vsctl set bridge s1 datapath_type=netdev设置所有的交换机,这是因为ovs版本的问题。

在编译ovs之前我克隆了一个虚拟机叫做ovs,由于编译后的ovs启动稍有些麻烦我就继续用克隆的虚拟机了。

Mininet

这里说一下Mininet怎么配合Ryu控制器实现对SDN的模拟。

端口的监听

Ryu会监听6653端口,Mininet会连接到6653端口。但是如果此时6653被占用的话就会失败,因此可以用

sudo lsof -i:6653
sudo kill -9 PID

关掉该进程。

simple_switch_13的使用

使用Ryu的app的时候需要开启两个终端,一个终端用来生成Mininet网络,另一个终端运行app。

Mininet需要在root权限下使用,一些详细的参数可以见这篇博客
https://blog.csdn.net/ten_sory/article/details/79593554

其中有一个很关键的参数 --controller
如果不加这个参数的话默认是使用Mininet自带的控制器(Mininet很强大吧,连控制器都有了)。但是一般我们用的是自己写的Ryu控制器,因此需要加上这个参数连接上远程的控制器:

mn --controller = remote,
      --ip = [控制器的IP地址]
      --port = [控制器的端口号]
如果--ip和--port省略的话,则默认使用本地ip地址,端口默认使用6653或6633端口号。

因此如果我们想创建一个深度为2,树状拓扑的网络,并且连接我们自己的Ryu控制器:

mn --topo=tree,depth=2 --controller=remote

Mininet树状网络结构
可以看到网络被创建成功了,但是pingall指令失败了,ping不通,这是因为我们还没有启动Ryu控制器,用dpctl dump-flows查询流表也是空的,因为没有控制器给OVS下发流表项。

这时候我们开启另一个终端,进入Ryu的app目录,用ryu-manager运行之前讲过的简单交换机app,即:

ryu-manager --observe-links simple_switch_13.py

这里要注意的是最好加上–observe-links这个参数,如果我们在控制器里想对链路信息进行获取或者操作的话,不加上这个参数是获取不到的。当时研究了一晚上才发现是少加了个参数。。

然后在Mininet中进行pingall:
连接上控制器

可以看到的是所有主机都ping通了,在左边的终端可以看到发过来的数据包。再次在Mininet中执行查看流表的操作,可以看到流表项被成功下发了,简单的学习交换机app起到了预期的作用。

REST api的使用

之前简单地提了一下北向接口是什么,这里咱就简单地使用一下。
和刚刚其实差不多,只是多加了个运行的app:ofctl_rest.py

ryu-manager --observe-links ofctl_rest.py simple_switch_13.py

默认的url是127.0.0.1:8080
ofctl_rest.py中定义了很多REST API,我们这里只以查询流表为例,一些源码的分析在后面讲北向接口的时候再说。
其他API的使用方法可以看这篇博客:
https://www.freesion.com/article/8090870623/
发送http://127.0.0.1:8080/stats/flow/1之后获取的是交换机1的流表信息:

{
  "1": [{
    "priority": 1,
    "cookie": 0,
    "idle_timeout": 0,
    "hard_timeout": 0,
    "byte_count": 238,
    "duration_sec": 17,
    "duration_nsec": 208000000,
    "packet_count": 3,
    "length": 104,
    "flags": 0,
    "actions": ["OUTPUT:1"],
    "match": {
      "in_port": 2,
      "dl_src": "a2:3f:c3:ed:16:19",
      "dl_dst": "e6:ba:06:11:98:23"
    },
    "table_id": 0
  }, {
    "priority": 1,
    "cookie": 0,
    "idle_timeout": 0,
    "hard_timeout": 0,
    "byte_count": 140,
    "duration_sec": 17,
    "duration_nsec": 206000000,
    "packet_count": 2,
    "length": 104,
    "flags": 0,
    "actions": ["OUTPUT:2"],
    "match": {
      "in_port": 1,
      "dl_src": "e6:ba:06:11:98:23",
      "dl_dst": "a2:3f:c3:ed:16:19"
    },
    "table_id": 0
  }, {
    "priority": 1,
    "cookie": 0,
    "idle_timeout": 0,
    "hard_timeout": 0,
    "byte_count": 238,
    "duration_sec": 17,
    "duration_nsec": 202000000,
    "packet_count": 3,
    "length": 104,
    "flags": 0,
    "actions": ["OUTPUT:1"],
    "match": {
      "in_port": 2,
      "dl_src": "ce:b7:7e:db:23:cf",
      "dl_dst": "e6:ba:06:11:98:23"
    },
    "table_id": 0
  }, {
    "priority": 1,
    "cookie": 0,
    "idle_timeout": 0,
    "hard_timeout": 0,
    "byte_count": 140,
    "duration_sec": 17,
    "duration_nsec": 200000000,
    "packet_count": 2,
    "length": 104,
    "flags": 0,
    "actions": ["OUTPUT:2"],
    "match": {
      "in_port": 1,
      "dl_src": "e6:ba:06:11:98:23",
      "dl_dst": "ce:b7:7e:db:23:cf"
    },
    "table_id": 0
  }, {
    "priority": 1,
    "cookie": 0,
    "idle_timeout": 0,
    "hard_timeout": 0,
    "byte_count": 238,
    "duration_sec": 17,
    "duration_nsec": 193000000,
    "packet_count": 3,
    "length": 104,
    "flags": 0,
    "actions": ["OUTPUT:1"],
    "match": {
      "in_port": 2,
      "dl_src": "a2:3f:c3:ed:16:19",
      "dl_dst": "a6:a1:9e:cc:b8:36"
    },
    "table_id": 0
  }, {
    "priority": 1,
    "cookie": 0,
    "idle_timeout": 0,
    "hard_timeout": 0,
    "byte_count": 140,
    "duration_sec": 17,
    "duration_nsec": 191000000,
    "packet_count": 2,
    "length": 104,
    "flags": 0,
    "actions": ["OUTPUT:2"],
    "match": {
      "in_port": 1,
      "dl_src": "a6:a1:9e:cc:b8:36",
      "dl_dst": "a2:3f:c3:ed:16:19"
    },
    "table_id": 0
  }, {
    "priority": 1,
    "cookie": 0,
    "idle_timeout": 0,
    "hard_timeout": 0,
    "byte_count": 238,
    "duration_sec": 17,
    "duration_nsec": 186000000,
    "packet_count": 3,
    "length": 104,
    "flags": 0,
    "actions": ["OUTPUT:1"],
    "match": {
      "in_port": 2,
      "dl_src": "ce:b7:7e:db:23:cf",
      "dl_dst": "a6:a1:9e:cc:b8:36"
    },
    "table_id": 0
  }, {
    "priority": 1,
    "cookie": 0,
    "idle_timeout": 0,
    "hard_timeout": 0,
    "byte_count": 140,
    "duration_sec": 17,
    "duration_nsec": 185000000,
    "packet_count": 2,
    "length": 104,
    "flags": 0,
    "actions": ["OUTPUT:2"],
    "match": {
      "in_port": 1,
      "dl_src": "a6:a1:9e:cc:b8:36",
      "dl_dst": "ce:b7:7e:db:23:cf"
    },
    "table_id": 0
  }, {
    "priority": 0,
    "cookie": 0,
    "idle_timeout": 0,
    "hard_timeout": 0,
    "byte_count": 812,
    "duration_sec": 17,
    "duration_nsec": 502000000,
    "packet_count": 14,
    "length": 80,
    "flags": 0,
    "actions": ["OUTPUT:CONTROLLER"],
    "match": {},
    "table_id": 0
  }]
}

可以看到的是json里面包含了很多的流表项,以该项为例:

{
    "priority": 1, //优先级
    "cookie": 0, //cookie
    "idle_timeout": 0,
    "hard_timeout": 0,
    "byte_count": 238, //匹配到的字节数
    "duration_sec": 17, //持续的时间
    "duration_nsec": 193000000,
    "packet_count": 3, //匹配到的数据包数
    "length": 104,
    "flags": 0,
    "actions": ["OUTPUT:1"], //执行的动作:从端口1转发
    "match": { //匹配域
      "in_port": 2, //从端口2进入
      "dl_src": "a2:3f:c3:ed:16:19", //源MAC地址
      "dl_dst": "a6:a1:9e:cc:b8:36" //目的MAC地址
    },
    "table_id": 0 //流表的编号

Mininet的网络拓扑

Mininet有三种创建网络拓扑的方法:

直接命令创建

通过在命令行中输入参数生成网络拓扑,优点是方便简单,缺点是有些复杂的结构没办法创建。

写Python文件

这种方法应该是最灵活的,但也是最麻烦的,以https://github.com/stainleebakhla/mininet-flow-generator的代码为例

from mininet.topo import Topo


class FatTreeTopo(Topo):

    def __init__(self):
        # Initialize topology
        Topo.__init__(self)

        # Adding hosts
        h1 = self.addHost('h1')
        h2 = self.addHost('h2')
        h3 = self.addHost('h3')
        h4 = self.addHost('h4')
        h5 = self.addHost('h5')
        h6 = self.addHost('h6')
        h7 = self.addHost('h7')
        h8 = self.addHost('h8')
        h9 = self.addHost('h9')
        h10 = self.addHost('h10')
        h11 = self.addHost('h11')
        h12 = self.addHost('h12')
        h13 = self.addHost('h13')
        h14 = self.addHost('h14')
        h15 = self.addHost('h15')
        h16 = self.addHost('h16')

        # Adding switches
        # s1 = self.addSwitch( 's1' )
        # s2 = self.addSwitch( 's2' )
        # s3 = self.addSwitch( 's3' )
        # s4 = self.addSwitch( 's4' )

        s5 = self.addSwitch('s5')
        s6 = self.addSwitch('s6')
        s7 = self.addSwitch('s7')
        s8 = self.addSwitch('s8')
        s9 = self.addSwitch('s9')
        s10 = self.addSwitch('s10')
        s11 = self.addSwitch('s11')
        s12 = self.addSwitch('s12')
        s13 = self.addSwitch('s13')
        s14 = self.addSwitch('s14')
        s15 = self.addSwitch('s15')
        s16 = self.addSwitch('s16')
        s17 = self.addSwitch('s17')
        s18 = self.addSwitch('s18')
        s19 = self.addSwitch('s19')
        s20 = self.addSwitch('s20')
        s21 = self.addSwitch('s21')
        s22 = self.addSwitch('s22')
        s23 = self.addSwitch('s23')
        s24 = self.addSwitch('s24')

        # Adding links between hosts and switches
        self.addLink(h1, s5)
        self.addLink(h2, s5)
        self.addLink(h3, s6)
        self.addLink(h4, s6)
        self.addLink(h5, s10)
        self.addLink(h6, s10)
        self.addLink(h7, s11)
        self.addLink(h8, s11)
        self.addLink(h9, s15)
        self.addLink(h10, s15)
        self.addLink(h11, s16)
        self.addLink(h12, s16)
        self.addLink(h13, s20)
        self.addLink(h14, s20)
        self.addLink(h15, s21)
        self.addLink(h16, s21)

        # Adding links between switches
        self.addLink(s5, s7)
        self.addLink(s5, s8)
        self.addLink(s6, s7)
        self.addLink(s6, s8)
        self.addLink(s10, s12)
        self.addLink(s10, s13)
        self.addLink(s11, s12)
        self.addLink(s11, s13)
        self.addLink(s15, s17)
        self.addLink(s15, s18)
        self.addLink(s16, s17)
        self.addLink(s16, s18)
        self.addLink(s20, s22)
        self.addLink(s20, s23)
        self.addLink(s21, s22)
        self.addLink(s21, s23)

        self.addLink(s7, s9)
        self.addLink(s7, s14)
        self.addLink(s8, s19)
        self.addLink(s8, s24)
        self.addLink(s12, s9)
        self.addLink(s12, s14)
        self.addLink(s13, s19)
        self.addLink(s13, s24)
        self.addLink(s17, s9)
        self.addLink(s17, s14)
        self.addLink(s18, s19)
        self.addLink(s18, s24)
        self.addLink(s22, s9)
        self.addLink(s22, s14)
        self.addLink(s23, s19)
        self.addLink(s23, s24)

        # set proper IP addresses
        # h1.cmd('ifconfig h1-eth0 10.0.0.1')
        # h2.cmd('ifconfig h2-eth0 10.5.0.1')
        # h3.cmd('ifconfig h3-eth0 10.100.0.1')
        # h4.cmd('ifconfig h4-eth0 10.145.0.1')


topos = {'fat_tree_topo': (lambda: FatTreeTopo())}

上述代码是用来实现Fat-tree胖树的网络拓扑结构。

代码倒是不难,就是有点麻烦,而且还要注意先后顺序,以我之前做的一个实验为例:
实验拓扑的代码
这几行代码是构建如下网络拓扑结构的部分代码:
网络拓扑结构
我在实验中
dpctl add-flow in_port=1,actions=output:2
dpctl add-flow in_port=2,actions=output:1  
注意:一般是成对添加,实现双方通信。
但我发现添加完还是h1 ping不通h2,后来实验半天发现是拓扑结构中s1的1端口和2端口并不是对应着h1和h2,因此ping不通。原因应该是拓扑结构python文件中的link顺序问题。注意此处的h1 ping h2需要在断开Ryu控制器的情况下进行,因为如果连接上的话ping指令会自动下发流表项。

代码中
s1的1端口连的是s2,2端口连的是h1,3端口连的是h2
s2的1端口连的是s1,2端口连的是h3
为了验证我的猜想,我先pingall,然后查看所有流表,和我的猜想一样。
为了让实验更明了,我修改了顺序。

当我下发如下的流表项后,该网络可全部ping通
下发流表项

因此自己写文件的时候一定要仔细,如果手动对流表项进行操作的话需要注意相应的端口号。

可视化GUI

还有一种方法是用Mininet自带的GUI来创建网络拓扑,这种方法虽然比较好理解,但是会在实际操作中遇到各种各样的问题,因此还是更推荐前两种方法。

详情见
https://blog.csdn.net/qq_43516973/article/details/105439205
https://blog.csdn.net/weixin_36372879/article/details/86306730

cd mininet/examples
sudo ./miniedit.py --topo=none

要注意的几个点:
1、控制器为remote。
2、交换机的type为OVS kernel mode,DPID为16位。
3、主机的IP地址由整体网络决定。
4、可以设置链路。
5、全局配置需要点击start CLI和OpenFlow1.3。
6、然后点击File中的Export Level 2 Script保存网络拓扑为python形式。
7、然后用sudo python 该文件,即可生成之前定义的网络拓扑结构。
8、先退出Mininet然后再退出GUI。

在实验的过程中我遇到了一个问题:保存为python文件后会显示
TypeError: a bytes-like object is required, not 'str’错误,无法写入到文件中。

我上网找了好久,后来发现是miniedit.py文件在1702行中为f = open(fileName, ‘wb’)

我改为下图所示的f = open(fileName, ‘w’)即可保存,原因是wb以二进制的形式写入,w是文本的形式写入。
修改文件源码

流量的生成

在一些网络实验中需要我们模拟网络的流量,我使用的是iperf命令。
我参考的是这个
https://github.com/stainleebakhla/mininet-flow-generator/blob/master/topo_launcher.py

每个主机之间随机生成大象流与老鼠流进行通信,具体的网络测量部分会在后面讲解。

总结

Mininet的参数还是挺多的,以后要是有时间的话我没准会研究下OVS的源码。

下一章会介绍北向协议和意图驱动。
https://blog.csdn.net/weixin_44480014/article/details/123274846

  • 5
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这是一个比较复杂的实验,需要一定的基础知识,以下是基本步骤: 1. 安装OpenStack:在一台物理机器上安装OpenStack,并且创建一个网络和两个虚拟机(VM1和VM2)以及一个路由器,将VM1和VM2分别连接在这个网络上。 2. 安装RYU控制器:在一台物理机器上安装RYU控制器,并且编写一个简单的SDN控制器程序。这个程序可以接收交换机发来的OpenFlow消息,并且根据需要下发流表或者修改流表。 3. 安装Docker:在一台物理机器上安装Docker,并且创建两个容器(Container1和Container2)。这两个容器将被用来模拟两个主机,它们将连接在同一个虚拟交换机上。 4. 安装Mininet:在一台物理机器上安装Mininet,并且使用Mininet创建一个虚拟交换机和两个虚拟主机(Host1和Host2)。这两个虚拟主机将连接在这个虚拟交换机上。 5. 配置OVS交换机:在VM1上安装OpenvSwitch(OVS),并且配置OVS交换机。将这个OVS交换机连接到OpenStack网络上,并且添加一个端口,将这个端口连接到RYU控制器上。 6. 配置Docker容器:在Container1和Container2中安装OpenvSwitch,并且配置OVS交换机。将这个OVS交换机连接到Mininet虚拟交换机上,并且添加一个端口,将这个端口连接到RYU控制器上。 7. 运行实验:现在可以运行实验了。在Host1和Host2之间发送一些数据包,观察RYU控制器的行为以及OVS交换机的流表。可以尝试修改SDN控制器程序,并且观察它的影响。 注意,以上仅是基本步骤,具体操作还需要结合实际情况进行调整。此外,这个实验需要一定的计算资源和网络资源,需要提前准备好。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值