一、前言
前面的博文写到了基于跳数的最短路径的案例实现(ryu实例---基于跳数的最短路径转发_北风-CSDN博客),然而基于跳数的最短路径转发并未考虑网络链路的质量(时延、可用带宽、丢包率...),所以针对这种情况,应该有一种基于链路质量的数据转发方式,所以接下来主要介绍实现链路质量转发的前提知识基础--即使用ryu实现链路网络时延的的探测。
二、时延探测原理
网络时延探测,主要利用LLDP的知识原理进行,关于ryu中的LLDP的原理,可以参考(Ryu拓扑发现原理分析 | SDNLAB | 专注网络创新技术),也就是利用了ryu自带的switches模块的数据,获取到了LLDP数据发送时的时间戳,然后和收到的时间戳进行相减,得到了LLDP数据包从控制器下发到交换机A,然后从交换机A到交换机B,再上报给控制器的时延T1,同理可得到反向的时延T2。此外,控制器到交换机的往返时延,此部分时延可由echo报文测试,分别为Ta,Tb。最后链路的前向后向平均时延T=(T1+T2-Ta-Tb)/2。
所以网络时延探测:(1) echo报文; (2) LLDP报文; (3) 运算,这里先主要讲述如何利用echo得到控制器到交换机的时延和LLDP时延,运算部分以及基于链路质量的数据转发后续更新。
三、程序设计
(1) 首先,进行网络拓扑的构建,为了更好提现程序的作用和各个链路时延探测的不同,网络拓扑构建(mininet)如下所示:
(2) 获取控制器到每个交换机的往返时延
这种时延的探测方法主要是控制器通过向交换机发送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
(3) 获取LLDP时延
获取几个交换机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)
(4) 整体代码解析
首先,创建类DelayDetector,进行初始化和数据结构的定义,程序用到的包和类代码如下:
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]
四、实验验证
代码编写完成后,需要对功能进行验证,网络拓扑的构建如上步的拓扑附图所示,运行拓扑。
在Ubuntu命令行输入命令运行ryu代码程序,查看打印的日志文件,如下图所示。
ryu-manager delay_detector_yjl2.py --verbose --observe-links
以上结果中{1: 0.0007786750793457031, 3: 0.0015261173248291016, 2: 0.0006613731384277344}就显示出了控制器到交换机s1、s2、s3往返时延;{'1-2-2': 0.0011858940124511719, '3-2-2': 0.0015857219696044922, '2-3-3': 0.0017540454864501953, '2-2-1': 0.0014941692352294922}显示出了c0-s1-s2-c0、c0-s2-s1-c0、c0-s2-s3-c0、c0-s3-s2-c0这四种LLDP时延。
以上就是网络时延探测的原理和编码,后续会持续更新基于链路质量的数据转发实例(https://blog.csdn.net/weixin_40042248/article/details/117733375)。
GitHub地址:https://github.com/Yang-Jianlin/ryu/blob/master/ryu/app/delay_detector_yjl.py
若有不当之处,请指正,谢谢。