from operator import attrgetterfrom ryu.app import simple_switch_13from ryu.controller.handler import set_ev_clsfrom ryu.controller import ofp_eventfrom ryu.controller.handler import MAIN_DISPATCHER,DEAD_DISPATCHERfrom ryu.lib import hub
#流量监控原理:控制器向交换机周期下发请求:获取统计消息,请求交换机信息:#1) 端口流量统计信息#2) 请求流表项统计信息
class MyMonitor13(simple_switch_13.SimpleSwitch13):#继承SimpleSwitch13这个父类就可以帮我们实现二层交换之类的功能,就不用自己写了。 def __init__(self,*args,**kwargs): super(MyMonitor13, self).__init__(*args,**kwargs) self.datapaths = {} #字典的格式:key-value:key是dpid,value是交换机:1-s1;2-s2 self.monitor_thread = hub.spawn(self._monitor) #生成协程,进而直接调用_monitor函数
#这个函数就是想要得到datapath而已:只不过是多了有效和无效datapath之分而已 #要设计监听,使ryu控制器知道有这么一个模块注册,监听了一个事件。设置监听也就相当于控制器进行处理了 @set_ev_cls(ofp_event.EventOFPStateChange,[MAIN_DISPATCHER,DEAD_DISPATCHER]) def _state_change_handler(self,ev): datapath = ev.datapath if ev.state == MAIN_DISPATCHER:#代表交换机已经注册并处于被监视的的状态 #类似注册的意思 if datapath.id not in self.datapaths: self.datapaths[datapath.id] = datapath self.logger.debug("register datapath: %16x",datapath.id)
elif ev.state == DEAD_DISPATCHER:#已经从注册状态解除 #类似删除记录 if datapath.id in self.datapaths: del self.datapaths[datapath.id] self.logger.debug("unregister datapath: %16x", datapath.id)
#定期向交换机下发统计信息的请求 def _monitor(self):#写了这个函数,但是还没调用,这里是放在初始化函数那里进行调用执行 while True: #现在我们要取的是value,不是key(key是dpid) for dp in self.datapaths.values(): self._request_stats(dp) hub.sleep(5)
#向交换机发送请求报文,参数为datapath,向谁发就引入谁 def _request_stats(self,datapath): ofproto = datapath.ofproto ofp_parser = datapath.ofproto_parser #这是不是:控制器向交换机发送请求报文,就是说分别由交换机向端口,流表下发统计流表条目信息的请求, #还是说控制器向交换机发送请求报文,就相当于直接向交换机端口和流表下发相应的消息 req = ofp_parser.OFPFlowStatsRequest(datapath)#对流表项进行统计,取得所有的流表项 datapath.send_msg(req) #参数:0:优先级;ofproto.OFPP_ANY:对任何一个端口都可以进行获取,取得所有的端口统计资料。 req = ofp_parser.OFPPortStatsRequest(datapath,0,ofproto.OFPP_ANY) datapath.send_msg(req)
self.logger.debug("send stats request to datapapth : %16x", datapath.id)
#为了接收来自交换机的回应,建立一个event handler来接受从交换机发送的回复信息 #(流表项的统计和端口的统计信息不能混为一谈,流表项的统计不包含被table-miss触发的packet-in以及packet-out传送封包信息。 # 例如h1 ping h2的过程:,host 1 最初的广播信息是 ARP request,而 host 2 回复了 host 1 的 ARP 訊息,host 1 对 host 2 发送了 # echo request 总共 3 個封包,這些都是透过 Packet-Out 所傳送的。因此端口的流量会远大于 Flow Entry 的流量 ) @set_ev_cls(ofp_event.EventOFPPortStatsReply,MAIN_DISPATCHER) def _port_stats_reply_handler(self,ev): # body = ev.msg.body #print self.logger.info('datapath port ' ' rx-pkts rx-bytes rx-errors ' ' tx-pkts tx-bytes tx-errors ')
self.logger.info('------------ ---------- ' ' -------- ---------- -------- ' ' -------- ---------- ----------')
#attrgetter是python自带的获取属性的工具 for stat in sorted(body,key=attrgetter("port_no")): self.logger.info("%8x %8x %8x %8x %8x %8x %8x %8x", ev.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,ev): #body是OPFFlowStatsReply的一个属性,是OFPFlowStats的列表,当中存储了每一个flow entry的统计信息,并作为flowstatsrequest的回应 #权限为0的table-miss flow除外的全部flow entry将会被选择,通过并符合该flow entry的封包数和位元数统计资料将会被回传,并以接受端口号和目的mac位址的方式排序。 body = ev.msg.body
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("%8x %8x %8d %8d %8d %8d %8d %8d", ev.msg.datapath.id, stat.match['in_port'],stat.match['eth_dst'], stat.instruction[0].actions[0].port, stat.packets_count,stat.byte_count)