blob: 38d841a4a2ec7442d437fd483a165e43a75c635a [file] [log] [blame]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -05001 # -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
Saurab Dulal8ae870a2018-07-31 05:17:49 +00002#
3# Copyright (C) 2015-2019, The University of Memphis
4#
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
30import heapq
Saurab Dulal8ae870a2018-07-31 05:17:49 +000031from math import sin, cos, sinh, cosh, acos, acosh
32import json
33import operator
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050034from collections import defaultdict
Saurab Dulal8ae870a2018-07-31 05:17:49 +000035
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050036from mininet.log import info, debug, error, warn
37from minindn.helpers.nfdc import Nfdc as nfdc
Saurab Dulal8ae870a2018-07-31 05:17:49 +000038
39UNKNOWN_DISTANCE = -1
40HYPERBOLIC_COST_ADJUSTMENT_FACTOR = 1000
41
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050042def dijkstra(graph, start, end, ignoredNode=None):
43 """
44 Compute shortest path and cost from a given source to a destination
45 using Dijkstra algorithm
46
47 :param Graph graph: given network topology/graph
48 :param Start start: source node in a given network graph/topology
49 :end End end: destination node in a given network graph/topology
50 :param Node ignoredNode: node to ignore computing shortest path from
51 """
52 queue = [(0, start, [])]
53 seen = set()
54 while True:
55 (cost, v, path) = heapq.heappop(queue)
56 if v not in seen:
57 path = path + [v]
58 seen.add(v)
59 if v == end:
60 debug("Distance from {} to {} is {}".format(start, end, cost))
61 return cost, path
62 for (_next, c) in graph[v].items():
63 if _next != ignoredNode: # Ignore path going via ignoreNode
64 heapq.heappush(queue, (cost + c, _next, path))
65
66 if not queue: # return if no path exist from source - destination except via ignoreNode
67 debug("Distance from {} to {} is {}".format(start, end, cost))
68 return cost, None
69
70def calculateAngularDistance(angleVectorI, angleVectorJ):
71 """
72 For hyperbolic/geohyperbolic routing algorithm, this function computes angular distance between
73 two nodes. A node can have two or more than two angular coordinates.
74
75 :param AngleVectorI angleVectorI: list of angular coordinate of a give node I
76 :param AngleVectorJ angleVectorJ: list of angular coordinate of a give node J
77
78 ref: https://en.wikipedia.org/wiki/N-sphere#Spherical_coordinates
79
80 """
81 innerProduct = 0.0
82 if len(angleVectorI) != len(angleVectorJ):
83 error("Angle vector sizes do not match")
84 return UNKNOWN_DISTANCE
85
86 # Calculate x0 of the vectors
87 x0i = cos(angleVectorI[0])
88 x0j = cos(angleVectorJ[0])
89
90 # Calculate xn of the vectors
91 xni = sin(angleVectorI[len(angleVectorI) - 1])
92 xnj = sin(angleVectorJ[len(angleVectorJ) - 1])
93
94 # Do the aggregation of the (n-1) coordinates (if there is more than one angle)
95 # i.e contraction of all (n-1)-dimensional angular coordinates to one variable
96 for k in range(0, len(angleVectorI)-1):
97 xni *= sin(angleVectorI[k])
98 xnj *= sin(angleVectorJ[k])
99
100 innerProduct += (x0i * x0j) + (xni * xnj)
101
102 if len(angleVectorI) > 1:
103 for m in range(1, len(angleVectorI)):
104 # Calculate euclidean coordinates given the angles and assuming R_sphere = 1
105 xmi = cos(angleVectorI[m])
106 xmj = cos(angleVectorJ[m])
107 for l in range(0, m):
108 xmi *= sin(angleVectorI[l])
109 xmj *= sin(angleVectorJ[l])
110
111 innerProduct += xmi * xmj
112
113 # ArcCos of the inner product gives the angular distance
114 # between two points on a d-dimensional sphere
115 angularDist = acos(innerProduct)
116 debug("Angular distance from {} to {} is {}".format(angleVectorI, angleVectorJ, angularDist))
117 return angularDist
118
119def getHyperbolicDistance(sourceNode, destNode):
120 """
121 Return hyperbolic or geohyperbolic distance between two nodes. The distance is computed
122 on the basis of following algorithm/mathematics
123 ref: https://en.wikipedia.org/wiki/Hyperbolic_geometry
124 """
125 r1 = [key for key in sourceNode][0]
126 r2 = [key for key in destNode][0]
127
128 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
149 for host in netObj.hosts:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500150 if 'radius' in host.params['params']:
151 radius = float(host.params['params']['radius'])
152 else:
153 radius = 0.0
154 if 'angles' in host.params['params']:
155 angles = [float(x) for x in host.params['params']['angle'].split(',')]
156 else:
157 angles = 0.0
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000158 self.nodeDict[host.name][radius] = angles
159
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500160 for link in netObj.topo.links(withInfo=True):
161 linkDelay = int(link[2]['delay'].replace("ms", ""))
162 self.adjacenctMatrix[link[0]][link[1]] = linkDelay
163 self.adjacenctMatrix[link[1]][link[0]] = linkDelay
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000164
165 def getNestedDictionary(self):
166 return defaultdict(self.getNestedDictionary)
167
168 def getRoutes(self, nFaces):
169 resultMatrix = self.getNestedDictionary()
170 routes = defaultdict(list)
171
172 if self.routingType == "link-state":
173 if nFaces == 1:
174 resultMatrix = self.computeDijkastra() # only best routes.
175 else:
176 resultMatrix = self.computeDijkastraAll() # all possible routes
177 elif self.routingType == "hr":
178 # Note: For hyperbolic, only way to find the best routes is by computing all possible
179 # routes and getting the best one.
180 resultMatrix = self.computeHyperbolic()
181 else:
182 info("Routing type not supported\n")
183 return []
184
185 for node in resultMatrix:
186 for destinationNode in resultMatrix[node]:
187 # Sort node - destination via neighbor based on their cost
188 tempDict = resultMatrix[node][destinationNode]
189 shortedTempDict = sorted(tempDict.items(), key=operator.itemgetter(1))
190 # nFaces option gets n-best faces based on shortest distance, default is all
191 if nFaces == 0:
192 for item in shortedTempDict:
193 viaNeighbor = item[0]
194 cost = item[1]
195 routes[node].append([destinationNode, str(cost), viaNeighbor])
196 else:
197 for index, item in enumerate(shortedTempDict):
198 if index >= nFaces:
199 break
200 viaNeighbor = item[0]
201 cost = item[1]
202 routes[node].append([destinationNode, str(cost), viaNeighbor])
203
204 debug("-routes----", routes)
205 return routes
206
207 def getNodeNames(self):
208 return [k for k in self.nodeDict]
209
210 def computeHyperbolic(self):
211 paths = self.getNestedDictionary()
212 nodeNames = self.getNodeNames()
213 for node in self.nodeDict:
214 neighbors = [k for k in self.adjacenctMatrix[node]]
215 for viaNeighbor in neighbors:
216 others = list(set(nodeNames) - set(viaNeighbor) - set(node))
217 paths[node][viaNeighbor][viaNeighbor] = 0
218 # Compute distance from neighbors to no-neighbors
219 for destinationNode in others:
220 hyperbolicDistance = getHyperbolicDistance(self.nodeDict[viaNeighbor],
221 self.nodeDict[destinationNode])
222 hyperbolicCost = int(HYPERBOLIC_COST_ADJUSTMENT_FACTOR \
223 * round(hyperbolicDistance, 6))
224 paths[node][destinationNode][viaNeighbor] = hyperbolicCost
225
226 debug("Shortest Distance Matrix: {}".format(json.dumps(paths)))
227 return paths
228
229 def computeDijkastra(self):
230 """
231 Dijkstra computation: Compute all the shortest paths from nodes to the destinations.
232 And fills the distance matrix with the corresponding source to destination cost
233 """
234 distanceMatrix = self.getNestedDictionary()
235 nodeNames = self.getNodeNames()
236 for node in nodeNames:
237 others = list(set(nodeNames) - set(node))
238 for destinationNode in others:
239 cost, path = dijkstra(self.adjacenctMatrix, node, destinationNode)
240 viaNeighbor = path[1]
241 distanceMatrix[node][destinationNode][viaNeighbor] = cost
242
243 debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrix)))
244 return distanceMatrix
245
246 def computeDijkastraAll(self):
247 """
248 Multi-path Dijkastra computation: Compute all the shortest paths from nodes to the
249 destinations via all of its neighbors individually. And fills the distanceMatrixViaNeighbor
250 with a corresponding source to its destination cost
251
252 Important: distanceMatrixViaNeighbor represents the shortest distance from a source to a
253 destination via specific neighbors
254 """
255 distanceMatrixViaNeighbor = self.getNestedDictionary()
256 nodeNames = self.getNodeNames()
257 for node in nodeNames:
258 neighbors = [k for k in self.adjacenctMatrix[node]]
259 for viaNeighbor in neighbors:
260 directCost = self.adjacenctMatrix[node][viaNeighbor]
261 distanceMatrixViaNeighbor[node][viaNeighbor][viaNeighbor] = directCost
262 others = list(set(nodeNames) - set(viaNeighbor) - set(node))
263 for destinationNode in others:
264 nodeNeighborCost = self.adjacenctMatrix[node][viaNeighbor]
265 # path variable is not used for now
266 cost, path = dijkstra(self.adjacenctMatrix, viaNeighbor, destinationNode, node)
267 if cost != 0 and path != None:
268 totalCost = cost + nodeNeighborCost
269 distanceMatrixViaNeighbor[node][destinationNode][viaNeighbor] = totalCost
270
271 debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrixViaNeighbor)))
272 return distanceMatrixViaNeighbor
273
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500274class NdnRoutingHelper(object):
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000275 """
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500276 This module is a helper class which helps to create face and register routes
277 to NFD from a given node to all of its neighbors.
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000278
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500279 :param NetObject netObject: Mininet net object
280 :param FaceType faceType: UDP, Ethernet etc.
281 :param Routing routingType: (optional) Routing algorithm, link-state or hr etc
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000282
283 """
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500284 def __init__(self, netObject, faceType=nfdc.PROTOCOL_UDP, routingType="link-state"):
285 self.net = netObject
286 self.faceType = faceType
287 self.routingType = routingType
288 self.routes = []
289 self.namePrefixes = {host_name.name: [] for host_name in self.net.hosts}
290 self.routeObject = _CalculateRoutes(self.net, self.routingType)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000291
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500292 def globalRoutingHelperHandler(self):
293 for host in self.net.hosts:
294 neighborIPs = self.getNeighbor(host)
295 self.createFaces(host, neighborIPs)
296 self.routeAdd(host, neighborIPs)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000297
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500298 info('Processed all the routes to NFD\n')
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000299
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500300 def addOrigin(self, nodes, prefix):
301 """
302 Add prefix/s as origin on node/s
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000303
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500304 :param Prefix prefix: Prefix that is originated by node/s (as producer) for this prefix
305 :param Nodes nodes: List of nodes from net object
306 """
307 for node in nodes:
308 self.namePrefixes[node.name] = prefix
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000309
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500310 def calculateNPossibleRoutes(self, nFaces=0):
311 """
312 By default, calculates all possible routes i.e. routes via all the faces of a node.
313 pass nFaces if want to compute routes via n number of faces. e.g. 2. For larger topology
314 the computation might take huge amount of time.
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000315
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500316 :param int nFaces: (optional) number of faces to consider while computing routes. Default
317 i.e. nFaces = 0 will compute all possible routes
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000318
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500319 """
320 self.routes = self.routeObject.getRoutes(nFaces)
321 if self.routes:
322 self.globalRoutingHelperHandler()
323 else:
324 warn("Route computation failed\n")
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000325
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500326 def calculateRoutes(self):
327 # Calculate shortest path for every node
328 self.calculateNPossibleRoutes(nFaces=1)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000329
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500330 def createFaces(self, node, neighborIPs):
331 for ip in neighborIPs.values():
332 nfdc.createFace(node, ip, self.faceType)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000333
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500334 def routeAdd(self, node, neighborIPs):
335 """
336 Add route from a node to its neighbors for each prefix/s advertised by destination node
337
338 :param Node node: source node (Mininet net.host)
339 :param IP neighborIPs: IP addresses of neighbors
340 """
341 neighbors = self.routes[node.name]
342 for route in neighbors:
343 destination = route[0]
344 cost = int(route[1])
345 nextHop = route[2]
346 defaultPrefix = "/ndn/{}-site/{}".format(destination, destination)
347 prefixes = [defaultPrefix] + self.namePrefixes[destination]
348 for prefix in prefixes:
349 # Register routes to all the available destination name prefix/s
350 nfdc.registerRoute(node, prefix, neighborIPs[nextHop], \
351 nfdc.PROTOCOL_UDP, cost=cost)
352 @staticmethod
353 def getNeighbor(node):
354 # Nodes to IP mapping
355 neighborIPs = defaultdict()
356 for intf in node.intfList():
357 link = intf.link
358 if link:
359 node1, node2 = link.intf1.node, link.intf2.node
360
361 if node1 == node:
362 other = node2
363 ip = other.IP(str(link.intf2))
364 else:
365 other = node1
366 ip = other.IP(str(link.intf1))
367
368 # Used later to create faces
369 neighborIPs[other.name] = ip
370 return neighborIPs