RYU,Chapter 1. 交换机(Switching Hub)

Created at 2018-06-04 Updated at 2018-06-06 Category Study Tag SDN / RYU

为什么使用RYU?

其实理由很简单:

  • 我喜欢用python
  • RYU 用着不卡(辣鸡虚拟机真跑不动ODL)

然后就尝试的去看了以下RYU的源码,发现真的很简洁,很有意思!瞬间就爱上了。


前期准备

RYU的安装

在拥有一系列组件的前提下:pip install ryu

接下来会面临一些问题就是,找不到RYU的目录在何方?

输入ryu-manager *** 在报错的后面就有ryu的目录地址

这个方案来自考拉小无
,RYU领路人

RYU各个文件的内容:

这些目录的主要内容在SDNLAB网站上有记录来自李呈,你也可以在RYU的官方文档上学习到。

而这次博客的内容主要是记录我在学习RYUBOOK上的一些感想。如果你想获得RYUBOOK这本书,请点击


交换机(Switching Hub)

关于交换机交换数据包的原理:

  • 学习源主机的入端口,MAC地址
  • 查找MAC表,若不存在目的主机的MAC地址,则按照生成树协议执行转发所有端口;若存在,则按照端口执行转发

这一块相信学过传统网络知识的同学都基本清楚。

关于OpenFlow交换机的最简单的功能:

  • 修改收到的数据包及传送到对应端口
  • Packet-in
  • Packet-out

借这些功能,RYU控制器大致通过以下内容实现最简单的交换机转发功能:

  • 利用Packin-in学习端口号及MAC地址
    • 控制器解析Packet-in,得到源主机的接入端口及MAC地址,记录在表
  • 利用Packin-out下发流表到交换机实现转发功能
    • 控制器解析Packet-in,得到目的主机的MAC地址,查询MAC表,若找到对应项,则使用Packet-out传送到先前对应的端口。否则,利用packet-out来达到flooding功能。

注:这边flooding采用的是OFFP-FLOOD(沿最小生成树发送数据包)

此时,在ovs交换机就有来自控制器所学习好了的流表项目,能够告诉交换机接下去的动作。例如优先级最低的流表是FLOOD动作,优先级高一丢丢的是正常的OUTPUT动作


RYU的源码实现:

先贴上整个源码

from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
class ExampleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

def __init__(self, *args, **kwargs):
super(ExampleSwitch13, self).__init__(*args, **kwargs)

# initialize mac address table.
self.mac_to_port = {}

@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

# install the table-miss flow entry.
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)

def add_flow(self, datapath, priority, match, actions):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

# construct flow_mod message and send it.
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod)

@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

# get Datapath ID to identify OpenFlow switches.
dpid = datapath.id
self.mac_to_port.setdefault(dpid, {})

# analyse the received packets using the packet library.
pkt = packet.Packet(msg.data)
eth_pkt = pkt.get_protocol(ethernet.ethernet)
dst = eth_pkt.dst
src = eth_pkt.src

# get the received port number from packet_in message.
in_port = msg.match['in_port']
self.logger.info("packet in %s %s %s %s ", dpid, src, dst)

# learn a mac address to avoid FLOOD next time.
self.mac_to_port[dpid][src] = in_port

# if the destination mac address is already learned,
# decide which port to output the packet, otherwise FLOOD.
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
out_port = ofproto.OFPP_FLOOD

# construct action list.
actions = [parser.OFPActionOutput(out_port)]

# install a flow to avoid packet_in next time.
if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
self.add_flow(datapath, 1, match, actions)

# construct packet_out message and send it.
out = parser.OFPPacketOut(datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=in_port, actions=actions,
data=msg.data)
datapath.send_msg(out)
```


-----

**接下来是对其的解析:**

``` python
class ExampleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

def __init__(self, *args, **kwargs):
super(ExampleSwitch13, self).__init__(*args, **kwargs)
# initialize mac address table.

self.mac_to_port = {}

