Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 1 | # -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */ |
| 2 | # |
dulalsaurab | 2085544 | 2021-05-21 20:37:03 +0000 | [diff] [blame] | 3 | # Copyright (C) 2015-2021, The University of Memphis, |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 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 | |
awlane | a169f57 | 2022-04-08 17:21:29 -0500 | [diff] [blame] | 24 | import os |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 25 | import argparse |
| 26 | import sys |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 27 | import configparser |
dulalsaurab | 2085544 | 2021-05-21 20:37:03 +0000 | [diff] [blame] | 28 | from subprocess import Popen, PIPE |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 29 | |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 30 | from mininet.log import info, debug |
| 31 | |
| 32 | from mn_wifi.topo import Topo as Topo_WiFi |
| 33 | from mn_wifi.net import Mininet_wifi |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 34 | from mn_wifi.link import WirelessLink |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 35 | from mn_wifi.link import wmediumd, adhoc |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 36 | |
| 37 | from minindn.minindn import Minindn |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 38 | from minindn.helpers.nfdc import Nfdc |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 39 | |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 40 | import ast |
| 41 | |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 42 | class MinindnWifi(Minindn): |
Alex Lane | 407c5f0 | 2021-03-09 22:13:23 -0600 | [diff] [blame] | 43 | """ Class for handling default args, Mininet-wifi object and home directories """ |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 44 | def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False, |
| 45 | link=WirelessLink, workDir=None, **mininetParams): |
| 46 | """ |
| 47 | Create Mini-NDN-Wifi object |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 48 | parser: Parent parser of Mini-NDN-Wifi parser (use to specify experiment arguments) |
| 49 | topo: Mininet topo object (optional) |
| 50 | topoFile: topology file location (optional) |
Alex Lane | 407c5f0 | 2021-03-09 22:13:23 -0600 | [diff] [blame] | 51 | noTopo: Allows specification of topology after network object is initialized (optional) |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 52 | link: Allows specification of default Mininet/Mininet-Wifi link type for |
| 53 | connections between nodes (optional)mininetParams: Any params to pass to Mininet-WiFi |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 54 | """ |
| 55 | self.parser = self.parseArgs(parser) |
| 56 | self.args = self.parser.parse_args() |
| 57 | |
awlane | a169f57 | 2022-04-08 17:21:29 -0500 | [diff] [blame] | 58 | if not workDir: |
| 59 | Minindn.workDir = os.path.abspath(self.args.workDir) |
| 60 | else: |
| 61 | Minindn.workDir = os.path.abspath(workDir) |
| 62 | |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 63 | Minindn.resultDir = self.args.resultDir |
| 64 | |
| 65 | self.topoFile = None |
| 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 | |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 72 | self.faces_to_create = {} |
Alex Lane | 407c5f0 | 2021-03-09 22:13:23 -0600 | [diff] [blame] | 73 | if topo is None and not noTopo: |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 74 | try: |
| 75 | info('Using topology file {}\n'.format(self.topoFile)) |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 76 | self.topo, self.faces_to_create = self.processTopo(self.topoFile) |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 77 | except configparser.NoSectionError as e: |
| 78 | info('Error reading config file: {}\n'.format(e)) |
| 79 | sys.exit(1) |
| 80 | else: |
| 81 | self.topo = topo |
| 82 | |
Alex Lane | 407c5f0 | 2021-03-09 22:13:23 -0600 | [diff] [blame] | 83 | if not noTopo: |
| 84 | self.net = Mininet_wifi(topo=self.topo, ifb=self.args.ifb, link=link, **mininetParams) |
| 85 | else: |
| 86 | self.net = Mininet_wifi(ifb=self.args.ifb, link=link, **mininetParams) |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 87 | |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 88 | # process mobility if specified |
| 89 | if not noTopo: |
| 90 | self.processMobility(self.topoFile) |
| 91 | |
Alex Lane | 407c5f0 | 2021-03-09 22:13:23 -0600 | [diff] [blame] | 92 | # Prevents crashes running mixed topos |
| 93 | nodes = self.net.stations + self.net.hosts + self.net.cars |
| 94 | self.initParams(nodes) |
| 95 | |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 96 | try: |
| 97 | process = Popen(['ndnsec-get-default', '-k'], stdout=PIPE, stderr=PIPE) |
| 98 | output, error = process.communicate() |
| 99 | if process.returncode == 0: |
awlane | c32a07b | 2022-04-19 14:53:41 -0500 | [diff] [blame] | 100 | Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in output.decode("utf-8") |
Alex Lane | 407c5f0 | 2021-03-09 22:13:23 -0600 | [diff] [blame] | 101 | info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n') |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 102 | else: |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 103 | debug(error + "\n") |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 104 | except: |
| 105 | pass |
| 106 | |
| 107 | self.cleanups = [] |
| 108 | |
| 109 | @staticmethod |
| 110 | def parseArgs(parent): |
| 111 | parser = argparse.ArgumentParser(prog='minindn-wifi', 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/singleap-topology.conf', |
| 115 | help='If no template_file is given, topologies/wifi/singleap-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 | parser.add_argument('--mobility',action='store_true',dest='mobility',default=False, |
| 124 | help='Enable custom mobility for topology (defined in topology file)') |
| 125 | |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 126 | parser.add_argument('--ifb',action='store_true',dest='ifb',default=False, |
| 127 | help='Simulate delay on receiver-side by use of virtual IFB devices (see docs)') |
| 128 | |
| 129 | return parser |
| 130 | |
| 131 | @staticmethod |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 132 | def convert_params(params): |
| 133 | converted_params = {} |
| 134 | for key, value in params.items(): |
| 135 | try: |
| 136 | converted_params[key] = ast.literal_eval(value) |
| 137 | except (ValueError, SyntaxError): |
| 138 | converted_params[key] = value |
| 139 | return converted_params |
| 140 | |
| 141 | @staticmethod |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 142 | def processTopo(topoFile): |
| 143 | config = configparser.ConfigParser(delimiters=' ') |
| 144 | config.read(topoFile) |
| 145 | topo = Topo_WiFi() |
| 146 | |
| 147 | items = config.items('stations') |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 148 | debug("Stations\n") |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 149 | for item in items: |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 150 | debug(str(item[0].split(':'))+"\n") |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 151 | name = item[0].split(':')[0] |
| 152 | params = {} |
| 153 | for param in item[1].split(' '): |
| 154 | if param == "_": |
| 155 | continue |
| 156 | key = param.split('=')[0] |
| 157 | value = param.split('=')[1] |
| 158 | if key in ['range']: |
| 159 | value = int(value) |
| 160 | params[key] = value |
| 161 | |
| 162 | topo.addStation(name, **params) |
| 163 | |
| 164 | try: |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 165 | debug("Switches\n") |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 166 | items = config.items('switches') |
| 167 | for item in items: |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 168 | debug(str(item[0].split(':'))+"\n") |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 169 | name = item[0].split(':')[0] |
| 170 | topo.addSwitch(name) |
| 171 | except configparser.NoSectionError: |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 172 | debug("Switches are optional\n") |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 173 | pass |
| 174 | |
| 175 | try: |
| 176 | debug("APs") |
| 177 | items = config.items('accessPoints') |
| 178 | for item in items: |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 179 | debug(str(item[0].split(':'))+"\n") |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 180 | name = item[0].split(':')[0] |
| 181 | ap_params = {} |
| 182 | for param in item[1].split(' '): |
| 183 | if param == "_": |
| 184 | continue |
| 185 | key = param.split('=')[0] |
| 186 | value = param.split('=')[1] |
| 187 | if key in ['range']: |
| 188 | value = int(value) |
| 189 | ap_params[key] = value |
| 190 | topo.addAccessPoint(name, **ap_params) |
| 191 | except configparser.NoSectionError: |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 192 | debug("APs are optional\n") |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 193 | pass |
| 194 | |
| 195 | items = config.items('links') |
| 196 | debug("Links") |
| 197 | for item in items: |
| 198 | link = item[0].split(':') |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 199 | debug(str(link) + "\n") |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 200 | params = {} |
| 201 | for param in item[1].split(' '): |
| 202 | if param == "_": |
| 203 | continue |
| 204 | key = param.split('=')[0] |
| 205 | value = param.split('=')[1] |
Varun Patil | e427d26 | 2024-10-30 12:37:04 -0700 | [diff] [blame^] | 206 | if key in ['max_queue_size']: |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 207 | value = int(value) |
awlane | 5158e5f | 2024-11-01 21:34:14 -0500 | [diff] [blame] | 208 | if key in ['loss', 'bw']: |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 209 | value = float(value) |
| 210 | params[key] = value |
| 211 | |
| 212 | topo.addLink(link[0], link[1], **params) |
| 213 | |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 214 | faces = {} |
| 215 | try: |
| 216 | items = config.items('faces') |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 217 | debug("Faces\n") |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 218 | for item in items: |
| 219 | face_a, face_b = item[0].split(':') |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 220 | debug(str(item)+"\n") |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 221 | cost = -1 |
| 222 | for param in item[1].split(' '): |
| 223 | if param.split("=")[0] == 'cost': |
| 224 | cost = param.split("=")[1] |
| 225 | face_info = (face_b, int(cost)) |
| 226 | if face_a not in faces: |
| 227 | faces[face_a] = [face_info] |
| 228 | else: |
| 229 | faces[face_a].append(face_info) |
| 230 | except configparser.NoSectionError: |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 231 | debug("Faces section is optional\n") |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 232 | pass |
| 233 | |
| 234 | return (topo, faces) |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 235 | |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 236 | def processMobility(self, topoFile): |
| 237 | config = configparser.ConfigParser(delimiters=' ') |
| 238 | config.read(topoFile) |
| 239 | |
| 240 | try: |
| 241 | debug("Mobility\n") |
| 242 | items = config.items('mobility') |
| 243 | if len(items) == 0: |
| 244 | return |
| 245 | params = {} |
| 246 | for param in items[0][1].split(' '): |
| 247 | if param == "_": |
| 248 | continue |
| 249 | key = param.split('=')[0] |
| 250 | value = param.split('=')[1] |
| 251 | params[key] = value |
| 252 | params = self.convert_params(params) |
| 253 | self.startMobilityModel(**params) |
| 254 | |
| 255 | items = config.items('stations') |
| 256 | debug("Start Mobility\n") |
| 257 | for item in items: |
| 258 | debug(str(item[0].split(':'))+"\n") |
| 259 | name = item[0].split(':')[0] |
| 260 | params = {} |
| 261 | for param in item[1].split(' '): |
| 262 | if param == "_": |
| 263 | continue |
| 264 | key = param.split('=')[0] |
| 265 | value = param.split('=')[1] |
| 266 | if key in ['range']: |
| 267 | value = int(value) |
| 268 | params[key] = value |
| 269 | # by default, nodes are moving |
| 270 | if "moving" not in params or params["moving"] == "True": |
| 271 | self.net.mobility(self.net[name],'start', time=0, **params) |
| 272 | |
| 273 | except configparser.NoSectionError: |
| 274 | debug("Mobility section is optional\n") |
| 275 | |
Alexander Lane | ea2d5d6 | 2019-10-04 16:48:52 -0500 | [diff] [blame] | 276 | def startMobility(self, max_x=1000, max_y=1000, **kwargs): |
| 277 | """ Method to run a basic mobility setup on your net""" |
| 278 | self.net.plotGraph(max_x=max_x, max_y=max_y) |
| 279 | self.net.startMobility(**kwargs) |
| 280 | |
| 281 | def startMobilityModel(self, max_x=1000, max_y=1000, **kwargs): |
| 282 | """ Method to run a mobility model on your net until exited""" |
| 283 | self.net.plotGraph(max_x=max_x, max_y=max_y) |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 284 | self.net.setMobilityModel(**kwargs) |
| 285 | |
| 286 | def getWifiInterfaceDelay(self, node, interface=None): |
| 287 | """Method to return the configured tc delay of a wifi node's interface as a float""" |
| 288 | if not interface: |
| 289 | wifi_interface = "{}-wlan0".format(node.name) |
| 290 | else: |
| 291 | wifi_interface = interface |
| 292 | tc_output = node.cmd("tc qdisc show dev {}".format(wifi_interface)) |
| 293 | for line in tc_output.splitlines(): |
| 294 | if "qdisc netem 10:" in line: |
| 295 | split_line = line.split(" ") |
| 296 | for index in range(0, len(split_line)): |
| 297 | if split_line[index] == "delay": |
| 298 | return float(split_line[index + 1][:-2]) |
| 299 | return 0.0 |
| 300 | |
| 301 | def setupFaces(self, faces_to_create=None): |
| 302 | """ |
| 303 | Method to create unicast faces between nodes connected by an AP based on name or faces |
| 304 | between connected nodes; Returns dict- {node: (other node name, other node IP, other |
| 305 | node's delay as int)}. This is intended to pass to the NLSR helper via the faceDict param |
| 306 | """ |
| 307 | if not faces_to_create: |
| 308 | faces_to_create = self.faces_to_create |
| 309 | # (nodeName, IP, delay as int) |
| 310 | # list of tuples |
| 311 | created_faces = dict() |
| 312 | batch_faces = dict() |
| 313 | for nodeAname in faces_to_create.keys(): |
| 314 | if not nodeAname in batch_faces.keys(): |
| 315 | batch_faces[nodeAname] = [] |
| 316 | for nodeBname, faceCost in faces_to_create[nodeAname]: |
| 317 | if not nodeBname in batch_faces.keys(): |
| 318 | batch_faces[nodeBname] = [] |
| 319 | nodeA = self.net[nodeAname] |
| 320 | nodeB = self.net[nodeBname] |
| 321 | if nodeA.connectionsTo(nodeB): |
| 322 | best_interface = None |
| 323 | delay = None |
| 324 | for interface in nodeA.connectionsTo(nodeB): |
| 325 | interface_delay = self.getInterfaceDelay(nodeA, interface[0]) |
| 326 | if not delay or int(interface_delay) < delay: |
| 327 | best_interface = interface |
| 328 | faceAIP = best_interface[0].IP() |
| 329 | faceBIP = best_interface[1].IP() |
| 330 | # Node delay should be symmetrical |
| 331 | nodeDelay = int(self.getInterfaceDelay(nodeA, best_interface[0])) |
| 332 | else: |
| 333 | # Default IP will be the primary wireless interface, unclear if multiple wireless |
| 334 | # interfaces should be handled |
| 335 | faceAIP = nodeA.IP() |
| 336 | faceBIP = nodeB.IP() |
| 337 | nodeADelay = self.getWifiInterfaceDelay(nodeA) |
| 338 | nodeBDelay = self.getWifiInterfaceDelay(nodeB) |
| 339 | nodeDelay = nodeADelay + nodeBDelay |
| 340 | |
| 341 | if not faceCost == -1: |
| 342 | nodeALink = (nodeA.name, faceAIP, faceCost) |
| 343 | nodeBLink = (nodeB.name, faceBIP, faceCost) |
| 344 | else: |
| 345 | nodeALink = (nodeA.name, faceAIP, nodeDelay) |
| 346 | nodeBLink = (nodeB.name, faceBIP, nodeDelay) |
| 347 | |
| 348 | batch_faces[nodeAname].append([faceBIP, "udp", True]) |
| 349 | batch_faces[nodeBname].append([faceAIP, "udp", True]) |
| 350 | |
| 351 | if nodeA not in created_faces: |
| 352 | created_faces[nodeA] = [nodeBLink] |
| 353 | else: |
| 354 | created_faces[nodeA].append(nodeBLink) |
| 355 | if nodeB not in created_faces: |
| 356 | created_faces[nodeB] = [nodeALink] |
| 357 | else: |
| 358 | created_faces[nodeB].append(nodeALink) |
| 359 | for station_name in batch_faces.keys(): |
| 360 | self.nfdcBatchProcessing(self.net[station_name], batch_faces[station_name]) |
matianxing1992 | 1f07a83 | 2024-09-25 12:32:38 -0500 | [diff] [blame] | 361 | return created_faces |
| 362 | |
| 363 | class MinindnAdhoc(MinindnWifi): |
| 364 | """ |
| 365 | Class for ad hoc network of Mininet-wifi |
| 366 | Topology example: topologies/wifi/adhoc-topology.conf |
| 367 | The link type is wmediumd by default |
| 368 | """ |
| 369 | def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False, |
| 370 | link=wmediumd, workDir=None, **mininetParams): |
| 371 | # call parent constructor |
| 372 | super().__init__(parser, topo, topoFile, noTopo, link, workDir, **mininetParams) |
| 373 | if not self.topoFile: |
| 374 | info("Without topoFile, ad hoc links are not added to the network, and you need to" |
| 375 | " add them manually. The example topology file can be found in" |
| 376 | " topologies/wifi/adhoc-topology.conf\n") |
| 377 | else: |
| 378 | self.addAdhocLinks() |
| 379 | |
| 380 | @staticmethod |
| 381 | def parseArgs(parent): |
| 382 | parser = argparse.ArgumentParser(prog='minindn-adhoc', parents=[parent], add_help=False) |
| 383 | |
| 384 | # nargs='?' required here since optional argument |
| 385 | parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/adhoc-topology.conf', |
| 386 | help='If no template_file is given, topologies/wifi/adhoc-topology.conf will be used.') |
| 387 | |
| 388 | parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn', |
| 389 | help='Specify the working directory; default is /tmp/minindn') |
| 390 | |
| 391 | parser.add_argument('--result-dir', action='store', dest='resultDir', default=None, |
| 392 | help='Specify the full path destination folder where experiment results will be moved') |
| 393 | |
| 394 | parser.add_argument('--mobility',action='store_true',dest='mobility',default=False, |
| 395 | help='Enable custom mobility for topology (defined in topology file)') |
| 396 | |
| 397 | parser.add_argument('--ifb',action='store_true',dest='ifb',default=False, |
| 398 | help='Simulate delay on receiver-side by use of virtual IFB devices (see docs)') |
| 399 | |
| 400 | return parser |
| 401 | |
| 402 | @staticmethod |
| 403 | def processTopo(topoFile): |
| 404 | config = configparser.ConfigParser(delimiters=' ') |
| 405 | config.read(topoFile) |
| 406 | topo = Topo_WiFi() |
| 407 | |
| 408 | items = config.items('stations') |
| 409 | debug("Stations\n") |
| 410 | id = 0 |
| 411 | for item in items: |
| 412 | debug(str(item[0].split(':'))+"\n") |
| 413 | name = item[0].split(':')[0] |
| 414 | params = {} |
| 415 | for param in item[1].split(' '): |
| 416 | if param == "_": |
| 417 | continue |
| 418 | key = param.split('=')[0] |
| 419 | value = param.split('=')[1] |
| 420 | if key in ['range']: |
| 421 | value = int(value) |
| 422 | params[key] = value |
| 423 | # ip6 address for each station using id |
| 424 | if 'ip6' not in params: |
| 425 | params['ip6'] = 'fe80::{}'.format(id) |
| 426 | topo.addStation(name, **params) |
| 427 | id += 1 |
| 428 | |
| 429 | faces = {} |
| 430 | try: |
| 431 | items = config.items('faces') |
| 432 | debug("Faces\n") |
| 433 | for item in items: |
| 434 | face_a, face_b = item[0].split(':') |
| 435 | debug(str(item)+"\n") |
| 436 | cost = -1 |
| 437 | for param in item[1].split(' '): |
| 438 | if param.split("=")[0] == 'cost': |
| 439 | cost = param.split("=")[1] |
| 440 | face_info = (face_b, int(cost)) |
| 441 | if face_a not in faces: |
| 442 | faces[face_a] = [face_info] |
| 443 | else: |
| 444 | faces[face_a].append(face_info) |
| 445 | except configparser.NoSectionError: |
| 446 | debug("Faces section is optional\n") |
| 447 | pass |
| 448 | |
| 449 | return (topo, faces) |
| 450 | |
| 451 | """Add adhoc links to the network""" |
| 452 | # In the topo.py, all the links require two stations, but in adhoc topology, we need to add links for all the nodes. |
| 453 | def addAdhocLinks(self): |
| 454 | config = configparser.ConfigParser(delimiters=' ') |
| 455 | config.read(self.topoFile) |
| 456 | |
| 457 | # read adhoc network parameters |
| 458 | # return if adhoc network is not defined |
| 459 | if 'adhocNetwork' not in config.sections(): |
| 460 | info("Adhoc network is not defined in the topology file\n") |
| 461 | return |
| 462 | |
| 463 | adhoc_params = config.items('adhocNetwork') |
| 464 | params = {} |
| 465 | for param in adhoc_params[0][1].split(' '): |
| 466 | if param == "_": |
| 467 | continue |
| 468 | key = param.split('=')[0] |
| 469 | value = param.split('=')[1] |
| 470 | if key in ['range']: |
| 471 | value = int(value) |
| 472 | params[key] = value |
| 473 | params = self.convert_params(params) |
| 474 | |
| 475 | # return if ssid, mode, channel not defined in params |
| 476 | if 'ssid' not in params or 'mode' not in params or 'channel' not in params: |
| 477 | info("ssid, mode, channel not defined in adhoc network parameters\n") |
| 478 | return |
| 479 | networkParams = params |
| 480 | |
| 481 | # add adhoc links |
| 482 | debug("Links\n") |
| 483 | items = config.items('stations') |
| 484 | for item in items: |
| 485 | debug(str(item[0].split(':'))+"\n") |
| 486 | name = item[0].split(':')[0] |
| 487 | params = {} |
| 488 | for param in item[1].split(' '): |
| 489 | if param == "_": |
| 490 | continue |
| 491 | key = param.split('=')[0] |
| 492 | value = param.split('=')[1] |
| 493 | params[key] = value |
| 494 | params = self.convert_params(params) |
| 495 | # replace | with space in bitrates because space is not allowed in the configuration file |
| 496 | if 'bitrates' in params: |
| 497 | params['bitrates'] = params['bitrates'].replace("|", " ") |
| 498 | self.net.addLink(name, cls=adhoc, intf='{}-wlan0'.format(name), **networkParams, **params) |