blob: 9a94407e27e8067edaa45531cc42f287da742232 [file] [log] [blame]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -05001# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2#
3# Copyright (C) 2015-2019, The University of Memphis,
4# 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
29from subprocess import call, check_output
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
52
53 def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, **mininetParams):
54 """Create MiniNDN object
55 parser: Parent parser of Mini-NDN parser
56 topo: Mininet topo object (optional)
57 topoFile: Mininet topology file location (optional)
58 mininetParams: Any params to pass to Mininet
59 """
60 self.parser = Minindn.parseArgs(parser)
61 self.args = self.parser.parse_args()
62
63 self.workDir = self.args.workDir
64 self.resultDir = self.args.resultDir
65
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
72 if topo is None:
73 try:
74 info('Using topology file {}\n'.format(self.topoFile))
75 self.topo = self.processTopo(self.topoFile)
76 except configparser.NoSectionError as e:
77 info('Error reading config file: {}\n'.format(e))
78 sys.exit(1)
79 else:
80 self.topo = topo
81
82 self.net = Mininet(topo=self.topo, link=TCLink, **mininetParams)
83
84 for host in self.net.hosts:
85 if 'params' not in host.params:
86 host.params['params'] = {}
87
88 homeDir = '{}/{}'.format(self.workDir, host.name)
89 host.params['params']['homeDir'] = homeDir
90 host.cmd('mkdir -p {}'.format(homeDir))
91 host.cmd('export HOME={} && cd ~'.format(homeDir))
92
93 self.cleanups = []
94
95 if not self.net.switches:
96 self.ethernetPairConnectivity()
97
98 try:
99 Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in \
100 check_output('ndnsec-get-default -k'.split()). \
phmollad8d37e2020-03-03 12:53:08 +0100101 decode('utf-8').split('\n')
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500102 info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
103 except:
104 pass
105
106 @staticmethod
107 def parseArgs(parent):
108 parser = argparse.ArgumentParser(prog='minindn', parents=[parent], add_help=False)
109
110 # nargs='?' required here since optional argument
111 parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/default-topology.conf',
112 help='If no template_file is given, topologies/default-topology.conf will be used.')
113
114 parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
115 help='Specify the working directory; default is /tmp/minindn')
116
117 parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
118 help='Specify the full path destination folder where experiment results will be moved')
119
120 return parser
121
122 def ethernetPairConnectivity(self):
123 ndnNetBase = '10.0.0.0'
124 interfaces = []
125 for host in self.net.hosts:
126 for intf in host.intfList():
127 link = intf.link
128 node1, node2 = link.intf1.node, link.intf2.node
129
130 if isinstance(node1, Switch) or isinstance(node2, Switch):
131 continue
132
133 if link.intf1 not in interfaces and link.intf2 not in interfaces:
134 interfaces.append(link.intf1)
135 interfaces.append(link.intf2)
136 node1.setIP(ipStr(ipParse(ndnNetBase) + 1) + '/30', intf=link.intf1)
137 node2.setIP(ipStr(ipParse(ndnNetBase) + 2) + '/30', intf=link.intf2)
138 ndnNetBase = ipStr(ipParse(ndnNetBase) + 4)
139
140 @staticmethod
141 def processTopo(topoFile):
142 config = configparser.ConfigParser(delimiters=' ')
143 config.read(topoFile)
144 topo = Topo()
145
146 items = config.items('nodes')
dulalsaurab5c79db02020-02-27 06:04:56 +0000147 coordinates = []
148
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500149 for item in items:
150 name = item[0].split(':')[0]
dulalsaurab5c79db02020-02-27 06:04:56 +0000151 if item[1] in coordinates:
phmollad8d37e2020-03-03 12:53:08 +0100152 error("FATAL: Duplicate Coordinate, \'{}\' used by multiple nodes\n" \
dulalsaurab5c79db02020-02-27 06:04:56 +0000153 .format(item[1]))
154 sys.exit(1)
155 coordinates.append(item[1])
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500156
157 params = {}
158 for param in item[1].split(' '):
159 if param == '_':
160 continue
161 params[param.split('=')[0]] = param.split('=')[1]
162
163 topo.addHost(name, params=params)
164
165 try:
166 items = config.items('switches')
167 for item in items:
168 name = item[0].split(':')[0]
169 topo.addSwitch(name)
170 except configparser.NoSectionError:
171 # Switches are optional
172 pass
173
174 items = config.items('links')
175 for item in items:
176 link = item[0].split(':')
177
178 params = {}
179 for param in item[1].split(' '):
180 key = param.split('=')[0]
181 value = param.split('=')[1]
182 if key in ['bw', 'jitter', 'max_queue_size']:
183 value = int(value)
184 if key == 'loss':
185 value = float(value)
186 params[key] = value
187
188 topo.addLink(link[0], link[1], **params)
189
190 return topo
191
192 def start(self):
193 self.net.start()
194 time.sleep(3)
195
196 def stop(self):
197 for cleanup in self.cleanups:
198 cleanup()
199 self.net.stop()
200
phmollad8d37e2020-03-03 12:53:08 +0100201 if self.resultDir is not None:
202 info("Moving results to \'{}\'\n".format(self.resultDir))
203 os.system("mkdir -p {}".format(self.resultDir))
204 for file in glob.glob('{}/*'.format(self.workDir)):
205 shutil.move(file, self.resultDir)
206
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500207 @staticmethod
208 def cleanUp():
209 devnull = open(os.devnull, 'w')
210 call('nfd-stop', stdout=devnull, stderr=devnull)
211 call('mn --clean'.split(), stdout=devnull, stderr=devnull)
212
213 @staticmethod
214 def verifyDependencies():
215 """Prevent MiniNDN from running without necessary dependencies"""
216 dependencies = ['nfd', 'nlsr', 'infoedit', 'ndnping', 'ndnpingserver']
217 devnull = open(os.devnull, 'w')
218 # Checks that each program is in the system path
219 for program in dependencies:
220 if call(['which', program], stdout=devnull):
221 error('{} is missing from the system path! Exiting...\n'.format(program))
222 sys.exit(1)
223 devnull.close()
224
225 @staticmethod
226 def sleep(seconds):
227 # sleep is not required if ndn-cxx is using in-memory keychain
228 if not Minindn.ndnSecurityDisabled:
229 time.sleep(seconds)