作为控制器的应用程序,需要继承app_manager.RyuApp这个类。该类位于ryu.base.app。在前提部分的链接可以详细了解

  • 首先OFP_VERSIONS指定了OpenFlow的协议版本号
  • init构造函数似乎没有什么作用
  • mac_to_port是MAC表,目前等于一个空dict

@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

# install the table-miss flow entry.

match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)

set_ev_cls是一个事件的装饰器。位于ryu.controller.handler。

在RYU中,事件管理(Event handler),用来处理OpenFlow信息对应发生的事件。如该控制器接收到SwitchFeatures(由ofp_event.EventOFPSwitchFeatures指明)即Features reply时,就会执行下列函数的内容。执行的状态前提是CONFIG_DISPATCHER(接收SwitchFeatures讯息)。

状态有四种,位于ryu.controller.handler中:

  • HANDSHAKE_DISPATCHER 交换HELLO信息
  • CONFIG_DISPATCHER 接收SwitchFeatures讯息
  • MAIN_DISPATCHER 一般状态,指交换机连接成功了
  • DEAD_DISPATCHER 连接中断

满足上述条件后,执行switch_features_handler函数。
其中:

  • ev.msg 为触发讯息的数据报文
  • msg.datapath 则为该报文下的交换机实体
  • datapath.ofproto 是一个OpenFlow协议数据结构的对象,成员包含OpenFlow协议的数据结构,如虚拟端口OFPP_FLOOD,OFPP_CONTROLLER。
  • datapath.ofp_parser 是一个按照OpenFlow解析的数据结构,即动作
  • parser.OFPMatch() 为匹配项,此时为空,完全匹配
  • actions 为一个列表,存放动作,动作来自datapath.ofp_parser中解析的数据结构,此例中为OFPActionOutput,其带有两个参数,ofproto.OFPP_CONTROLLER表示发送报文到控制器,ofproto.OFPCML_NO_BUFFER表示表示不应该应用缓冲,并且整个数据包将被发送到控制器。

这个函数完成的内容就是将所有的数据包传给控制器,其中的数据包为优先级为0的流表项匹配到的数据包。


def add_flow(self, datapath, priority, match, actions):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

# construct flow_mod message and send it.
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod)
  • OFPInstructionActions 该指令用于写入/应用/清除动作。后文的第一个参数为应用
  • OFPFlowMod 控制器发送此消息来修改流表。 参数instructions在源码中解释为list of OFPInstruction* instance,*为通配符,此例中为OFPInstructionActions
    • 其有诸多参数,多为OpenFlow协议的数据结构,具体不再解释,源码解释见下图:
    • 此例中,command为默认参数,OFPFC_ADD
  • send_msg 排队一个OpenFlow消息发送到相应的交换机。 如果msg.xid为None,则在排队之前会自动在消息上调用set_xid。

@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

# get Datapath ID to identify OpenFlow switches.
dpid = datapath.id
self.mac_to_port.setdefault(dpid, {})

# analyse the received packets using the packet library.
pkt = packet.Packet(msg.data)
eth_pkt = pkt.get_protocol(ethernet.ethernet)
dst = eth_pkt.dst
src = eth_pkt.src

# get the received port number from packet_in message.
in_port = msg.match['in_port']
self.logger.info("packet in %s %s %s %s ", dpid, src, dst)

# learn a mac address to avoid FLOOD next time.
self.mac_to_port[dpid][src] = in_port

根据上面的信息,我们可以很容易的得到, _packet_in_handler这个函数,在满足状态为MAIN_DISPATCHER,接收到的报文为EventOFPPacketIn(即Packet-in报文)的时候被调用,其中:

  • datapath.id 只用于MAIN_DISPATCHER。交换机标识符
  • setdefault 如果键不存在于字典中,将会添加键并将值设为默认值
  • pkt = packet.Packet(msg.data) 获取报文数据
  • get_protocol 返回首次找到的与指定协议匹配的协议
  • ethernet.ethernet 以太网头编码器/解码器类。Ethernet header encoder/decoder class.
  • dst,src为获取到的MAC
  • msg.match[‘in_port’] 返回匹配到的‘in_port’
  • mac_to_port[dpid][src] = in_port 记录MAC


