blob: 849621ea8216f9c552ed1e5ab6355d22cd1998b0 [file] [log] [blame]
Alexander Laneea2d5d62019-10-04 16:48:52 -05001# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2#
dulalsaurab20855442021-05-21 20:37:03 +00003# Copyright (C) 2015-2021, The University of Memphis,
Alexander Laneea2d5d62019-10-04 16:48:52 -05004# Arizona Board of Regents,
5# Regents of the University of California.
6#
7# This file is part of Mini-NDN.
8# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
9#
10# Mini-NDN is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# Mini-NDN is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with Mini-NDN, e.g., in COPYING.md file.
22# If not, see <http://www.gnu.org/licenses/>.
23
awlanea169f572022-04-08 17:21:29 -050024import os
Alexander Laneea2d5d62019-10-04 16:48:52 -050025import argparse
26import sys
Alexander Laneea2d5d62019-10-04 16:48:52 -050027import configparser
dulalsaurab20855442021-05-21 20:37:03 +000028from subprocess import Popen, PIPE
Alexander Laneea2d5d62019-10-04 16:48:52 -050029
Alexander Laneea2d5d62019-10-04 16:48:52 -050030from mininet.log import info, debug
31
32from mn_wifi.topo import Topo as Topo_WiFi
33from mn_wifi.net import Mininet_wifi
Alexander Laneea2d5d62019-10-04 16:48:52 -050034from mn_wifi.link import WirelessLink
matianxing19921f07a832024-09-25 12:32:38 -050035from mn_wifi.link import wmediumd, adhoc
Alexander Laneea2d5d62019-10-04 16:48:52 -050036
37from minindn.minindn import Minindn
awlaned8e6b8e2022-05-16 23:49:56 -050038from minindn.helpers.nfdc import Nfdc
Alexander Laneea2d5d62019-10-04 16:48:52 -050039
matianxing19921f07a832024-09-25 12:32:38 -050040import ast
41
Alexander Laneea2d5d62019-10-04 16:48:52 -050042class MinindnWifi(Minindn):
Alex Lane407c5f02021-03-09 22:13:23 -060043 """ Class for handling default args, Mininet-wifi object and home directories """
awlaned8e6b8e2022-05-16 23:49:56 -050044 def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False,
45 link=WirelessLink, workDir=None, **mininetParams):
46 """
47 Create Mini-NDN-Wifi object
Alexander Laneea2d5d62019-10-04 16:48:52 -050048 parser: Parent parser of Mini-NDN-Wifi parser (use to specify experiment arguments)
49 topo: Mininet topo object (optional)
50 topoFile: topology file location (optional)
Alex Lane407c5f02021-03-09 22:13:23 -060051 noTopo: Allows specification of topology after network object is initialized (optional)
awlaned8e6b8e2022-05-16 23:49:56 -050052 link: Allows specification of default Mininet/Mininet-Wifi link type for
53 connections between nodes (optional)mininetParams: Any params to pass to Mininet-WiFi
Alexander Laneea2d5d62019-10-04 16:48:52 -050054 """
55 self.parser = self.parseArgs(parser)
56 self.args = self.parser.parse_args()
57
awlanea169f572022-04-08 17:21:29 -050058 if not workDir:
59 Minindn.workDir = os.path.abspath(self.args.workDir)
60 else:
61 Minindn.workDir = os.path.abspath(workDir)
62
Alexander Laneea2d5d62019-10-04 16:48:52 -050063 Minindn.resultDir = self.args.resultDir
64
65 self.topoFile = None
66 if not topoFile:
67 # Args has default topology if none specified
68 self.topoFile = self.args.topoFile
69 else:
70 self.topoFile = topoFile
71
awlaned8e6b8e2022-05-16 23:49:56 -050072 self.faces_to_create = {}
Alex Lane407c5f02021-03-09 22:13:23 -060073 if topo is None and not noTopo:
Alexander Laneea2d5d62019-10-04 16:48:52 -050074 try:
75 info('Using topology file {}\n'.format(self.topoFile))
awlaned8e6b8e2022-05-16 23:49:56 -050076 self.topo, self.faces_to_create = self.processTopo(self.topoFile)
Alexander Laneea2d5d62019-10-04 16:48:52 -050077 except configparser.NoSectionError as e:
78 info('Error reading config file: {}\n'.format(e))
79 sys.exit(1)
80 else:
81 self.topo = topo
82
Alex Lane407c5f02021-03-09 22:13:23 -060083 if not noTopo:
84 self.net = Mininet_wifi(topo=self.topo, ifb=self.args.ifb, link=link, **mininetParams)
85 else:
86 self.net = Mininet_wifi(ifb=self.args.ifb, link=link, **mininetParams)
Alexander Laneea2d5d62019-10-04 16:48:52 -050087
matianxing19921f07a832024-09-25 12:32:38 -050088 # process mobility if specified
89 if not noTopo:
90 self.processMobility(self.topoFile)
91
Alex Lane407c5f02021-03-09 22:13:23 -060092 # Prevents crashes running mixed topos
93 nodes = self.net.stations + self.net.hosts + self.net.cars
94 self.initParams(nodes)
95
Alexander Laneea2d5d62019-10-04 16:48:52 -050096 try:
97 process = Popen(['ndnsec-get-default', '-k'], stdout=PIPE, stderr=PIPE)
98 output, error = process.communicate()
99 if process.returncode == 0:
awlanec32a07b2022-04-19 14:53:41 -0500100 Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in output.decode("utf-8")
Alex Lane407c5f02021-03-09 22:13:23 -0600101 info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
Alexander Laneea2d5d62019-10-04 16:48:52 -0500102 else:
matianxing19921f07a832024-09-25 12:32:38 -0500103 debug(error + "\n")
Alexander Laneea2d5d62019-10-04 16:48:52 -0500104 except:
105 pass
106
107 self.cleanups = []
108
109 @staticmethod
110 def parseArgs(parent):
111 parser = argparse.ArgumentParser(prog='minindn-wifi', parents=[parent], add_help=False)
112
113 # nargs='?' required here since optional argument
114 parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/singleap-topology.conf',
115 help='If no template_file is given, topologies/wifi/singleap-topology.conf will be used.')
116
117 parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
118 help='Specify the working directory; default is /tmp/minindn')
119
120 parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
121 help='Specify the full path destination folder where experiment results will be moved')
122
Alexander Laneea2d5d62019-10-04 16:48:52 -0500123 parser.add_argument('--ifb',action='store_true',dest='ifb',default=False,
124 help='Simulate delay on receiver-side by use of virtual IFB devices (see docs)')
125
126 return parser
127
128 @staticmethod
matianxing19921f07a832024-09-25 12:32:38 -0500129 def convert_params(params):
130 converted_params = {}
131 for key, value in params.items():
132 try:
133 converted_params[key] = ast.literal_eval(value)
134 except (ValueError, SyntaxError):
135 converted_params[key] = value
136 return converted_params
137
138 @staticmethod
Alexander Laneea2d5d62019-10-04 16:48:52 -0500139 def processTopo(topoFile):
140 config = configparser.ConfigParser(delimiters=' ')
141 config.read(topoFile)
142 topo = Topo_WiFi()
143
144 items = config.items('stations')
matianxing19921f07a832024-09-25 12:32:38 -0500145 debug("Stations\n")
Alexander Laneea2d5d62019-10-04 16:48:52 -0500146 for item in items:
matianxing19921f07a832024-09-25 12:32:38 -0500147 debug(str(item[0].split(':'))+"\n")
Alexander Laneea2d5d62019-10-04 16:48:52 -0500148 name = item[0].split(':')[0]
149 params = {}
150 for param in item[1].split(' '):
151 if param == "_":
152 continue
153 key = param.split('=')[0]
154 value = param.split('=')[1]
155 if key in ['range']:
156 value = int(value)
157 params[key] = value
158
159 topo.addStation(name, **params)
160
161 try:
matianxing19921f07a832024-09-25 12:32:38 -0500162 debug("Switches\n")
Alexander Laneea2d5d62019-10-04 16:48:52 -0500163 items = config.items('switches')
164 for item in items:
matianxing19921f07a832024-09-25 12:32:38 -0500165 debug(str(item[0].split(':'))+"\n")
Alexander Laneea2d5d62019-10-04 16:48:52 -0500166 name = item[0].split(':')[0]
167 topo.addSwitch(name)
168 except configparser.NoSectionError:
matianxing19921f07a832024-09-25 12:32:38 -0500169 debug("Switches are optional\n")
Alexander Laneea2d5d62019-10-04 16:48:52 -0500170 pass
171
172 try:
173 debug("APs")
174 items = config.items('accessPoints')
175 for item in items:
matianxing19921f07a832024-09-25 12:32:38 -0500176 debug(str(item[0].split(':'))+"\n")
Alexander Laneea2d5d62019-10-04 16:48:52 -0500177 name = item[0].split(':')[0]
178 ap_params = {}
179 for param in item[1].split(' '):
180 if param == "_":
181 continue
182 key = param.split('=')[0]
183 value = param.split('=')[1]
184 if key in ['range']:
185 value = int(value)
186 ap_params[key] = value
187 topo.addAccessPoint(name, **ap_params)
188 except configparser.NoSectionError:
matianxing19921f07a832024-09-25 12:32:38 -0500189 debug("APs are optional\n")
Alexander Laneea2d5d62019-10-04 16:48:52 -0500190 pass
191
192 items = config.items('links')
193 debug("Links")
194 for item in items:
195 link = item[0].split(':')
matianxing19921f07a832024-09-25 12:32:38 -0500196 debug(str(link) + "\n")
Alexander Laneea2d5d62019-10-04 16:48:52 -0500197 params = {}
198 for param in item[1].split(' '):
199 if param == "_":
200 continue
201 key = param.split('=')[0]
202 value = param.split('=')[1]
Varun Patile427d262024-10-30 12:37:04 -0700203 if key in ['max_queue_size']:
Alexander Laneea2d5d62019-10-04 16:48:52 -0500204 value = int(value)
awlane5158e5f2024-11-01 21:34:14 -0500205 if key in ['loss', 'bw']:
Alexander Laneea2d5d62019-10-04 16:48:52 -0500206 value = float(value)
207 params[key] = value
208
209 topo.addLink(link[0], link[1], **params)
210
awlaned8e6b8e2022-05-16 23:49:56 -0500211 faces = {}
212 try:
213 items = config.items('faces')
matianxing19921f07a832024-09-25 12:32:38 -0500214 debug("Faces\n")
awlaned8e6b8e2022-05-16 23:49:56 -0500215 for item in items:
216 face_a, face_b = item[0].split(':')
matianxing19921f07a832024-09-25 12:32:38 -0500217 debug(str(item)+"\n")
awlaned8e6b8e2022-05-16 23:49:56 -0500218 cost = -1
219 for param in item[1].split(' '):
220 if param.split("=")[0] == 'cost':
221 cost = param.split("=")[1]
222 face_info = (face_b, int(cost))
223 if face_a not in faces:
224 faces[face_a] = [face_info]
225 else:
226 faces[face_a].append(face_info)
227 except configparser.NoSectionError:
matianxing19921f07a832024-09-25 12:32:38 -0500228 debug("Faces section is optional\n")
awlaned8e6b8e2022-05-16 23:49:56 -0500229 pass
230
231 return (topo, faces)
Alexander Laneea2d5d62019-10-04 16:48:52 -0500232
matianxing19921f07a832024-09-25 12:32:38 -0500233 def processMobility(self, topoFile):
234 config = configparser.ConfigParser(delimiters=' ')
235 config.read(topoFile)
236
237 try:
238 debug("Mobility\n")
239 items = config.items('mobility')
240 if len(items) == 0:
241 return
242 params = {}
243 for param in items[0][1].split(' '):
244 if param == "_":
245 continue
246 key = param.split('=')[0]
247 value = param.split('=')[1]
248 params[key] = value
249 params = self.convert_params(params)
250 self.startMobilityModel(**params)
251
252 items = config.items('stations')
253 debug("Start Mobility\n")
254 for item in items:
255 debug(str(item[0].split(':'))+"\n")
256 name = item[0].split(':')[0]
257 params = {}
258 for param in item[1].split(' '):
259 if param == "_":
260 continue
261 key = param.split('=')[0]
262 value = param.split('=')[1]
263 if key in ['range']:
264 value = int(value)
265 params[key] = value
266 # by default, nodes are moving
267 if "moving" not in params or params["moving"] == "True":
268 self.net.mobility(self.net[name],'start', time=0, **params)
269
270 except configparser.NoSectionError:
271 debug("Mobility section is optional\n")
272
awlane1922ef42022-08-01 21:29:59 -0500273 def startMobility(self, max_x=1000, max_y=1000, plot=True, **kwargs):
Alexander Laneea2d5d62019-10-04 16:48:52 -0500274 """ Method to run a basic mobility setup on your net"""
awlane1922ef42022-08-01 21:29:59 -0500275 if plot:
276 self.net.plotGraph(max_x=max_x, max_y=max_y)
277 self.net.startMobility(max_x=max_x, max_y=max_y, **kwargs)
Alexander Laneea2d5d62019-10-04 16:48:52 -0500278
awlane1922ef42022-08-01 21:29:59 -0500279 def startMobilityModel(self, max_x=1000, max_y=1000, plot=True, **kwargs):
Alexander Laneea2d5d62019-10-04 16:48:52 -0500280 """ Method to run a mobility model on your net until exited"""
awlane1922ef42022-08-01 21:29:59 -0500281 if plot:
282 self.net.plotGraph(max_x=max_x, max_y=max_y)
283 self.net.setMobilityModel(max_x=max_x, max_y=max_y, **kwargs)
awlaned8e6b8e2022-05-16 23:49:56 -0500284
285 def getWifiInterfaceDelay(self, node, interface=None):
286 """Method to return the configured tc delay of a wifi node's interface as a float"""
287 if not interface:
288 wifi_interface = "{}-wlan0".format(node.name)
289 else:
290 wifi_interface = interface
291 tc_output = node.cmd("tc qdisc show dev {}".format(wifi_interface))
292 for line in tc_output.splitlines():
293 if "qdisc netem 10:" in line:
294 split_line = line.split(" ")
295 for index in range(0, len(split_line)):
296 if split_line[index] == "delay":
297 return float(split_line[index + 1][:-2])
298 return 0.0
299
300 def setupFaces(self, faces_to_create=None):
301 """
302 Method to create unicast faces between nodes connected by an AP based on name or faces
303 between connected nodes; Returns dict- {node: (other node name, other node IP, other
304 node's delay as int)}. This is intended to pass to the NLSR helper via the faceDict param
305 """
306 if not faces_to_create:
307 faces_to_create = self.faces_to_create
308 # (nodeName, IP, delay as int)
309 # list of tuples
310 created_faces = dict()
311 batch_faces = dict()
312 for nodeAname in faces_to_create.keys():
313 if not nodeAname in batch_faces.keys():
314 batch_faces[nodeAname] = []
315 for nodeBname, faceCost in faces_to_create[nodeAname]:
316 if not nodeBname in batch_faces.keys():
317 batch_faces[nodeBname] = []
318 nodeA = self.net[nodeAname]
319 nodeB = self.net[nodeBname]
320 if nodeA.connectionsTo(nodeB):
321 best_interface = None
322 delay = None
323 for interface in nodeA.connectionsTo(nodeB):
324 interface_delay = self.getInterfaceDelay(nodeA, interface[0])
325 if not delay or int(interface_delay) < delay:
326 best_interface = interface
327 faceAIP = best_interface[0].IP()
328 faceBIP = best_interface[1].IP()
329 # Node delay should be symmetrical
330 nodeDelay = int(self.getInterfaceDelay(nodeA, best_interface[0]))
331 else:
332 # Default IP will be the primary wireless interface, unclear if multiple wireless
333 # interfaces should be handled
334 faceAIP = nodeA.IP()
335 faceBIP = nodeB.IP()
336 nodeADelay = self.getWifiInterfaceDelay(nodeA)
337 nodeBDelay = self.getWifiInterfaceDelay(nodeB)
338 nodeDelay = nodeADelay + nodeBDelay
339
340 if not faceCost == -1:
341 nodeALink = (nodeA.name, faceAIP, faceCost)
342 nodeBLink = (nodeB.name, faceBIP, faceCost)
343 else:
344 nodeALink = (nodeA.name, faceAIP, nodeDelay)
345 nodeBLink = (nodeB.name, faceBIP, nodeDelay)
346
347 batch_faces[nodeAname].append([faceBIP, "udp", True])
348 batch_faces[nodeBname].append([faceAIP, "udp", True])
349
350 if nodeA not in created_faces:
351 created_faces[nodeA] = [nodeBLink]
352 else:
353 created_faces[nodeA].append(nodeBLink)
354 if nodeB not in created_faces:
355 created_faces[nodeB] = [nodeALink]
356 else:
357 created_faces[nodeB].append(nodeALink)
358 for station_name in batch_faces.keys():
359 self.nfdcBatchProcessing(self.net[station_name], batch_faces[station_name])
matianxing19921f07a832024-09-25 12:32:38 -0500360 return created_faces
361
362class MinindnAdhoc(MinindnWifi):
363 """
364 Class for ad hoc network of Mininet-wifi
365 Topology example: topologies/wifi/adhoc-topology.conf
366 The link type is wmediumd by default
367 """
368 def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False,
369 link=wmediumd, workDir=None, **mininetParams):
370 # call parent constructor
371 super().__init__(parser, topo, topoFile, noTopo, link, workDir, **mininetParams)
372 if not self.topoFile:
373 info("Without topoFile, ad hoc links are not added to the network, and you need to"
374 " add them manually. The example topology file can be found in"
375 " topologies/wifi/adhoc-topology.conf\n")
376 else:
377 self.addAdhocLinks()
378
379 @staticmethod
380 def parseArgs(parent):
381 parser = argparse.ArgumentParser(prog='minindn-adhoc', parents=[parent], add_help=False)
382
383 # nargs='?' required here since optional argument
384 parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/adhoc-topology.conf',
385 help='If no template_file is given, topologies/wifi/adhoc-topology.conf will be used.')
386
387 parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
388 help='Specify the working directory; default is /tmp/minindn')
389
390 parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
391 help='Specify the full path destination folder where experiment results will be moved')
392
matianxing19921f07a832024-09-25 12:32:38 -0500393 parser.add_argument('--ifb',action='store_true',dest='ifb',default=False,
394 help='Simulate delay on receiver-side by use of virtual IFB devices (see docs)')
395
396 return parser
397
398 @staticmethod
399 def processTopo(topoFile):
400 config = configparser.ConfigParser(delimiters=' ')
401 config.read(topoFile)
402 topo = Topo_WiFi()
403
404 items = config.items('stations')
405 debug("Stations\n")
406 id = 0
407 for item in items:
408 debug(str(item[0].split(':'))+"\n")
409 name = item[0].split(':')[0]
410 params = {}
411 for param in item[1].split(' '):
412 if param == "_":
413 continue
414 key = param.split('=')[0]
415 value = param.split('=')[1]
matianxing1992b95d1942025-01-28 15:04:02 -0600416 if key in ['range', 'min_v', 'max_v']:
matianxing19921f07a832024-09-25 12:32:38 -0500417 value = int(value)
418 params[key] = value
419 # ip6 address for each station using id
420 if 'ip6' not in params:
421 params['ip6'] = 'fe80::{}'.format(id)
422 topo.addStation(name, **params)
423 id += 1
424
425 faces = {}
426 try:
427 items = config.items('faces')
428 debug("Faces\n")
429 for item in items:
430 face_a, face_b = item[0].split(':')
431 debug(str(item)+"\n")
432 cost = -1
433 for param in item[1].split(' '):
434 if param.split("=")[0] == 'cost':
435 cost = param.split("=")[1]
436 face_info = (face_b, int(cost))
437 if face_a not in faces:
438 faces[face_a] = [face_info]
439 else:
440 faces[face_a].append(face_info)
441 except configparser.NoSectionError:
442 debug("Faces section is optional\n")
443 pass
444
445 return (topo, faces)
446
447 """Add adhoc links to the network"""
448 # In the topo.py, all the links require two stations, but in adhoc topology, we need to add links for all the nodes.
449 def addAdhocLinks(self):
450 config = configparser.ConfigParser(delimiters=' ')
451 config.read(self.topoFile)
452
453 # read adhoc network parameters
454 # return if adhoc network is not defined
455 if 'adhocNetwork' not in config.sections():
456 info("Adhoc network is not defined in the topology file\n")
457 return
458
459 adhoc_params = config.items('adhocNetwork')
460 params = {}
461 for param in adhoc_params[0][1].split(' '):
462 if param == "_":
463 continue
464 key = param.split('=')[0]
465 value = param.split('=')[1]
466 if key in ['range']:
467 value = int(value)
468 params[key] = value
469 params = self.convert_params(params)
470
471 # return if ssid, mode, channel not defined in params
472 if 'ssid' not in params or 'mode' not in params or 'channel' not in params:
473 info("ssid, mode, channel not defined in adhoc network parameters\n")
474 return
475 networkParams = params
476
477 # add adhoc links
478 debug("Links\n")
479 items = config.items('stations')
480 for item in items:
481 debug(str(item[0].split(':'))+"\n")
482 name = item[0].split(':')[0]
483 params = {}
484 for param in item[1].split(' '):
485 if param == "_":
486 continue
487 key = param.split('=')[0]
488 value = param.split('=')[1]
489 params[key] = value
490 params = self.convert_params(params)
491 # replace | with space in bitrates because space is not allowed in the configuration file
492 if 'bitrates' in params:
493 params['bitrates'] = params['bitrates'].replace("|", " ")
494 self.net.addLink(name, cls=adhoc, intf='{}-wlan0'.format(name), **networkParams, **params)