一个简单的SDN实验

使用Ryu控制器和Mininet做的SDN仿真实验

安装Ryu控制器和Mininet软件部分包括搭建软件实验环境网上已有许多教程,可以参考其他的资源。

本次实验的实验内容:
  • 使用ryu控制器自带的app案例中的simple_switch.py进行创建SDN控制器
  • 使用mininet图形化用户界面创建网络拓扑并保存为py文件;
  • 使用wireshark抓包来查看openflow协议的基本数据包类型以及查看SDN控制器的工作流程
  • 使用pingall命令查看主机的连通性
  • 使用mininet相关命令查看创建网络拓扑的各个端口并标注到拓扑结构上
  • 使用mininet的查看流表和添加drop流表等查看SDN控制器的工作流程
  • 断开某条链路,启用pingall命令查看流表和网络连通性。

mininet创建网络拓扑一共有三种方式,分别是命令行式、图形化用户界面方式还有python脚本代码,由于编者需要比较创建比较复杂的网络拓扑结构,所以使用命令行的形式创建网络拓扑结构达不到本次实验的要求。再加上图形化用户界面创建网络拓扑更加方便和随意,能够很方便的通过图形化用户界面生成python脚本,如果图形化用户界面生成的脚本不符合要求,也可以很方便的对脚本进行修改,可以减少很多工作量,因此本文采用了mininet图形化用户界面进行创建网络拓扑结构。

本次通过mininet创建的网络拓扑结构图


图中包含一个SDN控制器和5个支持Openflow协议的交换机以及8台主机

mininet创建上述网络拓扑的python代码:
#!/usr/bin/python

from mininet.net import Mininet
from mininet.node import Controller, RemoteController, OVSController
from mininet.node import CPULimitedHost, Host, Node
from mininet.node import OVSKernelSwitch, UserSwitch
from mininet.node import IVSSwitch
from mininet.cli import CLI
from mininet.log import setLogLevel, info
from mininet.link import TCLink, Intf
from subprocess import call

def myNetwork():

    net = Mininet( topo=None,
                   build=False,
                   ipBase='10.0.0.0/8')

    info( '*** Adding controller\n' )
    c0=net.addController(name='c0',
                      controller=RemoteController,
                      ip='127.0.0.1',
                      protocol='tcp',
                      port=6633)

    info( '*** Add switches\n')
    s4 = net.addSwitch('s4', cls=OVSKernelSwitch, dpid='0000000000000004')
    s2 = net.addSwitch('s2', cls=OVSKernelSwitch, dpid='0000000000000002')
    s3 = net.addSwitch('s3', cls=OVSKernelSwitch, dpid='0000000000000003')
    s1 = net.addSwitch('s1', cls=OVSKernelSwitch, dpid='0000000000000001')
    s5 = net.addSwitch('s5', cls=OVSKernelSwitch, dpid='0000000000000005')

    info( '*** Add hosts\n')
    h7 = net.addHost('h7', cls=Host, ip='10.0.0.7', defaultRoute=None)
    h3 = net.addHost('h3', cls=Host, ip='10.0.0.3', defaultRoute=None)
    h2 = net.addHost('h2', cls=Host, ip='10.0.0.2', defaultRoute=None)
    h8 = net.addHost('h8', cls=Host, ip='10.0.0.8', defaultRoute=None)
    h5 = net.addHost('h5', cls=Host, ip='10.0.0.5', defaultRoute=None)
    h6 = net.addHost('h6', cls=Host, ip='10.0.0.6', defaultRoute=None)
    h1 = net.addHost('h1', cls=Host, ip='10.0.0.1', defaultRoute=None)
    h4 = net.addHost('h4', cls=Host, ip='10.0.0.4', defaultRoute=None)

    info( '*** Add links\n')
    net.addLink(s1, s2)
    net.addLink(s2, s3)
    net.addLink(s3, s4)
    net.addLink(s4, s5)
    net.addLink(s1, h1)
    net.addLink(s1, h2)
    net.addLink(s2, h3)
    net.addLink(s3, h4)
    net.addLink(s3, h5)
    net.addLink(s4, h6)
    net.addLink(s4, h7)
    net.addLink(s5, h8)

    info( '*** Starting network\n')
    net.build()
    info( '*** Starting controllers\n')
    for controller in net.controllers:
        controller.start()

    info( '*** Starting switches\n')
    net.get('s4').start([c0])
    net.get('s2').start([c0])
    net.get('s3').start([c0])
    net.get('s1').start([c0])
    net.get('s5').start([c0])

    info( '*** Post configure switches and hosts\n')

    CLI(net)
    net.stop()

if __name__ == '__main__':
    setLogLevel( 'info' )
    myNetwork()

由于编者水平有限,对于Ryu控制器端的代码并不清楚,只知道一点基础的内容,所以本文的实验内容讲解的是使用安装好Ryu控制器后App目录下的自带的支持openflow协议1.0版本的simple_switch.py文件实现了SDN控制器对于交换机流表的下发操作。

simple_switch.py文件的代码

"""
An OpenFlow 1.0 L2 learning switch implementation.
"""


