Find the maximum capacity path with Dijkstra’s Algorithm in accordance with current network status
[Topology]
H2 will send traffic to H3 and H1 will also send traffic to H3. If we use dijkstra’s algorithm to find the shortest path. These two flows will go via s1 and s2 and they will compete for bandwidth.
So we will let the H2 send the traffic to H3 first. This flow will go via s1 and s2 and then reach H3. Following, we will let the flow sent from H1 to H3 use the modified version of Dijkstra’s Algorithm to find the maximum capacity path. Therefore, this flow will go via s1,s3,s2, and then H3.
[mininet-script]
from mininet.net import Mininet from mininet.node
import Controller, RemoteController, OVSKernelSwitch, UserSwitch, OVSSwitch from mininet.cli
import CLI from mininet.log import setLogLevel from mininet.link
import Link, TCLink def topology():
net = Mininet( controller=RemoteController, link=TCLink,
switch=OVSSwitch ) #
Add hosts and switches
h1= net.addHost( 'h1',
mac="00:00:00:00:00:01" ) h2
= net.addHost( 'h2',
mac="00:00:00:00:00:02" ) h3
= net.addHost( 'h3',
mac="00:00:00:00:00:03" ) s1
= net.addSwitch( 's1' ) s2
= net.addSwitch( 's2' ) s3
= net.addSwitch( 's3' ) c0
= net.addController( 'c0', controller=RemoteController, ip='127.0.0.1',
port=6633 )
linkopt1=dict(bw=10,delay='1ms',loss=0)
linkopt2=dict(bw=8,delay='1ms',loss=0)
linkopt3=dict(bw=100,delay='1ms',loss=0) net.addLink( h1, s1, **linkopt3) net.addLink( h2, s1, **linkopt3) net.addLink( h3, s2, **linkopt3) net.addLink( s1, s2, **linkopt1) net.addLink( s1, s3, **linkopt1) net.addLink( s2, s3, **linkopt2) net.build()
c0.start()
s1.start( [c0] )
s2.start( [c0] )
s3.start( [c0] )
print "*** Running CLI"
CLI( net )
print "*** Stopping network" net.stop() if __name__ == '__main__': setLogLevel(
'info' ) topology() |
Please prepare one text file (bw.txt) and put it under ryu/ryu/app folder. This text file will tell the controller application the link bandwidth between switches. Because we are running experiments under mininet. We cannot simply send feature requests to switches and get the correct port settings. (In the mininet, TC is used to limit the bandwidth). The first column is the first switch ID, the second is the second switch ID, and the third is bandwidth in Mbps. (1 2 10 means the link between s1 and s2 is 1OMbps)
1 2 10 1 3 10 2 3 8 |
[ryu application]
# 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 mac_to_port from ryu.controller
import ofp_event from ryu.controller.handler
import CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER from ryu.controller.handler
import set_ev_cls from ryu.ofproto
import ofproto_v1_3 from ryu.lib.mac import haddr_to_bin from ryu.lib.packet
import packet from ryu.lib.packet
import ethernet from ryu.lib.packet
import ether_types from ryu.lib import mac from ryu.topology
import event, switches from ryu.topology.api
import get_switch, get_link from ryu.app.wsgi
import ControllerBase from collections import defaultdict from ryu.lib import hub from operator import attrgetter from datetime
import datetime import time #switches myswitches = [] #mymac[srcmac]->(switch, port) mymac={} #adjacency map [sw1][sw2]->port from
sw1 to sw2 adjacency=defaultdict(lambda:defaultdict(lambda:None)) datapath_list={} byte=defaultdict(lambda:defaultdict(lambda:None)) clock=defaultdict(lambda:defaultdict(lambda:None)) bw_used=defaultdict(lambda:defaultdict(lambda:None)) bw_available=defaultdict(lambda:defaultdict(lambda:None)) bw=defaultdict(lambda:defaultdict(lambda:None)) target_srcmac="00:00:00:00:00:01" target_dstmac="00:00:00:00:00:03" def max_abw(abw,
Q):
max = float('-Inf')
node = 0
for v in Q: if abw[v]
> max: max = abw[v] node = v
return node def get_path2 (src,dst,first_port,final_port):
global bw_available
print "Dijkstra's widest path
algorithm"
print "src=",src,"
dst=",dst, " first_port=", first_port,
" final_port=", final_port
#available bandwidth
abw = {}
previous = {}
for dpid in myswitches: abw[dpid] = float('-Inf') previous[dpid] = None
abw[src]=float('Inf')
Q=set(myswitches)
print "Q:", Q
#print time.time()
while len(Q)>0: u = max_abw(abw, Q) Q.remove(u) print "Q:", Q,
"u:", u for p in myswitches: if
adjacency[u][p]!=None: link_abw = bw_available[str(u)][str(p)]
print "link_abw:", str(u),"->",str(p),":",link_abw, "kbps"
#alt=max(abw[p], min(width[u], abw_between(u,p))) if abw[u] < link_abw:
tmp = abw[u]
else:
tmp = link_abw if
abw[p] > tmp:
alt = abw[p]
else:
alt = tmp if
alt > abw[p]:
abw[p] = alt
previous[p] = u
#print "distance=", distance, " previous=",
previous
r=[]
p=dst
r.append(p)
q=previous[p]
while q is not None: if q == src: r.append(q) break p=q r.append(p) q=previous[p]
r.reverse()
if src==dst: path=[src]
else: path=r
# Now add the ports
r = []
in_port = first_port
for s1,s2 in zip(path[:-1],path[1:]): out_port
= adjacency[s1][s2] r.append((s1,in_port,out_port)) in_port
= adjacency[s2][s1]
r.append((dst,in_port,final_port))
return r def minimum_distance(distance, Q):
#print "minimum_distance() is
called", " distance=", distance, " Q=", Q
min = float('Inf')
node = 0
for v in Q: if distance[v] < min: min =
distance[v] node = v
return node def get_path (src,dst,first_port,final_port):
#Dijkstra's algorithm
global myswitches, adjacency
print "Dijkstra's shortest path
algorithm"
print "get_path is called, src=",src," dst=",dst, " first_port=", first_port,
" final_port=", final_port
distance = {}
previous = {}
for dpid in myswitches: distance[dpid] = float('Inf') previous[dpid] = None
distance[src]=0
Q=set(myswitches)
#print "Q=", Q
while len(Q)>0: u = minimum_distance(distance,
Q) #print "u=", u Q.remove(u) #print "After
removing ", u, " Q=", Q for p in myswitches: if
adjacency[u][p]!=None:
#print u, "--------",
p w
= 1 if
distance[u] + w < distance[p]:
distance[p] = distance[u] + w
previous[p] = u
#print "distance=", distance, " previous=",
previous
r=[]
p=dst
r.append(p)
q=previous[p]
while q is not None: if q == src: r.append(q) break p=q r.append(p) q=previous[p]
r.reverse()
if src==dst: path=[src]
else: path=r
# Now add the ports
r = []
in_port = first_port
for s1,s2 in zip(path[:-1],path[1:]): out_port
= adjacency[s1][s2] r.append((s1,in_port,out_port)) in_port
= adjacency[s2][s1]
r.append((dst,in_port,final_port))
return r class ProjectController(app_manager.RyuApp): OFP_VERSIONS =
[ofproto_v1_3.OFP_VERSION] def
__init__(self, *args, **kwargs):
super(ProjectController, self).__init__(*args, **kwargs) self.mac_to_port = {} self.topology_api_app = self self.datapaths = {} self.monitor_thread = hub.spawn(self._monitor)
global bw
try:
fin = open("bw.txt", "r")
for line in fin:
a=line.split()
if a: bw[str(a[0])][str(a[1])]=int(a[2])
bw[str(a[1])][str(a[0])]=int(a[2])
fin.close()
except IOError:
print "make bw.txt ready" #print
"bw:", bw @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.logger.debug('register datapath: %016x', datapath.id)
print 'register datapath:', datapath.id
self.datapaths[datapath.id] = datapath elif ev.state ==
DEAD_DISPATCHER:
if datapath.id in self.datapaths:
#self.logger.debug('unregister datapath:
%016x', datapath.id)
print 'unregister datapath:', datapath.id
del self.datapaths[datapath.id] def
_monitor(self):
while True:
for dp in self.datapaths.values():
self._request_stats(dp)
hub.sleep(3) def
_request_stats(self, datapath): #self.logger.debug('send stats request: %016x',
datapath.id)
#print 'send stats request:', datapath.id ofproto = datapath.ofproto
parser = datapath.ofproto_parser req = parser.OFPFlowStatsRequest(datapath) datapath.send_msg(req) req = parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY) datapath.send_msg(req) @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) def
_flow_stats_reply_handler(self, ev):
body = ev.msg.body
#self.logger.info('datapath
'
#
'in-port eth-dst '
#
'out-port packets bytes')
#self.logger.info('---------------- '
#
'-------- ----------------- '
#
'-------- -------- --------')
#for stat in sorted([flow for flow in body if flow.priority
== 1],
#
key=lambda flow: (flow.match['in_port'],
#
flow.match['eth_dst'])):
#
self.logger.info('%016x %8x %17s %8x %8d %8d',
#
ev.msg.datapath.id,
#
stat.match['in_port'],
stat.match['eth_dst'],
#
stat.instructions[0].actions[0].port,
#
stat.packet_count, stat.byte_count) @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) def
_port_stats_reply_handler(self, ev):
global byte, clock, bw_used, bw_available
#print time.time()," _port_stats_reply_handler"
body = ev.msg.body
dpid = ev.msg.datapath.id
for stat in sorted(body, key=attrgetter('port_no')):
#print dpid, stat.port_no,
stat.tx_packets
for p in myswitches:
if adjacency[dpid][p]==stat.port_no:
#print dpid, p, stat.port_no
if byte[dpid][p]>0:
bw_used[dpid][p] =
(stat.tx_bytes - byte[dpid][p])
* 8.0 / (time.time()-clock[dpid][p])
/ 1000
bw_available[str(dpid)][str(p)]=int(bw[str(dpid)][str(p)]) * 1024.0 - bw_used[dpid][p]
print str(dpid),"->",str(p),":",bw_available[str(dpid)][str(p)],"
kbps"
#print str(dpid),"->",str(p),":", bw[str(dpid)][str(p)],"
kbps"
byte[dpid][p]=stat.tx_bytes
clock[dpid][p]=time.time()
print
"-------------------------------------------------------------------"
#self.logger.info('datapath
port '
#
'rx-pkts rx-bytes rx-error '
#
'tx-pkts tx-bytes tx-error')
#self.logger.info('---------------- -------- '
#
'-------- -------- -------- '
#
'-------- -------- --------')
#for stat in sorted(body, key=attrgetter('port_no')):
#
self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d',
#
ev.msg.datapath.id, stat.port_no,
#
stat.rx_packets, stat.rx_bytes,
stat.rx_errors,
#
stat.tx_packets, stat.tx_bytes,
stat.tx_errors) # Handy function that
lists all attributes in the given object def
ls(self,obj): print("\n".join([x for
x in dir(obj) if x[0] !=
"_"])) def
add_flow(self, datapath, in_port, dst, actions): ofproto = datapath.ofproto parser = datapath.ofproto_parser
match = datapath.ofproto_parser.OFPMatch(
in_port=in_port, eth_dst=dst) inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath,
match=match, cookie=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=ofproto.OFP_DEFAULT_PRIORITY,
instructions=inst) datapath.send_msg(mod) def
install_path(self, p, ev,
src_mac, dst_mac): print
"install_path is called" #print
"p=", p, " src_mac=", src_mac, " dst_mac=",
dst_mac msg = ev.msg datapath = msg.datapath ofproto = datapath.ofproto parser =
datapath.ofproto_parser for sw, in_port, out_port in p:
print src_mac,"->", dst_mac, "via ", sw,
" in_port=", in_port,
" out_port=", out_port
match=parser.OFPMatch(in_port=in_port, eth_src=src_mac, eth_dst=dst_mac)
actions=[parser.OFPActionOutput(out_port)]
datapath=datapath_list[sw]
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS , actions)]
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath,
match=match, idle_timeout=0, hard_timeout=0,
priority=1, instructions=inst)
datapath.send_msg(mod) @set_ev_cls(ofp_event.EventOFPSwitchFeatures , CONFIG_DISPATCHER) def
switch_features_handler(self , ev):
print "switch_features_handler is
called"
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)]
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS , actions)]
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath,
match=match, cookie=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=0, instructions=inst)
datapath.send_msg(mod) @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def
_packet_in_handler(self, ev): global
target_srcmac, target_dstmac
#print "packet_in event:",
ev.msg.datapath.id, " in_port:", ev.msg.match['in_port'] 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_protocol(ethernet.ethernet)
#print "eth.ethertype=", eth.ethertype #avodi broadcast from LLDP if
eth.ethertype==35020:
return dst = eth.dst src = eth.src dpid = datapath.id
#print "src=", src,
" dst=", dst,
" type=", hex(eth.ethertype)
#print "adjacency=", adjacency self.mac_to_port.setdefault(dpid,
{}) if
src not in mymac.keys():
mymac[src]=( dpid, in_port)
#print "mymac=", mymac if
dst in mymac.keys():
if (src==target_srcmac
and dst==target_dstmac)
or (dst==target_srcmac
and src==target_dstmac):
p = get_path2(mymac[src][0],
mymac[dst][0], mymac[src][1], mymac[dst][1]) else:
p = get_path(mymac[src][0], mymac[dst][0], mymac[src][1], mymac[dst][1])
print "Path=", p
self.install_path(p, ev,
src, dst)
out_port = p[0][2]
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_src=src, eth_dst=dst)
data=None if
msg.buffer_id==ofproto.OFP_NO_BUFFER:
data=msg.data if
out_port == ofproto.OFPP_FLOOD:
#print "FLOOD"
while len(actions) > 0 : actions.pop()
for i in range(1,23):
actions.append(parser.OFPActionOutput(i))
#print "actions=", actions
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port,
actions=actions, data=data)
datapath.send_msg(out)
else:
#print "unicast"
out = parser.OFPPacketOut(
datapath=datapath,
buffer_id=msg.buffer_id, in_port=in_port,
actions=actions, data=data)
datapath.send_msg(out) events = [event.EventSwitchEnter,
event.EventSwitchLeave, event.EventPortAdd,
event.EventPortDelete, event.EventPortModify, event.EventLinkAdd, event.EventLinkDelete] @set_ev_cls(events) def
get_topology_data(self, ev):
print "get_topology_data() is
called"
global myswitches, adjacency, datapath_list switch_list = get_switch(self.topology_api_app, None) myswitches=[switch.dp.id for switch in switch_list]
for switch in switch_list:
datapath_list[switch.dp.id]=switch.dp
#print "datapath_list=", datapath_list
print "myswitches=", myswitches links_list = get_link(self.topology_api_app, None)
#print "links_list=", links_list
mylinks=[(link.src.dpid,link.dst.dpid,link.src.port_no,link.dst.port_no)
for link in links_list]
for s1,s2,port1,port2 in mylinks:
#print "type(s1)=", type(s1), " type(port1)=",
type(port1)
adjacency[s1][s2]=port1
adjacency[s2][s1]=port2 print s1,":", port1, "<--->",s2,":",port2 |
[Execution]
Open a terminal to run ryu application
Open another terminal to run the mininet script
Use xterm to open h1 h2 h3 h3
Send the traffic from h2 first.
Then send the traffic from H1. From the following we can see that the flow from H2 will not be affected by the flow of H1-H3. Because the flow of H1-H3 go different path (s1,s3,and then s2).
Dr. Chih-Heng Ke
Department of Computer Science and
Information Engineering, National Quemoy University, Kinmen, Taiwan
Email: smallko@gmail.com