之前,笔者已经发布了网络感知应用和基于跳数的最短路径转发应用。本文将介绍笔者开发的网络时延探测应用。该应用通过LLDP数据包的时延和Echo数据包的时延计算得出链路的时延数据,从而实现网络链路时延的感知。详细原理和实现步骤将在文章中详细介绍。

测试原理

网络时延探测应用利用了Ryu自带的Switches模块的数据,获取到了LLDP数据发送时的时间戳,然后和收到的时间戳进行相减,得到了LLDP数据包从控制器下发到交换机A,然后从交换机A到交换机B,再上报给控制器的时延T1,示例见图1的蓝色箭头。同理反向的时延T2由绿色的箭头组成。此外,控制器到交换机的往返时延由一个蓝色箭头和一个绿色箭头组成,此部分时延由echo报文测试,分别为Ta,Tb。最后链路的前向后向平均时延T=(T1+T2-Ta-Tb)/2。

link delay
图1. 测量链路时延原理图

获取LLDP时延

获取T1和T2的逻辑一样,均需要使用到Switches模块的数据。计算LLDP时延的处理逻辑如下代码所示。首先从Packet_in中解析LLDP数据包,获得源DPID,源端口。然后根据发送端口的数据获取到portdata中的发送时间戳数据,并用当下的系统时间减去发送时间戳,得到时延,最后将其保存到graph数据中。

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packet_in_handler(self, ev):
        msg = ev.msg
        try:
            src_dpid, src_port_no = LLDPPacket.lldp_parse(msg.data)
            dpid = msg.datapath.id
            in_port = msg.match['in_port']
            if self.sw_module is None:
                self.sw_module = lookup_service_brick('switches')

            for port in self.sw_module.ports.keys():
                if src_dpid == port.dpid and src_port_no == port.port_no:
                    port_data = self.sw_module.ports[port]
                    timestamp = port_data.timestamp
                    if timestamp:
                        delay = time.time() - timestamp
                        self._save_lldp_delay(src=src_dpid, dst=dpid,
                                              lldpdelay=delay)
        except LLDPPacket.LLDPUnknownFormat as e:
            return

获取Echo时延

之后,我们还需要测试控制器到交换机之间的echo往返时延。其测量方法是通过在控制器给交换机发送携带有时间戳的echo_request报文,然后解析交换机返回的echo_reply,并用当下时间减去data部分解析的发送时间,获得往返时间差。所以我们需要完成echo_request的定时发送和解析的实现,详细示例代码如下:

    def _measure(self):
        while True:
            self._send_echo_request()
            hub.sleep(self.SLEEP_PERIOD)

    def _send_echo_request(self):
        for datapath in self.datapaths.values():
            parser = datapath.ofproto_parser
            data = "%.6f" % time.time()
            echo_req = parser.OFPEchoRequest(datapath, data=data)
            datapath.send_msg(echo_req)

    @set_ev_cls(ofp_event.EventOFPEchoReply, MAIN_DISPATCHER)
    def echo_reply_handler(self, ev):
        try:
            latency = time.time() - eval(ev.msg.data)
            self.echo_latency[ev.msg.datapath.id] = latency
        except:
            return

完成echo时延的计算之后,将其保存到echo_latency字典中,已备后续计算使用。

计算链路时延

