本文章基于https://ryu.readthedocs.io/en/latest/writing_ryu_app.html里面的第一个应用,即hub的简单实现。但是,按照这里的原程序,出现了一些问题,这篇文章里我给出了一些我自己的解决方案。
接下来,我就按照官网的示例进行讲解。
第一部分:代码
新建一个类L2Switch,内容如下:
from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
class L2Switch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
从ryu.base import app_manager,在开发APP的时候只需要继承这个基类,就获得你想要的一个APP的一切了。from ryu.ofproto import ofproto_v1_3意思是导入openflow1.3协议的数据,本次开发就是使用openflow1.3协议。此时,这个程序就是一个完整的程序了,运行并没有错误,但是由于尚未添加如何处理代码,所以这段程序说明也做不了。
接下来,接需要继续向类中添加代码以完成hub功能的开发。
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
class L2Switch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(L2Switch, self).__init__(*args, **kwargs)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
msg = ev.msg
dp = msg.datapath
ofp = dp.ofproto
ofp_parser = dp.ofproto_parser
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
data = None
if msg.buffer_id == ofp.OFP_NO_BUFFER:
data = msg.data
out = ofp_parser.OFPPacketOut(
datapath=dp, buffer_id=msg.buffer_id, in_port=msg.in_port,
actions=actions, data = data)
dp.send_msg(out)
将方法'packet_in_handler'添加到L2Switch类中。当ryu控制器收到OpenFlow packet_in消息时,将调用此方法。
其中“ set_ev_cls”装饰器。该装饰器告诉Ryu何时应调用装饰的函数。装饰器的第一个参数表示事件发生时应调用此函数。即,每次Ryu收到packet_in消息时,都会调用此函数。第二个参数表示交换机的状态。ryu与交换机之间的握手完成之前忽略packet_in消息,使用“ MAIN_DISPATCHER”作为第二个参数表示仅在握手完成后才调用此函数。
接下来,介绍“ packet_in_handler”函数的前半部分。
接下来时函数的下半部分。
至此,一个完整的HUB功能就可以实现了。(当然,这只是官网这样说的,在我实际操作中发现有错误,接下来,我就介绍一下我遇到的问题和解决方案)
第二部分:实验
首先,利用mininet构建网络拓扑,如下所示。
在Ubuntu终端命令行中运行HUB程序,命令如下。
root@yang-VirtualBox:/home/yang/ryu/ryu/app# ryu-manager hub1_yjl.py
接下来,设置c0控制器,h1、h2、h3的ip地址分别为10.0.0.1、10.0.0.2、10.0.0.3。全部设置完成后,运行拓扑。
运行拓扑后,在Ubuntu命令行查看交换机s1的流表,可以发现s1并未有任何流表项存在,所以此时需要手动添加默认流表项,如下所示。
在mininet命令行中输入命令 h1 ping h3,此时观察控制器输出的日志信息,如下所示。
可以看出,报错AttributeError: 'OFPPacketIn' object has no attribute 'in_port'。也就是说,程序msg.in_port报错,并没有in_port。至于为什么没有,主要是官网示例给的是openflow1.0版本的,而这里我用的是openflow1.3版本的,所以收到的msg的格式有所差别。通过将msg打印到控制台上,打印结果如下。
可以看出,msg中包含的还是会有in_port的信息的,所以为了解决错误,我们只需要将in_port的信息提取出来就可以了。为此,我改进了一下代码,多了处理字符串的部分,提取出in_port的值。改进后的完整代码如下。(当然,这里完全可用json解析出in_port数据,但是为了更清晰看出示例,我使用字符串一点点解析的)
from ryu.base import app_manager
from ryu.controller.handler import set_ev_cls
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller import ofp_event
from ryu.ofproto import ofproto_v1_3
'''
实现了一个hub的功能,
即控制器通过packet_in数据包的解析,进行命令的下发,
这个程序的一切指令处理均在控制器进行,并不会进行流表的下发,只会下发转发指令,
所以,在运行之前需要手动在ovs上添加默认controller的流表
'''
class L2Switch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, ev):
msg = ev.msg
dp = msg.datapath
ofp = dp.ofproto
ofp_parser = dp.ofproto_parser
dp_id = dp.id
# 计算出in_port
start = str(msg).index('oxm_fields') + 11
end = str(msg).index('),reason')
inport_str = str(msg)[start:end]
instr = eval(inport_str)
in_port = instr['in_port']
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
data = None
if msg.buffer_id == ofp.OFP_NO_BUFFER:
data = msg.data
print('id:{0}'.format(dp_id))
print('in_port:{0}'.format(in_port))
out = ofp_parser.OFPPacketOut(
datapath=dp, buffer_id=msg.buffer_id, in_port=in_port,
actions=actions, data=data)
dp.send_msg(out)
接下来,重新在Ubuntu控制台运行程序。在mininet命令行中输入命令 h1 ping h3,此时可以看出h1 h3之间和相互ping通了,切换h2主机也可以,至此,HUB程序就实现了,各位也可以使用tcpdump监控一下链路的数据流,看看是否真的实现了,这里我就不在赘述。
github地址:https://github.com/Yang-Jianlin/ryu/blob/master/ryu/app/hub1_yjl.py