blob: 94eec0da59df8addde152b0c35fc2c13593eede5 [file] [log] [blame]
Saurab Dulal082e4442020-03-02 15:49:03 -06001# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
Saurab Dulal8ae870a2018-07-31 05:17:49 +00002#
Saurab Dulal082e4442020-03-02 15:49:03 -06003# Copyright (C) 2015-2020, The University of Memphis
Saurab Dulal8ae870a2018-07-31 05:17:49 +00004#
5# This file is part of Mini-NDN.
6# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
7#
8# Mini-NDN is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# Mini-NDN is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with Mini-NDN, e.g., in COPYING.md file.
20# If not, see <http://www.gnu.org/licenses/>.
21
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050022# IMPORTANT! This feature is in highly experimental phase and may go several changes
23# in future
24
Saurab Dulal8ae870a2018-07-31 05:17:49 +000025'''
26This module will compute link state, hyperbolic and geohyperbolic
27routes and their costs from the given Mini-NDN topology
28'''
29
Saurab Dulal082e4442020-03-02 15:49:03 -060030import sys
Saurab Dulal8ae870a2018-07-31 05:17:49 +000031import heapq
Saurab Dulal8ae870a2018-07-31 05:17:49 +000032from math import sin, cos, sinh, cosh, acos, acosh
33import json
34import operator
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050035from collections import defaultdict
Saurab Dulal8ae870a2018-07-31 05:17:49 +000036
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050037from mininet.log import info, debug, error, warn
38from minindn.helpers.nfdc import Nfdc as nfdc
Saurab Dulal8ae870a2018-07-31 05:17:49 +000039
40UNKNOWN_DISTANCE = -1
41HYPERBOLIC_COST_ADJUSTMENT_FACTOR = 1000
42
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050043def dijkstra(graph, start, end, ignoredNode=None):
44 """
45 Compute shortest path and cost from a given source to a destination
46 using Dijkstra algorithm
47
48 :param Graph graph: given network topology/graph
49 :param Start start: source node in a given network graph/topology
50 :end End end: destination node in a given network graph/topology
51 :param Node ignoredNode: node to ignore computing shortest path from
52 """
53 queue = [(0, start, [])]
54 seen = set()
55 while True:
56 (cost, v, path) = heapq.heappop(queue)
57 if v not in seen:
58 path = path + [v]
59 seen.add(v)
60 if v == end:
61 debug("Distance from {} to {} is {}".format(start, end, cost))
62 return cost, path
63 for (_next, c) in graph[v].items():
64 if _next != ignoredNode: # Ignore path going via ignoreNode
65 heapq.heappush(queue, (cost + c, _next, path))
66
67 if not queue: # return if no path exist from source - destination except via ignoreNode
68 debug("Distance from {} to {} is {}".format(start, end, cost))
69 return cost, None
70
71def calculateAngularDistance(angleVectorI, angleVectorJ):
72 """
73 For hyperbolic/geohyperbolic routing algorithm, this function computes angular distance between
74 two nodes. A node can have two or more than two angular coordinates.
75
76 :param AngleVectorI angleVectorI: list of angular coordinate of a give node I
77 :param AngleVectorJ angleVectorJ: list of angular coordinate of a give node J
78
79 ref: https://en.wikipedia.org/wiki/N-sphere#Spherical_coordinates
80
81 """
82 innerProduct = 0.0
83 if len(angleVectorI) != len(angleVectorJ):
84 error("Angle vector sizes do not match")
85 return UNKNOWN_DISTANCE
86
87 # Calculate x0 of the vectors
88 x0i = cos(angleVectorI[0])
89 x0j = cos(angleVectorJ[0])
90
91 # Calculate xn of the vectors
92 xni = sin(angleVectorI[len(angleVectorI) - 1])
93 xnj = sin(angleVectorJ[len(angleVectorJ) - 1])
94
95 # Do the aggregation of the (n-1) coordinates (if there is more than one angle)
96 # i.e contraction of all (n-1)-dimensional angular coordinates to one variable
97 for k in range(0, len(angleVectorI)-1):
98 xni *= sin(angleVectorI[k])
99 xnj *= sin(angleVectorJ[k])
100
101 innerProduct += (x0i * x0j) + (xni * xnj)
102
103 if len(angleVectorI) > 1:
104 for m in range(1, len(angleVectorI)):
105 # Calculate euclidean coordinates given the angles and assuming R_sphere = 1
106 xmi = cos(angleVectorI[m])
107 xmj = cos(angleVectorJ[m])
108 for l in range(0, m):
109 xmi *= sin(angleVectorI[l])
110 xmj *= sin(angleVectorJ[l])
111
112 innerProduct += xmi * xmj
113
114 # ArcCos of the inner product gives the angular distance
115 # between two points on a d-dimensional sphere
116 angularDist = acos(innerProduct)
117 debug("Angular distance from {} to {} is {}".format(angleVectorI, angleVectorJ, angularDist))
118 return angularDist
119
120def getHyperbolicDistance(sourceNode, destNode):
121 """
122 Return hyperbolic or geohyperbolic distance between two nodes. The distance is computed
123 on the basis of following algorithm/mathematics
124 ref: https://en.wikipedia.org/wiki/Hyperbolic_geometry
125 """
126 r1 = [key for key in sourceNode][0]
127 r2 = [key for key in destNode][0]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500128 zeta = 1.0
129 dtheta = calculateAngularDistance(sourceNode[r1], destNode[r2])
130 hyperbolicDistance = (1./zeta) * acosh(cosh(zeta * r1) * cosh(zeta * r2) -\
131 sinh(zeta * r1) * sinh(zeta * r2) * cos(dtheta))
132
133 debug("Distance from {} to {} is {}".format(sourceNode, destNode, hyperbolicDistance))
134 return hyperbolicDistance
135
136class _CalculateRoutes(object):
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000137 """
138 Creates a route calculation object, which is used to compute routes from a node to
139 every other nodes in a given topology topology using hyperbolic or geohyperbolic
140 routing algorithm
141
142 :param NetObject netObj: Mininet net object
143 :param RoutingType routingType: (optional) Routing algorithm, link-state or hr etc
144 """
145 def __init__(self, netObj, routingType):
146 self.adjacenctMatrix = defaultdict(dict)
147 self.nodeDict = defaultdict(dict)
148 self.routingType = routingType
Saurab Dulal082e4442020-03-02 15:49:03 -0600149 self.isHrConfigValid = True
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000150 for host in netObj.hosts:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500151 if 'radius' in host.params['params']:
152 radius = float(host.params['params']['radius'])
153 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600154 self.isHrConfigValid = False
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500155 radius = 0.0
Saurab Dulal082e4442020-03-02 15:49:03 -0600156 if 'angle' in host.params['params']:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500157 angles = [float(x) for x in host.params['params']['angle'].split(',')]
158 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600159 self.isHrConfigValid = False
160 angles = [0.0]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000161 self.nodeDict[host.name][radius] = angles
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500162 for link in netObj.topo.links(withInfo=True):
163 linkDelay = int(link[2]['delay'].replace("ms", ""))
164 self.adjacenctMatrix[link[0]][link[1]] = linkDelay
165 self.adjacenctMatrix[link[1]][link[0]] = linkDelay
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000166
167 def getNestedDictionary(self):
168 return defaultdict(self.getNestedDictionary)
169
170 def getRoutes(self, nFaces):
171 resultMatrix = self.getNestedDictionary()
172 routes = defaultdict(list)
173
174 if self.routingType == "link-state":
175 if nFaces == 1:
176 resultMatrix = self.computeDijkastra() # only best routes.
177 else:
178 resultMatrix = self.computeDijkastraAll() # all possible routes
179 elif self.routingType == "hr":
Saurab Dulal082e4442020-03-02 15:49:03 -0600180 if self.isHrConfigValid == True:
181 # Note: For hyperbolic, only way to find the best routes is by
182 # computing all possible routes and getting the best one.
183 resultMatrix = self.computeHyperbolic()
184 else:
185 warn('Hyperbolic coordinates in topology file are either missing or misconfigured.\n')
186 warn('Check that each node has one radius value and one or two angle value(s).\n')
187 return None
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000188
189 for node in resultMatrix:
190 for destinationNode in resultMatrix[node]:
191 # Sort node - destination via neighbor based on their cost
192 tempDict = resultMatrix[node][destinationNode]
193 shortedTempDict = sorted(tempDict.items(), key=operator.itemgetter(1))
194 # nFaces option gets n-best faces based on shortest distance, default is all
195 if nFaces == 0:
196 for item in shortedTempDict:
197 viaNeighbor = item[0]
198 cost = item[1]
199 routes[node].append([destinationNode, str(cost), viaNeighbor])
200 else:
201 for index, item in enumerate(shortedTempDict):
202 if index >= nFaces:
203 break
204 viaNeighbor = item[0]
205 cost = item[1]
206 routes[node].append([destinationNode, str(cost), viaNeighbor])
207
208 debug("-routes----", routes)
209 return routes
210
211 def getNodeNames(self):
212 return [k for k in self.nodeDict]
213
214 def computeHyperbolic(self):
215 paths = self.getNestedDictionary()
216 nodeNames = self.getNodeNames()
217 for node in self.nodeDict:
218 neighbors = [k for k in self.adjacenctMatrix[node]]
219 for viaNeighbor in neighbors:
Saurab Dulal082e4442020-03-02 15:49:03 -0600220 others = [x for x in nodeNames if x not in [viaNeighbor, node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000221 paths[node][viaNeighbor][viaNeighbor] = 0
222 # Compute distance from neighbors to no-neighbors
223 for destinationNode in others:
224 hyperbolicDistance = getHyperbolicDistance(self.nodeDict[viaNeighbor],
225 self.nodeDict[destinationNode])
226 hyperbolicCost = int(HYPERBOLIC_COST_ADJUSTMENT_FACTOR \
227 * round(hyperbolicDistance, 6))
228 paths[node][destinationNode][viaNeighbor] = hyperbolicCost
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000229 debug("Shortest Distance Matrix: {}".format(json.dumps(paths)))
230 return paths
231
232 def computeDijkastra(self):
233 """
234 Dijkstra computation: Compute all the shortest paths from nodes to the destinations.
235 And fills the distance matrix with the corresponding source to destination cost
236 """
237 distanceMatrix = self.getNestedDictionary()
238 nodeNames = self.getNodeNames()
239 for node in nodeNames:
Saurab Dulal082e4442020-03-02 15:49:03 -0600240 others = [x for x in nodeNames if x not in [node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000241 for destinationNode in others:
242 cost, path = dijkstra(self.adjacenctMatrix, node, destinationNode)
243 viaNeighbor = path[1]
244 distanceMatrix[node][destinationNode][viaNeighbor] = cost
245
246 debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrix)))
247 return distanceMatrix
248
249 def computeDijkastraAll(self):
250 """
251 Multi-path Dijkastra computation: Compute all the shortest paths from nodes to the
252 destinations via all of its neighbors individually. And fills the distanceMatrixViaNeighbor
253 with a corresponding source to its destination cost
254
255 Important: distanceMatrixViaNeighbor represents the shortest distance from a source to a
256 destination via specific neighbors
257 """
258 distanceMatrixViaNeighbor = self.getNestedDictionary()
259 nodeNames = self.getNodeNames()
260 for node in nodeNames:
261 neighbors = [k for k in self.adjacenctMatrix[node]]
262 for viaNeighbor in neighbors:
263 directCost = self.adjacenctMatrix[node][viaNeighbor]
264 distanceMatrixViaNeighbor[node][viaNeighbor][viaNeighbor] = directCost
Saurab Dulal082e4442020-03-02 15:49:03 -0600265 others = [x for x in nodeNames if x not in [viaNeighbor, node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000266 for destinationNode in others:
267 nodeNeighborCost = self.adjacenctMatrix[node][viaNeighbor]
268 # path variable is not used for now
269 cost, path = dijkstra(self.adjacenctMatrix, viaNeighbor, destinationNode, node)
270 if cost != 0 and path != None:
271 totalCost = cost + nodeNeighborCost
272 distanceMatrixViaNeighbor[node][destinationNode][viaNeighbor] = totalCost
273
274 debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrixViaNeighbor)))
275 return distanceMatrixViaNeighbor
276
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500277class NdnRoutingHelper(object):
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000278 """
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500279 This module is a helper class which helps to create face and register routes
280 to NFD from a given node to all of its neighbors.
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000281
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500282 :param NetObject netObject: Mininet net object
283 :param FaceType faceType: UDP, Ethernet etc.
284 :param Routing routingType: (optional) Routing algorithm, link-state or hr etc
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000285
286 """
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500287 def __init__(self, netObject, faceType=nfdc.PROTOCOL_UDP, routingType="link-state"):
288 self.net = netObject
289 self.faceType = faceType
290 self.routingType = routingType
291 self.routes = []
292 self.namePrefixes = {host_name.name: [] for host_name in self.net.hosts}
293 self.routeObject = _CalculateRoutes(self.net, self.routingType)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000294
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500295 def globalRoutingHelperHandler(self):
Saurab Dulal082e4442020-03-02 15:49:03 -0600296 info('Creating faces and adding routes to FIB\n')
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500297 for host in self.net.hosts:
298 neighborIPs = self.getNeighbor(host)
299 self.createFaces(host, neighborIPs)
300 self.routeAdd(host, neighborIPs)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000301
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500302 info('Processed all the routes to NFD\n')
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000303
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500304 def addOrigin(self, nodes, prefix):
305 """
306 Add prefix/s as origin on node/s
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000307
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500308 :param Prefix prefix: Prefix that is originated by node/s (as producer) for this prefix
309 :param Nodes nodes: List of nodes from net object
310 """
311 for node in nodes:
phmoll52223f72020-02-21 08:43:50 +0100312 if not node.name in self.namePrefixes:
313 self.namePrefixes[node.name] = []
314 self.namePrefixes[node.name] += prefix
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000315
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500316 def calculateNPossibleRoutes(self, nFaces=0):
317 """
318 By default, calculates all possible routes i.e. routes via all the faces of a node.
319 pass nFaces if want to compute routes via n number of faces. e.g. 2. For larger topology
320 the computation might take huge amount of time.
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000321
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500322 :param int nFaces: (optional) number of faces to consider while computing routes. Default
323 i.e. nFaces = 0 will compute all possible routes
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000324
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500325 """
326 self.routes = self.routeObject.getRoutes(nFaces)
Saurab Dulal082e4442020-03-02 15:49:03 -0600327 if self.routes is not None:
328 info('Route computation completed\n')
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500329 self.globalRoutingHelperHandler()
330 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600331 warn('Route computation failed\n')
332 self.net.stop()
333 sys.exit(1)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000334
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500335 def calculateRoutes(self):
336 # Calculate shortest path for every node
337 self.calculateNPossibleRoutes(nFaces=1)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000338
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500339 def createFaces(self, node, neighborIPs):
340 for ip in neighborIPs.values():
341 nfdc.createFace(node, ip, self.faceType)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000342
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500343 def routeAdd(self, node, neighborIPs):
344 """
345 Add route from a node to its neighbors for each prefix/s advertised by destination node
346
347 :param Node node: source node (Mininet net.host)
348 :param IP neighborIPs: IP addresses of neighbors
349 """
350 neighbors = self.routes[node.name]
351 for route in neighbors:
352 destination = route[0]
353 cost = int(route[1])
354 nextHop = route[2]
355 defaultPrefix = "/ndn/{}-site/{}".format(destination, destination)
356 prefixes = [defaultPrefix] + self.namePrefixes[destination]
357 for prefix in prefixes:
358 # Register routes to all the available destination name prefix/s
359 nfdc.registerRoute(node, prefix, neighborIPs[nextHop], \
360 nfdc.PROTOCOL_UDP, cost=cost)
361 @staticmethod
362 def getNeighbor(node):
363 # Nodes to IP mapping
364 neighborIPs = defaultdict()
365 for intf in node.intfList():
366 link = intf.link
367 if link:
368 node1, node2 = link.intf1.node, link.intf2.node
369
370 if node1 == node:
371 other = node2
372 ip = other.IP(str(link.intf2))
373 else:
374 other = node1
375 ip = other.IP(str(link.intf1))
376
377 # Used later to create faces
378 neighborIPs[other.name] = ip
379 return neighborIPs