blob: 7ea0de96ab66bcbca16d26c8c611cc7cb1d88fff [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
awlane21acd052024-06-13 21:12:51 -050041from minindn.util import MACToEther
42
Saurab Dulal8ae870a2018-07-31 05:17:49 +000043UNKNOWN_DISTANCE = -1
44HYPERBOLIC_COST_ADJUSTMENT_FACTOR = 1000
45
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050046def dijkstra(graph, start, end, ignoredNode=None):
47 """
48 Compute shortest path and cost from a given source to a destination
49 using Dijkstra algorithm
50
51 :param Graph graph: given network topology/graph
52 :param Start start: source node in a given network graph/topology
53 :end End end: destination node in a given network graph/topology
54 :param Node ignoredNode: node to ignore computing shortest path from
55 """
56 queue = [(0, start, [])]
57 seen = set()
58 while True:
59 (cost, v, path) = heapq.heappop(queue)
60 if v not in seen:
61 path = path + [v]
62 seen.add(v)
63 if v == end:
64 debug("Distance from {} to {} is {}".format(start, end, cost))
65 return cost, path
66 for (_next, c) in graph[v].items():
67 if _next != ignoredNode: # Ignore path going via ignoreNode
68 heapq.heappush(queue, (cost + c, _next, path))
69
70 if not queue: # return if no path exist from source - destination except via ignoreNode
71 debug("Distance from {} to {} is {}".format(start, end, cost))
72 return cost, None
73
74def calculateAngularDistance(angleVectorI, angleVectorJ):
75 """
76 For hyperbolic/geohyperbolic routing algorithm, this function computes angular distance between
77 two nodes. A node can have two or more than two angular coordinates.
78
79 :param AngleVectorI angleVectorI: list of angular coordinate of a give node I
80 :param AngleVectorJ angleVectorJ: list of angular coordinate of a give node J
81
82 ref: https://en.wikipedia.org/wiki/N-sphere#Spherical_coordinates
83
84 """
85 innerProduct = 0.0
86 if len(angleVectorI) != len(angleVectorJ):
87 error("Angle vector sizes do not match")
88 return UNKNOWN_DISTANCE
89
90 # Calculate x0 of the vectors
91 x0i = cos(angleVectorI[0])
92 x0j = cos(angleVectorJ[0])
93
94 # Calculate xn of the vectors
95 xni = sin(angleVectorI[len(angleVectorI) - 1])
96 xnj = sin(angleVectorJ[len(angleVectorJ) - 1])
97
98 # Do the aggregation of the (n-1) coordinates (if there is more than one angle)
99 # i.e contraction of all (n-1)-dimensional angular coordinates to one variable
100 for k in range(0, len(angleVectorI)-1):
101 xni *= sin(angleVectorI[k])
102 xnj *= sin(angleVectorJ[k])
103
104 innerProduct += (x0i * x0j) + (xni * xnj)
105
106 if len(angleVectorI) > 1:
107 for m in range(1, len(angleVectorI)):
108 # Calculate euclidean coordinates given the angles and assuming R_sphere = 1
109 xmi = cos(angleVectorI[m])
110 xmj = cos(angleVectorJ[m])
111 for l in range(0, m):
112 xmi *= sin(angleVectorI[l])
113 xmj *= sin(angleVectorJ[l])
114
115 innerProduct += xmi * xmj
116
117 # ArcCos of the inner product gives the angular distance
118 # between two points on a d-dimensional sphere
119 angularDist = acos(innerProduct)
120 debug("Angular distance from {} to {} is {}".format(angleVectorI, angleVectorJ, angularDist))
121 return angularDist
122
123def getHyperbolicDistance(sourceNode, destNode):
124 """
125 Return hyperbolic or geohyperbolic distance between two nodes. The distance is computed
126 on the basis of following algorithm/mathematics
127 ref: https://en.wikipedia.org/wiki/Hyperbolic_geometry
128 """
129 r1 = [key for key in sourceNode][0]
130 r2 = [key for key in destNode][0]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500131 zeta = 1.0
132 dtheta = calculateAngularDistance(sourceNode[r1], destNode[r2])
133 hyperbolicDistance = (1./zeta) * acosh(cosh(zeta * r1) * cosh(zeta * r2) -\
134 sinh(zeta * r1) * sinh(zeta * r2) * cos(dtheta))
135
136 debug("Distance from {} to {} is {}".format(sourceNode, destNode, hyperbolicDistance))
137 return hyperbolicDistance
138
139class _CalculateRoutes(object):
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000140 """
141 Creates a route calculation object, which is used to compute routes from a node to
142 every other nodes in a given topology topology using hyperbolic or geohyperbolic
143 routing algorithm
144
145 :param NetObject netObj: Mininet net object
146 :param RoutingType routingType: (optional) Routing algorithm, link-state or hr etc
147 """
148 def __init__(self, netObj, routingType):
149 self.adjacenctMatrix = defaultdict(dict)
150 self.nodeDict = defaultdict(dict)
151 self.routingType = routingType
Saurab Dulal082e4442020-03-02 15:49:03 -0600152 self.isHrConfigValid = True
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000153 for host in netObj.hosts:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500154 if 'radius' in host.params['params']:
155 radius = float(host.params['params']['radius'])
156 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600157 self.isHrConfigValid = False
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500158 radius = 0.0
Saurab Dulal082e4442020-03-02 15:49:03 -0600159 if 'angle' in host.params['params']:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500160 angles = [float(x) for x in host.params['params']['angle'].split(',')]
161 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600162 self.isHrConfigValid = False
163 angles = [0.0]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000164 self.nodeDict[host.name][radius] = angles
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500165 for link in netObj.topo.links(withInfo=True):
166 linkDelay = int(link[2]['delay'].replace("ms", ""))
167 self.adjacenctMatrix[link[0]][link[1]] = linkDelay
168 self.adjacenctMatrix[link[1]][link[0]] = linkDelay
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000169
170 def getNestedDictionary(self):
171 return defaultdict(self.getNestedDictionary)
172
173 def getRoutes(self, nFaces):
174 resultMatrix = self.getNestedDictionary()
175 routes = defaultdict(list)
176
177 if self.routingType == "link-state":
178 if nFaces == 1:
179 resultMatrix = self.computeDijkastra() # only best routes.
180 else:
181 resultMatrix = self.computeDijkastraAll() # all possible routes
182 elif self.routingType == "hr":
Saurab Dulal082e4442020-03-02 15:49:03 -0600183 if self.isHrConfigValid == True:
184 # Note: For hyperbolic, only way to find the best routes is by
185 # computing all possible routes and getting the best one.
186 resultMatrix = self.computeHyperbolic()
187 else:
188 warn('Hyperbolic coordinates in topology file are either missing or misconfigured.\n')
189 warn('Check that each node has one radius value and one or two angle value(s).\n')
190 return None
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000191
192 for node in resultMatrix:
193 for destinationNode in resultMatrix[node]:
194 # Sort node - destination via neighbor based on their cost
195 tempDict = resultMatrix[node][destinationNode]
196 shortedTempDict = sorted(tempDict.items(), key=operator.itemgetter(1))
197 # nFaces option gets n-best faces based on shortest distance, default is all
198 if nFaces == 0:
199 for item in shortedTempDict:
200 viaNeighbor = item[0]
201 cost = item[1]
202 routes[node].append([destinationNode, str(cost), viaNeighbor])
203 else:
204 for index, item in enumerate(shortedTempDict):
205 if index >= nFaces:
206 break
207 viaNeighbor = item[0]
208 cost = item[1]
209 routes[node].append([destinationNode, str(cost), viaNeighbor])
210
211 debug("-routes----", routes)
212 return routes
213
214 def getNodeNames(self):
215 return [k for k in self.nodeDict]
216
217 def computeHyperbolic(self):
218 paths = self.getNestedDictionary()
219 nodeNames = self.getNodeNames()
220 for node in self.nodeDict:
221 neighbors = [k for k in self.adjacenctMatrix[node]]
222 for viaNeighbor in neighbors:
Saurab Dulal082e4442020-03-02 15:49:03 -0600223 others = [x for x in nodeNames if x not in [viaNeighbor, node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000224 paths[node][viaNeighbor][viaNeighbor] = 0
225 # Compute distance from neighbors to no-neighbors
226 for destinationNode in others:
227 hyperbolicDistance = getHyperbolicDistance(self.nodeDict[viaNeighbor],
228 self.nodeDict[destinationNode])
229 hyperbolicCost = int(HYPERBOLIC_COST_ADJUSTMENT_FACTOR \
230 * round(hyperbolicDistance, 6))
231 paths[node][destinationNode][viaNeighbor] = hyperbolicCost
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000232 debug("Shortest Distance Matrix: {}".format(json.dumps(paths)))
233 return paths
234
235 def computeDijkastra(self):
236 """
237 Dijkstra computation: Compute all the shortest paths from nodes to the destinations.
238 And fills the distance matrix with the corresponding source to destination cost
239 """
240 distanceMatrix = self.getNestedDictionary()
241 nodeNames = self.getNodeNames()
242 for node in nodeNames:
Saurab Dulal082e4442020-03-02 15:49:03 -0600243 others = [x for x in nodeNames if x not in [node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000244 for destinationNode in others:
245 cost, path = dijkstra(self.adjacenctMatrix, node, destinationNode)
246 viaNeighbor = path[1]
247 distanceMatrix[node][destinationNode][viaNeighbor] = cost
248
249 debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrix)))
250 return distanceMatrix
251
252 def computeDijkastraAll(self):
253 """
254 Multi-path Dijkastra computation: Compute all the shortest paths from nodes to the
255 destinations via all of its neighbors individually. And fills the distanceMatrixViaNeighbor
256 with a corresponding source to its destination cost
257
258 Important: distanceMatrixViaNeighbor represents the shortest distance from a source to a
259 destination via specific neighbors
260 """
261 distanceMatrixViaNeighbor = self.getNestedDictionary()
262 nodeNames = self.getNodeNames()
263 for node in nodeNames:
264 neighbors = [k for k in self.adjacenctMatrix[node]]
265 for viaNeighbor in neighbors:
266 directCost = self.adjacenctMatrix[node][viaNeighbor]
267 distanceMatrixViaNeighbor[node][viaNeighbor][viaNeighbor] = directCost
Saurab Dulal082e4442020-03-02 15:49:03 -0600268 others = [x for x in nodeNames if x not in [viaNeighbor, node]]
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000269 for destinationNode in others:
270 nodeNeighborCost = self.adjacenctMatrix[node][viaNeighbor]
271 # path variable is not used for now
272 cost, path = dijkstra(self.adjacenctMatrix, viaNeighbor, destinationNode, node)
273 if cost != 0 and path != None:
274 totalCost = cost + nodeNeighborCost
275 distanceMatrixViaNeighbor[node][destinationNode][viaNeighbor] = totalCost
276
277 debug("Shortest Distance Matrix: {}".format(json.dumps(distanceMatrixViaNeighbor)))
278 return distanceMatrixViaNeighbor
279
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500280class NdnRoutingHelper(object):
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000281 """
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500282 This module is a helper class which helps to create face and register routes
283 to NFD from a given node to all of its neighbors.
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000284
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500285 :param NetObject netObject: Mininet net object
286 :param FaceType faceType: UDP, Ethernet etc.
287 :param Routing routingType: (optional) Routing algorithm, link-state or hr etc
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000288
289 """
awlane92bc4292023-05-22 18:35:58 -0500290 def __init__(self, netObject, faceType=nfdc.PROTOCOL_UDP, routingType="link-state", permanentFaces=False):
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500291 self.net = netObject
292 self.faceType = faceType
293 self.routingType = routingType
awlane92bc4292023-05-22 18:35:58 -0500294 self.permanentFaces = permanentFaces
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500295 self.routes = []
296 self.namePrefixes = {host_name.name: [] for host_name in self.net.hosts}
297 self.routeObject = _CalculateRoutes(self.net, self.routingType)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000298
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500299 def globalRoutingHelperHandler(self):
Saurab Dulal082e4442020-03-02 15:49:03 -0600300 info('Creating faces and adding routes to FIB\n')
Varun Patil63a330d2022-05-18 14:23:13 -0700301
awlane21acd052024-06-13 21:12:51 -0500302 if self.faceType == nfdc.PROTOCOL_ETHER:
303 add_route_method = self.addNodeRoutesEther
304 else:
305 add_route_method = self.addNodeRoutes
306
Varun Patil63a330d2022-05-18 14:23:13 -0700307 res = Parallel(n_jobs=-1, require='sharedmem',
awlane21acd052024-06-13 21:12:51 -0500308 prefer="threads", verbose=1)(delayed(add_route_method)(host) for host in self.net.hosts)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000309
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500310 info('Processed all the routes to NFD\n')
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000311
Varun Patil63a330d2022-05-18 14:23:13 -0700312 def addNodeRoutes(self, node):
313 """
314 Create faces to neighbors and add all routes for one node
315
316 :param Node node: Node from net object
317 """
awlane21acd052024-06-13 21:12:51 -0500318 neighborIPs = self.getNeighborIP(node)
tylerliu86647792023-03-03 15:18:48 -0800319 neighborFaces = self.createFaces(node, neighborIPs)
320 self.routeAdd(node, neighborFaces)
Varun Patil63a330d2022-05-18 14:23:13 -0700321
awlane21acd052024-06-13 21:12:51 -0500322 def addNodeRoutesEther(self, node):
323 """
324 Create faces to neighbors and add all routes for one node
325
326 :param Node node: Node from net object
327 """
328 neighborAddrs = self.getNeighborEther(node)
329 neighborFaces = self.createEtherFaces(node, neighborAddrs)
330 self.routeAdd(node, neighborFaces)
331
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500332 def addOrigin(self, nodes, prefix):
333 """
334 Add prefix/s as origin on node/s
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000335
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500336 :param Prefix prefix: Prefix that is originated by node/s (as producer) for this prefix
337 :param Nodes nodes: List of nodes from net object
338 """
339 for node in nodes:
phmoll52223f72020-02-21 08:43:50 +0100340 if not node.name in self.namePrefixes:
341 self.namePrefixes[node.name] = []
342 self.namePrefixes[node.name] += prefix
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000343
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500344 def calculateNPossibleRoutes(self, nFaces=0):
345 """
346 By default, calculates all possible routes i.e. routes via all the faces of a node.
347 pass nFaces if want to compute routes via n number of faces. e.g. 2. For larger topology
348 the computation might take huge amount of time.
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000349
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500350 :param int nFaces: (optional) number of faces to consider while computing routes. Default
351 i.e. nFaces = 0 will compute all possible routes
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000352
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500353 """
354 self.routes = self.routeObject.getRoutes(nFaces)
Saurab Dulal082e4442020-03-02 15:49:03 -0600355 if self.routes is not None:
356 info('Route computation completed\n')
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500357 self.globalRoutingHelperHandler()
358 else:
Saurab Dulal082e4442020-03-02 15:49:03 -0600359 warn('Route computation failed\n')
360 self.net.stop()
361 sys.exit(1)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000362
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500363 def calculateRoutes(self):
364 # Calculate shortest path for every node
365 self.calculateNPossibleRoutes(nFaces=1)
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000366
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500367 def createFaces(self, node, neighborIPs):
tylerliu86647792023-03-03 15:18:48 -0800368 neighborFaces = {}
369 for k, ip in neighborIPs.items():
awlane92bc4292023-05-22 18:35:58 -0500370 faceID = nfdc.createFace(node, ip, self.faceType, self.permanentFaces)
tylerliu86647792023-03-03 15:18:48 -0800371 if not isinstance(faceID, str): raise ValueError(faceID)
372 neighborFaces[k] = faceID
373 return neighborFaces
Saurab Dulal8ae870a2018-07-31 05:17:49 +0000374
awlane21acd052024-06-13 21:12:51 -0500375 def createEtherFaces(self, node, neighborLocations):
376 neighborFaces = {}
377 for k, intfLocTuple in neighborLocations.items():
378 localIntf, etherAddr = intfLocTuple
379 faceID = nfdc.createFace(node, etherAddr, self.faceType, self.permanentFaces, localIntf)
380 if not isinstance(faceID, str): raise ValueError(faceID)
381 neighborFaces[k] = faceID
382 return neighborFaces
383
tylerliu86647792023-03-03 15:18:48 -0800384
385 def routeAdd(self, node, neighborFaces):
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500386 """
387 Add route from a node to its neighbors for each prefix/s advertised by destination node
388
389 :param Node node: source node (Mininet net.host)
390 :param IP neighborIPs: IP addresses of neighbors
391 """
392 neighbors = self.routes[node.name]
393 for route in neighbors:
394 destination = route[0]
395 cost = int(route[1])
396 nextHop = route[2]
397 defaultPrefix = "/ndn/{}-site/{}".format(destination, destination)
398 prefixes = [defaultPrefix] + self.namePrefixes[destination]
399 for prefix in prefixes:
400 # Register routes to all the available destination name prefix/s
tylerliu86647792023-03-03 15:18:48 -0800401 nfdc.registerRoute(node, prefix, neighborFaces[nextHop], cost=cost)
awlane21acd052024-06-13 21:12:51 -0500402
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500403 @staticmethod
awlane21acd052024-06-13 21:12:51 -0500404 def getNeighborIP(node):
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500405 # Nodes to IP mapping
406 neighborIPs = defaultdict()
407 for intf in node.intfList():
408 link = intf.link
409 if link:
410 node1, node2 = link.intf1.node, link.intf2.node
411
412 if node1 == node:
413 other = node2
414 ip = other.IP(str(link.intf2))
415 else:
416 other = node1
417 ip = other.IP(str(link.intf1))
418
419 # Used later to create faces
420 neighborIPs[other.name] = ip
421 return neighborIPs
awlane21acd052024-06-13 21:12:51 -0500422
423 @staticmethod
424 def getNeighborEther(node):
425 # Nodes to IP mapping
426 neighborLocations = defaultdict()
427 for intf in node.intfList():
428 link = intf.link
429 if link:
430 node1, node2 = link.intf1.node, link.intf2.node
431 if node1 == node:
432 other = node2
433 intf_location = (link.intf1.name, MACToEther(link.intf2.MAC()))
434 else:
435 other = node1
436 intf_location = (link.intf2.name, MACToEther(link.intf1.MAC()))
437 # Used later to create faces
438 neighborLocations[other.name] = intf_location
439 return neighborLocations