blob: 93b196f0233718ce9a7aef902cb19589dca7e075 [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 """
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500288 def __init__(self, netObject, faceType=nfdc.PROTOCOL_UDP, routingType="link-state"):
289 self.net = netObject
290 self.faceType = faceType
291 self.routingType = routingType
292 self.routes = []
293 self.namePrefixes = {host_name.name: [] for host_name in self.net.hosts}
294 self.routeObject = _CalculateRoutes(self.net, self.routingType)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000295
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500296 def globalRoutingHelperHandler(self):
Saurab Dulal082e4442020-03-02 15:49:03 -0600297 info('Creating faces and adding routes to FIB\n')
Varun Patil63a330d2022-05-18 14:23:13 -0700298
299 res = Parallel(n_jobs=-1, require='sharedmem',
tylerliu86647792023-03-03 15:18:48 -0800300 prefer="threads", verbose=1)(delayed(self.addNodeRoutes)(host) for host in self.net.hosts)
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
Varun Patil63a330d2022-05-18 14:23:13 -0700304 def addNodeRoutes(self, node):
305 """
306 Create faces to neighbors and add all routes for one node
307
308 :param Node node: Node from net object
309 """
310 neighborIPs = self.getNeighbor(node)
tylerliu86647792023-03-03 15:18:48 -0800311 neighborFaces = self.createFaces(node, neighborIPs)
312 self.routeAdd(node, neighborFaces)
Varun Patil63a330d2022-05-18 14:23:13 -0700313
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500314 def addOrigin(self, nodes, prefix):
315 """
316 Add prefix/s as origin on node/s
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000317
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500318 :param Prefix prefix: Prefix that is originated by node/s (as producer) for this prefix
319 :param Nodes nodes: List of nodes from net object
320 """
321 for node in nodes:
phmoll52223f72020-02-21 08:43:50 +0100322 if not node.name in self.namePrefixes:
323 self.namePrefixes[node.name] = []
324 self.namePrefixes[node.name] += prefix
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000325
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500326 def calculateNPossibleRoutes(self, nFaces=0):
327 """
328 By default, calculates all possible routes i.e. routes via all the faces of a node.
329 pass nFaces if want to compute routes via n number of faces. e.g. 2. For larger topology
330 the computation might take huge amount of time.
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000331
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500332 :param int nFaces: (optional) number of faces to consider while computing routes. Default
333 i.e. nFaces = 0 will compute all possible routes
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000334
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500335 """
336 self.routes = self.routeObject.getRoutes(nFaces)
Saurab Dulal082e4442020-03-02 15:49:03 -0600337 if self.routes is not None:
338 info('Route computation completed\n')
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500339 self.globalRoutingHelperHandler()
340 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600341 warn('Route computation failed\n')
342 self.net.stop()
343 sys.exit(1)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000344
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500345 def calculateRoutes(self):
346 # Calculate shortest path for every node
347 self.calculateNPossibleRoutes(nFaces=1)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000348
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500349 def createFaces(self, node, neighborIPs):
tylerliu86647792023-03-03 15:18:48 -0800350 neighborFaces = {}
351 for k, ip in neighborIPs.items():
352 faceID = nfdc.createFace(node, ip, self.faceType)
353 if not isinstance(faceID, str): raise ValueError(faceID)
354 neighborFaces[k] = faceID
355 return neighborFaces
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000356
tylerliu86647792023-03-03 15:18:48 -0800357
358 def routeAdd(self, node, neighborFaces):
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500359 """
360 Add route from a node to its neighbors for each prefix/s advertised by destination node
361
362 :param Node node: source node (Mininet net.host)
363 :param IP neighborIPs: IP addresses of neighbors
364 """
365 neighbors = self.routes[node.name]
366 for route in neighbors:
367 destination = route[0]
368 cost = int(route[1])
369 nextHop = route[2]
370 defaultPrefix = "/ndn/{}-site/{}".format(destination, destination)
371 prefixes = [defaultPrefix] + self.namePrefixes[destination]
372 for prefix in prefixes:
373 # Register routes to all the available destination name prefix/s
tylerliu86647792023-03-03 15:18:48 -0800374 nfdc.registerRoute(node, prefix, neighborFaces[nextHop], cost=cost)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500375 @staticmethod
376 def getNeighbor(node):
377 # Nodes to IP mapping
378 neighborIPs = defaultdict()
379 for intf in node.intfList():
380 link = intf.link
381 if link:
382 node1, node2 = link.intf1.node, link.intf2.node
383
384 if node1 == node:
385 other = node2
386 ip = other.IP(str(link.intf2))
387 else:
388 other = node1
389 ip = other.IP(str(link.intf1))
390
391 # Used later to create faces
392 neighborIPs[other.name] = ip
393 return neighborIPs