blob: d6149260b7c01722c6800b720e16d356958bcc2e [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
Varun Patil63a330d2022-05-18 14:23:13 -070036from joblib import Parallel, delayed
Saurab Dulal8ae870a2018-07-31 05:17:49 +000037
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050038from mininet.log import info, debug, error, warn
39from minindn.helpers.nfdc import Nfdc as nfdc
Saurab Dulal8ae870a2018-07-31 05:17:49 +000040
41UNKNOWN_DISTANCE = -1
42HYPERBOLIC_COST_ADJUSTMENT_FACTOR = 1000
43
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050044def dijkstra(graph, start, end, ignoredNode=None):
45 """
46 Compute shortest path and cost from a given source to a destination
47 using Dijkstra algorithm
48
49 :param Graph graph: given network topology/graph
50 :param Start start: source node in a given network graph/topology
51 :end End end: destination node in a given network graph/topology
52 :param Node ignoredNode: node to ignore computing shortest path from
53 """
54 queue = [(0, start, [])]
55 seen = set()
56 while True:
57 (cost, v, path) = heapq.heappop(queue)
58 if v not in seen:
59 path = path + [v]
60 seen.add(v)
61 if v == end:
62 debug("Distance from {} to {} is {}".format(start, end, cost))
63 return cost, path
64 for (_next, c) in graph[v].items():
65 if _next != ignoredNode: # Ignore path going via ignoreNode
66 heapq.heappush(queue, (cost + c, _next, path))
67
68 if not queue: # return if no path exist from source - destination except via ignoreNode
69 debug("Distance from {} to {} is {}".format(start, end, cost))
70 return cost, None
71
72def calculateAngularDistance(angleVectorI, angleVectorJ):
73 """
74 For hyperbolic/geohyperbolic routing algorithm, this function computes angular distance between
75 two nodes. A node can have two or more than two angular coordinates.
76
77 :param AngleVectorI angleVectorI: list of angular coordinate of a give node I
78 :param AngleVectorJ angleVectorJ: list of angular coordinate of a give node J
79
80 ref: https://en.wikipedia.org/wiki/N-sphere#Spherical_coordinates
81
82 """
83 innerProduct = 0.0
84 if len(angleVectorI) != len(angleVectorJ):
85 error("Angle vector sizes do not match")
86 return UNKNOWN_DISTANCE
87
88 # Calculate x0 of the vectors
89 x0i = cos(angleVectorI[0])
90 x0j = cos(angleVectorJ[0])
91
92 # Calculate xn of the vectors
93 xni = sin(angleVectorI[len(angleVectorI) - 1])
94 xnj = sin(angleVectorJ[len(angleVectorJ) - 1])
95
96 # Do the aggregation of the (n-1) coordinates (if there is more than one angle)
97 # i.e contraction of all (n-1)-dimensional angular coordinates to one variable
98 for k in range(0, len(angleVectorI)-1):
99 xni *= sin(angleVectorI[k])
100 xnj *= sin(angleVectorJ[k])
101
102 innerProduct += (x0i * x0j) + (xni * xnj)
103
104 if len(angleVectorI) > 1:
105 for m in range(1, len(angleVectorI)):
106 # Calculate euclidean coordinates given the angles and assuming R_sphere = 1
107 xmi = cos(angleVectorI[m])
108 xmj = cos(angleVectorJ[m])
109 for l in range(0, m):
110 xmi *= sin(angleVectorI[l])
111 xmj *= sin(angleVectorJ[l])
112
113 innerProduct += xmi * xmj
114
115 # ArcCos of the inner product gives the angular distance
116 # between two points on a d-dimensional sphere
117 angularDist = acos(innerProduct)
118 debug("Angular distance from {} to {} is {}".format(angleVectorI, angleVectorJ, angularDist))
119 return angularDist
120
121def getHyperbolicDistance(sourceNode, destNode):
122 """
123 Return hyperbolic or geohyperbolic distance between two nodes. The distance is computed
124 on the basis of following algorithm/mathematics
125 ref: https://en.wikipedia.org/wiki/Hyperbolic_geometry
126 """
127 r1 = [key for key in sourceNode][0]
128 r2 = [key for key in destNode][0]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500129 zeta = 1.0
130 dtheta = calculateAngularDistance(sourceNode[r1], destNode[r2])
131 hyperbolicDistance = (1./zeta) * acosh(cosh(zeta * r1) * cosh(zeta * r2) -\
132 sinh(zeta * r1) * sinh(zeta * r2) * cos(dtheta))
133
134 debug("Distance from {} to {} is {}".format(sourceNode, destNode, hyperbolicDistance))
135 return hyperbolicDistance
136
137class _CalculateRoutes(object):
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000138 """
139 Creates a route calculation object, which is used to compute routes from a node to
140 every other nodes in a given topology topology using hyperbolic or geohyperbolic
141 routing algorithm
142
143 :param NetObject netObj: Mininet net object
144 :param RoutingType routingType: (optional) Routing algorithm, link-state or hr etc
145 """
146 def __init__(self, netObj, routingType):
147 self.adjacenctMatrix = defaultdict(dict)
148 self.nodeDict = defaultdict(dict)
149 self.routingType = routingType
Saurab Dulal082e4442020-03-02 15:49:03 -0600150 self.isHrConfigValid = True
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000151 for host in netObj.hosts:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500152 if 'radius' in host.params['params']:
153 radius = float(host.params['params']['radius'])
154 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600155 self.isHrConfigValid = False
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500156 radius = 0.0
Saurab Dulal082e4442020-03-02 15:49:03 -0600157 if 'angle' in host.params['params']:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500158 angles = [float(x) for x in host.params['params']['angle'].split(',')]
159 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600160 self.isHrConfigValid = False
161 angles = [0.0]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000162 self.nodeDict[host.name][radius] = angles
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500163 for link in netObj.topo.links(withInfo=True):
164 linkDelay = int(link[2]['delay'].replace("ms", ""))
165 self.adjacenctMatrix[link[0]][link[1]] = linkDelay
166 self.adjacenctMatrix[link[1]][link[0]] = linkDelay
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000167
168 def getNestedDictionary(self):
169 return defaultdict(self.getNestedDictionary)
170
171 def getRoutes(self, nFaces):
172 resultMatrix = self.getNestedDictionary()
173 routes = defaultdict(list)
174
175 if self.routingType == "link-state":
176 if nFaces == 1:
177 resultMatrix = self.computeDijkastra() # only best routes.
178 else:
179 resultMatrix = self.computeDijkastraAll() # all possible routes
180 elif self.routingType == "hr":
Saurab Dulal082e4442020-03-02 15:49:03 -0600181 if self.isHrConfigValid == True:
182 # Note: For hyperbolic, only way to find the best routes is by
183 # computing all possible routes and getting the best one.
184 resultMatrix = self.computeHyperbolic()
185 else:
186 warn('Hyperbolic coordinates in topology file are either missing or misconfigured.\n')
187 warn('Check that each node has one radius value and one or two angle value(s).\n')
188 return None
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000189
190 for node in resultMatrix:
191 for destinationNode in resultMatrix[node]:
192 # Sort node - destination via neighbor based on their cost
193 tempDict = resultMatrix[node][destinationNode]
194 shortedTempDict = sorted(tempDict.items(), key=operator.itemgetter(1))
195 # nFaces option gets n-best faces based on shortest distance, default is all
196 if nFaces == 0:
197 for item in shortedTempDict:
198 viaNeighbor = item[0]
199 cost = item[1]
200 routes[node].append([destinationNode, str(cost), viaNeighbor])
201 else:
202 for index, item in enumerate(shortedTempDict):
203 if index >= nFaces:
204 break
205 viaNeighbor = item[0]
206 cost = item[1]
207 routes[node].append([destinationNode, str(cost), viaNeighbor])
208
209 debug("-routes----", routes)
210 return routes
211
212 def getNodeNames(self):
213 return [k for k in self.nodeDict]
214
215 def computeHyperbolic(self):
216 paths = self.getNestedDictionary()
217 nodeNames = self.getNodeNames()
218 for node in self.nodeDict:
219 neighbors = [k for k in self.adjacenctMatrix[node]]
220 for viaNeighbor in neighbors:
Saurab Dulal082e4442020-03-02 15:49:03 -0600221 others = [x for x in nodeNames if x not in [viaNeighbor, node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000222 paths[node][viaNeighbor][viaNeighbor] = 0
223 # Compute distance from neighbors to no-neighbors
224 for destinationNode in others:
225 hyperbolicDistance = getHyperbolicDistance(self.nodeDict[viaNeighbor],
226 self.nodeDict[destinationNode])
227 hyperbolicCost = int(HYPERBOLIC_COST_ADJUSTMENT_FACTOR \
228 * round(hyperbolicDistance, 6))
229 paths[node][destinationNode][viaNeighbor] = hyperbolicCost
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000230 debug("Shortest Distance Matrix: {}".format(json.dumps(paths)))
231 return paths
232
233 def computeDijkastra(self):
234 """
235 Dijkstra computation: Compute all the shortest paths from nodes to the destinations.
236 And fills the distance matrix with the corresponding source to destination cost
237 """
238 distanceMatrix = self.getNestedDictionary()
239 nodeNames = self.getNodeNames()
240 for node in nodeNames:
Saurab Dulal082e4442020-03-02 15:49:03 -0600241 others = [x for x in nodeNames if x not in [node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000242 for destinationNode in others:
243 cost, path = dijkstra(self.adjacenctMatrix, node, destinationNode)
244 viaNeighbor = path[1]
245 distanceMatrix[node][destinationNode][viaNeighbor] = cost
246
247 debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrix)))
248 return distanceMatrix
249
250 def computeDijkastraAll(self):
251 """
252 Multi-path Dijkastra computation: Compute all the shortest paths from nodes to the
253 destinations via all of its neighbors individually. And fills the distanceMatrixViaNeighbor
254 with a corresponding source to its destination cost
255
256 Important: distanceMatrixViaNeighbor represents the shortest distance from a source to a
257 destination via specific neighbors
258 """
259 distanceMatrixViaNeighbor = self.getNestedDictionary()
260 nodeNames = self.getNodeNames()
261 for node in nodeNames:
262 neighbors = [k for k in self.adjacenctMatrix[node]]
263 for viaNeighbor in neighbors:
264 directCost = self.adjacenctMatrix[node][viaNeighbor]
265 distanceMatrixViaNeighbor[node][viaNeighbor][viaNeighbor] = directCost
Saurab Dulal082e4442020-03-02 15:49:03 -0600266 others = [x for x in nodeNames if x not in [viaNeighbor, node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000267 for destinationNode in others:
268 nodeNeighborCost = self.adjacenctMatrix[node][viaNeighbor]
269 # path variable is not used for now
270 cost, path = dijkstra(self.adjacenctMatrix, viaNeighbor, destinationNode, node)
271 if cost != 0 and path != None:
272 totalCost = cost + nodeNeighborCost
273 distanceMatrixViaNeighbor[node][destinationNode][viaNeighbor] = totalCost
274
275 debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrixViaNeighbor)))
276 return distanceMatrixViaNeighbor
277
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500278class NdnRoutingHelper(object):
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000279 """
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500280 This module is a helper class which helps to create face and register routes
281 to NFD from a given node to all of its neighbors.
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000282
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500283 :param NetObject netObject: Mininet net object
284 :param FaceType faceType: UDP, Ethernet etc.
285 :param Routing routingType: (optional) Routing algorithm, link-state or hr etc
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000286
287 """
awlane92bc4292023-05-22 18:35:58 -0500288 def __init__(self, netObject, faceType=nfdc.PROTOCOL_UDP, routingType="link-state", permanentFaces=False):
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500289 self.net = netObject
290 self.faceType = faceType
291 self.routingType = routingType
awlane92bc4292023-05-22 18:35:58 -0500292 self.permanentFaces = permanentFaces
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500293 self.routes = []
294 self.namePrefixes = {host_name.name: [] for host_name in self.net.hosts}
295 self.routeObject = _CalculateRoutes(self.net, self.routingType)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000296
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500297 def globalRoutingHelperHandler(self):
Saurab Dulal082e4442020-03-02 15:49:03 -0600298 info('Creating faces and adding routes to FIB\n')
Varun Patil63a330d2022-05-18 14:23:13 -0700299
300 res = Parallel(n_jobs=-1, require='sharedmem',
tylerliu86647792023-03-03 15:18:48 -0800301 prefer="threads", verbose=1)(delayed(self.addNodeRoutes)(host) for host in self.net.hosts)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000302
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500303 info('Processed all the routes to NFD\n')
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000304
Varun Patil63a330d2022-05-18 14:23:13 -0700305 def addNodeRoutes(self, node):
306 """
307 Create faces to neighbors and add all routes for one node
308
309 :param Node node: Node from net object
310 """
311 neighborIPs = self.getNeighbor(node)
tylerliu86647792023-03-03 15:18:48 -0800312 neighborFaces = self.createFaces(node, neighborIPs)
313 self.routeAdd(node, neighborFaces)
Varun Patil63a330d2022-05-18 14:23:13 -0700314
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500315 def addOrigin(self, nodes, prefix):
316 """
317 Add prefix/s as origin on node/s
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000318
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500319 :param Prefix prefix: Prefix that is originated by node/s (as producer) for this prefix
320 :param Nodes nodes: List of nodes from net object
321 """
322 for node in nodes:
phmoll52223f72020-02-21 08:43:50 +0100323 if not node.name in self.namePrefixes:
324 self.namePrefixes[node.name] = []
325 self.namePrefixes[node.name] += prefix
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000326
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500327 def calculateNPossibleRoutes(self, nFaces=0):
328 """
329 By default, calculates all possible routes i.e. routes via all the faces of a node.
330 pass nFaces if want to compute routes via n number of faces. e.g. 2. For larger topology
331 the computation might take huge amount of time.
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000332
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500333 :param int nFaces: (optional) number of faces to consider while computing routes. Default
334 i.e. nFaces = 0 will compute all possible routes
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000335
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500336 """
337 self.routes = self.routeObject.getRoutes(nFaces)
Saurab Dulal082e4442020-03-02 15:49:03 -0600338 if self.routes is not None:
339 info('Route computation completed\n')
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500340 self.globalRoutingHelperHandler()
341 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600342 warn('Route computation failed\n')
343 self.net.stop()
344 sys.exit(1)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000345
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500346 def calculateRoutes(self):
347 # Calculate shortest path for every node
348 self.calculateNPossibleRoutes(nFaces=1)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000349
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500350 def createFaces(self, node, neighborIPs):
tylerliu86647792023-03-03 15:18:48 -0800351 neighborFaces = {}
352 for k, ip in neighborIPs.items():
awlane92bc4292023-05-22 18:35:58 -0500353 faceID = nfdc.createFace(node, ip, self.faceType, self.permanentFaces)
tylerliu86647792023-03-03 15:18:48 -0800354 if not isinstance(faceID, str): raise ValueError(faceID)
355 neighborFaces[k] = faceID
356 return neighborFaces
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000357
tylerliu86647792023-03-03 15:18:48 -0800358
359 def routeAdd(self, node, neighborFaces):
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500360 """
361 Add route from a node to its neighbors for each prefix/s advertised by destination node
362
363 :param Node node: source node (Mininet net.host)
364 :param IP neighborIPs: IP addresses of neighbors
365 """
366 neighbors = self.routes[node.name]
367 for route in neighbors:
368 destination = route[0]
369 cost = int(route[1])
370 nextHop = route[2]
371 defaultPrefix = "/ndn/{}-site/{}".format(destination, destination)
372 prefixes = [defaultPrefix] + self.namePrefixes[destination]
373 for prefix in prefixes:
374 # Register routes to all the available destination name prefix/s
tylerliu86647792023-03-03 15:18:48 -0800375 nfdc.registerRoute(node, prefix, neighborFaces[nextHop], cost=cost)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500376 @staticmethod
377 def getNeighbor(node):
378 # Nodes to IP mapping
379 neighborIPs = defaultdict()
380 for intf in node.intfList():
381 link = intf.link
382 if link:
383 node1, node2 = link.intf1.node, link.intf2.node
384
385 if node1 == node:
386 other = node2
387 ip = other.IP(str(link.intf2))
388 else:
389 other = node1
390 ip = other.IP(str(link.intf1))
391
392 # Used later to create faces
393 neighborIPs[other.name] = ip
394 return neighborIPs