写在前面

在前面搭建好mininet的环境后。本文将进一步搭建ryu的环境,并基于此实现一个简单网络的负载均衡。

ryu简介

SDN(Software-Defined Networking)软件定义网络是一种网络架构和技术,通过将网络控制平面与数据转发平面分离,实现网络的集中管理和控制。传统的网络架构中,网络设备(如交换机和路由器)通常同时负责数据转发和网络控制,而SDN将这两个功能分开,将网络控制逻辑集中在一个中心控制器上。
而ryu是一个开源的软件定义网络控制器平台。它是一个用Python编写的SDN控制器框架,旨在简化SDN应用程序的开发。

ryu环境搭建

去github上克隆对应的库并进行安装。

git clone https://github.com/osrg/ryu.git
cd ryu
pip install -r tools/pip-requires
python setup.py install

随后使用ryu-manager测试安装是否成功,如下则代表安装成功。

cd ryu/ryu/app
ryu-manager example_switch_13.py
loading app example_switch_13.py
loading app ryu.controller.ofp_handler
instantiating app example_switch_13.py of ExampleSwitch13
instantiating app ryu.controller.ofp_handler of OFPHandler

采用ryu+mininet实现简单ping测试

关于使用python编写ryu相关代码可参考如下链接:
https://ryu.readthedocs.io/en/latest/writing_ryu_app.htmlhttps://github.com/knetsolutions/learn-sdn-with-ryu/blob/master/ryu_part1.md 关于使用python编写mininet相关代码可参考如下链接:
https://github.com/mininet/mininet/wiki/Introduction-to-Mininet 首先需要采用mininet搭建一个简单的拓扑网络,其中各个函数作用如下表所示。

api

作用

Topo

Mininet拓扑结构的基类

build()

在自定义拓扑网络类中需要重写的方法。其中的参数n将会自动通过Topo.__init()__传递给Topo基类。

addSwitch()

将一个交换机添加到拓扑中并返回交换机名称。

addHost()

将一个主机添加到拓扑中并返回主机名称。

addLink()

向拓扑中添加一个双向连接(并返回一个连接键link key)。在Mininet中,除非有额外的声明,链接都是双向的。

Mininet

创建和管理网络的类。

pingAll()

通过尝试让所有节点互相ping对方来测试连通性。

start()

启动创建的网络。

stop()

关闭创建的网络。

net.hosts

网络中的所有主机。

dumpNodeConnections()

导出拓扑结构。

setLogLevel( ‘info’ / ‘debug’ / ‘output’ )

设置Mininet日志的默认输出级别;建议使用’info’级别,它可以提供有用信息而不会太过冗杂。

根据上述函数,编写一个简单的线形拓扑网络,代码如下所示。通过Mininet搭建了一个3个主机节点的网络,并导出了网络结构,并通过节点之间相互ping来进行测试。

from mininet.topo import Topo
from mininet.net import Mininet
from mininet.util import dumpNodeConnections
from mininet.log import setLogLevel


class SimpleTopo(Topo):
    def __init__(self):
        Topo.__init__(self)
        
        host1 = self.addHost('h1')
        host2 = self.addHost('h2')
        host3 = self.addHost('h3')
        switch1 = self.addSwitch("s1")
        switch2 = self.addSwitch("s2")
        switch3 = self.addSwitch("s3")

        self.addLink(switch1, host1)
        self.addLink(switch2, host2)
        self.addLink(switch3, host3)
        self.addLink(switch1, switch2)
        self.addLink(switch2, switch3)

topos = {'mytopo': (lambda: SimpleTopo())}

其中采用该自定义网络结构进行启动的命令如下所示:

sudo mn --custom easytopo.py --topo mytopo

得到结果如下

*** Creating network
*** Adding controller
*** Adding hosts:
h1 h2 h3 
*** Adding switches:
s1 s2 s3 
*** Adding links:
(s1, h1) (s1, s2) (s2, h2) (s2, s3) (s3, h3) 
*** Configuring hosts
h1 h2 h3 
*** Starting controller
c0 
*** Starting 3 switches
s1 s2 s3 ...
*** Starting CLI:
mininet> pingall
*** Ping: testing ping reachability
h1 -> h2 h3 
h2 -> h1 h3 
h3 -> h1 h2 
*** Results: 0% dropped (6/6 received)

