blob: eaad17e19a5d77c7f4d4e8f487b02a88e3a66b3c [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')
Laqin Fancf8e7542020-08-13 17:30:19 -0500102 if Minindn.ndnSecurityDisabled:
103 info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500104 except:
105 pass
106
107 @staticmethod
108 def parseArgs(parent):
109 parser = argparse.ArgumentParser(prog='minindn', parents=[parent], add_help=False)
110
111 # nargs='?' required here since optional argument
112 parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/default-topology.conf',
113 help='If no template_file is given, topologies/default-topology.conf will be used.')
114
115 parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
116 help='Specify the working directory; default is /tmp/minindn')
117
118 parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
119 help='Specify the full path destination folder where experiment results will be moved')
120
121 return parser
122
123 def ethernetPairConnectivity(self):
124 ndnNetBase = '10.0.0.0'
125 interfaces = []
126 for host in self.net.hosts:
127 for intf in host.intfList():
128 link = intf.link
129 node1, node2 = link.intf1.node, link.intf2.node
130
131 if isinstance(node1, Switch) or isinstance(node2, Switch):
132 continue
133
134 if link.intf1 not in interfaces and link.intf2 not in interfaces:
135 interfaces.append(link.intf1)
136 interfaces.append(link.intf2)
137 node1.setIP(ipStr(ipParse(ndnNetBase) + 1) + '/30', intf=link.intf1)
138 node2.setIP(ipStr(ipParse(ndnNetBase) + 2) + '/30', intf=link.intf2)
139 ndnNetBase = ipStr(ipParse(ndnNetBase) + 4)
140
141 @staticmethod
142 def processTopo(topoFile):
143 config = configparser.ConfigParser(delimiters=' ')
144 config.read(topoFile)
145 topo = Topo()
146
147 items = config.items('nodes')
dulalsaurab5c79db02020-02-27 06:04:56 +0000148 coordinates = []
149
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500150 for item in items:
151 name = item[0].split(':')[0]
dulalsaurab5c79db02020-02-27 06:04:56 +0000152 if item[1] in coordinates:
phmollad8d37e2020-03-03 12:53:08 +0100153 error("FATAL: Duplicate Coordinate, \'{}\' used by multiple nodes\n" \
dulalsaurab5c79db02020-02-27 06:04:56 +0000154 .format(item[1]))
155 sys.exit(1)
156 coordinates.append(item[1])
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500157
158 params = {}
159 for param in item[1].split(' '):
160 if param == '_':
161 continue
162 params[param.split('=')[0]] = param.split('=')[1]
163
164 topo.addHost(name, params=params)
165
166 try:
167 items = config.items('switches')
168 for item in items:
169 name = item[0].split(':')[0]
170 topo.addSwitch(name)
171 except configparser.NoSectionError:
172 # Switches are optional
173 pass
174
175 items = config.items('links')
176 for item in items:
177 link = item[0].split(':')
178
179 params = {}
180 for param in item[1].split(' '):
181 key = param.split('=')[0]
182 value = param.split('=')[1]
183 if key in ['bw', 'jitter', 'max_queue_size']:
184 value = int(value)
185 if key == 'loss':
186 value = float(value)
187 params[key] = value
188
189 topo.addLink(link[0], link[1], **params)
190
191 return topo
192
193 def start(self):
194 self.net.start()
195 time.sleep(3)
196
197 def stop(self):
198 for cleanup in self.cleanups:
199 cleanup()
200 self.net.stop()
201
phmollad8d37e2020-03-03 12:53:08 +0100202 if self.resultDir is not None:
203 info("Moving results to \'{}\'\n".format(self.resultDir))
204 os.system("mkdir -p {}".format(self.resultDir))
205 for file in glob.glob('{}/*'.format(self.workDir)):
206 shutil.move(file, self.resultDir)
207
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500208 @staticmethod
209 def cleanUp():
210 devnull = open(os.devnull, 'w')
211 call('nfd-stop', stdout=devnull, stderr=devnull)
212 call('mn --clean'.split(), stdout=devnull, stderr=devnull)
213
214 @staticmethod
215 def verifyDependencies():
216 """Prevent MiniNDN from running without necessary dependencies"""
217 dependencies = ['nfd', 'nlsr', 'infoedit', 'ndnping', 'ndnpingserver']
218 devnull = open(os.devnull, 'w')
219 # Checks that each program is in the system path
220 for program in dependencies:
221 if call(['which', program], stdout=devnull):
222 error('{} is missing from the system path! Exiting...\n'.format(program))
223 sys.exit(1)
224 devnull.close()
225
226 @staticmethod
227 def sleep(seconds):
228 # sleep is not required if ndn-cxx is using in-memory keychain
229 if not Minindn.ndnSecurityDisabled:
230 time.sleep(seconds)