Create helper methods for specifying face creation via topology

and changes to enable users to run NLSR in Mini-NDN-Wifi.

Refs: #5232
Change-Id: Iac8bd170f34985aa2b7ee080033a6ceaf334bd0c
This commit is contained in:
awlane committed 2022-07-20 14:50:26 -05:00
1 parent c69041f603
commit d8e6b8e508
6 files changed
+335 -15

No files matched your search

+13
View File
@@ -238,6 +238,19 @@ When security is enabled, NLSR security certificates are stored in:
certificate, Mini-NDN installs root.cert in security folder for each
NLSR.
While a host's NLSR neighbors are by default populated by adjacent nodes in wired scenarios,
for those running NLSR on wifi stations it is required that you specify "neighbor" faces
manually. The framework for this is provided either via a dictionary object or through
additional sections in topology files, and may also be used for wired experiments.
See an example of a topo of this sort in ``mini-ndn/topologies/wifi/nlsr_wifi_example.conf``.
NLSR faces to be created can be manually specified from topology files in a ``[faces]``
section, with the format ``nodeA:nodeB [cost=X]``. You should then call the ``setupFaces()``
method of an initialized Mini-NDN object to get a dictionary based on this parse in the format
``faceA:[(faceB, cost), (faceC, cost),...]``, which can finally be passed to the NLSR
helper via the faceDict parameter. An example experiment using this methodology is located
at ``mini-ndn/examples/wifi/nlsr_wifi.py``. Note that the aforementioned dict can also be
created manually in the previously established format.
Routing Options
----------------
+44
View File
@@ -0,0 +1,44 @@
import time
from mininet.log import setLogLevel, info
from minindn.wifi.minindnwifi import MinindnWifi
from minindn.apps.app_manager import AppManager
from minindn.apps.nfd import Nfd
from minindn.apps.nlsr import Nlsr
from minindn.helpers.experiment import Experiment
setLogLevel('info')
# Setup code
sync = Nlsr.SYNC_PSYNC
ndn = MinindnWifi()
args = ndn.args
ndn.start()
info('Starting NFD on nodes\n')
nfds = AppManager(ndn, ndn.net.stations, Nfd, logLevel='INFO')
face_dict = ndn.setupFaces()
if not face_dict:
print("Hint: Are you sure you're running with a topology with a faces section? \
Try topologies/wifi/nlsr_wifi_example.conf")
info('Starting NLSR on nodes\n')
nlsrs = AppManager(ndn, [], Nlsr)
print(face_dict)
for host in ndn.net.stations:
nlsrs.startOnNode(host, sync=sync, logLevel='INFO', faceDict=face_dict)
time.sleep(0.1)
Experiment.checkConvergence(ndn, ndn.net.stations, 60, False)
# Uncomment for CLI access
from minindn.util import MiniNDNWifiCLI
MiniNDNWifiCLI(ndn.net)
ndn.stop()
+32 -3
View File
@@ -41,8 +41,16 @@ class Nlsr(Application):
SYNC_PSYNC = 'psync'
def __init__(self, node, logLevel='NONE', security=False, sync=SYNC_PSYNC,
faceType='udp', nFaces=3, routingType=ROUTING_LINK_STATE):
faceType='udp', nFaces=3, routingType=ROUTING_LINK_STATE, faceDict=None):
Application.__init__(self, node)
try:
from mn_wifi.node import Node_wifi
if isinstance(node, Node_wifi) and faceDict == None:
warn("Wifi nodes need to have faces configured manually. Please see \
documentation on provided helper methods.\r\n")
sys.exit(1)
except ImportError:
pass
self.network = '/ndn/'
self.node = node
@@ -63,6 +71,8 @@ class Nlsr(Application):
self.sync = sync
self.faceType = faceType
self.infocmd = 'infoedit -f nlsr.conf'
# Expected format- node : tuple (node name, IP, cost)
self.faceDict = faceDict
self.parameters = self.node.params['params']
@@ -181,7 +191,10 @@ class Nlsr(Application):
def createConfigFile(self):
self.__editGeneralSection()
self.__editNeighborsSection()
if self.faceDict:
self.__editNeighborsSectionManual()
else:
self.__editNeighborsSection()
self.__editHyperbolicSection()
self.__editFibSection()
self.__editAdvertisingSection()
@@ -225,6 +238,22 @@ class Nlsr(Application):
.format(self.infocmd, self.network, other.name, other.name,
self.faceType, ip, linkCost))
def __editNeighborsSectionManual(self):
self.node.cmd('{} -d neighbors.neighbor'.format(self.infocmd))
if self.node not in self.faceDict:
return
for link in self.faceDict[self.node]:
nodeName = link[0]
nodeIP = link[1]
linkCost = link[2]
self.node.cmd('{} -a neighbors.neighbor \
<<<\'name {}{}-site/%C1.Router/cs/{} face-uri {}://{}\n link-cost {}\''
.format(self.infocmd, self.network, nodeName, nodeName,
self.faceType, nodeIP, linkCost))
def __editHyperbolicSection(self):
self.node.cmd('{} -s hyperbolic.state -v {}'.format(self.infocmd, self.hyperbolicState))
@@ -254,4 +283,4 @@ class Nlsr(Application):
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))
self.node.cmd('{} -p security.cert-to-publish -v security/router.cert'.format(self.infocmd))
+118 -5
View File
@@ -38,7 +38,6 @@ from mininet.node import Switch
from mininet.util import ipStr, ipParse
from mininet.log import info, debug, error
class Minindn(object):
"""
This class provides the following features to the user:
@@ -83,10 +82,11 @@ class Minindn(object):
else:
self.topoFile = topoFile
self.faces_to_create = {}
if topo is None and not noTopo:
try:
info('Using topology file {}\n'.format(self.topoFile))
self.topo = self.processTopo(self.topoFile)
self.topo, self.faces_to_create = self.processTopo(self.topoFile)
except configparser.NoSectionError as e:
info('Error reading config file: {}\n'.format(e))
sys.exit(1)
@@ -122,13 +122,15 @@ class Minindn(object):
# nargs='?' required here since optional argument
parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/default-topology.conf',
help='If no template_file is given, topologies/default-topology.conf will be used.')
help='If no template_file is given, topologies/default-topology.conf \
will be used.')
parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
help='Specify the working directory; default is /tmp/minindn')
parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
help='Specify the full path destination folder where experiment results will be moved')
help='Specify the full path destination folder where experiment \
results will be moved')
return parser
@@ -201,6 +203,28 @@ class Minindn(object):
topo.addLink(link[0], link[1], **params)
faces = {}
try:
items = config.items('faces')
debug("Faces")
for item in items:
face_a, face_b = item[0].split(':')
debug(item)
cost = -1
for param in item[1].split(' '):
if param.split("=")[0] == 'cost':
cost = param.split("=")[1]
face_info = (face_b, int(cost))
if face_a not in faces:
faces[face_a] = [face_info]
else:
faces[face_a].append(face_info)
except configparser.NoSectionError:
debug("Faces section is optional")
pass
return (topo, faces)
return topo
def start(self):
@@ -249,6 +273,16 @@ class Minindn(object):
info(format_exc())
exit(1)
def getInterfaceDelay(self, node, interface):
tc_output = node.cmd("tc qdisc show dev {}".format(interface))
for line in tc_output.splitlines():
if "qdisc netem 10:" in line:
split_line = line.split(" ")
for index in range(0, len(split_line)):
if split_line[index] == "delay":
return float(split_line[index + 1][:-2])
return 0.0
def initParams(self, nodes):
"""Initialize Mini-NDN parameters for array of nodes"""
for host in nodes:
@@ -258,4 +292,83 @@ class Minindn(object):
homeDir = '{}/{}'.format(Minindn.workDir, host.name)
host.params['params']['homeDir'] = homeDir
host.cmd('mkdir -p {}'.format(homeDir))
host.cmd('export HOME={} && cd ~'.format(homeDir))
host.cmd('export HOME={} && cd ~'.format(homeDir))
def nfdcBatchProcessing(self, station, faces):
# Input format: [IP, protocol, isPermanent]
batch_file = open("{}/{}/nfdc.batch".format(Minindn.workDir, station.name), "w")
lines = []
for face in faces:
ip = face[0]
protocol = face[1]
if face[2]:
face_type = "permanent"
else:
face_type = "persistent"
nfdc_command = "face create {}://{} {}\n".format(protocol, ip, face_type)
lines.append(nfdc_command)
batch_file.writelines(lines)
batch_file.close()
debug(station.cmd("nfdc -f {}/{}/nfdc.batch".format(Minindn.workDir, station.name)))
def setupFaces(self, faces_to_create=None):
""" Method to create unicast faces between connected nodes;
Returns dict- {node: (other node name, other node IP, other node's delay as int)}.
This is intended to pass to the NLSR helper via the faceDict param """
if not faces_to_create:
faces_to_create = self.faces_to_create
# (nodeName, IP, delay as int)
# list of tuples
created_faces = dict()
batch_faces = dict()
for nodeAname in faces_to_create.keys():
if not nodeAname in batch_faces.keys():
batch_faces[nodeAname] = []
for nodeBname, faceCost in faces_to_create[nodeAname]:
if not nodeBname in batch_faces.keys():
batch_faces[nodeBname] = []
nodeA = self.net[nodeAname]
nodeB = self.net[nodeBname]
if nodeA.connectionsTo(nodeB):
best_interface = None
delay = None
for interface in nodeA.connectionsTo(nodeB):
interface_delay = self.getInterfaceDelay(nodeA, interface[0])
if not delay or int(interface_delay) < delay:
best_interface = interface
faceAIP = best_interface[0].IP()
faceBIP = best_interface[1].IP()
# Node delay will be symmetrical for connected nodes
nodeDelay = int(self.getInterfaceDelay(nodeA, best_interface[0]))
#nodeBDelay = int(self.getInterfaceDelay(nodeB, best_interface[1]))
else:
# If no direct wired connections exist (ie when using a switch),
# assume the default interface
faceAIP = nodeA.IP()
faceBIP = nodeB.IP()
nodeADelay = int(self.getInterfaceDelay(nodeA, nodeA.defaultIntf()))
nodeBDelay = int(self.getInterfaceDelay(nodeB, nodeB.defaultIntf()))
nodeDelay = nodeADelay + nodeBDelay
if not faceCost == -1:
nodeALink = (nodeA.name, faceAIP, faceCost)
nodeBLink = (nodeB.name, faceBIP, faceCost)
else:
nodeALink = (nodeA.name, faceAIP, nodeDelay)
nodeBLink = (nodeB.name, faceBIP, nodeDelay)
# Importing this before initialization causes issues
batch_faces[nodeAname].append([faceBIP, "udp", True])
batch_faces[nodeBname].append([faceAIP, "udp", True])
if nodeA not in created_faces:
created_faces[nodeA] = [nodeBLink]
else:
created_faces[nodeA].append(nodeBLink)
if nodeB not in created_faces:
created_faces[nodeB] = [nodeALink]
else:
created_faces[nodeB].append(nodeALink)
for station_name in batch_faces.keys():
self.nfdcBatchProcessing(self.net[station_name], batch_faces[station_name])
return created_faces
+108 -7
View File
@@ -34,17 +34,20 @@ from mn_wifi.net import Mininet_wifi
from mn_wifi.link import WirelessLink
from minindn.minindn import Minindn
from minindn.helpers.nfdc import Nfdc
class MinindnWifi(Minindn):
""" Class for handling default args, Mininet-wifi object and home directories """
def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False, link=WirelessLink, workDir=None, **mininetParams):
"""Create Mini-NDN-Wifi object
def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, noTopo=False,
link=WirelessLink, workDir=None, **mininetParams):
"""
Create Mini-NDN-Wifi object
parser: Parent parser of Mini-NDN-Wifi parser (use to specify experiment arguments)
topo: Mininet topo object (optional)
topoFile: topology file location (optional)
noTopo: Allows specification of topology after network object is initialized (optional)
link: Allows specification of default Mininet/Mininet-Wifi link type for connections between nodes (optional)
mininetParams: Any params to pass to Mininet-WiFi
link: Allows specification of default Mininet/Mininet-Wifi link type for
connections between nodes (optional)mininetParams: Any params to pass to Mininet-WiFi
"""
self.parser = self.parseArgs(parser)
self.args = self.parser.parse_args()
@@ -63,10 +66,11 @@ class MinindnWifi(Minindn):
else:
self.topoFile = topoFile
self.faces_to_create = {}
if topo is None and not noTopo:
try:
info('Using topology file {}\n'.format(self.topoFile))
self.topo = self.processTopo(self.topoFile)
self.topo, self.faces_to_create = self.processTopo(self.topoFile)
except configparser.NoSectionError as e:
info('Error reading config file: {}\n'.format(e))
sys.exit(1)
@@ -193,7 +197,27 @@ class MinindnWifi(Minindn):
topo.addLink(link[0], link[1], **params)
return topo
faces = {}
try:
items = config.items('faces')
debug("Faces")
for item in items:
face_a, face_b = item[0].split(':')
debug(item)
cost = -1
for param in item[1].split(' '):
if param.split("=")[0] == 'cost':
cost = param.split("=")[1]
face_info = (face_b, int(cost))
if face_a not in faces:
faces[face_a] = [face_info]
else:
faces[face_a].append(face_info)
except configparser.NoSectionError:
debug("Faces section is optional")
pass
return (topo, faces)
def startMobility(self, max_x=1000, max_y=1000, **kwargs):
""" Method to run a basic mobility setup on your net"""
@@ -203,4 +227,81 @@ class MinindnWifi(Minindn):
def startMobilityModel(self, max_x=1000, max_y=1000, **kwargs):
""" Method to run a mobility model on your net until exited"""
self.net.plotGraph(max_x=max_x, max_y=max_y)
self.net.setMobilityModel(**kwargs)
self.net.setMobilityModel(**kwargs)
def getWifiInterfaceDelay(self, node, interface=None):
"""Method to return the configured tc delay of a wifi node's interface as a float"""
if not interface:
wifi_interface = "{}-wlan0".format(node.name)
else:
wifi_interface = interface
tc_output = node.cmd("tc qdisc show dev {}".format(wifi_interface))
for line in tc_output.splitlines():
if "qdisc netem 10:" in line:
split_line = line.split(" ")
for index in range(0, len(split_line)):
if split_line[index] == "delay":
return float(split_line[index + 1][:-2])
return 0.0
def setupFaces(self, faces_to_create=None):
"""
Method to create unicast faces between nodes connected by an AP based on name or faces
between connected nodes; Returns dict- {node: (other node name, other node IP, other
node's delay as int)}. This is intended to pass to the NLSR helper via the faceDict param
"""
if not faces_to_create:
faces_to_create = self.faces_to_create
# (nodeName, IP, delay as int)
# list of tuples
created_faces = dict()
batch_faces = dict()
for nodeAname in faces_to_create.keys():
if not nodeAname in batch_faces.keys():
batch_faces[nodeAname] = []
for nodeBname, faceCost in faces_to_create[nodeAname]:
if not nodeBname in batch_faces.keys():
batch_faces[nodeBname] = []
nodeA = self.net[nodeAname]
nodeB = self.net[nodeBname]
if nodeA.connectionsTo(nodeB):
best_interface = None
delay = None
for interface in nodeA.connectionsTo(nodeB):
interface_delay = self.getInterfaceDelay(nodeA, interface[0])
if not delay or int(interface_delay) < delay:
best_interface = interface
faceAIP = best_interface[0].IP()
faceBIP = best_interface[1].IP()
# Node delay should be symmetrical
nodeDelay = int(self.getInterfaceDelay(nodeA, best_interface[0]))
else:
# Default IP will be the primary wireless interface, unclear if multiple wireless
# interfaces should be handled
faceAIP = nodeA.IP()
faceBIP = nodeB.IP()
nodeADelay = self.getWifiInterfaceDelay(nodeA)
nodeBDelay = self.getWifiInterfaceDelay(nodeB)
nodeDelay = nodeADelay + nodeBDelay
if not faceCost == -1:
nodeALink = (nodeA.name, faceAIP, faceCost)
nodeBLink = (nodeB.name, faceBIP, faceCost)
else:
nodeALink = (nodeA.name, faceAIP, nodeDelay)
nodeBLink = (nodeB.name, faceBIP, nodeDelay)
batch_faces[nodeAname].append([faceBIP, "udp", True])
batch_faces[nodeBname].append([faceAIP, "udp", True])
if nodeA not in created_faces:
created_faces[nodeA] = [nodeBLink]
else:
created_faces[nodeA].append(nodeBLink)
if nodeB not in created_faces:
created_faces[nodeB] = [nodeALink]
else:
created_faces[nodeB].append(nodeALink)
for station_name in batch_faces.keys():
self.nfdcBatchProcessing(self.net[station_name], batch_faces[station_name])
return created_faces
+20
View File
@@ -0,0 +1,20 @@
[stations]
sta1: range=50
sta2: range=50
sta3: range=50
sta4: range=50
[cars]
[accessPoints]
ap1: position=50,50,50 range=50
[links]
sta1:ap1 delay=10ms
sta2:ap1 delay=10ms
sta3:ap1 delay=10ms
sta4:ap1 delay=10ms
[faces]
sta1:sta2 cost=42
sta2:sta4 _
sta3:sta1 _