随后再进行 ryu程序的编写,这里主要参考ryu里的例程进行说明。

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_2
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import ether_types


class SimpleSwitch12(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_2.OFP_VERSION]

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

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

        match = datapath.ofproto_parser.OFPMatch(in_port=port,
                                                 eth_dst=dst,
                                                 eth_src=src)
        inst = [datapath.ofproto_parser.OFPInstructionActions(
                ofproto.OFPIT_APPLY_ACTIONS, actions)]

        mod = datapath.ofproto_parser.OFPFlowMod(
            datapath=datapath, cookie=0, cookie_mask=0, table_id=0,
            command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
            priority=0, buffer_id=ofproto.OFP_NO_BUFFER,
            out_port=ofproto.OFPP_ANY,
            out_group=ofproto.OFPG_ANY,
            flags=0, match=match, instructions=inst)
        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
        in_port = msg.match['in_port']

        pkt = packet.Packet(msg.data)
        eth = pkt.get_protocols(ethernet.ethernet)[0]

        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, in_port)

        # learn a mac address to avoid FLOOD next time.
        self.mac_to_port[dpid][src] = 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, 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=in_port,
            actions=actions, data=data)
        datapath.send_msg(out)

1.OFP_VERSIONS为设置的openflow的版本。
2.__init__为官方文档里所定义的构造方法。
3.其中_packet_in_handler函数的装饰器set_ev_cls的第一个参数表示调用该函数的事件类型。即每次收到packet_in 消息时,都会调用此函数。第二个参数指示开关的状态。使用“MAIN_DISPATCHER”作为第二个参数意味着仅在ryu与交换机协商完成后调用此函数。
4.ev.msg是一个表示packet_in的数据结构。
5.msg.dp代表一个数据路径(交换机)。
6.dp.ofproto 和 dp.ofproto_parser 代表 ryu 和交换机协商的 OpenFlow 协议的对象。7.actions代表动作列表。
8.OFPActionOutput指定将数据包发送出的交换机端口。如果使用 OFPP_FLOOD 标志则指示数据包应在所有端口上发送出去。
9.OFPPacketOut 用于构建 packet_out 消息。
10.对于datapath.send_msg 方法,ryu会构建数据格式并将其发送到交换机。
11.则整个_packet_in_handler函数先通过获取事件中的消息(msg),数据路径(datapath)和OpenFlow协议版本(ofproto)以及输入端口(in_port)。过程中判断以太网类型是否为LLDP类型,如果是则忽略该数据包。同时获取目的MAC地址(dst)和源MAC地址(src)并获取交换机的标识符(dpid),并将其作为键值存储在self.mac_to_port字典中。如果目的MAC地址在self.mac_to_port字典中存在,则输出端口为目的MAC地址对应的端口;否则,输出端口为所有端口(OFPP_FLOOD
)。再根据输出端口构建动作(actions)。如果输出端口不是洪泛端口,则调用add_flow方法,安装流表规则以避免下次数据包处理时再次触发PacketIn事件。
如果消息没有缓冲区ID,则直接获取消息里的数据。最后,发送数据包输出消息给交换机。
12.对于add_flow函数,则是根据协议类型与输入参数构建一个匹配条件,构建流表修改信息发送给交换机。

最后起两个终端对上述代码进行测试。首先启动ryu控制器

ryu-manager simple.py
loading app simple.py
loading app ryu.controller.ofp_handler
instantiating app simple.py of SimpleSwitch12
instantiating app ryu.controller.ofp_handler of OFPHandler

再启动mininet拓扑网络,控制器选项设置为remote

sudo mn --custom easytopo.py --topo mytopo --controller remote
*** Creating network
*** Adding controller
Connecting to remote controller at 127.0.0.1:6653
*** Adding hosts:
h1 h2 h3 
*** Adding switches:
s1 s2 s3 
*** Adding links:
(s1, h1) (s1, s2) (s2, h2) (s2, s3) (s3, h3) 
*** Configuring hosts
h1 h2 h3 
*** Starting controller
c0 
*** Starting 3 switches
s1 s2 s3 ...
*** Starting CLI:
mininet> pingall
*** Ping: testing ping reachability
h1 -> h2 h3 
h2 -> h1 h3 
h3 -> h1 h2 
*** Results: 0% dropped (6/6 received)

可以看到成功ping通