Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 1 | # -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */ |
| 2 | # |
awlane | f8a0a46 | 2025-05-30 12:49:17 -0500 | [diff] [blame] | 3 | # Copyright (C) 2015-2025, The University of Memphis, |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -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 | f8a0a46 | 2025-05-30 12:49:17 -0500 | [diff] [blame] | 24 | import os |
| 25 | from shlex import quote |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 26 | import shutil |
awlane | f8a0a46 | 2025-05-30 12:49:17 -0500 | [diff] [blame] | 27 | import sys |
| 28 | from typing import Dict, List, Optional, Tuple, Union |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 29 | |
| 30 | from mininet.clean import sh |
| 31 | from mininet.examples.cluster import RemoteMixin |
awlane | f8a0a46 | 2025-05-30 12:49:17 -0500 | [diff] [blame] | 32 | from mininet.log import debug, warn |
| 33 | from mininet.node import Node, Switch |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 34 | |
| 35 | from minindn.apps.application import Application |
awlane | 21acd05 | 2024-06-13 21:12:51 -0500 | [diff] [blame] | 36 | from minindn.util import scp, copyExistentFile, MACToEther |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 37 | from minindn.helpers.nfdc import Nfdc |
| 38 | from minindn.minindn import Minindn |
| 39 | |
| 40 | class Nlsr(Application): |
| 41 | ROUTING_LINK_STATE = 'link-state' |
| 42 | ROUTING_HYPERBOLIC = 'hr' |
| 43 | ROUTING_DRY_RUN = 'dry' |
| 44 | SYNC_PSYNC = 'psync' |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 45 | |
awlane | f8a0a46 | 2025-05-30 12:49:17 -0500 | [diff] [blame] | 46 | def __init__(self, node: Node, logLevel: str ='NONE', security: bool = False, sync: str = SYNC_PSYNC, |
| 47 | faceType: str = Nfdc.PROTOCOL_UDP, nFaces: int = 3, routingType: str = ROUTING_LINK_STATE, |
| 48 | faceDict: Optional[Dict[Node, List[Tuple[str, str, str]]]] = None, |
| 49 | infoeditChanges: Optional[List[Union[Tuple[str, str], Tuple[str, str, str]]]] = None): |
| 50 | """ |
| 51 | Set NLSR NFD application through wrapper on node. Most arguments are directly from nlsr.conf, |
| 52 | so please reference that documentation for more information. |
| 53 | |
| 54 | :param node: Mininet or Mininet-Wifi node object |
| 55 | :param logLevel: NLSR log level set as default (default "NONE") |
| 56 | :param security: Whether or not to set up signing and packet validation. |
| 57 | NDN security features are typically disabled in Mini-NDN for performance (default false) |
| 58 | :param sync: What sync to use with NLSR. PSync is the only one tested, but ndn-svs or chronosync |
| 59 | may be specified manually (default "psync") |
| 60 | :param faceType: What protocol to use to create for faces to neighbors during setup. |
| 61 | UDP faces are typically used in Mini-NDN (default "udp") |
| 62 | :param nFaces: Number of faces to maintain for each prefix after routing calculations (default 3) |
| 63 | :param routingType: Whether to use link state or hyperbolic routing. The latter requires additional setup and |
| 64 | is not recommended for novice users. |
| 65 | :param faceDict: If not creating faces via the included helper, this contains information on faces |
| 66 | to use as neighbors. This is helpful for running NLSR in any case where direct links between NDN routers |
| 67 | are not present in the topology (adhoc or infrastructure wifi, networks using IP switches or routers, etc). |
| 68 | :param infoeditChanges: Commands passed to infoedit other than the most commonly used arguments. |
| 69 | These either expect (key, value) (using the `section` command) or otherwise |
| 70 | (key, value, put|section|delete). |
| 71 | """ |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 72 | Application.__init__(self, node) |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 73 | try: |
| 74 | from mn_wifi.node import Node_wifi |
| 75 | if isinstance(node, Node_wifi) and faceDict == None: |
| 76 | warn("Wifi nodes need to have faces configured manually. Please see \ |
| 77 | documentation on provided helper methods.\r\n") |
| 78 | sys.exit(1) |
| 79 | except ImportError: |
| 80 | pass |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 81 | |
| 82 | self.network = '/ndn/' |
| 83 | self.node = node |
| 84 | self.parameters = self.node.params['params'] |
| 85 | |
| 86 | if self.parameters.get('nlsr-log-level', None) != None: |
| 87 | logLevel = self.parameters.get('nlsr-log-level') |
| 88 | |
| 89 | if logLevel in ['NONE', 'WARN', 'INFO', 'DEBUG', 'TRACE']: |
| 90 | self.envDict = {'NDN_LOG': 'nlsr.*={}'.format(logLevel)} |
| 91 | else: |
| 92 | self.envDict = {'NDN_LOG': logLevel} |
| 93 | |
| 94 | self.logFile = 'nlsr.log' |
| 95 | self.routerName = '/{}C1.Router/cs/{}'.format('%', node.name) |
| 96 | self.confFile = '{}/nlsr.conf'.format(self.homeDir) |
| 97 | self.security = security |
| 98 | self.sync = sync |
| 99 | self.faceType = faceType |
| 100 | self.infocmd = 'infoedit -f nlsr.conf' |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 101 | # Expected format- node : tuple (node name, IP, cost) |
| 102 | self.faceDict = faceDict |
awlane | f8a0a46 | 2025-05-30 12:49:17 -0500 | [diff] [blame] | 103 | self.infoeditManualChanges = infoeditChanges |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 104 | |
| 105 | self.parameters = self.node.params['params'] |
| 106 | |
| 107 | self.nFaces = nFaces |
| 108 | if routingType == Nlsr.ROUTING_HYPERBOLIC: |
| 109 | self.hyperbolicState = 'on' |
| 110 | elif routingType == Nlsr.ROUTING_DRY_RUN: |
| 111 | self.hyperbolicState = 'dry-run' |
| 112 | else: |
| 113 | self.hyperbolicState = 'off' |
| 114 | self.hyperRadius = self.parameters.get('radius', 0.0) |
| 115 | self.hyperAngle = self.parameters.get('angle', 0.0) |
| 116 | |
| 117 | if ((self.hyperbolicState == 'on' or self.hyperbolicState == 'dry-run') and |
| 118 | (self.hyperRadius == 0.0 or self.hyperAngle == 0.0)): |
| 119 | warn('Hyperbolic coordinates in topology file are either missing or misconfigured.') |
| 120 | warn('Check that each node has one radius value and one or two angle value(s).') |
| 121 | sys.exit(1) |
| 122 | |
awlane | 21acd05 | 2024-06-13 21:12:51 -0500 | [diff] [blame] | 123 | self.neighborLocations = [] |
| 124 | self.interfaceForNeighbor = dict() |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 125 | possibleConfPaths = ['/usr/local/etc/ndn/nlsr.conf.sample', '/etc/ndn/nlsr.conf.sample'] |
| 126 | copyExistentFile(node, possibleConfPaths, '{}/nlsr.conf'.format(self.homeDir)) |
| 127 | |
| 128 | self.createConfigFile() |
| 129 | |
| 130 | if security and not Minindn.ndnSecurityDisabled: |
| 131 | self.createKeysAndCertificates() |
| 132 | |
| 133 | def start(self): |
| 134 | self.createFaces() |
| 135 | Application.start(self, 'nlsr -f {}'.format(self.confFile), self.logFile, self.envDict) |
| 136 | Minindn.sleep(1) |
| 137 | |
| 138 | def createFaces(self): |
awlane | 21acd05 | 2024-06-13 21:12:51 -0500 | [diff] [blame] | 139 | for location in self.neighborLocations: |
| 140 | if self.faceType == Nfdc.PROTOCOL_ETHER: |
| 141 | localIntf = self.interfaceForNeighbor[location] |
| 142 | Nfdc.createFace(self.node, location, self.faceType, localInterface=localIntf, isPermanent=True) |
| 143 | else: |
| 144 | Nfdc.createFace(self.node, location, self.faceType, isPermanent=True) |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 145 | |
| 146 | @staticmethod |
awlane | f8a0a46 | 2025-05-30 12:49:17 -0500 | [diff] [blame] | 147 | def createKey(host: Node, name: str, outputFile: Union[str, bytes, os.PathLike]): |
suraviregmi | 9665f5c | 2023-11-09 20:05:26 +0000 | [diff] [blame] | 148 | host.cmd('ndnsec-key-gen {} > {}'.format(name, outputFile)) |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 149 | |
| 150 | @staticmethod |
awlane | f8a0a46 | 2025-05-30 12:49:17 -0500 | [diff] [blame] | 151 | def createCertificate(host: Node, signer: str, keyFile: str, outputFile: Union[str, bytes, os.PathLike] ): |
suraviregmi | 9665f5c | 2023-11-09 20:05:26 +0000 | [diff] [blame] | 152 | host.cmd('ndnsec-cert-gen -s {} -r {} > {}'.format(signer, keyFile, outputFile)) |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 153 | |
| 154 | def createKeysAndCertificates(self): |
Italo Valcy | ccd85b1 | 2020-07-24 12:35:20 -0500 | [diff] [blame] | 155 | securityDir = '{}/security'.format(Minindn.workDir) |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 156 | |
| 157 | if not os.path.exists(securityDir): |
| 158 | os.mkdir(securityDir) |
| 159 | |
| 160 | rootName = self.network |
| 161 | rootCertFile = '{}/root.cert'.format(securityDir) |
| 162 | if not os.path.isfile(rootCertFile): |
| 163 | # Create root certificate |
suraviregmi | 9665f5c | 2023-11-09 20:05:26 +0000 | [diff] [blame] | 164 | sh('ndnsec-key-gen {}'.format(rootName)) # Installs a self-signed cert into the system |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 165 | sh('ndnsec-cert-dump -i {} > {}'.format(rootName, rootCertFile)) |
| 166 | |
| 167 | # Create necessary certificates for each site |
| 168 | nodeSecurityFolder = '{}/security'.format(self.homeDir) |
| 169 | |
| 170 | self.node.cmd('mkdir -p {}'.format(nodeSecurityFolder)) |
| 171 | |
| 172 | # Create temp folders for remote nodes on this machine (localhost) to store site.key file |
| 173 | # from RemoteNodes |
| 174 | if not os.path.exists(nodeSecurityFolder) and \ |
| 175 | isinstance(self.node, RemoteMixin) and self.node.isRemote: |
| 176 | os.makedirs(nodeSecurityFolder) |
| 177 | |
| 178 | shutil.copyfile('{}/root.cert'.format(securityDir), |
| 179 | '{}/root.cert'.format(nodeSecurityFolder)) |
| 180 | |
| 181 | # Create site certificate |
| 182 | siteName = '{}{}-site'.format(self.network, self.node.name) |
| 183 | siteKeyFile = '{}/site.keys'.format(nodeSecurityFolder) |
| 184 | siteCertFile = '{}/site.cert'.format(nodeSecurityFolder) |
| 185 | Nlsr.createKey(self.node, siteName, siteKeyFile) |
| 186 | |
suraviregmi | 9665f5c | 2023-11-09 20:05:26 +0000 | [diff] [blame] | 187 | # Copy siteKeyFile from remote for ndnsec-cert-gen |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 188 | if isinstance(self.node, RemoteMixin) and self.node.isRemote: |
| 189 | login = 'mininet@{}'.format(self.node.server) |
| 190 | src = '{}:{}'.format(login, siteKeyFile) |
| 191 | dst = siteKeyFile |
| 192 | scp(src, dst) |
| 193 | |
| 194 | # Root key is in root namespace, must sign site key and then install on host |
suraviregmi | 9665f5c | 2023-11-09 20:05:26 +0000 | [diff] [blame] | 195 | sh('ndnsec-cert-gen -s {} -r {} > {}'.format(rootName, siteKeyFile, siteCertFile)) |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 196 | |
| 197 | # Copy root.cert and site.cert from localhost to remote host |
| 198 | if isinstance(self.node, RemoteMixin) and self.node.isRemote: |
| 199 | login = 'mininet@{}'.format(self.node.server) |
| 200 | src = '{}/site.cert'.format(nodeSecurityFolder) |
| 201 | src2 = '{}/root.cert'.format(nodeSecurityFolder) |
| 202 | dst = '{}:/tmp/'.format(login) |
| 203 | scp(src, src2, dst) |
| 204 | self.node.cmd('mv /tmp/*.cert {}'.format(nodeSecurityFolder)) |
| 205 | |
| 206 | self.node.cmd('ndnsec-cert-install -f {}'.format(siteCertFile)) |
| 207 | |
| 208 | # Create and install operator certificate |
| 209 | opName = '{}/%C1.Operator/op'.format(siteName) |
| 210 | opKeyFile = '{}/op.keys'.format(nodeSecurityFolder) |
| 211 | opCertFile = '{}/op.cert'.format(nodeSecurityFolder) |
| 212 | Nlsr.createKey(self.node, opName, opKeyFile) |
| 213 | Nlsr.createCertificate(self.node, siteName, opKeyFile, opCertFile) |
| 214 | self.node.cmd('ndnsec-cert-install -f {}'.format(opCertFile)) |
| 215 | |
| 216 | # Create and install router certificate |
| 217 | routerName = '{}/%C1.Router/cs/{}'.format(siteName, self.node.name) |
| 218 | routerKeyFile = '{}/router.keys'.format(nodeSecurityFolder) |
| 219 | routerCertFile = '{}/router.cert'.format(nodeSecurityFolder) |
| 220 | Nlsr.createKey(self.node, routerName, routerKeyFile) |
| 221 | Nlsr.createCertificate(self.node, opName, routerKeyFile, routerCertFile) |
| 222 | self.node.cmd('ndnsec-cert-install -f {}'.format(routerCertFile)) |
| 223 | |
| 224 | def createConfigFile(self): |
| 225 | |
| 226 | self.__editGeneralSection() |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 227 | if self.faceDict: |
| 228 | self.__editNeighborsSectionManual() |
| 229 | else: |
| 230 | self.__editNeighborsSection() |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 231 | self.__editHyperbolicSection() |
| 232 | self.__editFibSection() |
| 233 | self.__editAdvertisingSection() |
| 234 | self.__editSecuritySection() |
awlane | f8a0a46 | 2025-05-30 12:49:17 -0500 | [diff] [blame] | 235 | if self.infoeditManualChanges: |
| 236 | self.__editCustom() |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 237 | |
| 238 | def __editGeneralSection(self): |
| 239 | |
| 240 | self.node.cmd('{} -s general.network -v {}'.format(self.infocmd, self.network)) |
| 241 | self.node.cmd('{} -s general.site -v /{}-site'.format(self.infocmd, self.node.name)) |
| 242 | self.node.cmd('{} -s general.router -v /%C1.Router/cs/{}'.format(self.infocmd, self.node.name)) |
| 243 | self.node.cmd('{} -s general.state-dir -v {}/log'.format(self.infocmd, self.homeDir)) |
| 244 | self.node.cmd('{} -s general.sync-protocol -v {}'.format(self.infocmd, self.sync)) |
| 245 | |
| 246 | def __editNeighborsSection(self): |
| 247 | |
| 248 | self.node.cmd('{} -d neighbors.neighbor'.format(self.infocmd)) |
| 249 | for intf in self.node.intfList(): |
| 250 | link = intf.link |
| 251 | if not link: |
| 252 | continue |
| 253 | |
| 254 | node1, node2 = link.intf1.node, link.intf2.node |
| 255 | |
| 256 | # Todo: add some switch support |
| 257 | if isinstance(node1, Switch) or isinstance(node2, Switch): |
| 258 | continue |
| 259 | |
| 260 | if node1 == self.node: |
| 261 | other = node2 |
awlane | 21acd05 | 2024-06-13 21:12:51 -0500 | [diff] [blame] | 262 | if self.faceType == Nfdc.PROTOCOL_ETHER: |
| 263 | location = MACToEther(link.intf2.MAC()) |
| 264 | else: |
| 265 | location = link.intf2.IP() |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 266 | else: |
| 267 | other = node1 |
awlane | 21acd05 | 2024-06-13 21:12:51 -0500 | [diff] [blame] | 268 | if self.faceType == Nfdc.PROTOCOL_ETHER: |
| 269 | location = MACToEther(link.intf1.MAC()) |
| 270 | else: |
| 271 | location = link.intf1.IP() |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 272 | |
| 273 | linkCost = intf.params.get('delay', '10ms').replace('ms', '') |
| 274 | |
awlane | 21acd05 | 2024-06-13 21:12:51 -0500 | [diff] [blame] | 275 | self.neighborLocations.append(location) |
| 276 | if self.faceType == Nfdc.PROTOCOL_ETHER: |
| 277 | self.interfaceForNeighbor[location] = intf |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 278 | |
| 279 | self.node.cmd('{} -a neighbors.neighbor \ |
| 280 | <<<\'name {}{}-site/%C1.Router/cs/{} face-uri {}://{}\n link-cost {}\'' |
| 281 | .format(self.infocmd, self.network, other.name, other.name, |
awlane | 21acd05 | 2024-06-13 21:12:51 -0500 | [diff] [blame] | 282 | self.faceType, location, linkCost)) |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 283 | |
awlane | d8e6b8e | 2022-05-16 23:49:56 -0500 | [diff] [blame] | 284 | def __editNeighborsSectionManual(self): |
| 285 | |
| 286 | self.node.cmd('{} -d neighbors.neighbor'.format(self.infocmd)) |
| 287 | if self.node not in self.faceDict: |
| 288 | return |
| 289 | for link in self.faceDict[self.node]: |
| 290 | nodeName = link[0] |
| 291 | nodeIP = link[1] |
| 292 | linkCost = link[2] |
| 293 | |
| 294 | self.node.cmd('{} -a neighbors.neighbor \ |
| 295 | <<<\'name {}{}-site/%C1.Router/cs/{} face-uri {}://{}\n link-cost {}\'' |
| 296 | .format(self.infocmd, self.network, nodeName, nodeName, |
| 297 | self.faceType, nodeIP, linkCost)) |
| 298 | |
| 299 | |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 300 | def __editHyperbolicSection(self): |
| 301 | |
| 302 | self.node.cmd('{} -s hyperbolic.state -v {}'.format(self.infocmd, self.hyperbolicState)) |
| 303 | self.node.cmd('{} -s hyperbolic.radius -v {}'.format(self.infocmd, self.hyperRadius)) |
| 304 | self.node.cmd('{} -s hyperbolic.angle -v {}'.format(self.infocmd, self.hyperAngle)) |
| 305 | |
| 306 | def __editFibSection(self): |
| 307 | |
| 308 | self.node.cmd('{} -s fib.max-faces-per-prefix -v {}'.format(self.infocmd, self.nFaces)) |
| 309 | |
| 310 | def __editAdvertisingSection(self): |
| 311 | |
awlane | 0eb4ca4 | 2025-07-02 17:28:32 -0500 | [diff] [blame^] | 312 | self.node.cmd('{} -d advertising'.format(self.infocmd)) |
| 313 | self.node.cmd('{} -s advertising.{}{}-site/{} -v 0' |
Ashlesh Gawande | 6c86e30 | 2019-09-17 22:27:05 -0500 | [diff] [blame] | 314 | .format(self.infocmd, self.network, self.node.name, self.node.name)) |
| 315 | |
| 316 | def __editSecuritySection(self): |
| 317 | |
| 318 | self.node.cmd('{} -d security.cert-to-publish'.format(self.infocmd)) |
| 319 | if not self.security: |
| 320 | self.node.cmd('{} -s security.validator.trust-anchor.type -v any'.format(self.infocmd)) |
| 321 | self.node.cmd('{} -d security.validator.trust-anchor.file-name'.format(self.infocmd)) |
| 322 | self.node.cmd('{} -s security.prefix-update-validator.trust-anchor.type -v any'.format(self.infocmd)) |
| 323 | self.node.cmd('{} -d security.prefix-update-validator.trust-anchor.file-name'.format(self.infocmd)) |
| 324 | else: |
| 325 | self.node.cmd('{} -s security.validator.trust-anchor.file-name -v security/root.cert'.format(self.infocmd)) |
| 326 | self.node.cmd('{} -s security.prefix-update-validator.trust-anchor.file-name -v security/site.cert'.format(self.infocmd)) |
| 327 | self.node.cmd('{} -p security.cert-to-publish -v security/site.cert'.format(self.infocmd)) |
| 328 | self.node.cmd('{} -p security.cert-to-publish -v security/op.cert'.format(self.infocmd)) |
awlane | f8a0a46 | 2025-05-30 12:49:17 -0500 | [diff] [blame] | 329 | self.node.cmd('{} -p security.cert-to-publish -v security/router.cert'.format(self.infocmd)) |
| 330 | |
| 331 | def __editCustom(self): |
| 332 | # EXPECTED FORMAT: [<section>, <key>] OR [<section>, <key>, <operation>] |
| 333 | # Default behavior will replace all values for section with key |
| 334 | # Deletion only works for unique keys |
| 335 | INFOEDIT_COMMANDS = {"put": "-p", "delete": "-d", "section": "-s"} |
| 336 | for infoeditChange in self.infoeditManualChanges: |
| 337 | command = "-s" |
| 338 | if len(infoeditChange) > 2: |
| 339 | if infoeditChange[2] == "delete": |
| 340 | debug(f'{self.infocmd} -d {quote(infoeditChange[0])}\n') |
| 341 | debug(self.node.cmd(f'{self.infocmd} -d {quote(infoeditChange[0])}\n')) |
| 342 | continue |
| 343 | else: |
| 344 | command = INFOEDIT_COMMANDS[infoeditChange[2]] |
| 345 | debug(f'{self.infocmd} {command} {quote(infoeditChange[0])} -v {quote(infoeditChange[1])}\n') |
| 346 | debug(self.node.cmd(f'{self.infocmd} {command} {quote(infoeditChange[0])} -v {quote(infoeditChange[1])}\n')) |