目录
1.Ryu+Mininet应用案例一
1.1Hub+Learning
通过控制器来实现集线器算法(泛洪),然后指导数据平面实现集线器操作。
在Ryu控制器源码的ryu/app目录下创建hub.py文件。代码如下:
#集线器的应用
class Hub(app_manager.RyuApp):
""" 集线器一个端口输入,其余端口输出 """
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] #说明OpenFlow协议版本为1.3
def __init__(self,*args,**kwargs):
super(Hub, self).__init__(*args,**kwargs)
@set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER) # 注册:在配置状态下(CONFIG_DISPATCHER)监听事件(EventOFPSwitchFeatures)
def switch_features_handler(self,event): #处理交换机的连接,也就是处理上面所监听到的事件
#当接收到Switch Features(Features Reply)消息时(在交换机与控制器握手时接收),Table-miss流表项被添加。
#当交换机握手(handshake)完成后,Table-miss流表项被添加到流表中,准备接收Packet-In消息
#解析
datapath=event.msg.datapath #datapath在OpenFlow协议中定义,等同于数据平面的通道或Bridge网桥
ofproto=datapath.ofproto
ofp_parser=datapath.ofproto_parser
#install the table-miss flow entry 安装table-miss流表项
match =ofp_parser.OFPMatch() #指明Match域
actions=[ofp_parser.OFPActionOutput( #指明动作集
ofproto.OFPP_CONTROLLER, #说明发送端口为CONTROLLER
ofproto.OFPCML_NO_BUFFER)] #数据包在Buffer中存入的Buffer_id,此处不存放Buffer_id
self.add_flow(datapath,0,match,actions)
def add_flow(self,datapath,priority,match,actions): #添加流表
#add a flow entry,and install it into datapath
ofproto=datapath.ofproto
ofp_parser=datapath.ofproto_parser
#construct a flow_mod msg and sent it (通过Flow-mod消息增删交换机的流表项)
inst=[ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)] #添加指令 #指令的动作是:当执行当此指令集时,就执行其动作Actions #执行的对象
flow_mod=ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,instructions=inst)
datapath.send_msg(flow_mod) #发送信息 使用Ryu,当接收到OpenFlow消息时,将生成与该消息对应的事件
@set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER) #注册:在主状态下(MAIN_DISPATCHER)监听事件(EventOFPPacketIn)
def packet_in_handler(self,event):
msg=event.msg
datapath=msg.datapath
ofproto=datapath.ofproto
ofp_parser=datapath.ofproto_parser
in_port=msg.match['in_port']
#sent packetIn
#首先construct a flow entry
match=ofp_parser.OFPMatch()
actions=[ofp_parser.OFPActionOutput(ofproto.OFPP_FLOOD)] #对匹配到流表项的数据包,发送到OutPort的Flood端口
#install flow_mod to avoid PacketIn next time
self.add_flow(datapath,1,match,actions)
out=ofp_parser.OFPPacketOut(datapath=datapath,buffer_id=msg.buffer_id,in_port=in_port,actions=actions)
datapath.send_msg(out)
注:ryu控制器基础内容说明:
①使用Ryu,当接收到OpenFlow消息时,将生成与该消息对应的事件。
②事件处理(Event handler)程序定义了一个函数,该函数的参数为事件对象,并使用ryu.controller.handler.set_ev_cls 装饰器来装饰。
③事件(Event)的类名是: ryu.controller.ofp_event.EventOFP
+ <OpenFlow message name> 例如Packet-in消息,其事件的类名:EventOFPPacketIn。
④事件状态(Event State):
ryu.controller.handler.HANDSHAKE_DISPATCHER :交换HELLO消息
ryu.controller.handler.CONFIG_DISPATCHER :等待接收SwitchFeatures消息
ryu.controller.handler.MAIN_DISPATCHER :正常状态
ryu.controller.handler.DEAD_DISPATCHER :断开连接
1.2结果显示
在终端(Terminal)启动Ryu,输入命令ryu-manager hub.py --verbose,会出现如下结果:
可以看到,ofp_event事件提供EventOFPPacketIn和EventOFPSwitchFeatures,而Hub消费到EventOFPPacketIn和EventOFPSwitchFeatures,表示集线器(Hub)功能实现。
启动Mininet,输入命令sudo mn --controller=remote,ip=xx.xx.xx.xx,port=6633,连接控制器,应用系统自带拓扑结构。
进入Mininet后,输入pingall,Ryu控制器终端会有如下结果显示:
2.Ryu+Mininet应用案例二
2.1Learning Switch/自学习交换机
注:本实例来自Ryubook
通过控制器来实现自学习交换算法,然后指导数据平面实现交换机操作。
自学习交换机原理(4歩):
①初始状态
初始状态下流表为空,主机A(host A)连接端口1(port 1),主机B连接端口4,主机C连接端口3。
②Host A ->Host B
当数据包(Packets)要从主机A发送给主机B时,一条Packet-In消息被发送(由交换机发送)并且主机A的MAC地址被端口1获取到,因为主机B的端口还没有发现主机B的MAC地址,控制器也不知道主机B的地址,所以数据包(Packets)被洪泛(控制器下发的Packet-out:action),则主机B和主机C都会收到数据包。
Packet-In消息内容:
in-port:1
eth-dst:Host B
eth-src:Host A
Packet-out消息内容:
action:OUTPUT:Flooding
注:当控制器需要发送分组到数据平面,这时可以通过Packet-out消息封装好数据分组传给OpenFlow,并在该消息中指定特定的动作表指导交换机处理这个数据分组,而不再进行流表的匹配(除非动作表中包含转发到Table的动作)。
③Host B -> Host A
当主机B收到数据包后会返回(reply)消息,此时交换机会给控制器发送Packet-In,控制器会下发packet-out,此时一条流表项(Entry)下发被添加到流表中,数据包(Packets)从主机B返回到主机A。由于主机C并不是目的主机,所以之前主机C收到的数据包后不会返回到主机A(不会发送Reply)会丢弃掉,流表中也不会添加别的流表项。
Packet-In:
in-port:1
eth-dst:Host A
eth-src:Host B
Packet-Out:
action:OUTPUT:Port 1
④Host A ->Host B
当数据包再次从主机A发送到主机B时,交换机上发一条Packet-in消息给控制器,此时控制器知道怎么到达主机B,所以控制器发送Packet-out消息,此时一条流表项被添加到流表中,交换机收到Packet-out后就会将数据包(Packets)发送到数据B。
Packet-In:
in-port:1
eth-dst:Host B
eth-src:Host A
Packet-Out:
action:OUTPUT:Port 4
2.2案例实现
在Ryu控制器源码的ryu/app目录下创建example_switch.py文件,代码如下。
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
"""
Base组件只包含模块管理器(app_manager)单个组件,它是Ryu应用组件的管理中枢,
具有加载Ryu应用组件、为组件提供上下文(Context)及传递组件消息的作用
"""
class ExampleSwitch(app_manager.RyuApp):
"""
app_manager中定义的RyuApp类是Ryu应用组件的基础类,
Ryu应用组件需要定义继承该类的子类。
"""
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(ExampleSwitch, self).__init__(*args, **kwargs)
# initialize mac address table.
self.mac_to_port = {} #mac地址到port的对应关系
@set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER) # 注册:在配置状态下(CONFIG_DISPATCHER)监听事件(EventOFPSwitchFeatures)
def switch_features_handler(self, event): # 处理交换机的连接,也就是处理上面所监听到的事件
# 解析
datapath = event.msg.datapath # datapath在OpenFlow协议中定义,等同于数据平面的通道或Bridge网桥
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
# install the table-miss flow entry 安装table-miss流表项
match = ofp_parser.OFPMatch() # 指明Match域
actions = [ofp_parser.OFPActionOutput( # 指明动作集
ofproto.OFPP_CONTROLLER, # 说明发送端口为CONTROLLER
ofproto.OFPCML_NO_BUFFER)] # 数据包在Buffer中存入的Buffer_id,此处不存放Buffer_id
self.add_flow(datapath, 0, match, actions)
def add_flow(self, datapath, priority, match, actions): # 添加流表
# add a flow entry,and install it into datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
# construct a flow_mod msg and sent it (通过Flow-mod消息增删交换机的流表项)
inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)] # 添加指令 #指令的动作是:当执行当此指令集时,就执行其动作Actions #执行的对象
flow_mod = ofp_parser.OFPFlowMod(datapath=datapath, priority=priority, match=match, instructions=inst)
datapath.send_msg(flow_mod) # 发送信息
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) # 注册:在主状态下(MAIN_DISPATCHER)监听事件(EventOFPPacketIn)
def packet_in_handler(self, event):
msg = event.msg
datapath = msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
#逻辑:实现自学习算法
#1.获取datapath id 来定位或识别OpenFlow交换机
dpid=datapath.id
self.mac_to_port.setdefault(dpid,{})
#2.保存从OpenFlow交换机获取到的信息
#3.解析收到的packets信息 analyse the received packets using the packet library
pkt=packet.Packet(msg.data)
eth_pkt=pkt.get_protocol(ethernet.ethernet) #以太网数据包
dst=eth_pkt.dst #目的mac地址
src=eth_pkt.src #源mac地址
in_port=msg.match['in_port'] #从packet-in消息中收到端口号
self.logger.info("--- packet in %s %s %s %s",dpid,src,dst,in_port)
#4.学会源mac地址到port端口的映射信息,从而避免下一次出现FLOOD操作
self.mac_to_port[dpid][src]=in_port
#5.如果目的mac地址已经学到,就下发流表项指定端口发送数据包(packets);若果目的mac地址没有学到,则下发流表项的action为flooding(泛洪)
if dst in self.mac_to_port[dpid]:
out_port=self.mac_to_port[dpid][dst]
else:
out_port=ofproto.OFPP_FLOOD
#6.构造一个 actions
actions=[ofp_parser. OFPActionOutput(out_port)]
#7.安装一个flow消息 install a flow to avoid packet_in next time
if out_port !=ofproto.OFPP_FLOOD:
match=ofp_parser.OFPMatch(in_port=in_port,eth_dst=dst)
self.add_flow(datapath,1,match,actions) #下发流表
#8.构造packet_out消息并且发送packet-out
packetOut=ofp_parser.OFPPacketOut(datapath=datapath,buffer_id=msg.buffer_id,
in_port=in_port,actions=actions,data=msg.data)
datapath.send_msg(packetOut)
2.3结果显示
在终端(Terminal)启动Ryu,输入命令ryu-manager example_switch.py --verbose,会出现如下结果:
当开启Mininet后(控制平面与数据平面连接上后),有如下结果:
在Mininet中使用pingall/h1 ping h2(使用Mininet默认拓扑2个主机)命令后,有如下结果:
从结果中可以看出,主机1向主机2发出数据包(packets),首先主机1发出数据包后,交换机发送packet-in消息给控制器,由于控制器不知道主机B的地址,所以下发packet-out消息,action为OFPP_FLOOD,从上图可以看出第一次的目的mac地址为ff:ff:ff:ff:ff:ff(ARP请求);然后主机B收到后会向主机A发送packetReply消息,此时控制器就知道主机B的地址,然后下发流表项;最后当主机A再次向主机B发送数据包时,此时控制器就可以找到主机B,直接发送数据包。
3.Ryu+Mininet应用案例三
3.1流量监控
3.1.1流量监控原理:
1)控制器向交换机周期下发获取统计消息,请求交换机信息
①端口流量统计信息
②请求流表项统计信息
2)根据交换机统计信息计算流量信息
①流速公式:speed=(s(t1)-s(t0))/(t1-t0)
②端口/链路剩余带宽公式:free_bw=capability-speed
3.2案例实现
在Ryu控制器源码的ryu/app目录下创建 MyMonitor13.py文件,代码如下。
# 编码时间: 2021/3/3 17:25
# @File : my_monitor_13.py
# @software : PyCharm
from operator import attrgetter
from ryu.app import simple_switch_13
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.lib import hub
class MyMonitor13(simple_switch_13.SimpleSwitch13):
def __init__(self, *args, **kwargs): #初始化函数
super(MyMonitor13, self).__init__(*args, **kwargs)
self.datapaths = {} #初始化成员变量,用来存储数据
self.monitor_thread = hub.spawn(self._monitor) #用协程方法执行_monitor方法,这样其他方法可以被其他协程执行。 hub.spawn()创建协程
"""
Controller组件主要由OpenFlowController和Datapath两类构成,其中,OpenFlowController负责监听与Ryu连接的OpenFlow网络中的事件,
一旦有事件发生,会创建一个Datapath对象负责接收来自该事件的连接和数据,并将这些事件的数据分组进行解析,封装成Ryu的事件对象,然后派发。
"""
#get datapath info 获取datapath信息
#EventOFPStateChange事件用于检测连接和断开。
@set_ev_cls(ofp_event.EventOFPStateChange,[MAIN_DISPATCHER,DEAD_DISPATCHER])#通过ryu.controller.handler.set_ev_cls装饰器(decorator)进行注册,在运行时,ryu控制器就能知道MyMonitor13这个模块的函数_state_change_handler监听了一个事件
def _state_change_handler(self,event): #交换机状态发生变化后,让控制器数据于交换机一致
datapath=event.datapath
if event.state == MAIN_DISPATCHER: # 在MAIN_DISPATCHER状态下,交换机处于上线状态
if datapath.id not in self.datapaths:
self.logger.debug('register datapath: %016x',datapath.id)
self.datapaths[datapath.id]=datapath #datapath用字典来保存,key为id,value为datapath
elif event.state == DEAD_DISPATCHER: #在DEAD_DISPATCHER状态下
if datapath.id in self.datapaths:
self.logger.debug('unregister datapath:%016x',datapath.id)
del self.datapaths[datapath.id]
#send request msg periodically
def _monitor(self):
while True: #对已注册交换机发出统计信息获取请求每10秒无限地重复一次
for dp in self.datapaths.values(): #遍历所有的交换机或网桥
self._request_stats(dp)
hub.sleep(10) #休眠
#send stats request msg to datapath (完成控制器主动下发逻辑)
def _request_stats(self,datapath):
self.logger.debug('send stats request:%016x',datapath.id)
ofproto=datapath.ofproto
ofp_parser=datapath.ofproto_parser #解析器
# send flow stats request msg
request=ofp_parser.OFPFlowStatsRequest(datapath)
datapath.send_msg(request)
# send port stats request msg
request=ofp_parser.OFPPortStatsRequest(datapath,0,ofproto.OFPP_ANY)
datapath.send_msg(request)
#handle the port stats reply msg (完成交换机被动发送逻辑)
@set_ev_cls(ofp_event.EventOFPPortStatsReply,MAIN_DISPATCHER)
def _port_stats_reply_handler(self,event):
body=event.msg.body #消息体
self.logger.info('datapath port '
'rx-pkts rx-bytes rx-error '
'tx-pkts tx-bytes tx-error ') # rx-pkts:receive packets tx-pks:transmit packets
self.logger.info('---------------- -------- '
'-------- -------- -------- '
'-------- -------- --------')
for stat in sorted(body,key=attrgetter('port_no')): #attrgetter:属性获取工具
self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d',
event.msg.datapath.id, stat.port_no,
stat.rx_packets, stat.rx_bytes, stat.rx_errors,
stat.tx_packets, stat.tx_bytes, stat.tx_errors)
#handle the flow entry stats reply msg
@set_ev_cls(ofp_event.EventOFPFlowStatsReply,MAIN_DISPATCHER)
def _flow_stats_reply_handler(self,event):
body=event.msg.body # body:OFPFlowStats的列表,存储受FlowStatsRequest影响每个流表项的统计信息
self.logger.info('datapath '
'in-port eth-dst '
'out-port packets bytes')
self.logger.info('---------------- '
'-------- ----------------- '
'-------- -------- --------')
for stat in sorted([flow for flow in body if flow.priority==1]
,key=lambda flow:(flow.match['in_port'],flow.match['eth_dst'])):
self.logger.info('%016x %8x %17s %8x %8d %8d',
event.msg.datapath.id,stat.match['in_port'],
stat.match['eth_dst'],stat.instructions[0].actions[0].port,
stat.packet_count,stat.byte_count)
3.3结果显示
流量监控:
交换机连接上控制器,首先注册datapath
然后控制器主动下发请求request;最后通过处理Reply数据包得到相应数据。