使用Ryu实现交换式集线器(Switching hub)

  • Post author:
  • Post category:其他




Switching hub


Switching hub,即交换式集线器,是一个能在共享网络拓扑结构中减少竞争的设备。


假如你有一个由20个用户组成的网,因为信息传输量超载而停止运行,你可以将其分拆成两个段并桥接它们,这样就会减少整个系统的数据传输负载,并减少每一个新网络段上的竞争访问。位于同一网段上的所有用户或设备彼此之间能正常通信,只有这样才会减少段之间的通信量。如果通信量仍有问题,你还可以将LAN分成4段、6段等等。交换式集线器可恰到好处地完成这种分段。一个集线器上有很多端口,每一个端口是一个专用LAN段。

交换集线器有许多功能,在这里介绍以下几个简单的功能:

  1. 学习连接到端口的主机得MAC地址,并将其保留在MAC地址表中。
  2. 当接收到一个数据包且其MAC地址已经存在MAC地址表中时,直接将包转发到目的端口所连接的主机中。
  3. 当接收到的数据包没有在其MAC地址表中时,则进行泛洪。



Switching Hub by OpenFlow

OpenFlow交换机根据OpenFlow控制器(如RYU)的指令来执行以下操作:

  1. 重写接收包的地址或将包转发到指定端口。
  2. 转发包到控制器(

    packet-in消息

    ,即用于交换机传递包到控制器,通常是为了异常情况的处理)
  3. 控制器使用

    packet-out消息

    把数据包发送给交换机,交换机再转发到指定端口中。

类似,Switching hub的执行过程如下:

首先,控制器使用packet-in功能来接收交换机传递过来的包,交换机则分析该包来学习源端口所连接主机的MAC地址以及端口信息。

学习之后,交换机转发所接收到的数据包。它先检查该数据包的目的MAC地址是否在MAC地址表中,如果在,则使用packet-out功能来转发包到目的端口中,反之,则执行泛洪操作。

举个例子:

  1. 初始化状态

此时的流表为空。主机A连接端口1,主机B连接端口3,主机C连接端口4.

在这里插入图片描述

2. HostA->HostB

当主机A向主机B发送一个数据包时,一个packet-in消息被发送,交换机通过端口1学习到主机A的MAC地址并保存在MAC地址表中。而此时连接端口4的主机B的MAC地址没有在MAC地址表中,所以该包被泛洪,询问谁有该目的MAC地址。此时主机B和主机C均可接收到该包。

在这里插入图片描述
Packet-in:

in-port: 1

eth-dst: Host B

eth-src: Host A

Packet-Out:

action: OUTPUT:Flooding

  1. HostB->HostA

    主机B接收到主机A发送的包之后,发现该目的地址和自己匹配,因此封装自己的MAC地址到该包中并发回给主机A,主机B的MAC地址被保留在MAC地址表中,并且一条流表项加进流表中。因为此时主机A的MAC地址已经存在表中,所以不需要泛洪,直接发送到连接主机A的端口1即可。主机C不再接受该包。

    在这里插入图片描述

    Packet-In:

in-port: 4

eth-dst: Host A

eth-src: Host B

Packet-Out:

action: OUTPUT:Port 1

  1. HostA->HostB

    当主机A再次向主机B发送数据包时,流表又会新加一条流表项。

    在这里插入图片描述
    Packet-In:

in-port: 1

eth-dst: Host B

eth-src: Host A

Packet-Out:

action: OUTPUT:Port 4



Implementation of Switching Hub Using Ryu

在ryu/app中的simple_switch_13.py实现了Switching hub,后面的13表示支持OpenFlow1.3的版本。

在这里插入图片描述
源代码如下:

from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet


class ExampleSwitch13(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super(ExampleSwitch13, self).__init__(*args, **kwargs)
        # initialize mac address table.
        self.mac_to_port = {}

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        datapath = ev.msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # install the table-miss flow entry.
        match = parser.OFPMatch()
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                          ofproto.OFPCML_NO_BUFFER)]
        self.add_flow(datapath, 0, match, actions)

    def add_flow(self, datapath, priority, match, actions):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # construct flow_mod message and send it.
        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
                                             actions)]
        mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
                                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
        parser = datapath.ofproto_parser

        # get Datapath ID to identify OpenFlow switches.
        dpid = datapath.id
        self.mac_to_port.setdefault(dpid, {})

        # analyse the received packets using the packet library.
        pkt = packet.Packet(msg.data)
        eth_pkt = pkt.get_protocol(ethernet.ethernet)
        dst = eth_pkt.dst
        src = eth_pkt.src

        # get the received port number from packet_in message.
        in_port = msg.match['in_port']

        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 the destination mac address is already learned,
        # decide which port to output the packet, otherwise FLOOD.
        if dst in self.mac_to_port[dpid]:
            out_port = self.mac_to_port[dpid][dst]
        else:
            out_port = ofproto.OFPP_FLOOD

        # construct action list.
        actions = [parser.OFPActionOutput(out_port)]

        # install a flow to avoid packet_in next time.
        if out_port != ofproto.OFPP_FLOOD:
            match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
            self.add_flow(datapath, 1, match, actions)

        # construct packet_out message and send it.
        out = parser.OFPPacketOut(datapath=datapath,
                                  buffer_id=ofproto.OFP_NO_BUFFER,
                                  in_port=in_port, actions=actions,
                                  data=msg.data)
        datapath.send_msg(out)



Class Definition and Initialization

