blob: c2fed3e098d53101f6cc251ccfc132b8319b459c [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,
58 link=TCLink, **mininetParams):
59 """
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
Italo Valcyccd85b12020-07-24 12:35:20 -050073 Minindn.workDir = os.path.abspath(self.args.workDir)
74 Minindn.resultDir = self.args.resultDir
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050075
76 if not topoFile:
77 # Args has default topology if none specified
78 self.topoFile = self.args.topoFile
79 else:
80 self.topoFile = topoFile
81
Alex Lane407c5f02021-03-09 22:13:23 -060082 if topo is None and not noTopo:
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050083 try:
84 info('Using topology file {}\n'.format(self.topoFile))
85 self.topo = self.processTopo(self.topoFile)
86 except configparser.NoSectionError as e:
87 info('Error reading config file: {}\n'.format(e))
88 sys.exit(1)
89 else:
90 self.topo = topo
91
Alex Lane407c5f02021-03-09 22:13:23 -060092 if not noTopo:
93 self.net = Mininet(topo=self.topo, link=link, **mininetParams)
94 else:
95 self.net = Mininet(link=link, **mininetParams)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050096
Alex Lane407c5f02021-03-09 22:13:23 -060097 self.initParams(self.net.hosts)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050098
99 self.cleanups = []
100
101 if not self.net.switches:
102 self.ethernetPairConnectivity()
103
104 try:
Saurab Dulal576a4192020-08-25 00:55:22 -0500105 process = Popen(['ndnsec-get-default', '-k'], stdout=PIPE, stderr=PIPE)
106 output, error = process.communicate()
107 if process.returncode == 0:
Alex Lane407c5f02021-03-09 22:13:23 -0600108 Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in output
109 info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
Saurab Dulal576a4192020-08-25 00:55:22 -0500110 else:
Alex Lane407c5f02021-03-09 22:13:23 -0600111 debug(error)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500112 except:
113 pass
114
115 @staticmethod
116 def parseArgs(parent):
117 parser = argparse.ArgumentParser(prog='minindn', parents=[parent], add_help=False)
118
119 # nargs='?' required here since optional argument
120 parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/default-topology.conf',
121 help='If no template_file is given, topologies/default-topology.conf will be used.')
122
123 parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
124 help='Specify the working directory; default is /tmp/minindn')
125
126 parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
127 help='Specify the full path destination folder where experiment results will be moved')
128
129 return parser
130
131 def ethernetPairConnectivity(self):
132 ndnNetBase = '10.0.0.0'
133 interfaces = []
134 for host in self.net.hosts:
135 for intf in host.intfList():
136 link = intf.link
137 node1, node2 = link.intf1.node, link.intf2.node
138
139 if isinstance(node1, Switch) or isinstance(node2, Switch):
140 continue
141
142 if link.intf1 not in interfaces and link.intf2 not in interfaces:
143 interfaces.append(link.intf1)
144 interfaces.append(link.intf2)
145 node1.setIP(ipStr(ipParse(ndnNetBase) + 1) + '/30', intf=link.intf1)
146 node2.setIP(ipStr(ipParse(ndnNetBase) + 2) + '/30', intf=link.intf2)
147 ndnNetBase = ipStr(ipParse(ndnNetBase) + 4)
148
149 @staticmethod
150 def processTopo(topoFile):
Chad Cothraneef6ee82021-03-22 11:38:33 -0500151 config = configparser.ConfigParser(delimiters=' ', allow_no_value=True)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500152 config.read(topoFile)
153 topo = Topo()
154
155 items = config.items('nodes')
dulalsaurab5c79db02020-02-27 06:04:56 +0000156 coordinates = []
157
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500158 for item in items:
159 name = item[0].split(':')[0]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500160 params = {}
Saurab Dulalb4286602021-06-11 12:10:14 -0700161 if item[1]:
162 if all (x in item[1] for x in ['radius', 'angle']) and item[1] in coordinates:
163 error("FATAL: Duplicate Coordinate, \'{}\' used by multiple nodes\n" \
164 .format(item[1]))
165 sys.exit(1)
166 coordinates.append(item[1])
167
168 for param in item[1].split(' '):
169 if param == '_':
170 continue
171 params[param.split('=')[0]] = param.split('=')[1]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500172
173 topo.addHost(name, params=params)
174
175 try:
176 items = config.items('switches')
177 for item in items:
178 name = item[0].split(':')[0]
179 topo.addSwitch(name)
180 except configparser.NoSectionError:
181 # Switches are optional
182 pass
183
184 items = config.items('links')
185 for item in items:
186 link = item[0].split(':')
187
188 params = {}
189 for param in item[1].split(' '):
190 key = param.split('=')[0]
191 value = param.split('=')[1]
192 if key in ['bw', 'jitter', 'max_queue_size']:
193 value = int(value)
194 if key == 'loss':
195 value = float(value)
196 params[key] = value
197
198 topo.addLink(link[0], link[1], **params)
199
200 return topo
201
202 def start(self):
203 self.net.start()
204 time.sleep(3)
205
206 def stop(self):
207 for cleanup in self.cleanups:
208 cleanup()
209 self.net.stop()
210
Italo Valcyccd85b12020-07-24 12:35:20 -0500211 if Minindn.resultDir is not None:
212 info("Moving results to \'{}\'\n".format(Minindn.resultDir))
213 os.system("mkdir -p {}".format(Minindn.resultDir))
214 for file in glob.glob('{}/*'.format(Minindn.workDir)):
215 shutil.move(file, Minindn.resultDir)
phmollad8d37e2020-03-03 12:53:08 +0100216
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500217 @staticmethod
218 def cleanUp():
219 devnull = open(os.devnull, 'w')
220 call('nfd-stop', stdout=devnull, stderr=devnull)
221 call('mn --clean'.split(), stdout=devnull, stderr=devnull)
222
223 @staticmethod
224 def verifyDependencies():
225 """Prevent MiniNDN from running without necessary dependencies"""
226 dependencies = ['nfd', 'nlsr', 'infoedit', 'ndnping', 'ndnpingserver']
227 devnull = open(os.devnull, 'w')
228 # Checks that each program is in the system path
229 for program in dependencies:
230 if call(['which', program], stdout=devnull):
231 error('{} is missing from the system path! Exiting...\n'.format(program))
232 sys.exit(1)
233 devnull.close()
234
235 @staticmethod
236 def sleep(seconds):
237 # sleep is not required if ndn-cxx is using in-memory keychain
238 if not Minindn.ndnSecurityDisabled:
239 time.sleep(seconds)
Alexander Laneea2d5d62019-10-04 16:48:52 -0500240
241 @staticmethod
242 def handleException():
Saurab Dulalb4286602021-06-11 12:10:14 -0700243 """Utility method to perform cleanup steps and exit after catching exception"""
Alexander Laneea2d5d62019-10-04 16:48:52 -0500244 Minindn.cleanUp()
245 info(format_exc())
Alex Lane407c5f02021-03-09 22:13:23 -0600246 exit(1)
247
248 def initParams(self, nodes):
Saurab Dulalb4286602021-06-11 12:10:14 -0700249 """Initialize Mini-NDN parameters for array of nodes"""
Alex Lane407c5f02021-03-09 22:13:23 -0600250 for host in nodes:
251 if 'params' not in host.params:
252 host.params['params'] = {}
253 host.params['params']['workDir'] = Minindn.workDir
254 homeDir = '{}/{}'.format(Minindn.workDir, host.name)
255 host.params['params']['homeDir'] = homeDir
256 host.cmd('mkdir -p {}'.format(homeDir))
257 host.cmd('export HOME={} && cd ~'.format(homeDir))