blob: c2d1013d7241ec946d72545d2ae7f5db56f06c42 [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
Philipp Mollf53ab472021-06-03 11:10:11 +020036from tqdm import tqdm
Varun Patil63a330d2022-05-18 14:23:13 -070037from joblib import Parallel, delayed
Saurab Dulal8ae870a2018-07-31 05:17:49 +000038
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050039from mininet.log import info, debug, error, warn
40from minindn.helpers.nfdc import Nfdc as nfdc
Saurab Dulal8ae870a2018-07-31 05:17:49 +000041
42UNKNOWN_DISTANCE = -1
43HYPERBOLIC_COST_ADJUSTMENT_FACTOR = 1000
44
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050045def dijkstra(graph, start, end, ignoredNode=None):
46 """
47 Compute shortest path and cost from a given source to a destination
48 using Dijkstra algorithm
49
50 :param Graph graph: given network topology/graph
51 :param Start start: source node in a given network graph/topology
52 :end End end: destination node in a given network graph/topology
53 :param Node ignoredNode: node to ignore computing shortest path from
54 """
55 queue = [(0, start, [])]
56 seen = set()
57 while True:
58 (cost, v, path) = heapq.heappop(queue)
59 if v not in seen:
60 path = path + [v]
61 seen.add(v)
62 if v == end:
63 debug("Distance from {} to {} is {}".format(start, end, cost))
64 return cost, path
65 for (_next, c) in graph[v].items():
66 if _next != ignoredNode: # Ignore path going via ignoreNode
67 heapq.heappush(queue, (cost + c, _next, path))
68
69 if not queue: # return if no path exist from source - destination except via ignoreNode
70 debug("Distance from {} to {} is {}".format(start, end, cost))
71 return cost, None
72
73def calculateAngularDistance(angleVectorI, angleVectorJ):
74 """
75 For hyperbolic/geohyperbolic routing algorithm, this function computes angular distance between
76 two nodes. A node can have two or more than two angular coordinates.
77
78 :param AngleVectorI angleVectorI: list of angular coordinate of a give node I
79 :param AngleVectorJ angleVectorJ: list of angular coordinate of a give node J
80
81 ref: https://en.wikipedia.org/wiki/N-sphere#Spherical_coordinates
82
83 """
84 innerProduct = 0.0
85 if len(angleVectorI) != len(angleVectorJ):
86 error("Angle vector sizes do not match")
87 return UNKNOWN_DISTANCE
88
89 # Calculate x0 of the vectors
90 x0i = cos(angleVectorI[0])
91 x0j = cos(angleVectorJ[0])
92
93 # Calculate xn of the vectors
94 xni = sin(angleVectorI[len(angleVectorI) - 1])
95 xnj = sin(angleVectorJ[len(angleVectorJ) - 1])
96
97 # Do the aggregation of the (n-1) coordinates (if there is more than one angle)
98 # i.e contraction of all (n-1)-dimensional angular coordinates to one variable
99 for k in range(0, len(angleVectorI)-1):
100 xni *= sin(angleVectorI[k])
101 xnj *= sin(angleVectorJ[k])
102
103 innerProduct += (x0i * x0j) + (xni * xnj)
104
105 if len(angleVectorI) > 1:
106 for m in range(1, len(angleVectorI)):
107 # Calculate euclidean coordinates given the angles and assuming R_sphere = 1
108 xmi = cos(angleVectorI[m])
109 xmj = cos(angleVectorJ[m])
110 for l in range(0, m):
111 xmi *= sin(angleVectorI[l])
112 xmj *= sin(angleVectorJ[l])
113
114 innerProduct += xmi * xmj
115
116 # ArcCos of the inner product gives the angular distance
117 # between two points on a d-dimensional sphere
118 angularDist = acos(innerProduct)
119 debug("Angular distance from {} to {} is {}".format(angleVectorI, angleVectorJ, angularDist))
120 return angularDist
121
122def getHyperbolicDistance(sourceNode, destNode):
123 """
124 Return hyperbolic or geohyperbolic distance between two nodes. The distance is computed
125 on the basis of following algorithm/mathematics
126 ref: https://en.wikipedia.org/wiki/Hyperbolic_geometry
127 """
128 r1 = [key for key in sourceNode][0]
129 r2 = [key for key in destNode][0]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500130 zeta = 1.0
131 dtheta = calculateAngularDistance(sourceNode[r1], destNode[r2])
132 hyperbolicDistance = (1./zeta) * acosh(cosh(zeta * r1) * cosh(zeta * r2) -\
133 sinh(zeta * r1) * sinh(zeta * r2) * cos(dtheta))
134
135 debug("Distance from {} to {} is {}".format(sourceNode, destNode, hyperbolicDistance))
136 return hyperbolicDistance
137
138class _CalculateRoutes(object):
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000139 """
140 Creates a route calculation object, which is used to compute routes from a node to
141 every other nodes in a given topology topology using hyperbolic or geohyperbolic
142 routing algorithm
143
144 :param NetObject netObj: Mininet net object
145 :param RoutingType routingType: (optional) Routing algorithm, link-state or hr etc
146 """
147 def __init__(self, netObj, routingType):
148 self.adjacenctMatrix = defaultdict(dict)
149 self.nodeDict = defaultdict(dict)
150 self.routingType = routingType
Saurab Dulal082e4442020-03-02 15:49:03 -0600151 self.isHrConfigValid = True
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000152 for host in netObj.hosts:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500153 if 'radius' in host.params['params']:
154 radius = float(host.params['params']['radius'])
155 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600156 self.isHrConfigValid = False
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500157 radius = 0.0
Saurab Dulal082e4442020-03-02 15:49:03 -0600158 if 'angle' in host.params['params']:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500159 angles = [float(x) for x in host.params['params']['angle'].split(',')]
160 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600161 self.isHrConfigValid = False
162 angles = [0.0]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000163 self.nodeDict[host.name][radius] = angles
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500164 for link in netObj.topo.links(withInfo=True):
165 linkDelay = int(link[2]['delay'].replace("ms", ""))
166 self.adjacenctMatrix[link[0]][link[1]] = linkDelay
167 self.adjacenctMatrix[link[1]][link[0]] = linkDelay
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000168
169 def getNestedDictionary(self):
170 return defaultdict(self.getNestedDictionary)
171
172 def getRoutes(self, nFaces):
173 resultMatrix = self.getNestedDictionary()
174 routes = defaultdict(list)
175
176 if self.routingType == "link-state":
177 if nFaces == 1:
178 resultMatrix = self.computeDijkastra() # only best routes.
179 else:
180 resultMatrix = self.computeDijkastraAll() # all possible routes
181 elif self.routingType == "hr":
Saurab Dulal082e4442020-03-02 15:49:03 -0600182 if self.isHrConfigValid == True:
183 # Note: For hyperbolic, only way to find the best routes is by
184 # computing all possible routes and getting the best one.
185 resultMatrix = self.computeHyperbolic()
186 else:
187 warn('Hyperbolic coordinates in topology file are either missing or misconfigured.\n')
188 warn('Check that each node has one radius value and one or two angle value(s).\n')
189 return None
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000190
191 for node in resultMatrix:
192 for destinationNode in resultMatrix[node]:
193 # Sort node - destination via neighbor based on their cost
194 tempDict = resultMatrix[node][destinationNode]
195 shortedTempDict = sorted(tempDict.items(), key=operator.itemgetter(1))
196 # nFaces option gets n-best faces based on shortest distance, default is all
197 if nFaces == 0:
198 for item in shortedTempDict:
199 viaNeighbor = item[0]
200 cost = item[1]
201 routes[node].append([destinationNode, str(cost), viaNeighbor])
202 else:
203 for index, item in enumerate(shortedTempDict):
204 if index >= nFaces:
205 break
206 viaNeighbor = item[0]
207 cost = item[1]
208 routes[node].append([destinationNode, str(cost), viaNeighbor])
209
210 debug("-routes----", routes)
211 return routes
212
213 def getNodeNames(self):
214 return [k for k in self.nodeDict]
215
216 def computeHyperbolic(self):
217 paths = self.getNestedDictionary()
218 nodeNames = self.getNodeNames()
219 for node in self.nodeDict:
220 neighbors = [k for k in self.adjacenctMatrix[node]]
221 for viaNeighbor in neighbors:
Saurab Dulal082e4442020-03-02 15:49:03 -0600222 others = [x for x in nodeNames if x not in [viaNeighbor, node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000223 paths[node][viaNeighbor][viaNeighbor] = 0
224 # Compute distance from neighbors to no-neighbors
225 for destinationNode in others:
226 hyperbolicDistance = getHyperbolicDistance(self.nodeDict[viaNeighbor],
227 self.nodeDict[destinationNode])
228 hyperbolicCost = int(HYPERBOLIC_COST_ADJUSTMENT_FACTOR \
229 * round(hyperbolicDistance, 6))
230 paths[node][destinationNode][viaNeighbor] = hyperbolicCost
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000231 debug("Shortest Distance Matrix: {}".format(json.dumps(paths)))
232 return paths
233
234 def computeDijkastra(self):
235 """
236 Dijkstra computation: Compute all the shortest paths from nodes to the destinations.
237 And fills the distance matrix with the corresponding source to destination cost
238 """
239 distanceMatrix = self.getNestedDictionary()
240 nodeNames = self.getNodeNames()
241 for node in nodeNames:
Saurab Dulal082e4442020-03-02 15:49:03 -0600242 others = [x for x in nodeNames if x not in [node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000243 for destinationNode in others:
244 cost, path = dijkstra(self.adjacenctMatrix, node, destinationNode)
245 viaNeighbor = path[1]
246 distanceMatrix[node][destinationNode][viaNeighbor] = cost
247
248 debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrix)))
249 return distanceMatrix
250
251 def computeDijkastraAll(self):
252 """
253 Multi-path Dijkastra computation: Compute all the shortest paths from nodes to the
254 destinations via all of its neighbors individually. And fills the distanceMatrixViaNeighbor
255 with a corresponding source to its destination cost
256
257 Important: distanceMatrixViaNeighbor represents the shortest distance from a source to a
258 destination via specific neighbors
259 """
260 distanceMatrixViaNeighbor = self.getNestedDictionary()
261 nodeNames = self.getNodeNames()
262 for node in nodeNames:
263 neighbors = [k for k in self.adjacenctMatrix[node]]
264 for viaNeighbor in neighbors:
265 directCost = self.adjacenctMatrix[node][viaNeighbor]
266 distanceMatrixViaNeighbor[node][viaNeighbor][viaNeighbor] = directCost
Saurab Dulal082e4442020-03-02 15:49:03 -0600267 others = [x for x in nodeNames if x not in [viaNeighbor, node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000268 for destinationNode in others:
269 nodeNeighborCost = self.adjacenctMatrix[node][viaNeighbor]
270 # path variable is not used for now
271 cost, path = dijkstra(self.adjacenctMatrix, viaNeighbor, destinationNode, node)
272 if cost != 0 and path != None:
273 totalCost = cost + nodeNeighborCost
274 distanceMatrixViaNeighbor[node][destinationNode][viaNeighbor] = totalCost
275
276 debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrixViaNeighbor)))
277 return distanceMatrixViaNeighbor
278
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500279class NdnRoutingHelper(object):
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000280 """
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500281 This module is a helper class which helps to create face and register routes
282 to NFD from a given node to all of its neighbors.
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000283
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500284 :param NetObject netObject: Mininet net object
285 :param FaceType faceType: UDP, Ethernet etc.
286 :param Routing routingType: (optional) Routing algorithm, link-state or hr etc
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000287
288 """
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500289 def __init__(self, netObject, faceType=nfdc.PROTOCOL_UDP, routingType="link-state"):
290 self.net = netObject
291 self.faceType = faceType
292 self.routingType = routingType
293 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',
301 prefer="threads")(delayed(self.addNodeRoutes)(host) for host in tqdm(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)
312 self.createFaces(node, neighborIPs)
313 self.routeAdd(node, neighborIPs)
314
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):
351 for ip in neighborIPs.values():
352 nfdc.createFace(node, ip, self.faceType)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000353
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500354 def routeAdd(self, node, neighborIPs):
355 """
356 Add route from a node to its neighbors for each prefix/s advertised by destination node
357
358 :param Node node: source node (Mininet net.host)
359 :param IP neighborIPs: IP addresses of neighbors
360 """
361 neighbors = self.routes[node.name]
362 for route in neighbors:
363 destination = route[0]
364 cost = int(route[1])
365 nextHop = route[2]
366 defaultPrefix = "/ndn/{}-site/{}".format(destination, destination)
367 prefixes = [defaultPrefix] + self.namePrefixes[destination]
368 for prefix in prefixes:
369 # Register routes to all the available destination name prefix/s
Alex Lane1d3c0a82021-07-22 17:28:16 -0500370 faceID = nfdc.createFace(node, neighborIPs[nextHop])
371 nfdc.registerRoute(node, prefix, faceID, cost=cost)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500372 @staticmethod
373 def getNeighbor(node):
374 # Nodes to IP mapping
375 neighborIPs = defaultdict()
376 for intf in node.intfList():
377 link = intf.link
378 if link:
379 node1, node2 = link.intf1.node, link.intf2.node
380
381 if node1 == node:
382 other = node2
383 ip = other.IP(str(link.intf2))
384 else:
385 other = node1
386 ip = other.IP(str(link.intf1))
387
388 # Used later to create faces
389 neighborIPs[other.name] = ip
390 return neighborIPs