class ExampleSwitch13(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super(ExampleSwitch13, self).__init__(*args, **kwargs)
        # initialize mac address table.
        self.mac_to_port = {}

# ...

首先是类定义和初始化。

app_manager.RyuApp:当要实现一个Ryu应用时,首先需要继承ryu.base.app_manager.RyuApp

OFP_VERSIONS:用来指定OpenFlow的版本号

mac_to_port:定义了MAC地址表,初始为空表。

在OpenFlow协议中,定义了OpenFlow交换机和控制器握手的过程,但这些都有Ryu框架来进行处理,因此在这里我们不再深入了解。



Event Handler

当一个OpenFlow消息被接受后,就会生成一个对应的事件,Ryu应用程序实现对应消息的事件处理函数。

事件处理程序为参数定义一个具有事件对象的函数,使用ryu.controller.handler.set_ev_cls函数装饰器来装饰。

set_ev_cls为参数指定支持接受消息的事件的类以及OpenFlow交换机的状态。

事件类名为ryu.controller.ofp_event.EventOFP + “OpenFlow message name”,

例如,如果是packet-in消息,则类名为 EventOFPPacketIn。

交换机状态则根据情况指定下面列表中的其中之一。

在这里插入图片描述



Adding Table-miss Flow Entry

握手完成后,当一个packet-in消息到来时,就会添加一个Table-miss流条目到流表里。

这里首先接收到含有交换机特性的消息,然后将该条Table-miss流条目添加进去。

@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
    datapath = ev.msg.datapath
    ofproto = datapath.ofproto
    parser = datapath.ofproto_parser

# ...

ev.msg:用于存储于事件消息对应类的实例。

Datapath类执行重要的处理,如与OpenFlow交换机的实际通信和发出与接收到的消息对应的事件。

Ryu应用程序的主要属性如下:

在这里插入图片描述

def switch_features_handler(self, ev):
# ...

    # install the table-miss flow entry.
    match = parser.OFPMatch()
    actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                      ofproto.OFPCML_NO_BUFFER)]
    self.add_flow(datapath, 0, match, actions)

match:用于匹配所有包

actions:是交换机所要执行的动作。

ofproto.OFPP_CONTROLLER:控制器为输出目的端口

ofproto.OFPCML_NO_BUFFER:将OFPCML_NO_BUFFER指定为max_len,以便将所有数据包发送到控制器。



Packet-in Message

当接收到位置目的地的包时则执行packet-in事件处理程序

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

# ...

OFPPacketIn类属性如下:

在这里插入图片描述



Updating the MAC Address Table

def _packet_in_handler(self, ev):
# ...

    # get the received port number from packet_in message.
    in_port = msg.match['in_port']

    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

# ...

dpid为OpenFlow switches的标识符,因为一个控制器可以支持连接多个交换机,所以需指定dpid来定义每个OpenFlow交换机。



Execution of Ryu Application

理解了源代码的过程,就可以直接用mininet进行实验。

首先建立一个简单的拓扑结构,即三个主机连接同一个交换机。

$ sudo mn –topo single,3 –mac –switch ovsk –controller remote -x

*** Creating network

*** Adding controller

Unable to contact the remote controller at 127.0.0.1:6633

*** Adding hosts:

h1 h2 h3

*** Adding switches:

s1

*** Adding links:

(h1, s1) (h2, s1) (h3, s1)

*** Configuring hosts

h1 h2 h3

*** Running terms on localhost:10.0

*** Starting controller

*** Starting 1 switches

s1

*** Starting CLI:

mininet>

mn后面所跟着的属性如下:

在这里插入图片描述

当执行完上述命令后,会产生如下5个终端模拟器,即三个主机h1,h2,h3,一个交换机s1,一个控制器c0.

在这里插入图片描述

第一步首先查看Open vSwitch的状态

#ovs-vsctl show

在这里插入图片描述

接着设置OpenFlow版本为1.3

# ovs-vsctl set Bridge s1 protocols=OpenFlow13
#

查看流表,初始为空

# ovs-ofctl -O OpenFlow13 dump-flows s1



Executing the Switching Hub

在控制器c0上运行Ryu程序

# ryu-manager --verbose ryu.app.example_switch_13

在这里插入图片描述

OVS连接之后,即控制器和交换机之间的握手已经完成,并添加了Table-miss流条目,switching hub此时处于等到分组到来的状态。

在这里插入图片描述



Confirming Operation

对主机,首先执行tcpdump命令来检查每个主机都收了哪些包。

h1:

# tcpdump -en -i h1-eth0

在这里插入图片描述
h2,h3同理。

然后在mininet控制台上执行ping操作。这里我们进行h1 ping h2。

这里分析一下h1 ping h2的过程。

1、ARP请求

刚开始时,h1并不知道h2的MAC地址,因此在发送ICMP echo请求前,需要广播一个ARP请求,在上述例子上,这个广播将被h2和h3接收到。

2、ARP应答

h2看到ARP请求中的IP地址与自己匹配之后,于是向主机h1发送ARP响应报文。

3、ICMP echo请求

此时h1已经知道了h2的MAC地址,于是向h2发送一个 echo 请求。

4、ICMP echo应答

因为h2已经知道了h2的MAC地址,因此h2可以直接返回一个echo应答给h1.

在这里插入图片描述

现在检查一下s1中的流表

在这里插入图片描述

再看一下控制器c0中的log。

在这里插入图片描述

最后检查下每个主机,这里只贴h1,h2,h3同理。

h1:

在这里插入图片描述