blob: 10dbcb4e859f787e16ab307b304267ce6a2d69df [file] [log] [blame]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -05001# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2#
Saurab Dulalb4286602021-06-11 12:10:14 -07003# Copyright (C) 2015-2021, The University of Memphis,
Ashlesh Gawande6c86e302019-09-17 22:27:05 -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
24import argparse
25import sys
26import time
27import os
28import configparser
Saurab Dulal576a4192020-08-25 00:55:22 -050029from subprocess import call, Popen, PIPE
phmollad8d37e2020-03-03 12:53:08 +010030import shutil
31import glob
Alexander Laneea2d5d62019-10-04 16:48:52 -050032from traceback import format_exc
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050033
34from mininet.topo import Topo
35from mininet.net import Mininet
36from mininet.link import TCLink
37from mininet.node import Switch
38from mininet.util import ipStr, ipParse
Alexander Laneea2d5d62019-10-04 16:48:52 -050039from mininet.log import info, debug, error
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050040
phmollad8d37e2020-03-03 12:53:08 +010041
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050042class Minindn(object):
Saurab Dulalb4286602021-06-11 12:10:14 -070043 """
44 This class provides the following features to the user:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050045 1) Wrapper around Mininet object with option to pass topology directly
46 1.1) Users can pass custom argument parser to extend the default on here
47 2) Parses the topology file given via command line if user does not pass a topology object
48 3) Provides way to stop Mini-NDN via stop
49 3.1) Applications register their clean up function with this class
50 4) Sets IPs on neighbors for connectivity required in a switch-less topology
51 5) Some other utility functions
52 """
53 ndnSecurityDisabled = False
Italo Valcyccd85b12020-07-24 12:35:20 -050054 workDir = '/tmp/minindn'
55 resultDir = None
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050056
Saurab Dulalb4286602021-06-11 12:10:14 -070057 def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False,
awlanea169f572022-04-08 17:21:29 -050058 link=TCLink, workDir=None, **mininetParams):
Saurab Dulalb4286602021-06-11 12:10:14 -070059 """
60 Create MiniNDN object
61 :param parser: Parent parser of Mini-NDN parser
62 :param topo: Mininet topo object (optional)
63 :param topoFile: Mininet topology file location (optional)
64 :param noTopo: Allows specification of topology after network object is
65 initialized (optional)
66 :param link: Allows specification of default Mininet link type for connections between
67 nodes (optional)
68 :param mininetParams: Any params to pass to Mininet
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050069 """
70 self.parser = Minindn.parseArgs(parser)
71 self.args = self.parser.parse_args()
72
awlanea169f572022-04-08 17:21:29 -050073 if not workDir:
74 Minindn.workDir = os.path.abspath(self.args.workDir)
75 else:
76 Minindn.workDir = os.path.abspath(workDir)
77
Italo Valcyccd85b12020-07-24 12:35:20 -050078 Minindn.resultDir = self.args.resultDir
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050079
80 if not topoFile:
81 # Args has default topology if none specified
82 self.topoFile = self.args.topoFile
83 else:
84 self.topoFile = topoFile
85
Alex Lane407c5f02021-03-09 22:13:23 -060086 if topo is None and not noTopo:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050087 try:
88 info('Using topology file {}\n'.format(self.topoFile))
89 self.topo = self.processTopo(self.topoFile)
90 except configparser.NoSectionError as e:
91 info('Error reading config file: {}\n'.format(e))
92 sys.exit(1)
93 else:
94 self.topo = topo
95
Alex Lane407c5f02021-03-09 22:13:23 -060096 if not noTopo:
97 self.net = Mininet(topo=self.topo, link=link, **mininetParams)
98 else:
99 self.net = Mininet(link=link, **mininetParams)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500100
Alex Lane407c5f02021-03-09 22:13:23 -0600101 self.initParams(self.net.hosts)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500102
103 self.cleanups = []
104
105 if not self.net.switches:
106 self.ethernetPairConnectivity()
107
108 try:
Saurab Dulal576a4192020-08-25 00:55:22 -0500109 process = Popen(['ndnsec-get-default', '-k'], stdout=PIPE, stderr=PIPE)
110 output, error = process.communicate()
111 if process.returncode == 0:
awlanec32a07b2022-04-19 14:53:41 -0500112 Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in output.decode("utf-8")
Alex Lane407c5f02021-03-09 22:13:23 -0600113 info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
Saurab Dulal576a4192020-08-25 00:55:22 -0500114 else:
Alex Lane407c5f02021-03-09 22:13:23 -0600115 debug(error)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500116 except:
117 pass
118
119 @staticmethod
120 def parseArgs(parent):
121 parser = argparse.ArgumentParser(prog='minindn', parents=[parent], add_help=False)
122
123 # nargs='?' required here since optional argument
124 parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/default-topology.conf',
125 help='If no template_file is given, topologies/default-topology.conf will be used.')
126
127 parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
128 help='Specify the working directory; default is /tmp/minindn')
129
130 parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
131 help='Specify the full path destination folder where experiment results will be moved')
132
133 return parser
134
135 def ethernetPairConnectivity(self):
136 ndnNetBase = '10.0.0.0'
137 interfaces = []
138 for host in self.net.hosts:
139 for intf in host.intfList():
140 link = intf.link
141 node1, node2 = link.intf1.node, link.intf2.node
142
143 if isinstance(node1, Switch) or isinstance(node2, Switch):
144 continue
145
146 if link.intf1 not in interfaces and link.intf2 not in interfaces:
147 interfaces.append(link.intf1)
148 interfaces.append(link.intf2)
149 node1.setIP(ipStr(ipParse(ndnNetBase) + 1) + '/30', intf=link.intf1)
150 node2.setIP(ipStr(ipParse(ndnNetBase) + 2) + '/30', intf=link.intf2)
151 ndnNetBase = ipStr(ipParse(ndnNetBase) + 4)
152
153 @staticmethod
154 def processTopo(topoFile):
Chad Cothraneef6ee82021-03-22 11:38:33 -0500155 config = configparser.ConfigParser(delimiters=' ', allow_no_value=True)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500156 config.read(topoFile)
157 topo = Topo()
158
159 items = config.items('nodes')
dulalsaurab5c79db02020-02-27 06:04:56 +0000160 coordinates = []
161
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500162 for item in items:
163 name = item[0].split(':')[0]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500164 params = {}
Saurab Dulalb4286602021-06-11 12:10:14 -0700165 if item[1]:
166 if all (x in item[1] for x in ['radius', 'angle']) and item[1] in coordinates:
167 error("FATAL: Duplicate Coordinate, \'{}\' used by multiple nodes\n" \
168 .format(item[1]))
169 sys.exit(1)
170 coordinates.append(item[1])
171
172 for param in item[1].split(' '):
173 if param == '_':
174 continue
175 params[param.split('=')[0]] = param.split('=')[1]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500176
177 topo.addHost(name, params=params)
178
179 try:
180 items = config.items('switches')
181 for item in items:
182 name = item[0].split(':')[0]
183 topo.addSwitch(name)
184 except configparser.NoSectionError:
185 # Switches are optional
186 pass
187
188 items = config.items('links')
189 for item in items:
190 link = item[0].split(':')
191
192 params = {}
193 for param in item[1].split(' '):
194 key = param.split('=')[0]
195 value = param.split('=')[1]
196 if key in ['bw', 'jitter', 'max_queue_size']:
197 value = int(value)
198 if key == 'loss':
199 value = float(value)
200 params[key] = value
201
202 topo.addLink(link[0], link[1], **params)
203
204 return topo
205
206 def start(self):
207 self.net.start()
208 time.sleep(3)
209
210 def stop(self):
211 for cleanup in self.cleanups:
212 cleanup()
213 self.net.stop()
214
Italo Valcyccd85b12020-07-24 12:35:20 -0500215 if Minindn.resultDir is not None:
216 info("Moving results to \'{}\'\n".format(Minindn.resultDir))
217 os.system("mkdir -p {}".format(Minindn.resultDir))
218 for file in glob.glob('{}/*'.format(Minindn.workDir)):
219 shutil.move(file, Minindn.resultDir)
phmollad8d37e2020-03-03 12:53:08 +0100220
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500221 @staticmethod
222 def cleanUp():
223 devnull = open(os.devnull, 'w')
224 call('nfd-stop', stdout=devnull, stderr=devnull)
225 call('mn --clean'.split(), stdout=devnull, stderr=devnull)
226
227 @staticmethod
228 def verifyDependencies():
229 """Prevent MiniNDN from running without necessary dependencies"""
230 dependencies = ['nfd', 'nlsr', 'infoedit', 'ndnping', 'ndnpingserver']
231 devnull = open(os.devnull, 'w')
232 # Checks that each program is in the system path
233 for program in dependencies:
234 if call(['which', program], stdout=devnull):
235 error('{} is missing from the system path! Exiting...\n'.format(program))
236 sys.exit(1)
237 devnull.close()
238
239 @staticmethod
240 def sleep(seconds):
241 # sleep is not required if ndn-cxx is using in-memory keychain
242 if not Minindn.ndnSecurityDisabled:
243 time.sleep(seconds)
Alexander Laneea2d5d62019-10-04 16:48:52 -0500244
245 @staticmethod
246 def handleException():
Saurab Dulalb4286602021-06-11 12:10:14 -0700247 """Utility method to perform cleanup steps and exit after catching exception"""
Alexander Laneea2d5d62019-10-04 16:48:52 -0500248 Minindn.cleanUp()
249 info(format_exc())
Alex Lane407c5f02021-03-09 22:13:23 -0600250 exit(1)
251
252 def initParams(self, nodes):
Saurab Dulalb4286602021-06-11 12:10:14 -0700253 """Initialize Mini-NDN parameters for array of nodes"""
Alex Lane407c5f02021-03-09 22:13:23 -0600254 for host in nodes:
255 if 'params' not in host.params:
256 host.params['params'] = {}
257 host.params['params']['workDir'] = Minindn.workDir
258 homeDir = '{}/{}'.format(Minindn.workDir, host.name)
259 host.params['params']['homeDir'] = homeDir
260 host.cmd('mkdir -p {}'.format(homeDir))
261 host.cmd('export HOME={} && cd ~'.format(homeDir))