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