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