blob: d6eaffc14c653ae3703156e4103449709b24f9c8 [file] [log] [blame]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -05001# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2#
Saurab Dulal576a4192020-08-25 00:55:22 -05003# Copyright (C) 2015-2020, 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):
43 """ This class provides the following features to the user:
44 1) Wrapper around Mininet object with option to pass topology directly
45 1.1) Users can pass custom argument parser to extend the default on here
46 2) Parses the topology file given via command line if user does not pass a topology object
47 3) Provides way to stop Mini-NDN via stop
48 3.1) Applications register their clean up function with this class
49 4) Sets IPs on neighbors for connectivity required in a switch-less topology
50 5) Some other utility functions
51 """
52 ndnSecurityDisabled = False
Italo Valcyccd85b12020-07-24 12:35:20 -050053 workDir = '/tmp/minindn'
54 resultDir = None
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050055
Alex Lane407c5f02021-03-09 22:13:23 -060056 def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False, link=TCLink, **mininetParams):
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050057 """Create MiniNDN object
58 parser: Parent parser of Mini-NDN parser
59 topo: Mininet topo object (optional)
60 topoFile: Mininet topology file location (optional)
Alex Lane407c5f02021-03-09 22:13:23 -060061 noTopo: Allows specification of topology after network object is initialized (optional)
62 link: Allows specification of default Mininet link type for connections between nodes (optional)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050063 mininetParams: Any params to pass to Mininet
64 """
65 self.parser = Minindn.parseArgs(parser)
66 self.args = self.parser.parse_args()
67
Italo Valcyccd85b12020-07-24 12:35:20 -050068 Minindn.workDir = os.path.abspath(self.args.workDir)
69 Minindn.resultDir = self.args.resultDir
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050070
71 if not topoFile:
72 # Args has default topology if none specified
73 self.topoFile = self.args.topoFile
74 else:
75 self.topoFile = topoFile
76
Alex Lane407c5f02021-03-09 22:13:23 -060077 if topo is None and not noTopo:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050078 try:
79 info('Using topology file {}\n'.format(self.topoFile))
80 self.topo = self.processTopo(self.topoFile)
81 except configparser.NoSectionError as e:
82 info('Error reading config file: {}\n'.format(e))
83 sys.exit(1)
84 else:
85 self.topo = topo
86
Alex Lane407c5f02021-03-09 22:13:23 -060087 if not noTopo:
88 self.net = Mininet(topo=self.topo, link=link, **mininetParams)
89 else:
90 self.net = Mininet(link=link, **mininetParams)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050091
Alex Lane407c5f02021-03-09 22:13:23 -060092 self.initParams(self.net.hosts)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050093
94 self.cleanups = []
95
96 if not self.net.switches:
97 self.ethernetPairConnectivity()
98
99 try:
Saurab Dulal576a4192020-08-25 00:55:22 -0500100 process = Popen(['ndnsec-get-default', '-k'], stdout=PIPE, stderr=PIPE)
101 output, error = process.communicate()
102 if process.returncode == 0:
Alex Lane407c5f02021-03-09 22:13:23 -0600103 Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in output
104 info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
Saurab Dulal576a4192020-08-25 00:55:22 -0500105 else:
Alex Lane407c5f02021-03-09 22:13:23 -0600106 debug(error)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500107 except:
108 pass
109
110 @staticmethod
111 def parseArgs(parent):
112 parser = argparse.ArgumentParser(prog='minindn', parents=[parent], add_help=False)
113
114 # nargs='?' required here since optional argument
115 parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/default-topology.conf',
116 help='If no template_file is given, topologies/default-topology.conf will be used.')
117
118 parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
119 help='Specify the working directory; default is /tmp/minindn')
120
121 parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
122 help='Specify the full path destination folder where experiment results will be moved')
123
124 return parser
125
126 def ethernetPairConnectivity(self):
127 ndnNetBase = '10.0.0.0'
128 interfaces = []
129 for host in self.net.hosts:
130 for intf in host.intfList():
131 link = intf.link
132 node1, node2 = link.intf1.node, link.intf2.node
133
134 if isinstance(node1, Switch) or isinstance(node2, Switch):
135 continue
136
137 if link.intf1 not in interfaces and link.intf2 not in interfaces:
138 interfaces.append(link.intf1)
139 interfaces.append(link.intf2)
140 node1.setIP(ipStr(ipParse(ndnNetBase) + 1) + '/30', intf=link.intf1)
141 node2.setIP(ipStr(ipParse(ndnNetBase) + 2) + '/30', intf=link.intf2)
142 ndnNetBase = ipStr(ipParse(ndnNetBase) + 4)
143
144 @staticmethod
145 def processTopo(topoFile):
Chad Cothraneef6ee82021-03-22 11:38:33 -0500146 config = configparser.ConfigParser(delimiters=' ', allow_no_value=True)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500147 config.read(topoFile)
148 topo = Topo()
149
150 items = config.items('nodes')
dulalsaurab5c79db02020-02-27 06:04:56 +0000151 coordinates = []
152
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500153 for item in items:
154 name = item[0].split(':')[0]
Chad Cothraneef6ee82021-03-22 11:38:33 -0500155 if item[1] in coordinates and item[1] != '_':
phmollad8d37e2020-03-03 12:53:08 +0100156 error("FATAL: Duplicate Coordinate, \'{}\' used by multiple nodes\n" \
dulalsaurab5c79db02020-02-27 06:04:56 +0000157 .format(item[1]))
158 sys.exit(1)
159 coordinates.append(item[1])
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500160
161 params = {}
162 for param in item[1].split(' '):
163 if param == '_':
164 continue
165 params[param.split('=')[0]] = param.split('=')[1]
166
167 topo.addHost(name, params=params)
168
169 try:
170 items = config.items('switches')
171 for item in items:
172 name = item[0].split(':')[0]
173 topo.addSwitch(name)
174 except configparser.NoSectionError:
175 # Switches are optional
176 pass
177
178 items = config.items('links')
179 for item in items:
180 link = item[0].split(':')
181
182 params = {}
183 for param in item[1].split(' '):
184 key = param.split('=')[0]
185 value = param.split('=')[1]
186 if key in ['bw', 'jitter', 'max_queue_size']:
187 value = int(value)
188 if key == 'loss':
189 value = float(value)
190 params[key] = value
191
192 topo.addLink(link[0], link[1], **params)
193
194 return topo
195
196 def start(self):
197 self.net.start()
198 time.sleep(3)
199
200 def stop(self):
201 for cleanup in self.cleanups:
202 cleanup()
203 self.net.stop()
204
Italo Valcyccd85b12020-07-24 12:35:20 -0500205 if Minindn.resultDir is not None:
206 info("Moving results to \'{}\'\n".format(Minindn.resultDir))
207 os.system("mkdir -p {}".format(Minindn.resultDir))
208 for file in glob.glob('{}/*'.format(Minindn.workDir)):
209 shutil.move(file, Minindn.resultDir)
phmollad8d37e2020-03-03 12:53:08 +0100210
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500211 @staticmethod
212 def cleanUp():
213 devnull = open(os.devnull, 'w')
214 call('nfd-stop', stdout=devnull, stderr=devnull)
215 call('mn --clean'.split(), stdout=devnull, stderr=devnull)
216
217 @staticmethod
218 def verifyDependencies():
219 """Prevent MiniNDN from running without necessary dependencies"""
220 dependencies = ['nfd', 'nlsr', 'infoedit', 'ndnping', 'ndnpingserver']
221 devnull = open(os.devnull, 'w')
222 # Checks that each program is in the system path
223 for program in dependencies:
224 if call(['which', program], stdout=devnull):
225 error('{} is missing from the system path! Exiting...\n'.format(program))
226 sys.exit(1)
227 devnull.close()
228
229 @staticmethod
230 def sleep(seconds):
231 # sleep is not required if ndn-cxx is using in-memory keychain
232 if not Minindn.ndnSecurityDisabled:
233 time.sleep(seconds)
Alexander Laneea2d5d62019-10-04 16:48:52 -0500234
235 @staticmethod
236 def handleException():
237 'Utility method to perform cleanup steps and exit after catching exception'
238 Minindn.cleanUp()
239 info(format_exc())
Alex Lane407c5f02021-03-09 22:13:23 -0600240 exit(1)
241
242 def initParams(self, nodes):
243 '''Initialize Mini-NDN parameters for array of nodes'''
244 for host in nodes:
245 if 'params' not in host.params:
246 host.params['params'] = {}
247 host.params['params']['workDir'] = Minindn.workDir
248 homeDir = '{}/{}'.format(Minindn.workDir, host.name)
249 host.params['params']['homeDir'] = homeDir
250 host.cmd('mkdir -p {}'.format(homeDir))
251 host.cmd('export HOME={} && cd ~'.format(homeDir))