blob: 7b9c65826f1608e745111c49590719b2e3739690 [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
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050032
33from mininet.topo import Topo
34from mininet.net import Mininet
35from mininet.link import TCLink
36from mininet.node import Switch
37from mininet.util import ipStr, ipParse
38from mininet.log import info, error
39
phmollad8d37e2020-03-03 12:53:08 +010040
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050041class Minindn(object):
42 """ This class provides the following features to the user:
43 1) Wrapper around Mininet object with option to pass topology directly
44 1.1) Users can pass custom argument parser to extend the default on here
45 2) Parses the topology file given via command line if user does not pass a topology object
46 3) Provides way to stop Mini-NDN via stop
47 3.1) Applications register their clean up function with this class
48 4) Sets IPs on neighbors for connectivity required in a switch-less topology
49 5) Some other utility functions
50 """
51 ndnSecurityDisabled = False
Italo Valcyccd85b12020-07-24 12:35:20 -050052 workDir = '/tmp/minindn'
53 resultDir = None
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050054
55 def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, **mininetParams):
56 """Create MiniNDN object
57 parser: Parent parser of Mini-NDN parser
58 topo: Mininet topo object (optional)
59 topoFile: Mininet topology file location (optional)
60 mininetParams: Any params to pass to Mininet
61 """
62 self.parser = Minindn.parseArgs(parser)
63 self.args = self.parser.parse_args()
64
Italo Valcyccd85b12020-07-24 12:35:20 -050065 Minindn.workDir = os.path.abspath(self.args.workDir)
66 Minindn.resultDir = self.args.resultDir
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050067
68 if not topoFile:
69 # Args has default topology if none specified
70 self.topoFile = self.args.topoFile
71 else:
72 self.topoFile = topoFile
73
74 if topo is None:
75 try:
76 info('Using topology file {}\n'.format(self.topoFile))
77 self.topo = self.processTopo(self.topoFile)
78 except configparser.NoSectionError as e:
79 info('Error reading config file: {}\n'.format(e))
80 sys.exit(1)
81 else:
82 self.topo = topo
83
84 self.net = Mininet(topo=self.topo, link=TCLink, **mininetParams)
85
86 for host in self.net.hosts:
87 if 'params' not in host.params:
88 host.params['params'] = {}
89
Italo Valcyccd85b12020-07-24 12:35:20 -050090 homeDir = '{}/{}'.format(Minindn.workDir, host.name)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050091 host.params['params']['homeDir'] = homeDir
92 host.cmd('mkdir -p {}'.format(homeDir))
93 host.cmd('export HOME={} && cd ~'.format(homeDir))
94
95 self.cleanups = []
96
97 if not self.net.switches:
98 self.ethernetPairConnectivity()
99
100 try:
Saurab Dulal576a4192020-08-25 00:55:22 -0500101 process = Popen(['ndnsec-get-default', '-k'], stdout=PIPE, stderr=PIPE)
102 output, error = process.communicate()
103 if process.returncode == 0:
104 Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in output
105 info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
106 else:
107 debug(error)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500108 except:
109 pass
110
111 @staticmethod
112 def parseArgs(parent):
113 parser = argparse.ArgumentParser(prog='minindn', parents=[parent], add_help=False)
114
115 # nargs='?' required here since optional argument
116 parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/default-topology.conf',
117 help='If no template_file is given, topologies/default-topology.conf will be used.')
118
119 parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
120 help='Specify the working directory; default is /tmp/minindn')
121
122 parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
123 help='Specify the full path destination folder where experiment results will be moved')
124
125 return parser
126
127 def ethernetPairConnectivity(self):
128 ndnNetBase = '10.0.0.0'
129 interfaces = []
130 for host in self.net.hosts:
131 for intf in host.intfList():
132 link = intf.link
133 node1, node2 = link.intf1.node, link.intf2.node
134
135 if isinstance(node1, Switch) or isinstance(node2, Switch):
136 continue
137
138 if link.intf1 not in interfaces and link.intf2 not in interfaces:
139 interfaces.append(link.intf1)
140 interfaces.append(link.intf2)
141 node1.setIP(ipStr(ipParse(ndnNetBase) + 1) + '/30', intf=link.intf1)
142 node2.setIP(ipStr(ipParse(ndnNetBase) + 2) + '/30', intf=link.intf2)
143 ndnNetBase = ipStr(ipParse(ndnNetBase) + 4)
144
145 @staticmethod
146 def processTopo(topoFile):
147 config = configparser.ConfigParser(delimiters=' ')
148 config.read(topoFile)
149 topo = Topo()
150
151 items = config.items('nodes')
dulalsaurab5c79db02020-02-27 06:04:56 +0000152 coordinates = []
153
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500154 for item in items:
155 name = item[0].split(':')[0]
dulalsaurab5c79db02020-02-27 06:04:56 +0000156 if item[1] in coordinates:
phmollad8d37e2020-03-03 12:53:08 +0100157 error("FATAL: Duplicate Coordinate, \'{}\' used by multiple nodes\n" \
dulalsaurab5c79db02020-02-27 06:04:56 +0000158 .format(item[1]))
159 sys.exit(1)
160 coordinates.append(item[1])
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500161
162 params = {}
163 for param in item[1].split(' '):
164 if param == '_':
165 continue
166 params[param.split('=')[0]] = param.split('=')[1]
167
168 topo.addHost(name, params=params)
169
170 try:
171 items = config.items('switches')
172 for item in items:
173 name = item[0].split(':')[0]
174 topo.addSwitch(name)
175 except configparser.NoSectionError:
176 # Switches are optional
177 pass
178
179 items = config.items('links')
180 for item in items:
181 link = item[0].split(':')
182
183 params = {}
184 for param in item[1].split(' '):
185 key = param.split('=')[0]
186 value = param.split('=')[1]
187 if key in ['bw', 'jitter', 'max_queue_size']:
188 value = int(value)
189 if key == 'loss':
190 value = float(value)
191 params[key] = value
192
193 topo.addLink(link[0], link[1], **params)
194
195 return topo
196
197 def start(self):
198 self.net.start()
199 time.sleep(3)
200
201 def stop(self):
202 for cleanup in self.cleanups:
203 cleanup()
204 self.net.stop()
205
Italo Valcyccd85b12020-07-24 12:35:20 -0500206 if Minindn.resultDir is not None:
207 info("Moving results to \'{}\'\n".format(Minindn.resultDir))
208 os.system("mkdir -p {}".format(Minindn.resultDir))
209 for file in glob.glob('{}/*'.format(Minindn.workDir)):
210 shutil.move(file, Minindn.resultDir)
phmollad8d37e2020-03-03 12:53:08 +0100211
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500212 @staticmethod
213 def cleanUp():
214 devnull = open(os.devnull, 'w')
215 call('nfd-stop', stdout=devnull, stderr=devnull)
216 call('mn --clean'.split(), stdout=devnull, stderr=devnull)
217
218 @staticmethod
219 def verifyDependencies():
220 """Prevent MiniNDN from running without necessary dependencies"""
221 dependencies = ['nfd', 'nlsr', 'infoedit', 'ndnping', 'ndnpingserver']
222 devnull = open(os.devnull, 'w')
223 # Checks that each program is in the system path
224 for program in dependencies:
225 if call(['which', program], stdout=devnull):
226 error('{} is missing from the system path! Exiting...\n'.format(program))
227 sys.exit(1)
228 devnull.close()
229
230 @staticmethod
231 def sleep(seconds):
232 # sleep is not required if ndn-cxx is using in-memory keychain
233 if not Minindn.ndnSecurityDisabled:
234 time.sleep(seconds)