**breaking** mini-ndn: re-design
refs: #5062
Everything is now done through examples like Mininet.
bin/minindn no longer provided as a binary installed in the system
bin/minindnedit GUI: will no longer be maintained
Remove cluster edition, will be re-introduced later
Change-Id: Id4ef137cb2a04d1b0dd24d01941757363bbf7d26
diff --git a/minindn/apps/__init__.py b/minindn/apps/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/minindn/apps/__init__.py
diff --git a/minindn/apps/app_manager.py b/minindn/apps/app_manager.py
new file mode 100644
index 0000000..bfe2ddc
--- /dev/null
+++ b/minindn/apps/app_manager.py
@@ -0,0 +1,56 @@
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2015-2019, The University of Memphis,
+# Arizona Board of Regents,
+# Regents of the University of California.
+#
+# This file is part of Mini-NDN.
+# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
+#
+# Mini-NDN is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Mini-NDN is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mini-NDN, e.g., in COPYING.md file.
+# If not, see <http://www.gnu.org/licenses/>.
+
+from mininet.node import Node
+
+class AppManager(object):
+ def __init__(self, minindn, hosts, cls, **appParams):
+ self.cls = cls
+ self.apps = []
+ for host in hosts:
+ # Don't run NDN apps on switches
+ if isinstance(host, Node):
+ self.startOnNode(host, **appParams)
+
+ minindn.cleanups.append(self.cleanup)
+
+ def startOnNode(self, host, **appParams):
+ app = self.cls(host, **appParams)
+ app.start()
+ self.apps.append(app)
+
+ def cleanup(self):
+ for app in self.apps:
+ app.stop()
+
+ def __getitem__(self, nodeName):
+ for app in self.apps:
+ if app.node.name == nodeName:
+ return app
+ return None
+
+ def __iter__(self):
+ return self.apps.__iter__()
+
+ def __next__(self):
+ return self.apps.__next__()
diff --git a/minindn/apps/application.py b/minindn/apps/application.py
new file mode 100644
index 0000000..6970cb7
--- /dev/null
+++ b/minindn/apps/application.py
@@ -0,0 +1,48 @@
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2015-2019, The University of Memphis,
+# Arizona Board of Regents,
+# Regents of the University of California.
+#
+# This file is part of Mini-NDN.
+# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
+#
+# Mini-NDN is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Mini-NDN is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mini-NDN, e.g., in COPYING.md file.
+# If not, see <http://www.gnu.org/licenses/>.
+
+from minindn.util import getPopen
+
+class Application(object):
+ def __init__(self, node):
+ self.node = node
+ self.process = None
+ self.logfile = None
+ self.homeDir = self.node.params['params']['homeDir']
+
+ # Make directory for log file
+ self.logDir = '{}/log'.format(self.homeDir)
+ self.node.cmd('mkdir -p {}'.format(self.logDir))
+
+ def start(self, command, logfile, envDict=None):
+ if self.process is None:
+ self.logfile = open('{}/{}'.format(self.logDir, logfile), 'w')
+ self.process = getPopen(self.node, command.split(), envDict,
+ stdout=self.logfile, stderr=self.logfile)
+
+ def stop(self):
+ if self.process is not None:
+ self.process.kill()
+ self.process = None
+ if self.logfile is not None:
+ self.logfile.close()
diff --git a/minindn/apps/nfd.py b/minindn/apps/nfd.py
new file mode 100644
index 0000000..7130526
--- /dev/null
+++ b/minindn/apps/nfd.py
@@ -0,0 +1,74 @@
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2015-2019, The University of Memphis,
+# Arizona Board of Regents,
+# Regents of the University of California.
+#
+# This file is part of Mini-NDN.
+# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
+#
+# Mini-NDN is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Mini-NDN is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mini-NDN, e.g., in COPYING.md file.
+# If not, see <http://www.gnu.org/licenses/>.
+
+from minindn.apps.application import Application
+from minindn.util import copyExistentFile
+from minindn.minindn import Minindn
+
+class Nfd(Application):
+
+ def __init__(self, node, logLevel='NONE', csSize=65536,
+ csPolicy='lru', csUnsolicitedPolicy='drop-all'):
+ Application.__init__(self, node)
+
+ self.logLevel = node.params['params'].get('nfd-log-level', logLevel)
+
+ self.confFile = '{}/nfd.conf'.format(self.homeDir)
+ self.logFile = 'nfd.log'
+ self.sockFile = '/var/run/{}.sock'.format(node.name)
+ self.ndnFolder = '{}/.ndn'.format(self.homeDir)
+ self.clientConf = '{}/client.conf'.format(self.ndnFolder)
+
+ # Copy nfd.conf file from /usr/local/etc/ndn or /etc/ndn to the node's home directory
+ # Use nfd.conf as default configuration for NFD, else use the sample
+ possibleConfPaths = ['/usr/local/etc/ndn/nfd.conf.sample', '/usr/local/etc/ndn/nfd.conf',
+ '/etc/ndn/nfd.conf.sample', '/etc/ndn/nfd.conf']
+ copyExistentFile(node, possibleConfPaths, self.confFile)
+
+ # Set log level
+ node.cmd('infoedit -f {} -s log.default_level -v {}'.format(self.confFile, self.logLevel))
+ # Open the conf file and change socket file name
+ node.cmd('infoedit -f {} -s face_system.unix.path -v {}'.format(self.confFile, self.sockFile))
+
+ # Set CS parameters
+ node.cmd('infoedit -f {} -s tables.cs_max_packets -v {}'.format(self.confFile, csSize))
+ node.cmd('infoedit -f {} -s tables.cs_policy -v {}'.format(self.confFile, csPolicy))
+ node.cmd('infoedit -f {} -s tables.cs_unsolicited_policy -v {}'.format(self.confFile, csUnsolicitedPolicy))
+
+ # Make NDN folder
+ node.cmd('mkdir -p {}'.format(self.ndnFolder))
+
+ # Copy client configuration to host
+ possibleClientConfPaths = ['/usr/local/etc/ndn/client.conf.sample', '/etc/ndn/client.conf.sample']
+ copyExistentFile(node, possibleClientConfPaths, self.clientConf)
+
+ # Change the unix socket
+ node.cmd('sudo sed -i "s|nfd.sock|{}.sock|g" {}'.format(node.name, self.clientConf))
+
+ if not Minindn.ndnSecurityDisabled:
+ # Generate key and install cert for /localhost/operator to be used by NFD
+ node.cmd('ndnsec-keygen /localhost/operator | ndnsec-install-cert -')
+
+ def start(self):
+ Application.start(self, 'nfd --config {}'.format(self.confFile), logfile=self.logFile)
+ Minindn.sleep(2)
diff --git a/minindn/apps/nlsr.py b/minindn/apps/nlsr.py
new file mode 100644
index 0000000..36ef971
--- /dev/null
+++ b/minindn/apps/nlsr.py
@@ -0,0 +1,258 @@
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2015-2019, The University of Memphis,
+# Arizona Board of Regents,
+# Regents of the University of California.
+#
+# This file is part of Mini-NDN.
+# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
+#
+# Mini-NDN is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Mini-NDN is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mini-NDN, e.g., in COPYING.md file.
+# If not, see <http://www.gnu.org/licenses/>.
+
+import shutil
+import os, sys
+
+from mininet.clean import sh
+from mininet.examples.cluster import RemoteMixin
+from mininet.log import warn
+from mininet.node import Switch
+
+from minindn.apps.application import Application
+from minindn.util import scp, copyExistentFile
+from minindn.helpers.nfdc import Nfdc
+from minindn.minindn import Minindn
+
+class Nlsr(Application):
+ ROUTING_LINK_STATE = 'link-state'
+ ROUTING_HYPERBOLIC = 'hr'
+ ROUTING_DRY_RUN = 'dry'
+ SYNC_PSYNC = 'psync'
+ SYNC_CHRONOSYNC = 'chronosync'
+
+ def __init__(self, node, logLevel='NONE', security=False, sync=SYNC_PSYNC,
+ faceType='udp', nFaces=3, routingType=ROUTING_LINK_STATE):
+ Application.__init__(self, node)
+
+ self.network = '/ndn/'
+ self.node = node
+ self.parameters = self.node.params['params']
+
+ if self.parameters.get('nlsr-log-level', None) != None:
+ logLevel = self.parameters.get('nlsr-log-level')
+
+ if logLevel in ['NONE', 'WARN', 'INFO', 'DEBUG', 'TRACE']:
+ self.envDict = {'NDN_LOG': 'nlsr.*={}'.format(logLevel)}
+ else:
+ self.envDict = {'NDN_LOG': logLevel}
+
+ self.logFile = 'nlsr.log'
+ self.routerName = '/{}C1.Router/cs/{}'.format('%', node.name)
+ self.confFile = '{}/nlsr.conf'.format(self.homeDir)
+ self.security = security
+ self.sync = sync
+ self.faceType = faceType
+ self.infocmd = 'infoedit -f nlsr.conf'
+
+ self.parameters = self.node.params['params']
+
+ self.nFaces = nFaces
+ if routingType == Nlsr.ROUTING_HYPERBOLIC:
+ self.hyperbolicState = 'on'
+ elif routingType == Nlsr.ROUTING_DRY_RUN:
+ self.hyperbolicState = 'dry-run'
+ else:
+ self.hyperbolicState = 'off'
+ self.hyperRadius = self.parameters.get('radius', 0.0)
+ self.hyperAngle = self.parameters.get('angle', 0.0)
+
+ if ((self.hyperbolicState == 'on' or self.hyperbolicState == 'dry-run') and
+ (self.hyperRadius == 0.0 or self.hyperAngle == 0.0)):
+ warn('Hyperbolic coordinates in topology file are either missing or misconfigured.')
+ warn('Check that each node has one radius value and one or two angle value(s).')
+ sys.exit(1)
+
+ self.neighborIPs = []
+ possibleConfPaths = ['/usr/local/etc/ndn/nlsr.conf.sample', '/etc/ndn/nlsr.conf.sample']
+ copyExistentFile(node, possibleConfPaths, '{}/nlsr.conf'.format(self.homeDir))
+
+ self.createConfigFile()
+
+ if security and not Minindn.ndnSecurityDisabled:
+ self.createKeysAndCertificates()
+
+ def start(self):
+ self.createFaces()
+ Application.start(self, 'nlsr -f {}'.format(self.confFile), self.logFile, self.envDict)
+ Minindn.sleep(1)
+
+ def createFaces(self):
+ for ip in self.neighborIPs:
+ Nfdc.createFace(self.node, ip, self.faceType, isPermanent=True)
+
+ @staticmethod
+ def createKey(host, name, outputFile):
+ host.cmd('ndnsec-keygen {} > {}'.format(name, outputFile))
+
+ @staticmethod
+ def createCertificate(host, signer, keyFile, outputFile):
+ host.cmd('ndnsec-certgen -s {} -r {} > {}'.format(signer, keyFile, outputFile))
+
+ def createKeysAndCertificates(self):
+ securityDir = '{}/security'.format(self.parameters['workDir'])
+
+ if not os.path.exists(securityDir):
+ os.mkdir(securityDir)
+
+ rootName = self.network
+ rootCertFile = '{}/root.cert'.format(securityDir)
+ if not os.path.isfile(rootCertFile):
+ # Create root certificate
+ sh('ndnsec-keygen {}'.format(rootName)) # Installs a self-signed cert into the system
+ sh('ndnsec-cert-dump -i {} > {}'.format(rootName, rootCertFile))
+
+ # Create necessary certificates for each site
+ nodeSecurityFolder = '{}/security'.format(self.homeDir)
+
+ self.node.cmd('mkdir -p {}'.format(nodeSecurityFolder))
+
+ # Create temp folders for remote nodes on this machine (localhost) to store site.key file
+ # from RemoteNodes
+ if not os.path.exists(nodeSecurityFolder) and \
+ isinstance(self.node, RemoteMixin) and self.node.isRemote:
+ os.makedirs(nodeSecurityFolder)
+
+ shutil.copyfile('{}/root.cert'.format(securityDir),
+ '{}/root.cert'.format(nodeSecurityFolder))
+
+ # Create site certificate
+ siteName = '{}{}-site'.format(self.network, self.node.name)
+ siteKeyFile = '{}/site.keys'.format(nodeSecurityFolder)
+ siteCertFile = '{}/site.cert'.format(nodeSecurityFolder)
+ Nlsr.createKey(self.node, siteName, siteKeyFile)
+
+ # Copy siteKeyFile from remote for ndnsec-certgen
+ if isinstance(self.node, RemoteMixin) and self.node.isRemote:
+ login = 'mininet@{}'.format(self.node.server)
+ src = '{}:{}'.format(login, siteKeyFile)
+ dst = siteKeyFile
+ scp(src, dst)
+
+ # Root key is in root namespace, must sign site key and then install on host
+ sh('ndnsec-certgen -s {} -r {} > {}'.format(rootName, siteKeyFile, siteCertFile))
+
+ # Copy root.cert and site.cert from localhost to remote host
+ if isinstance(self.node, RemoteMixin) and self.node.isRemote:
+ login = 'mininet@{}'.format(self.node.server)
+ src = '{}/site.cert'.format(nodeSecurityFolder)
+ src2 = '{}/root.cert'.format(nodeSecurityFolder)
+ dst = '{}:/tmp/'.format(login)
+ scp(src, src2, dst)
+ self.node.cmd('mv /tmp/*.cert {}'.format(nodeSecurityFolder))
+
+ self.node.cmd('ndnsec-cert-install -f {}'.format(siteCertFile))
+
+ # Create and install operator certificate
+ opName = '{}/%C1.Operator/op'.format(siteName)
+ opKeyFile = '{}/op.keys'.format(nodeSecurityFolder)
+ opCertFile = '{}/op.cert'.format(nodeSecurityFolder)
+ Nlsr.createKey(self.node, opName, opKeyFile)
+ Nlsr.createCertificate(self.node, siteName, opKeyFile, opCertFile)
+ self.node.cmd('ndnsec-cert-install -f {}'.format(opCertFile))
+
+ # Create and install router certificate
+ routerName = '{}/%C1.Router/cs/{}'.format(siteName, self.node.name)
+ routerKeyFile = '{}/router.keys'.format(nodeSecurityFolder)
+ routerCertFile = '{}/router.cert'.format(nodeSecurityFolder)
+ Nlsr.createKey(self.node, routerName, routerKeyFile)
+ Nlsr.createCertificate(self.node, opName, routerKeyFile, routerCertFile)
+ self.node.cmd('ndnsec-cert-install -f {}'.format(routerCertFile))
+
+ def createConfigFile(self):
+
+ self.__editGeneralSection()
+ self.__editNeighborsSection()
+ self.__editHyperbolicSection()
+ self.__editFibSection()
+ self.__editAdvertisingSection()
+ self.__editSecuritySection()
+
+ def __editGeneralSection(self):
+
+ self.node.cmd('{} -s general.network -v {}'.format(self.infocmd, self.network))
+ self.node.cmd('{} -s general.site -v /{}-site'.format(self.infocmd, self.node.name))
+ self.node.cmd('{} -s general.router -v /%C1.Router/cs/{}'.format(self.infocmd, self.node.name))
+ self.node.cmd('{} -s general.state-dir -v {}/log'.format(self.infocmd, self.homeDir))
+ self.node.cmd('{} -s general.sync-protocol -v {}'.format(self.infocmd, self.sync))
+
+ def __editNeighborsSection(self):
+
+ self.node.cmd('{} -d neighbors.neighbor'.format(self.infocmd))
+ for intf in self.node.intfList():
+ link = intf.link
+ if not link:
+ continue
+
+ node1, node2 = link.intf1.node, link.intf2.node
+
+ # Todo: add some switch support
+ if isinstance(node1, Switch) or isinstance(node2, Switch):
+ continue
+
+ if node1 == self.node:
+ other = node2
+ ip = other.IP(str(link.intf2))
+ else:
+ other = node1
+ ip = other.IP(str(link.intf1))
+
+ linkCost = intf.params.get('delay', '10ms').replace('ms', '')
+
+ self.neighborIPs.append(ip)
+
+ self.node.cmd('{} -a neighbors.neighbor \
+ <<<\'name {}{}-site/%C1.Router/cs/{} face-uri {}://{}\n link-cost {}\''
+ .format(self.infocmd, self.network, other.name, other.name,
+ self.faceType, ip, linkCost))
+
+ def __editHyperbolicSection(self):
+
+ self.node.cmd('{} -s hyperbolic.state -v {}'.format(self.infocmd, self.hyperbolicState))
+ self.node.cmd('{} -s hyperbolic.radius -v {}'.format(self.infocmd, self.hyperRadius))
+ self.node.cmd('{} -s hyperbolic.angle -v {}'.format(self.infocmd, self.hyperAngle))
+
+ def __editFibSection(self):
+
+ self.node.cmd('{} -s fib.max-faces-per-prefix -v {}'.format(self.infocmd, self.nFaces))
+
+ def __editAdvertisingSection(self):
+
+ self.node.cmd('{} -d advertising.prefix'.format(self.infocmd))
+ self.node.cmd('{} -s advertising.prefix -v {}{}-site/{}'
+ .format(self.infocmd, self.network, self.node.name, self.node.name))
+
+ def __editSecuritySection(self):
+
+ self.node.cmd('{} -d security.cert-to-publish'.format(self.infocmd))
+ if not self.security:
+ self.node.cmd('{} -s security.validator.trust-anchor.type -v any'.format(self.infocmd))
+ self.node.cmd('{} -d security.validator.trust-anchor.file-name'.format(self.infocmd))
+ self.node.cmd('{} -s security.prefix-update-validator.trust-anchor.type -v any'.format(self.infocmd))
+ self.node.cmd('{} -d security.prefix-update-validator.trust-anchor.file-name'.format(self.infocmd))
+ else:
+ self.node.cmd('{} -s security.validator.trust-anchor.file-name -v security/root.cert'.format(self.infocmd))
+ self.node.cmd('{} -s security.prefix-update-validator.trust-anchor.file-name -v security/site.cert'.format(self.infocmd))
+ self.node.cmd('{} -p security.cert-to-publish -v security/site.cert'.format(self.infocmd))
+ self.node.cmd('{} -p security.cert-to-publish -v security/op.cert'.format(self.infocmd))
+ self.node.cmd('{} -p security.cert-to-publish -v security/router.cert'.format(self.infocmd))