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:
1 parent
c69041f603
commit
d8e6b8e508
6 files changed
+335
-15
No files matched your search
@@ -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
|
||||
----------------
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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 _
|
||||
Reference in new issue
Block a user