实验6:开源控制器实践——RYU
(一)基本要求
搭建下图所示SDN拓扑,协议使用Open Flow 1.0,并连接Ryu控制器。


并连接Ryu控制器,通过Ryu的图形界面查看网络拓扑

阅读Ryu文档的The First Application一节,运行并使用 tcpdump 验证L2Switch,分析和POX的Hub模块有何不同。
(1)运行并使用 tcpdump 验证L2Switch
创建L2Switch.py文件,并保存在/home/用户名/032002105/lab6/中
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_0
class L2Switch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_0.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)

h1 ping h2

h1 ping h3

下发流表

(2)分析和POX的Hub模块有何不同
由图可见,h1 ping h2时h3也能收到数据包,h1 ping h3时h2也能收到数据包,说明L2Switch模块的功能同hub模块,都采用洪泛转发,但L2Switch模块下发的流表无法查看,而Hub模块下发的流表可以查看
(3)编程修改L2Switch.py,另存为L2032002105.py,使之和POX的Hub模块的变得一致?
from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls
class hub(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def init(self, args, **kwargs):
super(hub, self).init(args, **kwargs)
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_feathers_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
install flow table-miss flow entry
match = ofp_parser.OFPMatch()
actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)]
# 1\OUTPUT PORT, 2\BUFF IN SWITCH?
self.add_flow(datapath, 0, match, actions)
def add_flow(self, datapath, priority, match, actions):
# 1\ datapath for the switch, 2\priority for flow entry, 3\match field, 4\action for packet
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
# install flow
inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]
mod = ofp_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
ofp_parser = datapath.ofproto_parser
in_port = msg.match['in_port'] # get in port of the packet
add a flow entry for the packet
match = ofp_parser.OFPMatch()
actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_FLOOD)]
self.add_flow(datapath, 1, match, actions)
to output the current packet. for install rules only output later packets
out = ofp_parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id, in_port=in_port, actions=actions)
# buffer id: locate the buffered packet
datapath.send_msg(out)