完成时延数据获取之后,还需要基于这些数据,计算出链路的时延,公式就是T=(T1+T2-Ta-Tb)/2。所以编写计算方法,示例代码如下。其中get_delay方法用于计算对应交换机之间的链路时延,_save_delay_data可以用于计算和存储lldp的时延和链路时延,其功能根据传入参数而定。而calculate_link_delay方法则用于调用计算方法,并将时延结果存储到networkx图数据结构中。

    def get_dalay(self, src, dst):
        try:
            fwd_delay = self.graph[src][dst]['lldpdelay']
            re_delay = self.graph[dst][src]['lldpdelay']
            src_latency = self.echo_latency[src]
            dst_latency = self.echo_latency[dst]

            delay = (fwd_delay + re_delay - src_latency - dst_latency)/2
            return max(delay, 0)
        except:
            return float('inf')

    def _save_lldp_delay(self, src=0, dst=0, lldpdelay=0):
        try:
            self.graph[src][dst]['lldpdelay'] = lldpdelay
        except:
            if self.graph is None:
                self.network_aware = lookup_service_brick('network_aware')
                self.graph = self.network_aware.graph
            return

    def create_link_delay(self):
        try:
            for src in self.graph:
                for dst in self.graph[src]:
                    if src == dst:
                        self.graph[src][dst]['delay'] = 0
                        continue
                    self.graph[src][dst]['delay'] = self.get_dalay(src, dst)
        except:
            if self.graph is None:
                self.network_aware = lookup_service_brick('network_aware')
                self.graph = self.network_aware.graph
            return

至此关于网络拓扑中链路时延的获取应用开发完成。需要注意的是,本应用需要依赖Ryu的topology/switches.py模块,所以如果单独使用时,需要配套启动switches.py。另外,与前面发表的应用相互结合,此应用中的graph是之前的network_aware模块感知的网络拓扑数据graph。

时延探测应用运行结果截图如图2所示。

图2.时延监控应用运行结果

总结

网络时延数据是网络重要数据,是许多网络决策的重要依据,所以网络时延数据测量非常重要。本文介绍了如何在Ryu中开发时延探测应用,并粘贴了关键的代码,希望对读者的学习提供一定的帮助。此外,还需要注意两点:(1)此时延探测模块十分初级,并没有精确性方面的考虑,比如需要将其放在核心层实现,在发送的最后时刻才添加时间戳,收到数据包的第一时刻马上解析时间戳等等,所以精确性不足。在Mininet模拟场景下,最开始的几组数据将会异常,但很快就可以恢复正常。(2)此处的拓扑数据均基于两个交换机之间仅有单链路存在的假设。若存在多链路,则数据会被最后获取的链路覆盖。解决这一问题的办法就是采用Neworkx的MultiGraph图结构来存储数据。最后希望本文能给读者带来一定的帮助,完整代码将于6月发布,敬请期待。


基于Ryu打造自定义控制器

2015-11-20 by muzi

控制器是SDN网络中最重要的组成部分。在开发SDN应用时,需要基于某一个控制器开发,而大部分开源控制器都是一个框架或者平台,更多个性化的设置和应用需要开发者自己完成。对于开发者而言,一个自定义的控制器可以让控制器更加适配开发场景,发挥控制器最大的作用,提高开发效率。本篇文章将以Ryu为例,介绍如何修改Ryu源码,打造属于自己风格的自定义控制器。其内容包括自定义参数,事件,启动顺序,报文,协议和底层服务。

自定义参数

很多应用都会涉及相关参数的输入才能运行, 如OpenFlow协议的启动需要配置监听端口。在编写新应用时,需要获取相关参数的值来运行应用,所以需要开发应用时注册参数。目前已有的参数可以通过ryu-manager -h查看。而不同的应用注册的参数很可能名字一样,这就有可能发生冲突。为解决这个问题,Ryu采用了OpenStack的Oslo库,支持全局的命令注册和解析。Oslo支持全局的命令注册和解析,成功解除了命令冲突的难题,也大大降低了参数注册和解析的难度。注册新参数的方法很简单,只需要新建一个文件,然后按照如下案例完成注册,最后再在cmd/manager.py中import即可。

from ryu import cfg

