网络时延探测,主要利用LLDP的知识原理进行,也就是利用了Ryu自带的switches模块的数据,获取到了LLDP数据发送时的时间戳,然后和收到的时间戳进行相减,得到了LLDP数据包从控制器下发到交换机1,然后从交换机1到交换机2,再上报给控制器的时延T1,同理可得到反向的时延T2。此外,控制器到交换机的往返时延,此部分时延可由echo报文测试,分别为Ta,Tb。最后链路的前向后向平均时延T=(T1+T2-Ta-Tb)/2。
所以网络时延探测主要有3步:(1) echo报文; (2) LLDP报文;(3) 运算。本文先主要讲述如何利用echo得到控制器到交换机的时延和LLDP时延,运算部分以及基于链路质量的数据转发后续更新。
#!/usr/bin/env python
from mininet.net import Mininet
from mininet.node import Controller, RemoteController, OVSController
from mininet.node import CPULimitedHost, Host, Node
from mininet.node import OVSKernelSwitch, UserSwitch
from mininet.node import IVSSwitch
from mininet.cli import CLI
from mininet.log import setLogLevel, info
from mininet.link import TCLink, Intf
from subprocess import call
def myNetwork():
net = Mininet( topo=None,
build=False,
ipBase='10.0.0.0/8')
info( '*** Adding controller\n' )
c0=net.addController(name='c0',
controller=RemoteController,
protocol='tcp',
port=6633)
info( '*** Add switches\n')
s1 = net.addSwitch('s1', cls=OVSKernelSwitch, dpid='0000000000000001')
s2 = net.addSwitch('s2', cls=OVSKernelSwitch, dpid='0000000000000002')
s3 = net.addSwitch('s3', cls=OVSKernelSwitch, dpid='0000000000000003')
s4 = net.addSwitch('s4', cls=OVSKernelSwitch, dpid='0000000000000004')
info( '*** Add hosts\n')
h1 = net.addHost('h1', cls=Host, ip='10.0.0.1', defaultRoute=None)
h2 = net.addHost('h2', cls=Host, ip='10.0.0.2', defaultRoute=None)
h3 = net.addHost('h3', cls=Host, ip='10.0.0.3', defaultRoute=None)
h4 = net.addHost('h4', cls=Host, ip='10.0.0.4', defaultRoute=None)
info( '*** Add links\n')
net.addLink(s1, s2)
net.addLink(s2, s3)
net.addLink(s3, s4)
net.addLink(s1, h1)
net.addLink(s2, h2)
net.addLink(s3, h3)
net.addLink(s4, h4)
info( '*** Starting network\n')
net.build()
info( '*** Starting controllers\n')
for controller in net.controllers:
controller.start()
info( '*** Starting switches\n')
net.get('s1').start([c0])
net.get('s2').start([c0])
net.get('s3').start([c0])
net.get('s4').start([c0])
info( '*** Post configure switches and hosts\n')
CLI(net)
net.stop()
if __name__ == '__main__':
setLogLevel( 'info' )
myNetwork()
这种时延的探测方法主要是控制器通过向交换机发送echo报文,同时记录此时的时间戳,然后交换机收到echo报文之后,就会给控制器返回echo响应报文,当控制器收到响应报文之后,用当前的系统时间减去时间戳,即得到了控制器到交换机的往返时延。具体的代码如下:
# 由控制器向交换机发送echo报文,同时记录此时时间
def send_echo_request(self):
# 循环遍历交换机,逐一向存在的交换机发送echo探测报文
for datapath in self.dpidSwitch.values():
parser = datapath.ofproto_parser
echo_req = parser.OFPEchoRequest(datapath, data=bytes("%.12f" % time.time(), encoding="utf8")) # 获取当前时间
datapath.send_msg(echo_req)
# 每隔0.5秒向下一个交换机发送echo报文,防止回送报文同时到达控制器
hub.sleep(0.5)
# 交换机向控制器的echo请求回应报文,收到此报文时,控制器通过当前时间-时间戳,计算出往返时延
@set_ev_cls(ofp_event.EventOFPEchoReply, [MAIN_DISPATCHER, CONFIG_DISPATCHER, HANDSHAKE_DISPATCHER])
def echo_reply_handler(self, ev):
now_timestamp = time.time()
try:
echo_delay = now_timestamp - eval(ev.msg.data)
# 将交换机对应的echo时延写入字典保存起来
self.echoDelay[ev.msg.datapath.id] = echo_delay
print('*******************echo delay*****************')
print(self.echoDelay)
except Exception as error:
print(error)
return
获取几个交换机LLDP的逻辑一样,均需要使用到switches模块的数据。计算LLDP时延的处理逻辑如下代码所示。首先从Packet_in中解析LLDP数据包,获得源DPID,源端口。然后根据发送端口的数据获取到portdata中的发送时间戳数据,并用当下的系统时间减去发送时间戳,得到时延,最后将其保存到字典中。
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev): # 处理到达的LLDP报文,从而获得LLDP时延
msg = ev.msg
try:
src_dpid, src_outport = LLDPPacket.lldp_parse(msg.data) # 获取两个相邻交换机的源交换机dpid和port_no(与目的交换机相连的端口)
dst_dpid = msg.datapath.id # 获取目的交换机(第二个),因为来到控制器的消息是由第二个(目的)交换机上传过来的
if self.switches is None:
self.switches = lookup_service_brick("switches") # 获取交换机模块实例
# 获得key(Port类实例)和data(PortData类实例)
for port in self.switches.ports.keys(): # 开始获取对应交换机端口的发送时间戳
if src_dpid == port.dpid and src_outport == port.port_no: # 匹配key
port_data = self.switches.ports[port] # 获取满足key条件的values值PortData实例,内部保存了发送LLDP报文时的timestamp信息
timestamp = port_data.timestamp
if timestamp:
delay = time.time() - timestamp
self._save_delay_data(src=src_dpid, dst=dst_dpid, src_port=src_outport, lldp_dealy=delay)
except Exception as error:
print(error)
return
def _save_delay_data(self, src, dst, src_port, lldp_dealy):
key = "%s-%s-%s" % (src, src_port, dst)
self.src_dstDelay[key] = lldp_dealy
print('------------------lldp delay--------------------')
print(self.src_dstDelay)
import time
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER, CONFIG_DISPATCHER, HANDSHAKE_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.lib import hub
from ryu.ofproto import ofproto_v1_3
from ryu.topology.switches import LLDPPacket
from ryu.base.app_manager import lookup_service_brick
# 导入这些主要是为了让网络链路中产生LLDP数据包,只有产生了LLDP数据报,才能进行LLDP时延探测
from ryu.topology.api import get_switch, get_link, get_host
class DelayDetector(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(DelayDetector, self).__init__(*args, *kwargs)
self.name = 'delay_detector'
self.switches = lookup_service_brick('switches')
# 存储网络拓扑的交换机id
self.dpidSwitch = {}
# 存储echo往返时延
self.echoDelay = {}
# 存储LLDP时延
self.src_dstDelay = {}
# 实现协程,进行时延的周期探测
self.detector_thread = hub.spawn(self.detector)
# 每隔3秒进行控制器向交换机发送一次echo报文,用以获取往返时延
def detector(self):
while True:
self.send_echo_request()
hub.sleep(3)
def add_flow(self, datapath, priority, match, actions):
ofp = datapath.ofproto
ofp_parser = datapath.ofproto_parser
command = ofp.OFPFC_ADD
inst = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
req = ofp_parser.OFPFlowMod(datapath=datapath, command=command,
priority=priority, match=match, instructions=inst)
datapath.send_msg(req)
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofp = datapath.ofproto
ofp_parser = datapath.ofproto_parser
# add table-miss
match = ofp_parser.OFPMatch()
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_CONTROLLER, ofp.OFPCML_NO_BUFFER)]
self.add_flow(datapath=datapath, priority=0, match=match, actions=actions)
@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 not datapath.id in self.dpidSwitch:
self.dpidSwitch[datapath.id] = datapath
elif ev.state == DEAD_DISPATCHER:
if datapath.id in self.dpidSwitch:
del self.dpidSwitch[datapath.id]
# 由控制器向交换机发送echo报文,同时记录此时时间
def send_echo_request(self):
# 循环遍历交换机,逐一向存在的交换机发送echo探测报文
for datapath in self.dpidSwitch.values():
parser = datapath.ofproto_parser
echo_req = parser.OFPEchoRequest(datapath, data=bytes("%.12f" % time.time(), encoding="utf8")) # 获取当前时间
datapath.send_msg(echo_req)
# 每隔0.5秒向下一个交换机发送echo报文,防止回送报文同时到达控制器
hub.sleep(0.5)
# 交换机向控制器的echo请求回应报文,收到此报文时,控制器通过当前时间-时间戳,计算出往返时延
@set_ev_cls(ofp_event.EventOFPEchoReply, [MAIN_DISPATCHER, CONFIG_DISPATCHER, HANDSHAKE_DISPATCHER])
def echo_reply_handler(self, ev):
now_timestamp = time.time()
try:
echo_delay = now_timestamp - eval(ev.msg.data)
# 将交换机对应的echo时延写入字典保存起来
self.echoDelay[ev.msg.datapath.id] = echo_delay
print('*******************echo delay*****************')
print(self.echoDelay)
except Exception as error:
print(error)
return
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev): # 处理到达的LLDP报文,从而获得LLDP时延
msg = ev.msg
try:
src_dpid, src_outport = LLDPPacket.lldp_parse(msg.data) # 获取两个相邻交换机的源交换机dpid和port_no(与目的交换机相连的端口)
dst_dpid = msg.datapath.id # 获取目的交换机(第二个),因为来到控制器的消息是由第二个(目的)交换机上传过来的
if self.switches is None:
self.switches = lookup_service_brick("switches") # 获取交换机模块实例
# 获得key(Port类实例)和data(PortData类实例)
for port in self.switches.ports.keys(): # 开始获取对应交换机端口的发送时间戳
if src_dpid == port.dpid and src_outport == port.port_no: # 匹配key
port_data = self.switches.ports[port] # 获取满足key条件的values值PortData实例,内部保存了发送LLDP报文时的timestamp信息
timestamp = port_data.timestamp
if timestamp:
delay = time.time() - timestamp
self._save_delay_data(src=src_dpid, dst=dst_dpid, src_port=src_outport, lldp_dealy=delay)
except Exception as error:
print(error)
return
def _save_delay_data(self, src, dst, src_port, lldp_dealy):
key = "%s-%s-%s" % (src, src_port, dst)
self.src_dstDelay[key] = lldp_dealy
print('------------------lldp delay--------------------')
print(self.src_dstDelay)
ryu-manager delay_detector.py --verbose --observe-links
sudo python3 delay_detector.py
以上结果中,{1: 0.0004951953887939453, 2: 0.00040435791015625, 3: 0.0005011558532714844, 4: 0.00046634674072265625}表示控制器到交换机s1、s2、s3、s4的往返时延;{‘2-1-1’: 0.0009016990661621094, ‘1-1-2’: 0.0007228851318359375, ‘3-1-2’: 0.0007128715515136719, ‘4-1-3’: 0.0010399818420410156, ‘2-2-3’: 0.0008199214935302734, ‘3-2-4’: 0.0008656978607177734}表示c0-s1-s2-c0、c0-s2-s1-c0、c0-s2-s3-c0、c0-s3-s2-c0、c0-s3-s4-c0、c0-s4-s3-c0这六种LLDP时延。