blob: ba3c434978db00d77d70f3fb912cdcdce5d23720 [file] [log] [blame]
Alexander Laneea2d5d62019-10-04 16:48:52 -05001# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2#
dulalsaurab20855442021-05-21 20:37:03 +00003# Copyright (C) 2015-2021, The University of Memphis,
Alexander Laneea2d5d62019-10-04 16:48:52 -05004# 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
awlanea169f572022-04-08 17:21:29 -050024import os
Alexander Laneea2d5d62019-10-04 16:48:52 -050025import argparse
26import sys
Alexander Laneea2d5d62019-10-04 16:48:52 -050027import configparser
dulalsaurab20855442021-05-21 20:37:03 +000028from subprocess import Popen, PIPE
Alexander Laneea2d5d62019-10-04 16:48:52 -050029
Alexander Laneea2d5d62019-10-04 16:48:52 -050030from mininet.log import info, debug
31
32from mn_wifi.topo import Topo as Topo_WiFi
33from mn_wifi.net import Mininet_wifi
Alexander Laneea2d5d62019-10-04 16:48:52 -050034from mn_wifi.link import WirelessLink
35
36from minindn.minindn import Minindn
awlaned8e6b8e2022-05-16 23:49:56 -050037from minindn.helpers.nfdc import Nfdc
Alexander Laneea2d5d62019-10-04 16:48:52 -050038
39class MinindnWifi(Minindn):
Alex Lane407c5f02021-03-09 22:13:23 -060040 """ Class for handling default args, Mininet-wifi object and home directories """
awlaned8e6b8e2022-05-16 23:49:56 -050041 def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False,
42 link=WirelessLink, workDir=None, **mininetParams):
43 """
44 Create Mini-NDN-Wifi object
Alexander Laneea2d5d62019-10-04 16:48:52 -050045 parser: Parent parser of Mini-NDN-Wifi parser (use to specify experiment arguments)
46 topo: Mininet topo object (optional)
47 topoFile: topology file location (optional)
Alex Lane407c5f02021-03-09 22:13:23 -060048 noTopo: Allows specification of topology after network object is initialized (optional)
awlaned8e6b8e2022-05-16 23:49:56 -050049 link: Allows specification of default Mininet/Mininet-Wifi link type for
50 connections between nodes (optional)mininetParams: Any params to pass to Mininet-WiFi
Alexander Laneea2d5d62019-10-04 16:48:52 -050051 """
52 self.parser = self.parseArgs(parser)
53 self.args = self.parser.parse_args()
54
awlanea169f572022-04-08 17:21:29 -050055 if not workDir:
56 Minindn.workDir = os.path.abspath(self.args.workDir)
57 else:
58 Minindn.workDir = os.path.abspath(workDir)
59
Alexander Laneea2d5d62019-10-04 16:48:52 -050060 Minindn.resultDir = self.args.resultDir
61
62 self.topoFile = None
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
awlaned8e6b8e2022-05-16 23:49:56 -050069 self.faces_to_create = {}
Alex Lane407c5f02021-03-09 22:13:23 -060070 if topo is None and not noTopo:
Alexander Laneea2d5d62019-10-04 16:48:52 -050071 try:
72 info('Using topology file {}\n'.format(self.topoFile))
awlaned8e6b8e2022-05-16 23:49:56 -050073 self.topo, self.faces_to_create = self.processTopo(self.topoFile)
Alexander Laneea2d5d62019-10-04 16:48:52 -050074 except configparser.NoSectionError as e:
75 info('Error reading config file: {}\n'.format(e))
76 sys.exit(1)
77 else:
78 self.topo = topo
79
Alex Lane407c5f02021-03-09 22:13:23 -060080 if not noTopo:
81 self.net = Mininet_wifi(topo=self.topo, ifb=self.args.ifb, link=link, **mininetParams)
82 else:
83 self.net = Mininet_wifi(ifb=self.args.ifb, link=link, **mininetParams)
Alexander Laneea2d5d62019-10-04 16:48:52 -050084
Alex Lane407c5f02021-03-09 22:13:23 -060085 # Prevents crashes running mixed topos
86 nodes = self.net.stations + self.net.hosts + self.net.cars
87 self.initParams(nodes)
88
Alexander Laneea2d5d62019-10-04 16:48:52 -050089 try:
90 process = Popen(['ndnsec-get-default', '-k'], stdout=PIPE, stderr=PIPE)
91 output, error = process.communicate()
92 if process.returncode == 0:
awlanec32a07b2022-04-19 14:53:41 -050093 Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in output.decode("utf-8")
Alex Lane407c5f02021-03-09 22:13:23 -060094 info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
Alexander Laneea2d5d62019-10-04 16:48:52 -050095 else:
Alex Lane407c5f02021-03-09 22:13:23 -060096 debug(error)
Alexander Laneea2d5d62019-10-04 16:48:52 -050097 except:
98 pass
99
100 self.cleanups = []
101
102 @staticmethod
103 def parseArgs(parent):
104 parser = argparse.ArgumentParser(prog='minindn-wifi', parents=[parent], add_help=False)
105
106 # nargs='?' required here since optional argument
107 parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/singleap-topology.conf',
108 help='If no template_file is given, topologies/wifi/singleap-topology.conf will be used.')
109
110 parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
111 help='Specify the working directory; default is /tmp/minindn')
112
113 parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
114 help='Specify the full path destination folder where experiment results will be moved')
115
116 parser.add_argument('--mobility',action='store_true',dest='mobility',default=False,
117 help='Enable custom mobility for topology (defined in topology file)')
118
119 parser.add_argument('--model-mob',action='store_true',dest='modelMob',default=False,
120 help='Enable model mobility for topology (defined in topology file)')
121
122 parser.add_argument('--ifb',action='store_true',dest='ifb',default=False,
123 help='Simulate delay on receiver-side by use of virtual IFB devices (see docs)')
124
125 return parser
126
127 @staticmethod
128 def processTopo(topoFile):
129 config = configparser.ConfigParser(delimiters=' ')
130 config.read(topoFile)
131 topo = Topo_WiFi()
132
133 items = config.items('stations')
134 debug("Stations")
135 for item in items:
136 debug(item[0].split(':'))
137 name = item[0].split(':')[0]
138 params = {}
139 for param in item[1].split(' '):
140 if param == "_":
141 continue
142 key = param.split('=')[0]
143 value = param.split('=')[1]
144 if key in ['range']:
145 value = int(value)
146 params[key] = value
147
148 topo.addStation(name, **params)
149
150 try:
151 debug("Switches")
152 items = config.items('switches')
153 for item in items:
154 debug(item[0].split(':'))
155 name = item[0].split(':')[0]
156 topo.addSwitch(name)
157 except configparser.NoSectionError:
158 debug("Switches are optional")
159 pass
160
161 try:
162 debug("APs")
163 items = config.items('accessPoints')
164 for item in items:
165 debug(item[0].split(':'))
166 name = item[0].split(':')[0]
167 ap_params = {}
168 for param in item[1].split(' '):
169 if param == "_":
170 continue
171 key = param.split('=')[0]
172 value = param.split('=')[1]
173 if key in ['range']:
174 value = int(value)
175 ap_params[key] = value
176 topo.addAccessPoint(name, **ap_params)
177 except configparser.NoSectionError:
178 debug("APs are optional")
179 pass
180
181 items = config.items('links')
182 debug("Links")
183 for item in items:
184 link = item[0].split(':')
185 debug(link)
186 params = {}
187 for param in item[1].split(' '):
188 if param == "_":
189 continue
190 key = param.split('=')[0]
191 value = param.split('=')[1]
192 if key in ['bw', 'jitter', 'max_queue_size']:
193 value = int(value)
194 if key == 'loss':
195 value = float(value)
196 params[key] = value
197
198 topo.addLink(link[0], link[1], **params)
199
awlaned8e6b8e2022-05-16 23:49:56 -0500200 faces = {}
201 try:
202 items = config.items('faces')
203 debug("Faces")
204 for item in items:
205 face_a, face_b = item[0].split(':')
206 debug(item)
207 cost = -1
208 for param in item[1].split(' '):
209 if param.split("=")[0] == 'cost':
210 cost = param.split("=")[1]
211 face_info = (face_b, int(cost))
212 if face_a not in faces:
213 faces[face_a] = [face_info]
214 else:
215 faces[face_a].append(face_info)
216 except configparser.NoSectionError:
217 debug("Faces section is optional")
218 pass
219
220 return (topo, faces)
Alexander Laneea2d5d62019-10-04 16:48:52 -0500221
222 def startMobility(self, max_x=1000, max_y=1000, **kwargs):
223 """ Method to run a basic mobility setup on your net"""
224 self.net.plotGraph(max_x=max_x, max_y=max_y)
225 self.net.startMobility(**kwargs)
226
227 def startMobilityModel(self, max_x=1000, max_y=1000, **kwargs):
228 """ Method to run a mobility model on your net until exited"""
229 self.net.plotGraph(max_x=max_x, max_y=max_y)
awlaned8e6b8e2022-05-16 23:49:56 -0500230 self.net.setMobilityModel(**kwargs)
231
232 def getWifiInterfaceDelay(self, node, interface=None):
233 """Method to return the configured tc delay of a wifi node's interface as a float"""
234 if not interface:
235 wifi_interface = "{}-wlan0".format(node.name)
236 else:
237 wifi_interface = interface
238 tc_output = node.cmd("tc qdisc show dev {}".format(wifi_interface))
239 for line in tc_output.splitlines():
240 if "qdisc netem 10:" in line:
241 split_line = line.split(" ")
242 for index in range(0, len(split_line)):
243 if split_line[index] == "delay":
244 return float(split_line[index + 1][:-2])
245 return 0.0
246
247 def setupFaces(self, faces_to_create=None):
248 """
249 Method to create unicast faces between nodes connected by an AP based on name or faces
250 between connected nodes; Returns dict- {node: (other node name, other node IP, other
251 node's delay as int)}. This is intended to pass to the NLSR helper via the faceDict param
252 """
253 if not faces_to_create:
254 faces_to_create = self.faces_to_create
255 # (nodeName, IP, delay as int)
256 # list of tuples
257 created_faces = dict()
258 batch_faces = dict()
259 for nodeAname in faces_to_create.keys():
260 if not nodeAname in batch_faces.keys():
261 batch_faces[nodeAname] = []
262 for nodeBname, faceCost in faces_to_create[nodeAname]:
263 if not nodeBname in batch_faces.keys():
264 batch_faces[nodeBname] = []
265 nodeA = self.net[nodeAname]
266 nodeB = self.net[nodeBname]
267 if nodeA.connectionsTo(nodeB):
268 best_interface = None
269 delay = None
270 for interface in nodeA.connectionsTo(nodeB):
271 interface_delay = self.getInterfaceDelay(nodeA, interface[0])
272 if not delay or int(interface_delay) < delay:
273 best_interface = interface
274 faceAIP = best_interface[0].IP()
275 faceBIP = best_interface[1].IP()
276 # Node delay should be symmetrical
277 nodeDelay = int(self.getInterfaceDelay(nodeA, best_interface[0]))
278 else:
279 # Default IP will be the primary wireless interface, unclear if multiple wireless
280 # interfaces should be handled
281 faceAIP = nodeA.IP()
282 faceBIP = nodeB.IP()
283 nodeADelay = self.getWifiInterfaceDelay(nodeA)
284 nodeBDelay = self.getWifiInterfaceDelay(nodeB)
285 nodeDelay = nodeADelay + nodeBDelay
286
287 if not faceCost == -1:
288 nodeALink = (nodeA.name, faceAIP, faceCost)
289 nodeBLink = (nodeB.name, faceBIP, faceCost)
290 else:
291 nodeALink = (nodeA.name, faceAIP, nodeDelay)
292 nodeBLink = (nodeB.name, faceBIP, nodeDelay)
293
294 batch_faces[nodeAname].append([faceBIP, "udp", True])
295 batch_faces[nodeBname].append([faceAIP, "udp", True])
296
297 if nodeA not in created_faces:
298 created_faces[nodeA] = [nodeBLink]
299 else:
300 created_faces[nodeA].append(nodeBLink)
301 if nodeB not in created_faces:
302 created_faces[nodeB] = [nodeALink]
303 else:
304 created_faces[nodeB].append(nodeALink)
305 for station_name in batch_faces.keys():
306 self.nfdcBatchProcessing(self.net[station_name], batch_faces[station_name])
307 return created_faces