CONF = cfg.CONF
CONF.register_cli_opts([
    cfg.StrOpt ...
read more

Ryu:模块间通信机制分析

2015-09-08 by muzi

Ryu是一款非常轻便的SDN控制器,在科研方面得到了广泛的应用。相比其他控制器,受益于Python语言,在Ryu上开发SDN应用的效率要远高于其他控制器。为了解决复杂的业务,有时需要在Ryu上开发多模块来协同工作,从而共同完成复杂的业务。本文将介绍Ryu模块之间通信的包括Context等方式的多种通信方式。

_CONTEXTS

在RyuApp类中有一个属性是_CONTEXTS。_CONTEXTS中的内容将作为当前模块的服务在模块初始化时得到加载。示例如下:

    _CONTEXTS = {
        "Network_Aware": network_aware.Network_Aware,
        "Network_Monitor": network_monitor.Network_Monitor,
    }

    def __init__(self, *args, **kwargs):
        super(Shortest_forwarding, self).__init__(*args, **kwargs)
        self.name = 'shortest_forwarding'
        self.network_aware = kwargs["Network_Aware"]
        self.network_monitor = kwargs["Network_Monitor"]

在模块启动时,首先会将_CONTEXTS中的模块先启动,在模块的初始化函数中可以通过self.network_aware = kwargs["Network_Aware"]的形式获得该服务模块的实例,从而获取到该模块的数据 ...

read more

Ryu:OpenFlow协议源码分析

2015-07-24 by muzi

Ryu支持OpenFlow所有的版本,是所有SDN控制器中对OpenFlow支持最好的控制器之一。这得益于Ryu的代码设计,Ryu中关于OpenFlow协议的代码量不多。阅读Ryu源码,不仅让我了解到了Ryu的运行细节,也学会了许多的编码知识。这为我当前开发的协议提供了很大的帮助。

本篇将从交换机与控制器建立连接开始,介绍OpenFlow报文的解析的相关代码实现。关于如何注册handler和发送报文,可查看之前的RYU核心源码解读:OFPHandler,Controller,RyuApp和AppManager。该篇侧重点为Ryu整体架构的运作,重点在RyuApp和AppManager;本篇重点在与详细介绍OpenFlow的解析和封装实现。希望对读者提供帮助。

Ofp_handler

负责底层数据通信的模块是ofp_handler模块。ofp_handler启动之后,start函数实例化了一个controller.OpenFlowController实例。OpenFlowController实例化之后,立即调用_call_()函数,call函数启动了server_loop去创建server socket,其handler为domain_connection_factory函数。每当收到一个switch连接,domain_connection_factory就会实例化一个datapath对象。这个对象用于描述交换机的所有行为。其中定义了接收循环和发送循环。

Datapath

datapath.serve函数是socket通信收发逻辑的入口。该函数启动了一个绿色线程去处理发送循环,然后本线程负责接收循环的处理。self._send_loop是发送主循环。其主要逻辑为:不断获取发送队列是否有数据,若有,则发送;底层调用的是socket.send_all()函数 ...

read more

SDN网络感知服务与最短路径应用

2015-07-08 by muzi

本文将介绍RYU中的网络感知服务,与基于网络服务的最短路径应用,主要内容包括网络资源感知模块,网络监控模块和基于网络信息的最短路由模块介绍。在一个真实的网络环境下,需掌握网络的实时动态,包括网络的资源以及网络流量状况,其中网络的信息包括交换机,端口,主机的信息,以及基于流的流量统计信息和基于端口的流量统计信息。在掌握这些关键的网络信息后,控制器就可以根据这些信息作出当下最正确的路由决策,完成网络的通信。

网络资源感知

网络资源感知模块用于感知网络资源的实时变化,包括拓扑信息以及主机信息的变化。任何网络应用,可达性都是最基本的要求。SDN网络的集中控制,使得控制器可以根据全局的信息作出最佳决策而无需在交换节点上采用分布式的路由算法。所以感知网络资源是SDN应用最基础的一项服务。网络资源感知模块源码链接:Network_Awareness.

实现该模块的类为NetworkAwareness类,该类描述如下:

class NetworkAwareness(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    SLEEP_PERIOD = 10
    IS_UPDATE = True

    def __init__(self, *args, **kwargs):
        super(NetworkAwareness, self).__init__(*args, **kwargs)
        self.topology_api_app ...
read more