# if the destination mac address is already learned,
# decide which port to output the packet, otherwise FLOOD.
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
out_port = ofproto.OFPP_FLOOD

# construct action list.
actions = [parser.OFPActionOutput(out_port)]

# install a flow to avoid packet_in next time.
if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
self.add_flow(datapath, 1, match, actions)

# construct packet_out message and send it.
out = parser.OFPPacketOut(datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=in_port, actions=actions,
data=msg.data)
datapath.send_msg(out)

相信经过了上面的阅读,这部分的大部分内容都是能够看懂了。其中:

  • 如果dst在mac_to_port[dpid],就可以知道转发的out_port端口,否则out_port = ofproto.OFPP_FLOOD
  • OFPMatch 流匹配结构Flow Match Structure, 具体可以阅读RYU源码,位于ryu.ofproto.ofproto_v1_3_parser.py
  • add_flow(datapath, 1, match, actions) 这条语句下发较高优先级(大于刚才的0)的流表,这样新的数据包过来后会先匹配该流表,实现已知端口的转发功能
  • parser.OFPPacketOut Packet-out报文

RYU程序的执行:

完成了一个ryu程序代码是不是很开心,接下来就去实现它吧!

在Linux终端下运行sudo mn --topo single,3 --mac --switch ovsk --controller remote -x,其中:

  • single ,3 为创建一个三个主机连接一台交换机的拓扑
  • switch 采用ovs交换机
  • 控制器当然是远端(remote)的RYU
  • -x 开启各个主机、交换机、控制器的终端

如图所示:

根据书本的要求,设置OpenFLow协议为1.3

在s1交换机终端执行:

ovs-vsctl set Bridge s1 protocols=OpenFlow13

我们先查看s1的流表,发现其为空:

此时我们所有的前期准备已经都完成了,接下来就是执行控制器应用程序了。我们先到我们写好的控制器程序目录,这里我用一个first.py程序
输入ryu-manager first.py

显示界面如下:

控制器应用程序开启后,与交换机建立了连接,Features请求与响应。根据所写的程序,我们会有一个流表添加到OVS交换机上,如图:

这个流表作用就是把未知目的地数据包packet-in到控制器。让控制器进行处理。

接下来我们在mininet中执行pingall操作,同时监控h1端口的数据包,为了方便,我们仅仅使用tupdump。

在h1终端输入tcpdump -en -i h1-eth0开启监控端口功能。在此我们只观察 h1 与 h2。

我们可以看到图中的中部, h1 arp h2, h2 request h1 。紧接着,h1 ping h2,h2 request h1 。实现了通讯。右边的是控制器的日志输出,我在程序中加了个计数的功能,每次收到一个packet-in 技术及就加1。

接着,我们查看s1 的流表。

h1、h2、h3 共有三个端口有六个流表,分别指定了各自的数据包如何发送。很高兴程序没有出现BUG!

至此,本章节内容结束。


总结:

学习RYU有一周了,但是其实看的内容并不多,每天都是有一件一件一件事情被解决,但是就是轮不到RYU。很亢奋用一个下午半个晚上的时间重新阅读第一章的内容。并写下了这篇博客。虽然有些概念随着时间还是会一点一点忘记了,希望通过这篇博客能够尽快找到解决的地方。今天也算是完成了一件很重要的事情了!

希望在考试月再接再厉!不仅仅为了自己。

还有这个模板下的代码风格各种BUG,找个时间解决它

……三分钟解决了,把自带的代码高亮置flase。 根目录的_config,yml 中highlight:
enable: false


爱你所爱,行你所行。

愿你是你所期待的样子

愿你还是你所期待的样子

Hide