Ryu拓扑发现:代码解析

Ryu is a component-based software defined networking framework. Ryu provides software components with well defined API that make it easy for developers to create new network management and control applications. Ryu supports various protocols for managing network devices, such as OpenFlow, Netconf, OF-config, etc. About OpenFlow, Ryu supports fully 1.0, 1.2, 1.3, 1.4 and Nicira Extensions.
上面引用官网的一段话,Ryu针对于SDN的一种控制器之一,也是我重点关注的控制器之一。究其有点,我觉得主要有两点,其一是更新快,开发迅速,因此其也成为了率先支持OpenFlow1.4协议的控制器之一。其二是开发难度低,结合Python语言的优势,其开发难度与对应用的代码量要明显的低于同功能下的例如OpenDaylight,FloodLight,Beacon等Java语言开发的控制器。整个代码框架采用了Oberserver模型,便于理解;文档与相当齐全。
但不好的地方也肯定会有,最大的问题就是性能问题,因为每个模块是单个线程,所以并发度很低。而Python语言本身在执行速度上也较之于其他语言来说,会慢很多。因此,在性能上面肯定比不过其他的控制器。Anyway,我们的真机实验环境下,感觉其也能够满足我们的需求。

LLDP概述

首先了解下拓扑发现所依赖的协议LLDP,引用来自维基百科的解释:
The Link Layer Discovery Protocol (LLDP) is a vendor-neutral link layer protocol in the Internet Protocol Suite used by network devices for advertising their identity, capabilities, and neighbors on an IEEE 802 local area network, principally wired Ethernet.[1] The protocol is formally referred to by the IEEE as Station and Media Access Control Connectivity Discovery specified in standards document IEEE 802.1AB
LLDP实际上是一种链路层协议,与其等价的还有Cisco Discovery Protocol (CDP),Extreme Discovery Protocol, Nortel Discovery Protocol (also known as SONMP),和Microsoft's Link Layer Topology Discovery (LLTD).而最后一个LLTD也正是诸位Win7中自带的链路发现协议。
接下附上LLDP的Frame结构:
Preamble|01:80:c2:00:00:0e|SourceMAC|0x88CC|ChassisID|PortID|TTL|...
当然最重要的两个字段是ChassisID和PortID.

Ryu拓扑发现程序结构

在ryu/topology/switches.py中,有9个类,接下来依次解析。
Port:主要是监听EventPortXXX消息的程序所传入的类,保存的主要有交换机ID,硬件地址,端口的名字,端口序号。
Switch:主要是监听EventSwitchXXX消息的程序所传入的类,保存的信息有Datapath类和ports这样一个由Port组成的list.
Link:主要是监听EventLinkXXX消息的程序所传入的类,保存的是源端口和目的端口,因为每个端口有交换机信息,所以能对应到每个交换机的每个端口。
PortData:这个类是个Port的附加类,用于保存每个端口与LLDP相关的数据部分,例如保存端口是否down的信息,LLDP的包内容,LLDP的是否。。。。。timestamp.sent
PortState:这个类是个dic,建立了从int port_no到OFP port的映射。
PortDataState:这个类同样是个dic,建立了Port Class到PortData Class的映射,即从整个代码维护了从Port到Port Data的映射关系。
LinkState:这个类同样是个dic,建立了由Link到timestamp的映射关系,主要提供了get_peer,update_link,link_down,rev_link_set_timestamp,port_deleted方法。
Switches:这个类继承了app_manager.RyuApp,因此也就是我们所谓的模块。这个类里面主要是LLDP实现拓扑收集的主要逻辑。接下来我们从这个类入手。

LLDP实现逻辑

假设最简单的拓扑
switch0:eth0------------eth1:switch1
                        +
                        +
                 Controller
那如何利用上述的LLDP实现拓扑的收集呢?
首先,Controller利用PacketOut消息,命令连接它的swich0向所有端口发送LLDP[switch0,eth0]。这个LLDP[switch0,eth0]就随着链路来到了eth1:swich1上,swich1也不知道这个LLDP怎么处理,就直接把LLDP[switch0,eth0]利用Packet-In消息送给了Controller,Controller就查看这个LLDP[switch0,eth0],并利用Packet-In的消息头知道了是switch1:eth1收到的这个消息。于是乎,就知道了switch:eth0的另外一边是switch:eth1。这样循环的发送LLDP消息,并解析LLDP消息,最终使得Controller知道了全局拓扑结构。

LLDP拓扑发现主要代码分析

首先我们注意这个函数
def __init__(self, *args, **kwargs):
这个函数是由Ryu的框架自动调用。当然其中最重要的要属
self.threads.append(hub.spawn(self.lldp_loop))
self.threads.append(hub.spawn(self.link_loop))
这两句通过hub创建了新的进程来实现lldp_loop和link_loop的函数调用。其中hub就是一个eventlet来创建的新的线程。不断跟踪,发现其实质是
eventlet.spawn(_launch, *args, **kwargs)
再进入lldp_loop来看看其干些什么,lldp_loop的本质其实就是不断的查看控制器所掌控的所有适合的端口,使其让其发送lldp packet。
通过下面的代码能够看出些端倪
for port in ports_now:
    self.send_lldp_packet(port)
for port in ports:
    self.send_lldp_packet(port)
    hub.sleep(self.LLDP_SEND_GUARD)      # don't burst
当然,让我最为疑惑的还是那个
self.lldp_event.wait(timeout=timeout)
OK,lldp_event其实一个异步的事件处理框架的原型,这里通过调用lldp_event.wait来让其在lldp_event事件上等待timeout秒或者有其他线程调用了lldp_event.set(),当然,我们后来会讲到这个lldp_event.set()函数。
其次,在进入link_loop看看他又在干些什么,两个for循环也清晰的标兵了,这个函数实际上是在查看每个端口的timestamp,如果其timeout,则将其删除。
当然,此处不免有个陷阱,第一个for循环用于遍历拿出需要deleted的port,第二个循环再来删除这些port,倘若在一个循环里面,很大可能性会造成遍历不全。
此处还是有一个类似于lldp_event的事件,link_event
self.link_event.wait(timeout=self.TIMEOUT_CHECK_PERIOD)
再看看一个重要的函数
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
这个函数调用了内置的decorator语法。使该函数能够被包装起来,使其能够监听EventOFPacketIn消息。
而在此监听Packet-In的函数里面,又调用了如下的函数来解析这个LLDP Packet。并把之前说到ChassisID和PortID字段提取起出来
src_dpid, src_port_no = LLDPPacket.lldp_parse(msg.data)
而接收到这个Packet-In的ChassisID和PortID又通过如下的语句实现
dst_dpid = msg.datapath.id
dst_port_no = msg.in_port
这两句的意思是谁传给控制器的这个Packet-In,并将其放到dst_dpid和dst_port_no中,而这个PacketIn又在其LLDP中包含了src_dpid和src_port_no,因此也就说明其两端有一条边。

1 comment: