本文主要包含ryu,faker劫反杀ryu比赛,ryu邪道长,江波ryu,kalina ryu等服务器相关知识,网友希望可以进行参考
Ryu拓扑发现原理分析,Ryu拓扑原理分析
Ryu拓扑发现的核心模块是ryu/topology目录下的switches.py,拓扑发现的应用是同目录下的dumper.py。在dumper.py中,会利用_CONTEXTS来实例化switches.py中的Switches类,然后将拓扑发现的相关信息通过日志方式(LOG.debug)显示。启动命令如下所示:ryu-manager --verbose --observe-links ryu.topology.dumper (pip安装的)或者
ryu-manager --verbose --observe-links ./ryu/topology/dumper.py(源码)
其中--verbose参数用于显示LOG.debug信息,--observe-links用于指明拓扑发现。
接下来对拓扑发现的核心模块switches.py进行分析。
1. Port类:
存储端口相关信息,数据成员有:
self.dpid = dpid
self._ofproto = ofproto
self._config = ofpport.config
self._state = ofpport.state
self.port_no = ofpport.port_no
self.hw_addr = ofpport.hw_addr
self.name = ofpport.name
其中要特别注意的是dpid和port_no,即交换机ID和端口号,这两个信息在下发流表项时很重要。
2. Switch类:
存储交换机相关信息,数据成员有:
self.dp = dp
self.ports = []
其中dp是Datapath类的实例,该类定义在在ryu/controller/controller.py,主要属性有:
self.socket = socket
self.address = address
self.is_active = True
self.id = None # datapath_id is unknown yet
self.ports = None
ports是一个由Port类实例组成的列表,存储该交换机的端口。
3. Link类:
保存的是源端口和目的端口(都是Port类实例),数据成员有:
self.src = src
self.dst = dst
4. PortState类
该类继承自dict,保存了从port_no(int型)到port(OFPPort类实例)的映射。该类主要用作self.port_state字典的值(键是dpid),用于存储dpid对应的交换机的所有端口情况。
OFPPort类定义在ryu/ofproto目录下对应的ofproto_v1_X_parser.py中(X代表版本号),继承自一个namedtuple,保存有port_no等信息。
5. PortData类
保存每个端口与对应的LLDP报文数据,数据成员有:
self.is_down = is_down
self.lldp_data = lldp_data(这是LLDP报文的数据)
self.timestamp = None
self.sent = 0
每调用一次lldp_sent函数,便会把self.timestamp置为当前的时间(time.time()),并将self.sent加1;每调用一次lldp_received函数,便会把self.sent置为0。
6.PortDataState类
继承自dict类,保存从Port类到PortData类的映射。该类维护了一个类似双向循环链表的数据结构,并重写了__iter__(),使得遍历该类的实例(self.ports)时,会按照该双向循环链表从哨兵节点(self._root)后一个节点开始遍历。
包含一个add_port函数,传入port和lldp_data,port作键,构建的PortData类实例作为值。
包含一个lldp_sent(self,port)函数,根据传入的port(Port类实例)获得对应的PortData类实例port_data,然后调用port_data.lldp_sent()(该函数会设置时间戳),再调用self._move_last_key(port),把该port移到类似双向循环链表的数据结构中哨兵节点的前面(相当于下次遍历的末尾);最后返回port_data。
7. LinkState类
继承自dict,保存从Link类到时间戳的映射。数据成员self._map字典用于存储Link两端互相映射的关系。
8. LLDPPacket类
静态方法lldp_packet(dpid,port_no,dl_addr,ttl)用于构造LLDP报文,静态方法lldp_parse(data)用于解析LLDP包,并返回源DPID和源端口号。
9. Switches类
该类是Ryu拓扑发现的核心所在。Switches类是app_manager.RyuApp类的子类,当运行switches应用时会被实例化,其__init__函数主要包括:
self.name = 'switches'
self.dps = {} # datapath_id => Datapath class
self.port_state = {} # datapath_id => ports
self.ports = PortDataState() # Port class -> PortData class
self.links = LinkState() # Link class -> timestamp
self.is_active = True
self.dps字典用于保存dpid到Datapath类实例的映射,会在_register函数中添加新成员,_unregister函数中删除成员。遍历该字典可以得到连接的所有交换机。
self.port_state字典中键为dpid,值为PortState类型。遍历该字典可以得到所有交换机对应的端口情况。当交换机连接时,会检查交换机的id是否在self.port_state中,不在则创建PortState类实例,把交换机的所有端口号和端口存储到该实例中;交换机断开时,会从self.port_state中删除。
self.ports是PortDataState类的实例,保存每个端口(Port类型)对应的LLDP报文数据(保存在PortData类实例中),遍历self.ports用于发送LLDP报文。
self.links是LinkState类的实例,保存所有连接(Link类型)到时间戳的映射。遍历self.links的键即可得到所有交换机之间的连接情况。
如果ryu-manager启动时加了--observe-links参数,则下面的self.link_discovery将为真,从而执行if下面的语句:
self.link_discovery = self.CONF.observe_links
if self.link_discovery:
self.install_flow = self.CONF.install_lldp_flow
self.explicit_drop = self.CONF.explicit_drop
self.lldp_event = hub.Event()
self.link_event = hub.Event()
self.threads.append(hub.spawn(self.lldp_loop))
self.threads.append(hub.spawn(self.link_loop))
综上所述,该初始化函数__init__()主要是创建用于存储相关信息的数据结构,创建两个事件,然后调用hub.spawn创建两个新线程执行self.lldp_loop和self.link_loop两个函数。
9.1 lldp_loop函数
lldp_loop函数里是一个while循环,只要self.is_active为真,就一直循环执行。(close函数会把self.is_active置为False,该函数在离开模块时自动被调用)
(1)执行self.lldp_event.clear(),将Event类实例lldp_event的_cond属性设为False,用于线程间同步。
提到线程同步,常用的函数有:
Event.wait()
Event对象的wait的方法只有内部信号为真的时候才会很快的执行并完成返回。当Event对象的内部信号标识为假时,则wait方法一直等待其为真时才返回。同时可以对wait设置timeout,当达到timeout设置的时间的时候就可以完成返回或执行。
Event.set()
将标识位设为Ture
Event.clear()
将标识伴设为False。
Event.isSet()
判断标识位是否为Ture
(2)创建ports_now和ports两个列表,分别存储尚未发送过LLDP报文的端口和已发送过LLDP报文并且超时的端口
(3)遍历self.ports(PortDataState类的实例),获得key(Port类实例)和data(PortData类实例),如果data.timestamp为None(该端口还没发送过LLDP报文),则将key(端口)加入ports_now列表;否则,计算下次应该发送LLDP报文的时间expire,如果已经超时,则放到ports列表,否则就是还没到发送时间,停止遍历(发送LLDP报文时是按序发的,找到第一个未超时的端口,后面的端口肯定更没有超时,因为后面端口上次发送LLDP是在前一端口之后,前一个都没超时后面的自然也没超时)
(4)遍历ports_now列表,对每个端口调用self.send_lldp_packet(port),发送LLDP报文。
send_lldp_packet函数执行过程如下:
a. 调用PortDataState类的lldp_sent函数,该函数会设置时间戳,移动相应端口在双向循环链表中的位置,最后返回PortData类实例port_data。
b. 如果该端口已经down掉,直接返回,否则执行下一步
c. 根据port.dpid得到对应的Datapath类实例dp,如果不存在,则直接返回,否则执行下一步
d. 发送LLDP报文。具体地:(1)生成actions:从port.port_no端口发出消息;(2)生成PacketOut消息:datapath指定为上一步得到的dp,actions为前面的,data为步骤a中返回的port_data的lldp_data
(5)遍历ports列表,对每个端口调用self.send_lldp_packet(port),发送LLDP报文。
9.2 link_loop函数
link_loop函数也是一个while循环,只要self.is_active为真,就一直循环执行。
(1)执行self.link_event.clear(),将Event类实例link_event的_cond属性设为False,用于线程间同步。
(2)创建deleted列表,
(3)遍历self.links(LinkState类实例),获得link(Link类实例)和timestamp时间戳。如果已经超时,且该link对应的源端口是否在self.ports中,并且发送LLDP次数已超过self.LINK_LLDP_DROP,则添加到deleted列表中。
(4)遍历deleted列表,执行:
a. 对其中的每条需要删除的link调用link_down函数(该函数会删除self.links中link对应的项目,并删除self.links._map中link对应的项目),并触发EventLinkDelete事件。
b. 得到link对应的反向link,如果反向link不在deleted列表中,则将self.links中反向link的时间戳置为超时的事件,并将对端端口从self.ports的双向循环链表中移动到哨兵节点的后面(下次检查的开头),以便尽早检查反向link是否也断开了。
9.3 state_change_han

