1 实验题目
部署一个如下图的网络环境,在此拓扑的基础上完成动态转发规则的改变和链路故障恢复功能。
- 实验资料下载
https://www.aliyundrive.com/s/Lycd3MP5CbZ
2 实验内容
2.1 动态改变转发规则
在上图所示的拓扑结构中,h1到h2有两条通路,所谓动态改变转发规则,就是要让h1到h2的包在两条路径上交替转发,具体描述如下:
假设h1 ping h2,初始路由规则为s1-s4-s5,5秒后,路由转发规则变为s1-s2-s3-s5,再过5秒,转发规则又回到最初的s1-s4-s5,通过这个循环调度的例子动态改变交换机的转发规则。
2.1.1 实现思路
因为实验拓扑比较简单,仅存在两个主机,并且两主机之间有且仅存在两条路径。每经过5秒,install_path()中选择另外一条path即可。
2.1.2 具体实现
- hard_timeout为硬超时,指流标下发之后开始,经过指定时间之后无条件将流表删除。设置hard_timeout=5,5秒后交换机上的流表就会被删除,重新向控制器询问如何转发。
- 求取最长路径。选择将short_path()中的w=1改成w=-1,即为最长路径求取函数。
- 设置全局变量,确定在下发流表时,选择长路径还是短路径。记录第一次下发流表时间firstTime,在之后下发流表时计算当前时间和fistTime的时间差,从而确定选择装载的路径。
2.1.3 测试截图
- 启动控制器
sudo ryu-manager dynamic_rules.py --observe-links
- 启动加载自定义拓扑文件
sudo mn --custom SDNexp2Topo.py --topo mytopo --controller remote
-
用
xterm h1
打开h1终端,使其不断地ping h2
-
观察路径随时间变化
2.2 链路故障恢复功能
在上图所示的拓扑结构中,h1到h2有两条通路,若其中正在进行传输的路径因为发生故障而断开连接,系统应当及时做出反应,改变转发规则到另外一条路径上;若故障修复,系统也应当即时做出反应,改变转发规则到优先级较高的路径上
假设h1 ping h2,首选的路由规则为s1-s4-s5,由于故障,s1-s4之间的链路被断开,系统应当将转发规则改变为备选路径s1-s2-s3-s5,若此时s1-s4之间的故障被修复,链路恢复连接,系统应该将路径重新确定为首选路径s1-s4-s5
本实验中,路径的优先级有链路上的跳数决定,跳数越少的优先级越高
2.2.1 实现思路
当链路改变时,删除现有流表,重新计算最短路径,重新生成流表。
2.2.2 具体实现
- 修改
install_path()
函数,使其始终获取short_path()
的结果,以达到始终选取当前拓扑中最短路径的效果。 - 增加删除流表函数。参考此博客
def delete_flow(self, datapath,match):
ofp = datapath.ofproto
ofp_parser = datapath.ofproto_parser
cookie = cookie_mask = 0
table_id = 0
priority=100 # 使用最短路,priority并没有什么用
idle_timeout = 10
hard_timeout = 60
buffer_id = ofp.OFP_NO_BUFFER
#match = ofp_parser.OFPMatch(in_port=3, eth_type=flow_info[0], ipv4_src="10.0.0.1", ipv4_dst="10.0.0.2")
actions = []
inst = []
req = ofp_parser.OFPFlowMod(datapath,
cookie, cookie_mask, table_id,
ofp.OFPFC_DELETE, idle_timeout,
hard_timeout, priority, buffer_id,
ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM,
match, inst)
datapath.send_msg(req)
- 当链路发生改变时,删除旧流表项,重新获取最短路径,重新安装流表。
def get_OFPPortStatus_msg(self, ev):
msg=ev.msg
datapath=ev.msg.datapath
dpid = msg.datapath.id
ofproto=datapath.ofproto
parser = datapath.ofproto_parser
# self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port) #{ip:(dpid,port)}
for ip in self.ip_to_port:
# self.logger.info(ip, str(self.ip_to_port[ip]))
if self.ip_to_port[ip] == self.path[-1]:
dstip=ip
dst=self.ip_to_mac[dstip]
for ip in self.ip_to_port:
if self.ip_to_port[ip] == self.path[0]:
srcip=ip
src=self.ip_to_mac[srcip]
# for example,path=[(1,1),(1,3),(4,1),(4,2),(5,3),(5,1)]
# 表示从s1的1号端口进入,然后s1的3号端口发送给s4的1号端口,再从s4的2哈端口发送给s5的3号端口,再从s5的1号端口发出
for i in range(len(self.path) - 2, -1, -2):
datapath_path = self.datapaths[self.path[i][0]]
match1 = parser.OFPMatch(in_port=self.path[i][1], eth_src=src, eth_dst=dst, eth_type=0x0800,
ipv4_src=srcip, ipv4_dst=dstip)
self.delete_flow(datapath=datapath_path,match=match1)
match2 = parser.OFPMatch(in_port=self.path[i+1][1], eth_src=dst, eth_dst=src, eth_type=0x0800,
ipv4_src=dstip, ipv4_dst=srcip)
self.delete_flow(datapath=datapath_path,match=match2)
2.2.3 测试截图
- 启动控制器
sudo ryu-manager recovery_rules.py --observe-links
- 启动加载自定义拓扑文件
sudo mn --custom SDNexp2Topo.py --topo mytopo --controller remote
-
用
xterm h1
打开h1终端,使其不断地ping h2
-
输入
link s1 s4 down
和link s1 s4 up
,会发现路径变化
-
查看s1流表
3 代码
3.1 拓扑
# SDNexp2Topo.py
from mininet.topo import Topo
from mininet.net import Mininet
from mininet.node import RemoteController
from mininet.node import Node
from mininet.node import CPULimitedHost
from mininet.link import TCLink
from mininet.cli import CLI
class MyTopo(Topo):
def __init__(self):
super(MyTopo,self).__init__()
s=[]
for i in range(5):
s_sw=self.addSwitch('s{}'.format(i+1))
s.append(s_sw)
self.addLink(s[0],s[1])
self.addLink(s[1],s[2])
self.addLink(s[2],s[4])
self.addLink(s[0],s[3])
self.addLink(s[3],s[4])
hs = self.addHost('h{}'.format(1))
self.addLink(hs,s[0])
hs = self.addHost('h{}'.format(2))
self.addLink(hs,s[4])
topos = {"mytopo":(lambda:MyTopo())}
3.2 依赖文件(network_monitor.py)
本实验提供两个文件dynamic_rules.py和network_monitor.py,后者是前者在运行时所需要用到的文件,两者必须放在同一文件夹下
# network_monitor.py
from __future__ import division
from collections import defaultdict
from operator import attrgetter
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER
from ryu.controller.handler import CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib import hub
from ryu.lib.packet import packet
from collections import defaultdict
import time
SLEEP_PERIOD = 2
ISOTIMEFORMAT='%Y-%m-%d %X'
class Network_Monitor(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
_NAME = 'Network_Monitor'
def __init__(self, *args, **kwargs):
super(Network_Monitor, self).__init__(*args, **kwargs)
self.datapaths = {}
self.port_stats = {}
self.port_speed = {}
self.flow_stats = {}
self.flow_speed = {}
self.get_flow_speed_dict = {}
self.stats = {}
self.DpidPort_to_ip = {}
self.monitor_thread = hub.spawn(self._monitor)
@set_ev_cls(ofp_event.EventOFPStateChange, [MAIN_DISPATCHER, DEAD_DISPATCHER])
def _state_change_handler(self, ev):
datapath = ev.datapath
if ev.state == MAIN_DISPATCHER:
if not datapath.id in self.datapaths:
self.datapaths[datapath.id] = datapath
elif ev.state == DEAD_DISPATCHER:
if datapath.id in self.datapaths:
del self.datapaths[datapath.id]
def _monitor(self):
while True:
self.stats['port'] = defaultdict(lambda: None)
self.stats['flow'] = defaultdict(lambda: None)
for datapath in self.datapaths.values():
self._request_stats(datapath)
hub.sleep(SLEEP_PERIOD)
# self.logger.info("port speed : %s", self.get_port_speed(1, 2))
def _request_stats(self, datapath):
self.logger.debug('send stats request: %016x', datapath.id)
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# The controller uses this message to query information about ports statistics.
req = parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY)
datapath.send_msg(req)
# The controller uses this message to query individual flow statistics.
req = parser.OFPFlowStatsRequest(datapath)
datapath.send_msg(req)
def _save_stats(self, dist, key, value, length):
if key not in dist:
dist[key] = []
dist[key].append(value)
if len(dist[key]) > length:
dist[key].pop(0)
def _get_speed(self, now, pre, period):
if period:
return (now - pre) / period
else:
return 0
def _get_time(self, sec, nsec):
return sec + nsec / (10 ** 9)
def _get_period(self, n_sec, n_nsec, p_sec, p_nsec):
return self._get_time(n_sec, n_nsec) - self._get_time(p_sec, p_nsec)
def get_port_speed(self, sw_src=None, src_port=None):
if sw_src is None or src_port is None:
return self.port_speed
return self.port_speed.get((sw_src, src_port), (None, None))
# def get_port_speed(self, dpid=None, port=None):
# if dpid is None or port is None:
# return self.port_speed
# return self.port_speed.get((dpid, port), None)
#
# def get_flow_speed(self, dpid=None):
# if dpid is None:
# return self.flow_speed
# else:
# return self.flow_speed[dpid]
# EventOFPFlowStatsReply: switch will reply all of its flows (on flow table) to controller
@set_ev_cls(ofp_event.EventOFPFlowStatsReply, [MAIN_DISPATCHER, DEAD_DISPATCHER])
def _flow_stats_reply_handler(self, ev):
body = ev.msg.body
dpid = ev.msg.datapath.id
self.stats['flow'][dpid] = body
self.flow_stats.setdefault(dpid, {})
self.flow_speed.setdefault(dpid, {})
flow_list = {}
for flow in body:
if (flow.priority != 0) and (flow.priority != 65535):
key = (flow.match.get('in_port', 0), flow.match.get('ipv4_src', 'all'), flow.match.get('ipv4_dst', 'all'))
value = (flow.packet_count, flow.byte_count, flow.duration_sec, flow.duration_nsec)
flow_list[key] = value
# 从每一条流表项中取出outport消息
out_port = flow.instructions[0].actions[-1].port
key1 = (dpid, out_port)
value1 = (flow.match.get('ipv4_src', 'all'), flow.match.get('ipv4_dst', 'all'))
self.DpidPort_to_ip[key1] = value1
# self.logger.info("tmp : %s", str(tmp))
# self.logger.info("flow : %s", str(flow))
# self.logger.info("DpidPort_to_ip : %s", str(self.DpidPort_to_ip))
# self.logger.info("flow_list : %s", str(flow_list))
# have delete the flow_list in switch, so the key in flow_stats is not equal flow_list
for key in self.flow_stats[dpid]:
if key not in flow_list:
# the flow has been delete
#self.logger.info("key : %s", str(key))
value = (0, 0, 0, 0)
self._save_stats(self.flow_stats[dpid], key, value, 20)
self._save_stats(self.flow_speed[dpid], key, 0, 20)
for key in flow_list:
self._save_stats(self.flow_stats[dpid], key, flow_list[key], 20)
# Get flow's speed.
pre = 0
period = SLEEP_PERIOD
tmp = self.flow_stats[dpid][key]
if len(tmp) > 1:
pre = tmp[-2][1]
period = self._get_period(tmp[-1][2], tmp[-1][3], tmp[-2][2], tmp[-2][3])
speed = self._get_speed(self.flow_stats[dpid][key][-1][1], pre, period) * 8
# self.logger.info("flow_speed: %s", speed)
self.save_flow_speed(dpid, key[1], key[2], speed)
self._save_stats(self.flow_speed[dpid], key, speed, 20)
# self.logger.info("monitor get_flow_speed_dict address: %s", id(self.get_flow_speed_dict))
for key in self.flow_stats[dpid]:
if key not in flow_list:
temp_key = (dpid, key[1], key[2])
if self.get_flow_speed_dict.get(temp_key) is not None:
del self.get_flow_speed_dict[temp_key]
# self.logger.info("get_flow_speed_dict : %s", self.get_flow_speed_dict)
def save_flow_speed(self, dpid, src_ip, dst_ip, speed):
# judge the key???
key = (dpid, src_ip, dst_ip)
value = speed
self.get_flow_speed_dict[key] = value
# def get_flow_speed(self, dpid, src_ip, dst_ip):
# if dpid and src_ip and dst_ip:
# key = (dpid, src_ip, dst_ip)
# #self.logger.info("flow_speed_dict: %s", self.get_flow_speed_dict.keys())
# return self.get_flow_speed_dict.keys()
#
# else:
# return
@set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
def _port_stats_reply_handler(self, ev):
body = ev.msg.body
self.stats['port'][ev.msg.datapath.id] = body
for stat in sorted(body, key=attrgetter('port_no')):
if stat.port_no != ofproto_v1_3.OFPP_LOCAL:
key = (ev.msg.datapath.id, stat.port_no)
value = (stat.tx_bytes, stat.rx_bytes, stat.rx_errors, stat.duration_sec, stat.duration_nsec)
self._save_stats(self.port_stats, key, value, 5)
# Get port speed.
pre = 0
period = SLEEP_PERIOD
tmp = self.port_stats[key]
if len(tmp) > 1:
pre = tmp[-2][0]
period = self._get_period(tmp[-1][3], tmp[-1][4], tmp[-2][3], tmp[-2][4])
speed = self._get_speed(self.port_stats[key][-1][0], pre, period)
# Downlink bandwidth
# self.port_speed = {(dpid,port):speed}
# speed bps
self.port_speed[key] = (speed * 8, time.strftime( ISOTIMEFORMAT, time.localtime()))
# self.logger.info("port speed : %s", str(self.port_speed))
3.3 动态改变转发规则
network_monitor.py是dynamic_rules.py在运行时所需要用到的文件,两者必须放在同一文件夹下
# dynamic_rules.py
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.topology import event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER, HANDSHAKE_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 arp
from ryu.lib.packet import ipv4
from ryu.lib.packet import tcp
from ryu.topology.api import get_link
from ryu.lib.packet import ether_types
from ryu.app.wsgi import WSGIApplication
from collections import defaultdict
from datetime import datetime,timedelta
import network_monitor
class dynamic_rules(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
_CONTEXTS = {
"Network_Monitor": network_monitor.Network_Monitor,
"wsgi": WSGIApplication
}
def __init__(self, *args, **kwargs):
super(dynamic_rules, self).__init__(*args, **kwargs)
# 增加一些全局变量
self.firstrequestFlag=False # 记录是否下发过流表
self.firstrequestTime=datetime.now() # 记录第一次下发流表的时间
self.mac_to_port = {}
self.ip_to_mac = {}
self.mac_to_dpid = {} # {mac:(dpid,port)}
self.datapaths = defaultdict(lambda: None)
self.topology_api_app = self
self.src_links = defaultdict(lambda: defaultdict(lambda: None))
self.check_ip_dpid = defaultdict(list)
self.qos_ip_bw_list = []
self.network_monitor = kwargs["Network_Monitor"]
self.ip_to_switch = {}
self.port_name_to_num = {}
self.ip_to_port = {} #{ip:(dpid,port)}
#promise me, use it well :)
self.pathmod = 0
self.path = None
@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
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, idle_timeout=0, 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,
idle_timeout=idle_timeout,
hard_timeout=hard_timeout,
instructions=inst)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
idle_timeout=idle_timeout,
hard_timeout=hard_timeout,
match=match, instructions=inst)
datapath.send_msg(mod)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
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]
pkt_arp = pkt.get_protocol(arp.arp)
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
pkt_tcp = pkt.get_protocol(tcp.tcp)
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
return
dst = eth.dst
src = eth.src
dpid = datapath.id
# self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
# in rest_topology, self.mac_to_port is for the find for host
self.mac_to_port.setdefault(dpid, {})
self.mac_to_port[dpid][src] = in_port
# arp handle
if pkt_arp and pkt_arp.opcode == arp.ARP_REQUEST:
if pkt_arp.src_ip not in self.ip_to_mac:
self.ip_to_mac[pkt_arp.src_ip] = src
self.mac_to_dpid[src] = (dpid, in_port)
self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port)
if pkt_arp.dst_ip in self.ip_to_mac:
#self.logger.info("[PACKET] ARP packet_in.")
self.handle_arpre(datapath=datapath, port=in_port,
src_mac=self.ip_to_mac[pkt_arp.dst_ip],
dst_mac=src, src_ip=pkt_arp.dst_ip, dst_ip=pkt_arp.src_ip)
else:
# to avoid flood when the dst ip not in the network
if datapath.id not in self.check_ip_dpid[pkt_arp.dst_ip]:
self.check_ip_dpid[pkt_arp.dst_ip].append(datapath.id)
out_port = ofproto.OFPP_FLOOD
actions = [parser.OFPActionOutput(out_port)]
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)
return
elif pkt_arp and pkt_arp.opcode == arp.ARP_REPLY:
if pkt_arp.src_ip not in self.ip_to_mac:
self.ip_to_mac[pkt_arp.src_ip] = src
self.mac_to_dpid[src] = (dpid, in_port)
self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port)
dst_mac = self.ip_to_mac[pkt_arp.dst_ip]
(dst_dpid, dst_port) = self.mac_to_dpid[dst_mac]
self.handle_arpre(datapath=self.datapaths[dst_dpid], port=dst_port, src_mac=src, dst_mac=dst_mac,
src_ip=pkt_arp.src_ip, dst_ip=pkt_arp.dst_ip)
return
if pkt_ipv4 and (self.ip_to_port.get(pkt_ipv4.dst)) and (self.ip_to_port.get(pkt_ipv4.src)):
(src_dpid, src_port) = self.ip_to_port[pkt_ipv4.src] # src dpid and port
(dst_dpid, dst_port) = self.ip_to_port[pkt_ipv4.dst] # dst dpid and port
self.install_path(src_dpid=src_dpid, dst_dpid=dst_dpid, src_port=src_port, dst_port=dst_port,
ev=ev, src=src, dst=dst, pkt_ipv4=pkt_ipv4, pkt_tcp=pkt_tcp)
def send_pkt(self, datapath, port, pkt):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
pkt.serialize()
data = pkt.data
actions = [parser.OFPActionOutput(port=port)]
out = parser.OFPPacketOut(datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER, in_port=ofproto.OFPP_CONTROLLER,
actions=actions, data=data)
datapath.send_msg(out)
def handle_arpre(self, datapath, port, src_mac, dst_mac, src_ip, dst_ip):
pkt = packet.Packet()
pkt.add_protocol(ethernet.ethernet(ethertype=0x0806, dst=dst_mac, src=src_mac))
pkt.add_protocol(arp.arp(opcode=arp.ARP_REPLY, src_mac=src_mac, src_ip=src_ip, dst_mac=dst_mac, dst_ip=dst_ip))
self.send_pkt(datapath, port, pkt)
def install_path(self, src_dpid, dst_dpid, src_port, dst_port, ev, src, dst, pkt_ipv4, pkt_tcp):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
if not self.firstrequestFlag:
self.firstrequestFlag=True
self.firstrequestTime=datetime.now()
nowTime=datetime.now()
gap=(nowTime-self.firstrequestTime)//(timedelta(seconds=5)) # 时间差
# print(self.short_path(src=1, dst=5))
# print(self.short_path(src=5, dst=1))
# print(self.long_path(src=1, dst=5))
# print(self.long_path(src=5, dst=1))
mid_path = None
if gap%2==0:
self.logger.info("short_path:")
mid_path = self.short_path(src=src_dpid, dst=dst_dpid)
#print(mid_path)
else:
self.logger.info("long_path:")
mid_path=self.long_path(src=src_dpid, dst=dst_dpid)
#print(mid_path)
# mid_path = self.short_path(src=src_dpid, dst=dst_dpid)
if mid_path is None:
return
self.path = None
self.path = [(src_dpid, src_port)] + mid_path + [(dst_dpid, dst_port)]
self.logger.info("path : %s", str(self.path))
for i in range(len(self.path) - 2, -1, -2):
datapath_path = self.datapaths[self.path[i][0]]
match = parser.OFPMatch(in_port=self.path[i][1], eth_src=src, eth_dst=dst, eth_type=0x0800,
ipv4_src=pkt_ipv4.src, ipv4_dst=pkt_ipv4.dst)
if i < (len(self.path) - 2):
actions = [parser.OFPActionOutput(self.path[i + 1][1])]
else:
actions = [parser.OFPActionSetField(eth_dst=self.ip_to_mac.get(pkt_ipv4.dst)),
parser.OFPActionOutput(self.path[i + 1][1])]
self.add_flow(datapath_path, 100, match, actions, idle_timeout=0, hard_timeout=5)
# time_install = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
# self.logger.info("time_install: %s", time_install)
def short_path(self, src, dst, bw=0):
if src == dst:
return []
result = defaultdict(lambda: defaultdict(lambda: None))
distance = defaultdict(lambda: None)
# the node is checked
seen = [src]
# the distance to src
distance[src] = 0
w = 1 # weight
while len(seen) < len(self.src_links):
node = seen[-1]
if node == dst:
break
for (temp_src, temp_dst) in self.src_links[node]:
if temp_dst not in seen:
temp_src_port = self.src_links[node][(temp_src, temp_dst)][0]
temp_dst_port = self.src_links[node][(temp_src, temp_dst)][1]
if (distance[temp_dst] is None) or ( distance[temp_dst] > distance[temp_src] + w ):
distance[temp_dst] = distance[temp_src] + w
# result = {"dpid":(link_src, src_port, link_dst, dst_port)}
result[temp_dst] = (temp_src, temp_src_port, temp_dst, temp_dst_port)
min_node = None
min_path = 999
# get the min_path node
for temp_node in distance:
if (temp_node not in seen) and (distance[temp_node] is not None):
if distance[temp_node] < min_path:
min_node = temp_node
min_path = distance[temp_node]
if min_node is None:
break
seen.append(min_node)
path = []
if dst not in result:
return None
while (dst in result) and (result[dst] is not None):
path = [result[dst][2:4]] + path
path = [result[dst][0:2]] + path
dst = result[dst][0]
#self.logger.info("path : %s", str(path))
return path
def long_path(self, src, dst, bw=0):
if src == dst:
return []
if src==1 and dst==5:
return [(1,2),(2,1),(2,2),(3,1),(3,2),(5,2)]
if src==5 and dst==1:
return [(5,2),(3,2),(3,1),(2,2),(2,1),(1,2)]
result = defaultdict(lambda: defaultdict(lambda: None))
distance = defaultdict(lambda: None)
# the node is checked
seen = [src]
# the distance to src
distance[src] = 0
w = -1 # weight
while len(seen) < len(self.src_links):
node = seen[-1]
if node == dst:
break
for (temp_src, temp_dst) in self.src_links[node]:
if temp_dst not in seen:
temp_src_port = self.src_links[node][(temp_src, temp_dst)][0]
temp_dst_port = self.src_links[node][(temp_src, temp_dst)][1]
if (distance[temp_dst] is None) or (distance[temp_dst] > distance[temp_src] + w):
distance[temp_dst] = distance[temp_src] + w
# result = {"dpid":(link_src, src_port, link_dst, dst_port)}
result[temp_dst] = (temp_src, temp_src_port, temp_dst, temp_dst_port)
min_node = None
min_path = 999
# get the min_path node
for temp_node in distance:
if (temp_node not in seen) and (distance[temp_node] is not None):
if distance[temp_node] < min_path:
min_node = temp_node
min_path = distance[temp_node]
if min_node is None:
break
seen.append(min_node)
path = []
if dst not in result:
return None
while (dst in result) and (result[dst] is not None):
path = [result[dst][2:4]] + path
path = [result[dst][0:2]] + path
dst = result[dst][0]
#self.logger.info("path : %s", str(path))
return path
# this function might be useful, but who knows anyway
# def long_path(self, src, dst, bw=0):
@set_ev_cls(ofp_event.EventOFPStateChange, [MAIN_DISPATCHER, DEAD_DISPATCHER])
def state_change_handler(self, ev):
datapath = ev.datapath
if ev.state == MAIN_DISPATCHER:
if datapath.id not in self.datapaths:
self.datapaths[datapath.id] = datapath
elif ev.state == DEAD_DISPATCHER:
if datapath.id in self.datapaths:
del self.datapaths[datapath.id]
#self.logger.info("datapaths : %s", self.datapaths)
@set_ev_cls([event.EventSwitchEnter, event.EventSwitchLeave, event.EventPortAdd, event.EventPortDelete,
event.EventPortModify, event.EventLinkAdd, event.EventLinkDelete])
def get_topology(self, ev):
links_list = get_link(self.topology_api_app, None)
self.src_links.clear()
for link in links_list:
sw_src = link.src.dpid
sw_dst = link.dst.dpid
src_port = link.src.port_no
dst_port = link.dst.port_no
src_port_name = link.src.name
dst_port_name = link.dst.name
self.port_name_to_num[src_port_name] = src_port
self.port_name_to_num[dst_port_name] = dst_port
self.src_links[sw_src][(sw_src, sw_dst)] = (src_port, dst_port)
self.src_links[sw_dst][(sw_dst, sw_src)] = (dst_port, src_port)
#self.logger.info("****src_port_name : %s", str(src_port_name))
#self.logger.info("src_links : %s", str(self.src_links))
#self.logger.info("port_name_to_num : %s", str(self.port_name_to_num))
# these two functions need to be coded in your own way
#def delete_flow(self, datapath, priority, match, actions, idle_timeout=10, hard_timeout=60):
#@set_ev_cls(ofp_event.EventOFPPortStatus, [CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER, HANDSHAKE_DISPATCHER])
#def get_OFPPortStatus_msg(self, ev):
3.4 链路故障恢复功能
network_monitor.py是recovery_rules.py在运行时所需要用到的文件,两者必须放在同一文件夹下
# recovery_rules.py
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.topology import event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER, HANDSHAKE_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 arp
from ryu.lib.packet import ipv4
from ryu.lib.packet import tcp
from ryu.topology.api import get_link
from ryu.lib.packet import ether_types
from ryu.app.wsgi import WSGIApplication
from collections import defaultdict
from datetime import datetime,timedelta
import network_monitor
class dynamic_rules(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
_CONTEXTS = {
"Network_Monitor": network_monitor.Network_Monitor,
"wsgi": WSGIApplication
}
def __init__(self, *args, **kwargs):
super(dynamic_rules, self).__init__(*args, **kwargs)
self.mac_to_port = {}
self.ip_to_mac = {}
self.mac_to_dpid = {} # {mac:(dpid,port)}
self.datapaths = defaultdict(lambda: None)
self.topology_api_app = self
self.src_links = defaultdict(lambda: defaultdict(lambda: None))
self.check_ip_dpid = defaultdict(list)
self.qos_ip_bw_list = []
self.network_monitor = kwargs["Network_Monitor"]
self.ip_to_switch = {}
self.port_name_to_num = {}
self.ip_to_port = {} #{ip:(dpid,port)}
#promise me, use it well :)
self.pathmod = 0 #0:short,1:long
self.path = None # {(dpid,port)}
# [(1,1),(1,3),(4,1),(4,2),(5,3),(5,1)]
# 表示从s1的1号端口进入,然后s1的3号端口发送给s4的1号端口,再从s4的2哈端口发送给s5的3号端口,再从s5的1号端口发出
@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
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, idle_timeout=0, 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,
idle_timeout=idle_timeout,
hard_timeout=hard_timeout,
instructions=inst)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
idle_timeout=idle_timeout,
hard_timeout=hard_timeout,
match=match, instructions=inst)
datapath.send_msg(mod)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
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]
pkt_arp = pkt.get_protocol(arp.arp)
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
pkt_tcp = pkt.get_protocol(tcp.tcp)
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
return
dst = eth.dst
src = eth.src
dpid = datapath.id
#print ("pac in")
# self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
# in rest_topology, self.mac_to_port is for the find for host
self.mac_to_port.setdefault(dpid, {})
self.mac_to_port[dpid][src] = in_port
# arp handle
if pkt_arp and pkt_arp.opcode == arp.ARP_REQUEST:
if pkt_arp.src_ip not in self.ip_to_mac:
self.ip_to_mac[pkt_arp.src_ip] = src
self.mac_to_dpid[src] = (dpid, in_port)
self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port)
if pkt_arp.dst_ip in self.ip_to_mac:
#self.logger.info("[PACKET] ARP packet_in.")
self.handle_arpre(datapath=datapath, port=in_port,
src_mac=self.ip_to_mac[pkt_arp.dst_ip],
dst_mac=src, src_ip=pkt_arp.dst_ip, dst_ip=pkt_arp.src_ip)
else:
# to avoid flood when the dst ip not in the network
if datapath.id not in self.check_ip_dpid[pkt_arp.dst_ip]:
self.check_ip_dpid[pkt_arp.dst_ip].append(datapath.id)
out_port = ofproto.OFPP_FLOOD
actions = [parser.OFPActionOutput(out_port)]
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)
return
elif pkt_arp and pkt_arp.opcode == arp.ARP_REPLY:
if pkt_arp.src_ip not in self.ip_to_mac:
self.ip_to_mac[pkt_arp.src_ip] = src
self.mac_to_dpid[src] = (dpid, in_port)
self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port)
dst_mac = self.ip_to_mac[pkt_arp.dst_ip]
(dst_dpid, dst_port) = self.mac_to_dpid[dst_mac]
self.handle_arpre(datapath=self.datapaths[dst_dpid], port=dst_port, src_mac=src, dst_mac=dst_mac,
src_ip=pkt_arp.src_ip, dst_ip=pkt_arp.dst_ip)
return
if pkt_ipv4 and (self.ip_to_port.get(pkt_ipv4.dst)) and (self.ip_to_port.get(pkt_ipv4.src)):
(src_dpid, src_port) = self.ip_to_port[pkt_ipv4.src] # src dpid and port
(dst_dpid, dst_port) = self.ip_to_port[pkt_ipv4.dst] # dst dpid and port
self.install_path(src_dpid=src_dpid, dst_dpid=dst_dpid, src_port=src_port, dst_port=dst_port,
ev=ev, src=src, dst=dst, pkt_ipv4=pkt_ipv4, pkt_tcp=pkt_tcp)
def send_pkt(self, datapath, port, pkt):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
pkt.serialize()
data = pkt.data
actions = [parser.OFPActionOutput(port=port)]
out = parser.OFPPacketOut(datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER, in_port=ofproto.OFPP_CONTROLLER,
actions=actions, data=data)
datapath.send_msg(out)
def handle_arpre(self, datapath, port, src_mac, dst_mac, src_ip, dst_ip):
pkt = packet.Packet()
pkt.add_protocol(ethernet.ethernet(ethertype=0x0806, dst=dst_mac, src=src_mac))
pkt.add_protocol(arp.arp(opcode=arp.ARP_REPLY, src_mac=src_mac, src_ip=src_ip, dst_mac=dst_mac, dst_ip=dst_ip))
self.send_pkt(datapath, port, pkt)
def install_path(self, src_dpid, dst_dpid, src_port, dst_port, ev, src, dst, pkt_ipv4, pkt_tcp):
print ("install path")
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
mid_path = self.short_path(src=src_dpid, dst=dst_dpid)
if mid_path is None:
return
self.path = None
self.path = [(src_dpid, src_port)] + mid_path + [(dst_dpid, dst_port)]
self.logger.info("path : %s", str(self.path))
#print (self.pathmod)
for i in range(len(self.path) - 2, -1, -2):
datapath_path = self.datapaths[self.path[i][0]]
match = parser.OFPMatch(in_port=self.path[i][1], eth_src=src, eth_dst=dst, eth_type=0x0800,
ipv4_src=pkt_ipv4.src, ipv4_dst=pkt_ipv4.dst)
if i < (len(self.path) - 2):
actions = [parser.OFPActionOutput(self.path[i + 1][1])]
else:
actions = [parser.OFPActionSetField(eth_dst=self.ip_to_mac.get(pkt_ipv4.dst)),
parser.OFPActionOutput(self.path[i + 1][1])]
self.add_flow(datapath_path, 100, match, actions, idle_timeout=0, hard_timeout=0)
# time_install = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
# self.logger.info("time_install: %s", time_install)
def short_path(self, src, dst, bw=0):
if src == dst:
return []
result = defaultdict(lambda: defaultdict(lambda: None))
distance = defaultdict(lambda: None)
# the node is checked
seen = [src]
# the distance to src
distance[src] = 0
w = 1 # weight
while len(seen) < len(self.src_links):
node = seen[-1]
if node == dst:
break
for (temp_src, temp_dst) in self.src_links[node]:
if temp_dst not in seen:
temp_src_port = self.src_links[node][(temp_src, temp_dst)][0]
temp_dst_port = self.src_links[node][(temp_src, temp_dst)][1]
if (distance[temp_dst] is None) or (distance[temp_dst] > distance[temp_src] + w):
distance[temp_dst] = distance[temp_src] + w
# result = {"dpid":(link_src, src_port, link_dst, dst_port)}
result[temp_dst] = (temp_src, temp_src_port, temp_dst, temp_dst_port)
min_node = None
min_path = 999
# get the min_path node
for temp_node in distance:
if (temp_node not in seen) and (distance[temp_node] is not None):
if distance[temp_node] < min_path:
min_node = temp_node
min_path = distance[temp_node]
if min_node is None:
break
seen.append(min_node)
path = []
if dst not in result:
return None
while (dst in result) and (result[dst] is not None):
path = [result[dst][2:4]] + path
path = [result[dst][0:2]] + path
dst = result[dst][0]
#self.logger.info("path : %s", str(path))
return path
# this function might be useful, but who knows anyway
def long_path(self, src, dst, bw=0):
if src == dst:
return []
result = defaultdict(lambda: defaultdict(lambda: None))
if src==1 and dst == 5:
fixed_path=[(1,2),(2,3),(3,5)]
elif src == 5 and dst == 1:
fixed_path=[(5,3),(3,2),(2,1)]
for (temp_src, temp_dst) in fixed_path:
if (temp_src, temp_dst) in self.src_links[temp_src]:
temp_src_port = self.src_links[temp_src][(temp_src, temp_dst)][0]
temp_dst_port = self.src_links[temp_src][(temp_src, temp_dst)][1]
result[temp_dst] = (temp_src, temp_src_port, temp_dst, temp_dst_port)
path = []
if dst not in result:
return None
while (dst in result) and (result[dst] is not None):
path = [result[dst][2:4]] + path
path = [result[dst][0:2]] + path
dst = result[dst][0]
#self.logger.info("path : %s", str(path))
return path
@set_ev_cls(ofp_event.EventOFPStateChange, [MAIN_DISPATCHER, DEAD_DISPATCHER])
def state_change_handler(self, ev):
datapath = ev.datapath
if ev.state == MAIN_DISPATCHER:
if datapath.id not in self.datapaths:
self.datapaths[datapath.id] = datapath
elif ev.state == DEAD_DISPATCHER:
if datapath.id in self.datapaths:
del self.datapaths[datapath.id]
#self.logger.info("datapaths : %s", self.datapaths)
@set_ev_cls([event.EventSwitchEnter, event.EventSwitchLeave, event.EventPortAdd, event.EventPortDelete,
event.EventPortModify, event.EventLinkAdd, event.EventLinkDelete])
def get_topology(self, ev):
links_list = get_link(self.topology_api_app, None)
self.src_links.clear()
for link in links_list:
sw_src = link.src.dpid
sw_dst = link.dst.dpid
src_port = link.src.port_no
dst_port = link.dst.port_no
src_port_name = link.src.name
dst_port_name = link.dst.name
self.port_name_to_num[src_port_name] = src_port
self.port_name_to_num[dst_port_name] = dst_port
self.src_links[sw_src][(sw_src, sw_dst)] = (src_port, dst_port)
self.src_links[sw_dst][(sw_dst, sw_src)] = (dst_port, src_port)
# self.logger.info("****src_port_name : %s", str(src_port_name))
# self.logger.info("src_links : %s", str(self.src_links))
# self.logger.info("port_name_to_num : %s", str(self.port_name_to_num))
# these two functions need to be coded in your own way
def delete_flow(self, datapath,match):
ofp = datapath.ofproto
ofp_parser = datapath.ofproto_parser
cookie = cookie_mask = 0
table_id = 0
priority=100 # 使用最短路,priority并没有什么用
idle_timeout = 10
hard_timeout = 60
buffer_id = ofp.OFP_NO_BUFFER
#match = ofp_parser.OFPMatch(in_port=3, eth_type=flow_info[0], ipv4_src="10.0.0.1", ipv4_dst="10.0.0.2")
actions = []
inst = []
req = ofp_parser.OFPFlowMod(datapath,
cookie, cookie_mask, table_id,
ofp.OFPFC_DELETE, idle_timeout,
hard_timeout, priority, buffer_id,
ofp.OFPP_ANY, ofp.OFPG_ANY, ofp.OFPFF_SEND_FLOW_REM,
match, inst)
datapath.send_msg(req)
@set_ev_cls(ofp_event.EventOFPPortStatus, [CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER, HANDSHAKE_DISPATCHER])
def get_OFPPortStatus_msg(self, ev):
msg=ev.msg
datapath=ev.msg.datapath
dpid = msg.datapath.id
ofproto=datapath.ofproto
parser = datapath.ofproto_parser
# self.ip_to_port[pkt_arp.src_ip] = (dpid, in_port) #{ip:(dpid,port)}
for ip in self.ip_to_port:
# self.logger.info(ip, str(self.ip_to_port[ip]))
if self.ip_to_port[ip] == self.path[-1]:
dstip=ip
dst=self.ip_to_mac[dstip]
for ip in self.ip_to_port:
if self.ip_to_port[ip] == self.path[0]:
srcip=ip
src=self.ip_to_mac[srcip]
# for example,path=[(1,1),(1,3),(4,1),(4,2),(5,3),(5,1)]
# 表示从s1的1号端口进入,然后s1的3号端口发送给s4的1号端口,再从s4的2哈端口发送给s5的3号端口,再从s5的1号端口发出
for i in range(len(self.path) - 2, -1, -2):
datapath_path = self.datapaths[self.path[i][0]]
match1 = parser.OFPMatch(in_port=self.path[i][1], eth_src=src, eth_dst=dst, eth_type=0x0800,
ipv4_src=srcip, ipv4_dst=dstip)
self.delete_flow(datapath=datapath_path,match=match1)
match2 = parser.OFPMatch(in_port=self.path[i+1][1], eth_src=dst, eth_dst=src, eth_type=0x0800,
ipv4_src=dstip, ipv4_dst=srcip)
self.delete_flow(datapath=datapath_path,match=match2)