(二)进阶要求
1.阅读Ryu关于simple_switch.py和simple_switch_1x.py的实现,以simple_switch_13.py为例,完成其代码的注释工作,并回答下列问题:
Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
See the License for the specific language governing permissions and
limitations under the License.
引入数据包
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
from ryu.lib.packet import ether_types
class SimpleSwitch13(app_manager.RyuApp):
# 定义openflow版本
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def init(self, args, **kwargs):
super(SimpleSwitch13, self).init(args, **kwargs)
self.mac_to_port = {} # 定义保存mac地址到端口的一个映射
处理SwitchFeatures事件
@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 table-miss flow entry
We specify NO BUFFER to max_len of the output action due to
OVS bug. At this moment, if we specify a lesser number, e.g.,
128, OVS will send Packet-In with invalid buffer_id and
truncated packet data. In that case, we cannot output packets
correctly. The bug has been fixed in OVS v2.1.0.
match = parser.OFPMatch() # match指流表项匹配,OFPMatch()指不匹配任何信息
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)
add_flow()增加流表项
def add_flow(self, datapath, priority, match, actions, buffer_id=None): # datapath:指定的 Switch;priority:此规则的优先权;match:此规则的 Match 条件;actions:动作
# 获取交换机信息
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# 对action进行包装
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
# 判断是否存在buffer_id,并生成mod对象
if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
# 发送出去
datapath.send_msg(mod)
处理PacketIn事件
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
# If you hit this you might want to increase
# the "miss_send_length" of your switch
if ev.msg.msg_len < ev.msg.total_len:
self.logger.debug("packet truncated: only %s of %s bytes",
ev.msg.msg_len, ev.msg.total_len)
# 解析数据结构
msg = ev.msg # ev.msg 是代表packet_in data structure对象
datapath = msg.datapath
# dp. ofproto 和 dp.ofproto_parser 是代表 Ryu 和交换机谈判的 OpenFlow 协议的对象
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
in_port = msg.match['in_port'] # 获取源端口
pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
# 忽略LLDP类型的数据包
# ignore lldp packet
return
dst = eth.dst # 获取目的端口
src = eth.src # 获取源端口
dpid = format(datapath.id, "d").zfill(16)
self.mac_to_port.setdefault(dpid, {})
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
学习包的源地址,和交换机上的入端口绑定
learn a mac address to avoid FLOOD next time.
self.mac_to_port[dpid][src] = in_port
查看是否已经学习过该目的mac地址
if dst in self.mac_to_port[dpid]: # 如果目的地址存在于mac_to_port中
out_port = self.mac_to_port[dpid][dst]
# 没有学习过该目的mac地址
else:
out_port = ofproto.OFPP_FLOOD # OFPP_FLOOD标志表示应在所有端口发送数据包,即洪泛
actions = [parser.OFPActionOutput(out_port)]
下发流表避免下次触发 packet in 事件
if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src)
# 验证我们是否有一个有效的buffer_id,如果是避免发送两个
# flow_mod & packet_out
if msg.buffer_id != ofproto.OFP_NO_BUFFER:
self.add_flow(datapath, 1, match, actions, msg.buffer_id)
return
else:
self.add_flow(datapath, 1, match, actions)
data = None
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
data = msg.data
发送Packet_out数据包
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=data)
# 发送流表
datapath.send_msg(out)
a) 代码当中的mac_to_port的作用是什么?
mac_to_port的作用是保存mac地址到交换机端口的映射
b) simple_switch和simple_switch_13在dpid的输出上有何不同?
在simple_switch_13中
dpid = format(datapath.id, "d").zfill(16) self.mac_to_port.setdefault(dpid, {})
在simple_switch中
dpid = datapath.id self.mac_to_port.setdefault(dpid, {})
c) 相比simple_switch,simple_switch_13增加的switch_feature_handler实现了什么功能?
增加了实现交换机以特性应答消息响应特性请求功能
d) simple_switch_13是如何实现流规则下发的?
在触发PacketIn事件后,先解析相关数据结构,获取协议信息、获取源端口、包学习,交换机信息,以太网信息等。如果以太网类型是LLDP类型,则忽略。如果不是LLDP类型,则获取目的端口和源端口以及交换机id,然后进行交换机自学习,先学习源地址对应的交换机的入端口,再查看是否已经学习目的mac地址,如果没有就进行洪泛转发。如果学习过,查看是否有buffer_id,如果有则在添加流时加上buffer_id,向交换机发送数据包和流表。
e) switch_features_handler和_packet_in_handler两个事件在发送流规则的优先级上有何不同?
switch_features_handler在发送流规则的优先级比_packet_in_handler要更高.
2.编程实现和ODL实验的一样的硬超时功能。
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
from ryu.lib.packet import ether_types
class SimpleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def init(self, args, **kwargs):
super(SimpleSwitch13, self).init(args, **kwargs)
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 table-miss flow entry
We specify NO BUFFER to max_len of the output action due to
OVS bug. At this moment, if we specify a lesser number, e.g.,
128, OVS will send Packet-In with invalid buffer_id and
truncated packet data. In that case, we cannot output packets
correctly. The bug has been fixed in OVS v2.1.0.
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, buffer_id=None, hard_timeout=0):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst, hard_timeout=hard_timeout)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst, hard_timeout=hard_timeout)
datapath.send_msg(mod)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
# If you hit this you might want to increase
# the "miss_send_length" of your switch
if ev.msg.msg_len < ev.msg.total_len:
self.logger.debug("packet truncated: only %s of %s bytes",
ev.msg.msg_len, ev.msg.total_len)
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
in_port = msg.match['in_port']
pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
# ignore lldp packet
return
dst = eth.dst
src = eth.src
dpid = format(datapath.id, "d").zfill(16)
self.mac_to_port.setdefault(dpid, {})
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
learn a mac address to avoid FLOOD next time.
self.mac_to_port[dpid][src] = in_port
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
out_port = ofproto.OFPP_FLOOD
actions = [parser.OFPActionOutput(out_port)]\
actions_timeout=[]
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, eth_src=src)
# verify if we have a valid buffer_id, if yes avoid to send both
# flow_mod & packet_out
hard_timeout=10
if msg.buffer_id != ofproto.OFP_NO_BUFFER:
self.add_flow(datapath, 2, match,actions_timeout, msg.buffer_id,hard_timeout=10)
self.add_flow(datapath, 1, match, actions, msg.buffer_id)
return
else:
self.add_flow(datapath, 2, match, actions_timeout, hard_timeout=10)
self.add_flow(datapath, 1, match, actions)
data = None
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
data = msg.data
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=data)
datapath.send_msg(out)


个人总结
在运行L2032002103=5.py时,pingall无法ping通,报错unsupported version 0x1. If possible, set the switch to use one of the versions [4] on datapath ('127.0.0.1', 59310),通过查看别人报告的总结后得知是openflow版本问题,删去openflow10或使用openflow13便可解决。
本次实验偏难,花费的时间也较多,因为又要阅读文档,且文档也有很多内容看不懂,代码也不懂要怎么写。
通过这次实验学习了一些ryu的基础知识,稍微理解了ryu和pox的一些区别,对ryu的理解还不是很深入还需要更多的学习。

浙公网安备 33010602011771号