Multi-Path Dijkstra Algorithm & ECMP (Equal Cost Multi-Path Routing)
[Description]
Based on this post, I implement the multi-path Dijkstra algorithm in pyretic controller. With this algorithm. Sender can transmit packets through different paths to the destination. The basic idea is that when original Dijkstra algorithm is used and (distance [u] + distance_between(u,v) ) < distance [v], we will set the distance[v] to distance [u] + distance_between(u,v) and the previous node of v is u. The modified version is to remember all the previous nodes of u when (distance [u] + distance_between(u,v) ) = distance [v]. Take the following figure as an example, in original Dijkstra algorithm, the previous node of r3 will be r2 or r4. Only one node will be remembered. But for the modified version, r2 and r4 will be both remembered. After calculation, if r2 is chosen for the previous node of r3, the path from h1 to h2 will be h1-r1-r2-r3-h2. If r4 is chosen, the path will be h1-r1-r4-r3-h2. So there will exists two paths with the same cost.

[pyretic controller: myroute_dijkstra_ecmp.py]
|
from pyretic.lib.corelib import* from pyretic.lib.std import * from multiprocessing import Lock from pyretic.lib.query import * from collections import defaultdict import random #switches switches = [] #myhost[srcmac]->(switch, port) myhost={} #adjacency map [sw1][sw2]->port from sw1 to sw2 adjacency=defaultdict(lambda:defaultdict(lambda:None)) def minimum_distance(distance, 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 print "src=",src," dst=",dst, " first_port=", first_port, " final_port=", final_port distance = {} previous = {} for dpid in switches: distance[dpid] = float('Inf') previous[dpid] = []
distance[src]=0 Q=set(switches)
while len(Q)>0: u =
minimum_distance(distance, Q) Q.remove(u) for p in switches: if
adjacency[u][p]!=None: w
= 1
alt = distance[u] + w if
alt < distance[p]: distance[p]
= alt
if len(previous[p])!=0:
del previous[p][:]
previous[p].append(u)
elif alt == distance[p]: previous[p].append(u) r=[] p=dst r.append(p) q=random.choice(previous[p]) while q is not None: if q == src: r.append(q) break p=q r.append(p) q=random.choice(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 find_route(DynamicPolicy): def __init__(self): super(find_route,self).__init__() self.flood = flood() self.set_initial_state()
def set_initial_state(self): self.query = packets(1,['srcmac','dstmac', 'srcip', 'dstip', 'protocol', 'ethtype', 'srcport', 'dstport']) self.query.register_callback(self.myroute) self.forward = self.flood self.update_policy() def set_network(self,network): self.set_initial_state() def update_policy(self): self.policy = self.forward + self.query def myroute(self,pkt): print pkt['srcmac'], pkt['dstmac'], pkt['srcip'], pkt['dstip'], pkt['protocol'], pkt['srcport'], pkt['dstport'] if (pkt['srcmac'] not in myhost.keys()) or (pkt['dstmac'] not in myhost.keys()): return #print myhost[pkt['srcmac']][0], myhost[pkt['dstmac']][0] #if match(ethtype=IP_TYPE): # print "ipv4 packet" p1 = get_path(myhost[pkt['srcmac']][0], myhost[pkt['dstmac']][0],myhost[pkt['srcmac']][1], myhost[pkt['dstmac']][1]) print p1 #p2 = get_path(myhost[pkt['dstmac']][0], myhost[pkt['srcmac']][0],myhost[pkt['dstmac']][1], myhost[pkt['srcmac']][1]) #print p2
#print "srcport=", pkt['srcport'] #print "protocol=", pkt['protocol'], "ethtype=",pkt['ethtype'] if pkt['protocol']==1: r1 = parallel([(match(switch=a,srcip=pkt['srcip'],dstip=pkt['dstip']) >> fwd(c)) for a,b,c in p1]) self.forward = if_(match(dstip=pkt['dstip'],srcip=pkt['srcip']),r1,self.forward) else: r1 = parallel([(match(switch=a,ethtype=pkt['ethtype'],srcip=pkt['srcip'],dstip=pkt['dstip'], protocol=pkt['protocol'], srcport=pkt['srcport'],dstport=pkt['dstport']) >> fwd(c)) for a,b,c in p1]) self.forward = if_(match(ethtype=pkt['ethtype'],dstip=pkt['dstip'],srcip=pkt['srcip'], protocol=pkt['protocol'], srcport=pkt['srcport'],dstport=pkt['dstport']),r1,self.forward)
self.update_policy()
def find_host(): q = packets(1,['srcmac','switch']) q.register_callback(mymac_learner) return q def mymac_learner(pkt): #print pkt['srcmac'], pkt['dstmac'], pkt['switch'], pkt['inport'] #if match(ethtype=ARP_TYPE): # print "arp packet"
if pkt['srcmac'] not in myhost.keys(): myhost[pkt['srcmac']]=( pkt['switch'], pkt['inport'])
#for a in myhost.keys(): # print a, myhost[a][0], myhost[a][1]
class find_switch(DynamicPolicy): def __init__(self): self.last_topology = None self.lock = Lock() super(find_switch,self).__init__() def set_network(self, network): with self.lock: for x in network.switch_list(): switches.append(x) for (s1,s2,data) in network.topology.edges(data=True): adjacency[s1][s2]=data[s1] adjacency[s2][s1]=data[s2] self.last_topology = network.topology
def arp_and_ip(): return if_(match(ethtype=ARP_TYPE), flood(), find_route())
def main():
return ( find_switch() + find_host() + arp_and_ip()) |
[mininet script:test_ecmp1.py]
|
#!/usr/bin/python from mininet.net import Mininet from mininet.node import Controller, RemoteController, UserSwitch from mininet.cli import CLI from mininet.log import setLogLevel from mininet.link import Link, TCLink, Intf def topology(): "Create a network." net = Mininet( controller=RemoteController, link=TCLink ) print "*** Creating nodes" h1 = net.addHost( 'h1') h2 = net.addHost( 'h2') SwitchList = [] # set n to different numbers, you can set that each path to contain n switches n = 3 for x in range(1, n*2-2+1): PREFIX="s" SwitchList.append(net.addSwitch(PREFIX+str(x))) c0 = net.addController( 'c0', controller=RemoteController, ip='127.0.0.1', port=6633 ) print "*** Creating links" linkBW = {'bw':10} linkBW2 = {'bw':100} net.addLink(h1, SwitchList[0], cls=TCLink, **linkBW2) net.addLink(h2, SwitchList[n-1], cls=TCLink, **linkBW2) for i in range(n*2-2): if i==(n-1): net.addLink(SwitchList[0], SwitchList[i+1], cls=TCLink, **linkBW) elif i!=(n-1) and i!=(n*2-2-1): net.addLink(SwitchList[i], SwitchList[i+1], cls=TCLink, **linkBW) else: net.addLink(SwitchList[i], SwitchList[n-1], cls=TCLink, **linkBW) print "*** Starting network" net.build() c0.start() for sw in SwitchList: sw.start([c0]) #using the static arp, the hosts don't need to run arp protocol. Speed up the emulation. #h1.cmd('arp -s '+ h2.IP()+' '+h2.MAC()) #h1.cmdPrint('arp -n') #h2.cmd('arp -s '+h1.IP()+' '+h1.MAC()) #h2.cmdPrint('arp -n') #print "*** Running CLI" CLI( net ) print "*** Stopping network" net.stop() if __name__ == '__main__': setLogLevel( 'info' ) topology() |
[Execution]
Before running the script, we need to use iperf2. You can download iperf2 from here.

User xterm h1 h2 to open two terminals. (p.s. –P means number of parallel client threads to run)

From the following figure, we can see that there are two flows from h1 to h2. These two flows take different paths. The total throughput is around 19.1 Mbps.
If these two flows go the same paths, no more than 10Mbps can be obtained. Therefore, the performance is greatly improved.

Dr. Chih-Heng Ke (smallko@gmail.com)
Department of Computer
Science and Information Engineering,
National Quemoy
University, Kinmen, Taiwan.