blob: 847e003179d4f7a3a40f79319dde3b8dc2fa59bf [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')
144 for item in items:
145 name = item[0].split(':')[0]
146
147 params = {}
148 for param in item[1].split(' '):
149 if param == '_':
150 continue
151 params[param.split('=')[0]] = param.split('=')[1]
152
153 topo.addHost(name, params=params)
154
155 try:
156 items = config.items('switches')
157 for item in items:
158 name = item[0].split(':')[0]
159 topo.addSwitch(name)
160 except configparser.NoSectionError:
161 # Switches are optional
162 pass
163
164 items = config.items('links')
165 for item in items:
166 link = item[0].split(':')
167
168 params = {}
169 for param in item[1].split(' '):
170 key = param.split('=')[0]
171 value = param.split('=')[1]
172 if key in ['bw', 'jitter', 'max_queue_size']:
173 value = int(value)
174 if key == 'loss':
175 value = float(value)
176 params[key] = value
177
178 topo.addLink(link[0], link[1], **params)
179
180 return topo
181
182 def start(self):
183 self.net.start()
184 time.sleep(3)
185
186 def stop(self):
187 for cleanup in self.cleanups:
188 cleanup()
189 self.net.stop()
190
191 @staticmethod
192 def cleanUp():
193 devnull = open(os.devnull, 'w')
194 call('nfd-stop', stdout=devnull, stderr=devnull)
195 call('mn --clean'.split(), stdout=devnull, stderr=devnull)
196
197 @staticmethod
198 def verifyDependencies():
199 """Prevent MiniNDN from running without necessary dependencies"""
200 dependencies = ['nfd', 'nlsr', 'infoedit', 'ndnping', 'ndnpingserver']
201 devnull = open(os.devnull, 'w')
202 # Checks that each program is in the system path
203 for program in dependencies:
204 if call(['which', program], stdout=devnull):
205 error('{} is missing from the system path! Exiting...\n'.format(program))
206 sys.exit(1)
207 devnull.close()
208
209 @staticmethod
210 def sleep(seconds):
211 # sleep is not required if ndn-cxx is using in-memory keychain
212 if not Minindn.ndnSecurityDisabled:
213 time.sleep(seconds)