Switching hub
Switching hub,即交换式集线器,是一个能在共享网络拓扑结构中减少竞争的设备。
假如你有一个由20个用户组成的网,因为信息传输量超载而停止运行,你可以将其分拆成两个段并桥接它们,这样就会减少整个系统的数据传输负载,并减少每一个新网络段上的竞争访问。位于同一网段上的所有用户或设备彼此之间能正常通信,只有这样才会减少段之间的通信量。如果通信量仍有问题,你还可以将LAN分成4段、6段等等。交换式集线器可恰到好处地完成这种分段。一个集线器上有很多端口,每一个端口是一个专用LAN段。
交换集线器有许多功能,在这里介绍以下几个简单的功能:
- 学习连接到端口的主机得MAC地址,并将其保留在MAC地址表中。
- 当接收到一个数据包且其MAC地址已经存在MAC地址表中时,直接将包转发到目的端口所连接的主机中。
- 当接收到的数据包没有在其MAC地址表中时,则进行泛洪。
Switching Hub by OpenFlow
OpenFlow交换机根据OpenFlow控制器(如RYU)的指令来执行以下操作:
- 重写接收包的地址或将包转发到指定端口。
-
转发包到控制器(
packet-in消息
,即用于交换机传递包到控制器,通常是为了异常情况的处理) -
控制器使用
packet-out消息
把数据包发送给交换机,交换机再转发到指定端口中。
类似,Switching hub的执行过程如下:
首先,控制器使用packet-in功能来接收交换机传递过来的包,交换机则分析该包来学习源端口所连接主机的MAC地址以及端口信息。
学习之后,交换机转发所接收到的数据包。它先检查该数据包的目的MAC地址是否在MAC地址表中,如果在,则使用packet-out功能来转发包到目的端口中,反之,则执行泛洪操作。
举个例子:
- 初始化状态
此时的流表为空。主机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
-
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
-
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: