实验6:开源控制器实践——RYU
一、基础要求
1.搭建下图所示SDN拓扑,协议使用Open Flow 1.0,并连接Ryu控制器,通过Ryu的图形界面查看网络拓扑。


2.阅读Ryu文档的The First Application一节,运行当中的L2Switch,h1 ping h2或h3,在目标主机使用 tcpdump 验证L2Switch,分析L2Switch和POX的Hub模块有何不同。

h1 ping h2

h1 ping h3

3.编程修改L2Switch.py,另存为L2xxxxxxxxx.py,使之和POX的Hub模块的变得一致?(xxxxxxxxx为学号)
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):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] # OpenFlow1.3版本
def __init__(self, *args, **kwargs):
super(SimpleSwitch13, self).__init__(*args, **kwargs)
self.mac_to_port = {} # 保存(交换机id, 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)] # actions是动作,表示匹配成功不缓存数据包并发送给控制器
self.add_flow(datapath, 0, match, actions) # add_flow是添加流表项的函数,我们可以从add_flow的函数中看到其调用了send_msg(mod),因此本函数的目的即为下发流表。
# 增加流表项
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
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)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod)
# 说明控制器在MAIN_DISPATCHER状态并且触发Packet_In事件时调用_packet_in_handler函数
@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: # 传输出错,打印debug信息
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 # 交换机自学习,取来往数据包的交换机id、源mac和入端口绑定来构造表。
# 查看是否已经学习过该目的mac地址
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)]
# 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
if msg.buffer_id != ofproto.OFP_NO_BUFFER: # 有buffer_id,带上buffer_id,然后只发送Flow_mod报文,因为交换机已经有缓存数据包,就不需要发送packet_out报文
self.add_flow(datapath, 1, match, actions, msg.buffer_id)
return
else:
self.add_flow(datapath, 1, match, actions) # 若没有buffer_id,发送的Flow_Mod报文就无需要带上buffer_id,但是下一步要再发送一个Packet_out报文带上原数据包信息。
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.py中为dpid = format(datapath.id, "d").zfill(16)
在simple_switch.py中为dpid = datapath.id
在simple_switch_13.py中使用了zfill() 方法返回指定长度为16的字符串,原字符串右对齐,前面填充0;而simple_switch.py直接输出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实验的一样的硬超时功能。
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):
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)`


个人总结
本次实验遇到的问题比较多,基础部分在查看流表时由于构建mininet拓扑网络时使用的是OpenFlow13(用OpenFlow10时,报错如下图),因此查看流表报错
s1.mgmt: version negotiation failed (we support version 0x01, peer supports version 0x04) ovs-ofctl: s1: failed to connect to socket (Broken pipe)

解决方法
https://www.cnblogs.com/fjlinww/p/10753160.html
本次的进阶要求总的来说比较难,做的时间也比较久,特别是源码的注释,查阅了很多资料才完成,但是对于我的提升还是蛮大的,另外通过这次实践我学会了独立部署RYU控制器,理解了RYU控制器实现软件定义的集线器原理,对于ryu有了更多的了解。
浙公网安备 33010602011771号