blob: de23aa3bc072f2a23ab6a9e004a00a9cbdd0bee5 [file] [log] [blame]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -05001# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2#
awlanef8a0a462025-05-30 12:49:17 -05003# Copyright (C) 2015-2025, The University of Memphis,
Ashlesh Gawande6c86e302019-09-17 22:27:05 -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
awlanef8a0a462025-05-30 12:49:17 -050024import os
25from shlex import quote
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050026import shutil
awlanef8a0a462025-05-30 12:49:17 -050027import sys
28from typing import Dict, List, Optional, Tuple, Union
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050029
30from mininet.clean import sh
31from mininet.examples.cluster import RemoteMixin
awlanef8a0a462025-05-30 12:49:17 -050032from mininet.log import debug, warn
33from mininet.node import Node, Switch
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050034
35from minindn.apps.application import Application
awlane21acd052024-06-13 21:12:51 -050036from minindn.util import scp, copyExistentFile, MACToEther
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050037from minindn.helpers.nfdc import Nfdc
38from minindn.minindn import Minindn
39
40class Nlsr(Application):
41 ROUTING_LINK_STATE = 'link-state'
42 ROUTING_HYPERBOLIC = 'hr'
43 ROUTING_DRY_RUN = 'dry'
44 SYNC_PSYNC = 'psync'
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050045
awlanef8a0a462025-05-30 12:49:17 -050046 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 Gawande6c86e302019-09-17 22:27:05 -050072 Application.__init__(self, node)
awlaned8e6b8e2022-05-16 23:49:56 -050073 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 Gawande6c86e302019-09-17 22:27:05 -050081
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'
awlaned8e6b8e2022-05-16 23:49:56 -0500101 # Expected format- node : tuple (node name, IP, cost)
102 self.faceDict = faceDict
awlanef8a0a462025-05-30 12:49:17 -0500103 self.infoeditManualChanges = infoeditChanges
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500104
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
awlane21acd052024-06-13 21:12:51 -0500123 self.neighborLocations = []
124 self.interfaceForNeighbor = dict()
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500125 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):
awlane21acd052024-06-13 21:12:51 -0500139 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 Gawande6c86e302019-09-17 22:27:05 -0500145
146 @staticmethod
awlanef8a0a462025-05-30 12:49:17 -0500147 def createKey(host: Node, name: str, outputFile: Union[str, bytes, os.PathLike]):
suraviregmi9665f5c2023-11-09 20:05:26 +0000148 host.cmd('ndnsec-key-gen {} > {}'.format(name, outputFile))
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500149
150 @staticmethod
awlanef8a0a462025-05-30 12:49:17 -0500151 def createCertificate(host: Node, signer: str, keyFile: str, outputFile: Union[str, bytes, os.PathLike] ):
suraviregmi9665f5c2023-11-09 20:05:26 +0000152 host.cmd('ndnsec-cert-gen -s {} -r {} > {}'.format(signer, keyFile, outputFile))
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500153
154 def createKeysAndCertificates(self):
Italo Valcyccd85b12020-07-24 12:35:20 -0500155 securityDir = '{}/security'.format(Minindn.workDir)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500156
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
suraviregmi9665f5c2023-11-09 20:05:26 +0000164 sh('ndnsec-key-gen {}'.format(rootName)) # Installs a self-signed cert into the system
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500165 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
suraviregmi9665f5c2023-11-09 20:05:26 +0000187 # Copy siteKeyFile from remote for ndnsec-cert-gen
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500188 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
suraviregmi9665f5c2023-11-09 20:05:26 +0000195 sh('ndnsec-cert-gen -s {} -r {} > {}'.format(rootName, siteKeyFile, siteCertFile))
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500196
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()
awlaned8e6b8e2022-05-16 23:49:56 -0500227 if self.faceDict:
228 self.__editNeighborsSectionManual()
229 else:
230 self.__editNeighborsSection()
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500231 self.__editHyperbolicSection()
232 self.__editFibSection()
233 self.__editAdvertisingSection()
234 self.__editSecuritySection()
awlanef8a0a462025-05-30 12:49:17 -0500235 if self.infoeditManualChanges:
236 self.__editCustom()
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500237
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
awlane21acd052024-06-13 21:12:51 -0500262 if self.faceType == Nfdc.PROTOCOL_ETHER:
263 location = MACToEther(link.intf2.MAC())
264 else:
265 location = link.intf2.IP()
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500266 else:
267 other = node1
awlane21acd052024-06-13 21:12:51 -0500268 if self.faceType == Nfdc.PROTOCOL_ETHER:
269 location = MACToEther(link.intf1.MAC())
270 else:
271 location = link.intf1.IP()
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500272
273 linkCost = intf.params.get('delay', '10ms').replace('ms', '')
274
awlane21acd052024-06-13 21:12:51 -0500275 self.neighborLocations.append(location)
276 if self.faceType == Nfdc.PROTOCOL_ETHER:
277 self.interfaceForNeighbor[location] = intf
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500278
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,
awlane21acd052024-06-13 21:12:51 -0500282 self.faceType, location, linkCost))
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500283
awlaned8e6b8e2022-05-16 23:49:56 -0500284 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 Gawande6c86e302019-09-17 22:27:05 -0500300 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
awlane0eb4ca42025-07-02 17:28:32 -0500312 self.node.cmd('{} -d advertising'.format(self.infocmd))
313 self.node.cmd('{} -s advertising.{}{}-site/{} -v 0'
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500314 .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))
awlanef8a0a462025-05-30 12:49:17 -0500329 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'))