本文章基于https://ryu.readthedocs.io/en/latest/ofproto_v1_3_ref.html里面的Modify State Messages的内容进行讲解的,即openflow1.3的流表的操作的简单实现。通过这里的讲解,可以实现控制器对交换机的流表的增加删除等操作。
接下来,着重讲一下ryu如何实现对交换机的流表的操作。(重点以默认流表项的添加为例)
第一部分:涉及的openflow知识
在学习ryu之前,我认为初学者需要先大致的了解openflow协议,理解交换机和控制器之间如何进行数据的交换。所以,如果对openflow不了解,还请先阅读openflow spec或者openflow rfc文档,下载地址(https://github.com/Yang-Jianlin/ryu/tree/master/spec_book)。接下来,本文只介绍接下来涉及的主要的openflow知识。
首先,控制器和交换机通过交换hello消息建立安全通道后,执行openflow控制器和交换机的握手。在两者握手完成后,即可对openflow交换机进行控制。握手过程中,控制器向交换机发送问询功能features请求消息,交换机返回features响应消息,从而完成握手。握手的数据解析过程(https://github.com/Yang-Jianlin/ryu/blob/master/ryu/app/switch_feature_yjl.py),即send_feature_request()方法是控制器发送问询功能的features请求消息;switch_features_handler()方法是交换机返回features响应消息。
所以说,交换机和控制器之间的所有操作均是在握手之后进行的。
在ovs执行操作之前,一般会有一个默认的流表项,即到控制器的默认流表项,优先级为0,比如前两篇博客的hub1(https://blog.csdn.net/weixin_40042248/article/details/115680952?spm=1001.2014.3001.5501)的操作,由于没有在ryu中编写下发默认流表的操作,所以需要自己手动在命令行添加流表。那么默认流表(table-miss)何时添加?如何添加呢?
显然,默认流表项肯定是在控制器控制数据平面对数据处理之前就要添加进交换机的流表中,即在握手完成后就需要控制器下发默认流表项到交换机中,也就是switch_features_handler()中进行默认流表项的下发。知道何时添加默认流表项后,那么,接下来重点讲述如何进行默认流表项的下发。
(1)Flow-Mod消息:
通过Flow-Mod消息,可对流表项进行添加、删除、变更设置等操作。在openflow1.3中,Flow-Mod的命令种类定义为command,具体由以下五种。
(2)数据包的匹配(ofp_match)
匹配字段,我们可以直接打开ryu源码的ofpro_v1_3_parser找到OFPMatch()方法,可以看出给出的例子如下。
>>> # compose
>>> match = parser.OFPMatch(
... in_port=1,
... eth_type=0x86dd,
... ipv6_src=('2001:db8:bd05:1d2:288a:1fc0:1:10ee',
... 'ffff:ffff:ffff:ffff::'),
... ipv6_dst='2001:db8:bd05:1d2:288a:1fc0:1:10ee')
具体的ofp_match字段可以参考openflow spec。
(3)actions字段:
这里的动作,我们也可以直接打开ryu源码的ofpro_v1_3r找到ryu支持的actions,可以看出给出的部分动作如下。
OFPP_TABLE = 0xfffffff9 # Perform actions in flow table.
# NB: This can only be the destination
# port for packet-out messages.
OFPP_NORMAL = 0xfffffffa # Process with normal L2/L3 switching.
OFPP_FLOOD = 0xfffffffb # All physical ports except input port and
# those disabled by STP.
OFPP_ALL = 0xfffffffc # All physical ports except input port.
OFPP_CONTROLLER = 0xfffffffd # Send to controller.
OFPP_LOCAL = 0xfffffffe # Local openflow "port".
OFPP_ANY = 0xffffffff # Not associated with a physical port.
第二部分:代码
新建一个类Sendflow,内容如下:
from ryu.base import app_manager
class Sendflow(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协议。此时,这个程序就是一个完整的程序了,运行并没有错误,但是由于尚未添加如何处理代码,所以这段程序说明也做不了。
接下来,接需要继续向类中添加代码以完成添加流表功能的开发。
定义一个发送流表的方法,内容如下:
def send_flow_mod(self, datapath, command, priority, match, actions):
ofp = datapath.ofproto
ofp_parser = datapath.ofproto_parser
inst = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
req = ofp_parser.OFPFlowMod(datapath=datapath, command=command,
priority=priority, match=match, instructions=inst)
datapath.send_msg(req) # 发送流表项
这段代码就是发送流表的方法,写完之后,我们只需要在想要修改流表的时候调用就可以了。
由于,本文主要以默认流表项为例,所以接下来就是在控制器收到握手(feature)的响应消息后,控制器向交换机下发流表项,内容如下:
# install table-miss flow entry
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
# 关于ofp_parser和ofp之间的区别,凡是方法即加()的都是ofp_parser
# 凡是单个的不是方法,是变量的都是ofp
ofp_parser = datapath.ofproto_parser
ofp = datapath.ofproto
print('--------------receive reply-------------')
print('OFPSwitchFeatures received: datapath_id={0} n_buffers={1} n_tables={2} capabilities={3}'
.format(msg.datapath_id, msg.n_buffers, msg.n_tables, msg.capabilities))
print()
# 这里的command表示操作流表的方式,以下之一
# OFPFC_ADD,OFPFC_MODIFY,OFPFC_MODIFY_STRICT,OFPFC_DELETE,OFPFC_DELETE_STRICT
flag = int(input('Please enter num to choose the way of add flow:'))
# 添加流表,priority=1 actions=CONTROLLER:65535
if flag == 0:
command = ofp.OFPFC_ADD
match = ofp_parser.OFPMatch()
# actions=CONTROLLER:65535,ofp.OFPCML_NO_BUFFER表示设定为max_len以便接下来的封包传送
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_CONTROLLER, ofp.OFPCML_NO_BUFFER)]
self.send_flow_mod(datapath, command, 1, match, actions)
# 添加流表,priority=1,in_port="s1-eth1" actions=ALL
elif flag == 1:
command = ofp.OFPFC_ADD
# 匹配域in_port="s1-eth1"
match = ofp_parser.OFPMatch(in_port=1)
# actions=ALL
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_ALL)]
self.send_flow_mod(datapath, command, 1, match, actions)
else:
exit()
这里此程序在收到握手的feature应答信息后,开始向交换机下发流表项。为了清晰的显示流表下发的不同效果,我设置了flag来控制不同的流表下发。
第三部分:实验
首先,利用mininet构建网络拓扑,如下所示。
各项参数设置完成后,运行拓扑。此时,s1交换机的流表为空,这时运行send_flow_yjl.py程序,输入1或者2之后,就会向s1添加流表项,如下图所示。
完整代码如下:
from ryu.base import app_manager
from ryu.controller.handler import set_ev_cls
from ryu.controller.handler import CONFIG_DISPATCHER
from ryu.controller import ofp_event
from ryu.ofproto import ofproto_v1_3
class Sendflow(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def send_flow_mod(self, datapath, command, priority, match, actions):
ofp = datapath.ofproto
ofp_parser = datapath.ofproto_parser
inst = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
req = ofp_parser.OFPFlowMod(datapath=datapath, command=command,
priority=priority, match=match, instructions=inst)
datapath.send_msg(req)
# install table-miss flow entry
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
# 关于ofp_parser和ofp之间的区别,凡是方法即加()的都是ofp_parser
# 凡是单个的不是方法,是变量的都是ofp
ofp_parser = datapath.ofproto_parser
ofp = datapath.ofproto
print('--------------receive reply-------------')
print('OFPSwitchFeatures received: datapath_id={0} n_buffers={1} n_tables={2} capabilities={3}'
.format(msg.datapath_id, msg.n_buffers, msg.n_tables, msg.capabilities))
print()
# 这里的command表示操作流表的方式,以下之一
# OFPFC_ADD,OFPFC_MODIFY,OFPFC_MODIFY_STRICT,OFPFC_DELETE,OFPFC_DELETE_STRICT
flag = int(input('Please enter num to choose the way of add flow:'))
# 添加流表,priority=1 actions=CONTROLLER:65535
if flag == 0:
command = ofp.OFPFC_ADD
match = ofp_parser.OFPMatch()
# actions=CONTROLLER:65535,ofp.OFPCML_NO_BUFFER表示设定为max_len以便接下来的封包传送
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_CONTROLLER, ofp.OFPCML_NO_BUFFER)]
self.send_flow_mod(datapath, command, 1, match, actions)
# 添加流表,priority=1,in_port="s1-eth1" actions=ALL
elif flag == 1:
command = ofp.OFPFC_ADD
# 匹配域in_port="s1-eth1"
match = ofp_parser.OFPMatch(in_port=1)
# actions=ALL
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_ALL)]
self.send_flow_mod(datapath, command, 1, match, actions)
else:
exit()
github地址:https://github.com/Yang-Jianlin/ryu/blob/master/ryu/app/send_flow_yjl.py