from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_0
from ryu.lib.mac import haddr_to_bin
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import ether_types


class SimpleSwitch(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super(SimpleSwitch, self).__init__(*args, **kwargs)
        self.mac_to_port = {}

    def add_flow(self, datapath, in_port, dst, src, actions):
        ofproto = datapath.ofproto

        match = datapath.ofproto_parser.OFPMatch(
            in_port=in_port,
            dl_dst=haddr_to_bin(dst), dl_src=haddr_to_bin(src))

        mod = datapath.ofproto_parser.OFPFlowMod(
            datapath=datapath, match=match, cookie=0,
            command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
            priority=ofproto.OFP_DEFAULT_PRIORITY,
            flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
        datapath.send_msg(mod)

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto

        pkt = packet.Packet(msg.data)
        eth = pkt.get_protocol(ethernet.ethernet)

        if eth.ethertype == ether_types.ETH_TYPE_LLDP:
            # ignore lldp packet
            return
        dst = eth.dst
        src = eth.src

        dpid = datapath.id
        self.mac_to_port.setdefault(dpid, {})

        self.logger.info("packet in %s %s %s %s", dpid, src, dst, msg.in_port)

        # learn a mac address to avoid FLOOD next time.
        self.mac_to_port[dpid][src] = msg.in_port

        if dst in self.mac_to_port[dpid]:
            out_port = self.mac_to_port[dpid][dst]
        else:
            out_port = ofproto.OFPP_FLOOD

        actions = [datapath.ofproto_parser.OFPActionOutput(out_port)]

        # install a flow to avoid packet_in next time
        if out_port != ofproto.OFPP_FLOOD:
            self.add_flow(datapath, msg.in_port, dst, src, actions)

        data = None
        if msg.buffer_id == ofproto.OFP_NO_BUFFER:
            data = msg.data

        out = datapath.ofproto_parser.OFPPacketOut(
            datapath=datapath, buffer_id=msg.buffer_id, in_port=msg.in_port,
            actions=actions, data=data)
        datapath.send_msg(out)

    @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
    def _port_status_handler(self, ev):
        msg = ev.msg
        reason = msg.reason
        port_no = msg.desc.port_no

        ofproto = msg.datapath.ofproto
        if reason == ofproto.OFPPR_ADD:
            self.logger.info("port added %s", port_no)
        elif reason == ofproto.OFPPR_DELETE:
            self.logger.info("port deleted %s", port_no)
        elif reason == ofproto.OFPPR_MODIFY:
            self.logger.info("port modified %s", port_no)
        else:
            self.logger.info("Illeagal port state %s %s", port_no, reason)

实验步骤和现象

首先启动Ryu控制器,使用ryu-manager simple_switch.py命令启动Ryu控制器

注意:在使用mininet进行网络拓扑创建之前,一定要首先船舰Ryu远程控制器,否则mininet会自动创建一个控制器。

启动Ryu控制器之后,等待创建网络拓扑;

启动已经创建好的mininet网络拓扑脚本


可以看到Ryu控制器端进行了响应,包括了packet in的端口等信息

创建完成之后,马上使用dpctl dump-flows 命令查看交换机中的流表,由于是刚创建的,所以SDN控制器并没有下发流表,因此各个交换机中的流表应该是空的才对


执行pingall命令测试连通性,可以看到,所有的主机之间都能进行数据传输,整个网络都是连接的。

由于执行了pingall命令,所以当交换机查找内部没有流表匹配项的时候,就会向SDN控制器请求流表的下发,以让自己能够知道应该怎样将数据通过哪个端口和哪个交换机传输出去,因此本次使用dpctl dump-flows命令查看流表的时候,每个交换机都应该有流表,且流表数据项很多,包括了到每个主机的通信需要走的道路。

再执行pingall命令之前,使用命令wireshark,可以开启wireshark软件(编者是最开始安装了wires hark抓包软件的),然后通过抓包获取查看openflow协议的数据包,和整个数据包的分析。

可以看到,本次实验中,支持的是openflow协议1.0版本,同时包括了许多的数据包类型,包括Packet_in和Packet_out数据包、Request和Reply数据包等。下面进行简单的解释。

  1. 主机①想要向主机②发送数据,即执行pingair h1 h2时,数据通过某端口到达交换机;
  2. 左边的交换机查找内部的流表,没有发现流表匹配项,所以向控制器发送了Packet_in数据包,请求下发流表;
  3. 控制器接收到数据包之后,发送Request数据包给交换机,请求交换机的Mac地址、IP地址,支持的协议版本的信息,然后交换机发送reply数据包给控制器;控制器知道了之后,就发送Packet_out数据包给交换机
  4. 交换机接收到流表之后就匹配,知道自己要怎么才能发送数据给主机②了,然后就发送数据给右边的交换机了。
  5. 左边的交换机接收到数据包之后,也没有找到流表项应该怎么发送给主机②,所以它也向控制器发送请求,请求下发流表
  6. 控制器下发流表
  7. 交换机②知道流表之后,就按照流表的转发规则发送给了主机②,自此主机②与主机①进行了数据传输,以后再次从主机一到主机二传输就不需要这样了,因为交换机内部已经有了流表。

接下来删除交换机中的流表,使之变为空表,然后进行接下来的实验,使用dpctl del-flows 删除所有交换机中的流表,然后使用dpctl dump-flows查看是否删除流表成功

现在看到上面的网络拓扑结构,然后添加Drop流表,Drop流表的意思是,交换机查询到这个流表项之后,就将从那个端口接收到的数据包直接扔掉,不转发。命令是 dpctl add-flow in_port=3,action=drop

现在可以再次执行pingall命令查看各个主机之间的连通性,可以看到只有主机h8、h7、h5、h1才能够进行数据通信,其它的主机不能进行通信

解释:可以再次看到前面的拓扑图

其中主机2、主机3、主机4、主机6分别连接了交换机s1的端口3、交换机s2的端口3、交换机s3的端口3、交换机s4的端口3,所以当数据发送到这个端口的时候,交换机查到了流表匹配项,然后将数据包直接删除,所以这四个主机不能与其他主机之间进行数据通信。

再次使用命令dpctl dump-flows查看交换机中的流表,可以看到交换机1内部的流表除了有我们之前添加的drop流表之外,还有许多的其它流表,这就是因为没有在交换机内部匹配到流表项,所以主动向控制器发送请求,控制器下发了流表。

接下来删除所有的流表,并断开s3与h5,s4与h7之间的连接,并删除交换机中的所有流表,并查看流表。

拓扑图示为:
先使用link s3 h5 down;link s4 h7 down 断开物理连接,然后使用dpctl del-flows删除前面的所有流表

再次执行pingall命令,测试网络的连通性并查看交换机中的流表项。

可以看到,h5、h7不能与其他主机进行连接,因为直接将物理连接就关闭了,所以控制器也没有办法解决。

然后使用link s3 h5 up,启用s3与h5之间的连接,再删除所有流表,测试连通性

其网路拓扑如图所示:

删除所有流表,并查看是否删除成功;

再次执行pingall命令,查看网络的连通性,可以看到除了h7不能通信,其余的都可以。

再启用s4与h7之间的链路连接,然后不删除流表项,直接pingall查看连通性,可以看到全部都可以进行通信。

通过本次实验,验证了SDN的可编程性,同时验证了SDN控制器自适应下发流表,对网络有很好的适应性,同时能够看到有openflow协议在SDN南向接口中的具体应用。由于编者能力有限,有错误和解释错误的请批评指正,另外关于数据包解释的图片来源于网络,侵权私信联系删除,谢谢!

资料附(mininet基本命令和基本知识)

mn --controller=remote

如果controller没有指定则使用mininet自带的控制器,那样就只是模拟了传统的网络架构,没有使用SDN的架构,同时在使用mn --controller-remote的时候,如果没有开启ryuSDN控制器,那么路由器之间由于没有流表也是不能相互通信的,即不能ping通的。

Mininet部分命令

help获取帮助列表

nodes查看网络拓扑中节点的状态

links显示链路健壮性信息

net显示网络拓扑

dump显示每个节点的接口设置和表示每个节点的进程PID

pingall测试网络中所有节点的连通性

pingpair只测试前两个主机间的连通性

iperf两个节点之间进行iperftcp带框测试

link 禁用或启用节点间的链路 如(link s1 s2 up 启用,link s1 s2 down禁用)

h1 ping h2 主机h1和h2节点之间进行ping测试

h1 ifconfig 查看host1的终端

xterm h1 打开host1的终端

mininet创建网络拓扑的三种方式:

命令行创建:

​ 单一拓扑:整个网络只有一个交换机,这个交换机下可以下挂1-n个交换机

​ 命令:mn --topo=single,3

​ 线性拓扑:交换机呈线性排列,且每个交换机所连接的主机数目只有一个

​ 命令:mn --topo=linear,3

​ 树形拓扑:交换机呈树形排列,其中depth代表深度,交换机的深度,fanout代表广度,即每个交换机连接的数目。

​ 命令:mn --topo=tree,depth=2,fanout=3

python代码文件创建:

图形化用户界面创建:

openflow协议工作原理

openflow流表常用配置命令

查看流表:dpctl dump-flows

添加流表:dpctl add-flow in_port=1,actions=output:2

添加丢弃数据包流表:dpctl add-flow in_port=2,actions=drop

删除所有交换机的所有流表:dpctl del-flows

删除所有交换机的特定流表项:dpctl del-flows in_port=2

删除某个交换机的流表:sh ovs_ofctl del-flows s1 in_port=2

度,即每个交换机连接的数目。

​ 命令:mn --topo=tree,depth=2,fanout=3

python代码文件创建:

图形化用户界面创建:

openflow协议工作原理

openflow流表常用配置命令

查看流表:dpctl dump-flows

添加流表:dpctl add-flow in_port=1,actions=output:2

添加丢弃数据包流表:dpctl add-flow in_port=2,actions=drop

删除所有交换机的所有流表:dpctl del-flows

删除所有交换机的特定流表项:dpctl del-flows in_port=2

删除某个交换机的流表:sh ovs_ofctl del-flows s1 in_port=2

  • 12
    点赞
  • 104
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值