2013-08-29 15:21:13 +01:00
|
|
|
#
|
|
|
|
# CORE
|
2014-05-25 21:08:41 +01:00
|
|
|
# Copyright (c)2010-2014 the Boeing Company.
|
2013-08-29 15:21:13 +01:00
|
|
|
# See the LICENSE file included in this distribution.
|
|
|
|
#
|
|
|
|
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
|
|
|
|
#
|
|
|
|
'''
|
2014-09-17 23:00:11 +01:00
|
|
|
nodes.py: definition of an EmaneNode class for implementing configuration
|
2013-08-29 15:21:13 +01:00
|
|
|
control of an EMANE emulation. An EmaneNode has several attached NEMs that
|
|
|
|
share the same MAC+PHY model.
|
|
|
|
'''
|
|
|
|
|
|
|
|
import sys
|
2015-06-01 18:52:49 +01:00
|
|
|
import os.path
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
from core.api import coreapi
|
|
|
|
from core.coreobj import PyCoreNet
|
2014-05-25 21:08:41 +01:00
|
|
|
try:
|
|
|
|
from emanesh.events import EventService
|
|
|
|
from emanesh.events import LocationEvent
|
|
|
|
except Exception, e:
|
|
|
|
pass
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
try:
|
|
|
|
import emaneeventservice
|
|
|
|
import emaneeventlocation
|
|
|
|
except Exception, e:
|
|
|
|
''' Don't require all CORE users to have EMANE libeventservice and its
|
|
|
|
Python bindings installed.
|
2014-09-17 23:00:11 +01:00
|
|
|
'''
|
2013-08-29 15:21:13 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
class EmaneNet(PyCoreNet):
|
|
|
|
''' EMANE network base class.
|
|
|
|
'''
|
|
|
|
apitype = coreapi.CORE_NODE_EMANE
|
|
|
|
linktype = coreapi.CORE_LINK_WIRELESS
|
|
|
|
type = "wlan" # icon used
|
|
|
|
|
|
|
|
class EmaneNode(EmaneNet):
|
|
|
|
''' EMANE node contains NEM configuration and causes connected nodes
|
|
|
|
to have TAP interfaces (instead of VEth). These are managed by the
|
|
|
|
Emane controller object that exists in a session.
|
|
|
|
'''
|
|
|
|
def __init__(self, session, objid = None, name = None, verbose = False,
|
|
|
|
start = True):
|
|
|
|
PyCoreNet.__init__(self, session, objid, name, verbose, start)
|
|
|
|
self.verbose = verbose
|
|
|
|
self.conf = ""
|
|
|
|
self.up = False
|
|
|
|
self.nemidmap = {}
|
|
|
|
self.model = None
|
|
|
|
self.mobility = None
|
|
|
|
|
|
|
|
def linkconfig(self, netif, bw = None, delay = None,
|
|
|
|
loss = None, duplicate = None, jitter = None, netif2 = None):
|
|
|
|
''' The CommEffect model supports link configuration.
|
|
|
|
'''
|
|
|
|
if not self.model:
|
|
|
|
return
|
|
|
|
return self.model.linkconfig(netif=netif, bw=bw, delay=delay, loss=loss,
|
|
|
|
duplicate=duplicate, jitter=jitter, netif2=netif2)
|
|
|
|
|
|
|
|
def config(self, conf):
|
|
|
|
#print "emane", self.name, "got config:", conf
|
|
|
|
self.conf = conf
|
|
|
|
|
|
|
|
def shutdown(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def link(self, netif1, netif2):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def unlink(self, netif1, netif2):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def setmodel(self, model, config):
|
|
|
|
''' set the EmaneModel associated with this node
|
|
|
|
'''
|
|
|
|
if (self.verbose):
|
|
|
|
self.info("adding model %s" % model._name)
|
|
|
|
if model._type == coreapi.CORE_TLV_REG_WIRELESS:
|
|
|
|
# EmaneModel really uses values from ConfigurableManager
|
|
|
|
# when buildnemxml() is called, not during init()
|
|
|
|
self.model = model(session=self.session, objid=self.objid,
|
|
|
|
verbose=self.verbose)
|
|
|
|
elif model._type == coreapi.CORE_TLV_REG_MOBILITY:
|
|
|
|
self.mobility = model(session=self.session, objid=self.objid,
|
|
|
|
verbose=self.verbose, values=config)
|
|
|
|
|
|
|
|
def setnemid(self, netif, nemid):
|
2014-09-17 23:00:11 +01:00
|
|
|
''' Record an interface to numerical ID mapping. The Emane controller
|
2013-08-29 15:21:13 +01:00
|
|
|
object manages and assigns these IDs for all NEMs.
|
|
|
|
'''
|
|
|
|
self.nemidmap[netif] = nemid
|
|
|
|
|
|
|
|
def getnemid(self, netif):
|
|
|
|
''' Given an interface, return its numerical ID.
|
|
|
|
'''
|
|
|
|
if netif not in self.nemidmap:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return self.nemidmap[netif]
|
|
|
|
|
|
|
|
def getnemnetif(self, nemid):
|
|
|
|
''' Given a numerical NEM ID, return its interface. This returns the
|
|
|
|
first interface that matches the given NEM ID.
|
|
|
|
'''
|
|
|
|
for netif in self.nemidmap:
|
|
|
|
if self.nemidmap[netif] == nemid:
|
|
|
|
return netif
|
|
|
|
return None
|
2014-09-17 23:00:11 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def netifs(self, sort=True):
|
|
|
|
''' Retrieve list of linked interfaces sorted by node number.
|
|
|
|
'''
|
|
|
|
return sorted(self._netif.values(), key=lambda ifc: ifc.node.objid)
|
|
|
|
|
|
|
|
def buildplatformxmlentry(self, doc):
|
|
|
|
''' Return a dictionary of XML elements describing the NEMs
|
|
|
|
connected to this EmaneNode for inclusion in the platform.xml file.
|
|
|
|
'''
|
|
|
|
ret = {}
|
|
|
|
if self.model is None:
|
|
|
|
self.info("warning: EmaneNode %s has no associated model" % \
|
|
|
|
self.name)
|
|
|
|
return ret
|
|
|
|
for netif in self.netifs():
|
|
|
|
# <nem name="NODE-001" definition="rfpipenem.xml">
|
|
|
|
nementry = self.model.buildplatformxmlnementry(doc, self, netif)
|
|
|
|
# <transport definition="transvirtual.xml" group="1">
|
|
|
|
# <param name="device" value="n1.0.158" />
|
|
|
|
# </transport>
|
|
|
|
trans = self.model.buildplatformxmltransportentry(doc, self, netif)
|
|
|
|
nementry.appendChild(trans)
|
|
|
|
ret[netif] = nementry
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def buildnemxmlfiles(self, emane):
|
|
|
|
''' Let the configured model build the necessary nem, mac, and phy
|
|
|
|
XMLs.
|
|
|
|
'''
|
|
|
|
if self.model is None:
|
|
|
|
return
|
|
|
|
# build XML for overall network (EmaneNode) configs
|
|
|
|
self.model.buildnemxmlfiles(emane, ifc=None)
|
|
|
|
# build XML for specific interface (NEM) configs
|
|
|
|
need_virtual = False
|
|
|
|
need_raw = False
|
|
|
|
vtype = "virtual"
|
|
|
|
rtype = "raw"
|
|
|
|
for netif in self.netifs():
|
|
|
|
self.model.buildnemxmlfiles(emane, netif)
|
|
|
|
if "virtual" in netif.transport_type:
|
|
|
|
need_virtual = True
|
|
|
|
vtype = netif.transport_type
|
|
|
|
else:
|
|
|
|
need_raw = True
|
|
|
|
rtype = netif.transport_type
|
|
|
|
# build transport XML files depending on type of interfaces involved
|
|
|
|
if need_virtual:
|
|
|
|
self.buildtransportxml(emane, vtype)
|
|
|
|
if need_raw:
|
|
|
|
self.buildtransportxml(emane, rtype)
|
2014-09-17 23:00:11 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def buildtransportxml(self, emane, type):
|
|
|
|
''' Write a transport XML file for the Virtual or Raw Transport.
|
|
|
|
'''
|
|
|
|
transdoc = emane.xmldoc("transport")
|
|
|
|
trans = transdoc.getElementsByTagName("transport").pop()
|
|
|
|
trans.setAttribute("name", "%s Transport" % type.capitalize())
|
|
|
|
trans.setAttribute("library", "trans%s" % type.lower())
|
|
|
|
trans.appendChild(emane.xmlparam(transdoc, "bitrate", "0"))
|
2014-09-17 23:00:11 +01:00
|
|
|
|
2013-12-03 23:10:21 +00:00
|
|
|
flowcontrol = False
|
|
|
|
names = self.model.getnames()
|
|
|
|
values = emane.getconfig(self.objid, self.model._name,
|
|
|
|
self.model.getdefaultvalues())[1]
|
|
|
|
if "flowcontrolenable" in names and values:
|
|
|
|
i = names.index("flowcontrolenable")
|
|
|
|
if self.model.booltooffon(values[i]) == "on":
|
|
|
|
flowcontrol = True
|
2014-09-17 23:00:11 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
if "virtual" in type.lower():
|
2015-06-01 18:52:49 +01:00
|
|
|
if os.path.exists("/dev/net/tun_flowctl"):
|
|
|
|
trans.appendChild(emane.xmlparam(transdoc, "devicepath",
|
|
|
|
"/dev/net/tun_flowctl"))
|
|
|
|
else:
|
|
|
|
trans.appendChild(emane.xmlparam(transdoc, "devicepath",
|
|
|
|
"/dev/net/tun"))
|
2013-12-03 23:10:21 +00:00
|
|
|
if flowcontrol:
|
|
|
|
trans.appendChild(emane.xmlparam(transdoc, "flowcontrolenable",
|
|
|
|
"on"))
|
2013-08-29 15:21:13 +01:00
|
|
|
emane.xmlwrite(transdoc, self.transportxmlname(type.lower()))
|
2014-09-17 23:00:11 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def transportxmlname(self, type):
|
2014-09-17 23:00:11 +01:00
|
|
|
''' Return the string name for the Transport XML file,
|
2013-08-29 15:21:13 +01:00
|
|
|
e.g. 'n3transvirtual.xml'
|
|
|
|
'''
|
|
|
|
return "n%strans%s.xml" % (self.objid, type)
|
|
|
|
|
|
|
|
|
2014-09-23 17:26:22 +01:00
|
|
|
def installnetifs(self, do_netns=True):
|
2013-08-29 15:21:13 +01:00
|
|
|
''' Install TAP devices into their namespaces. This is done after
|
|
|
|
EMANE daemons have been started, because that is their only chance
|
|
|
|
to bind to the TAPs.
|
|
|
|
'''
|
2015-09-23 21:22:43 +01:00
|
|
|
if self.session.emane.genlocationevents() and \
|
2013-08-29 15:21:13 +01:00
|
|
|
self.session.emane.service is None:
|
|
|
|
warntxt = "unable to publish EMANE events because the eventservice "
|
|
|
|
warntxt += "Python bindings failed to load"
|
|
|
|
self.session.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.name,
|
|
|
|
self.objid, warntxt)
|
|
|
|
|
|
|
|
for netif in self.netifs():
|
2014-09-23 17:26:22 +01:00
|
|
|
if do_netns and "virtual" in netif.transport_type.lower():
|
2013-08-29 15:21:13 +01:00
|
|
|
netif.install()
|
2014-10-28 17:24:31 +00:00
|
|
|
netif.setaddrs()
|
2015-09-23 21:22:43 +01:00
|
|
|
if not self.session.emane.genlocationevents():
|
2013-08-29 15:21:13 +01:00
|
|
|
netif.poshook = None
|
|
|
|
continue
|
|
|
|
# at this point we register location handlers for generating
|
|
|
|
# EMANE location events
|
|
|
|
netif.poshook = self.setnemposition
|
|
|
|
(x,y,z) = netif.node.position.get()
|
|
|
|
self.setnemposition(netif, x, y, z)
|
2014-09-17 23:00:11 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def deinstallnetifs(self):
|
2014-09-17 23:00:11 +01:00
|
|
|
''' Uninstall TAP devices. This invokes their shutdown method for
|
2013-08-29 15:21:13 +01:00
|
|
|
any required cleanup; the device may be actually removed when
|
|
|
|
emanetransportd terminates.
|
|
|
|
'''
|
|
|
|
for netif in self.netifs():
|
|
|
|
if "virtual" in netif.transport_type.lower():
|
|
|
|
netif.shutdown()
|
|
|
|
netif.poshook = None
|
|
|
|
|
|
|
|
def setnemposition(self, netif, x, y, z):
|
|
|
|
''' Publish a NEM location change event using the EMANE event service.
|
|
|
|
'''
|
|
|
|
if self.session.emane.service is None:
|
|
|
|
if self.verbose:
|
|
|
|
self.info("position service not available")
|
|
|
|
return
|
|
|
|
nemid = self.getnemid(netif)
|
|
|
|
ifname = netif.localname
|
|
|
|
if nemid is None:
|
|
|
|
self.info("nemid for %s is unknown" % ifname)
|
|
|
|
return
|
|
|
|
(lat, long, alt) = self.session.location.getgeo(x, y, z)
|
|
|
|
if self.verbose:
|
|
|
|
self.info("setnemposition %s (%s) x,y,z=(%d,%d,%s)"
|
|
|
|
"(%.6f,%.6f,%.6f)" % \
|
|
|
|
(ifname, nemid, x, y, z, lat, long, alt))
|
2014-09-18 16:50:09 +01:00
|
|
|
if self.session.emane.version >= self.session.emane.EMANE091:
|
2014-05-25 21:08:41 +01:00
|
|
|
event = LocationEvent()
|
|
|
|
else:
|
|
|
|
event = emaneeventlocation.EventLocation(1)
|
2013-08-29 15:21:13 +01:00
|
|
|
# altitude must be an integer or warning is printed
|
|
|
|
# unused: yaw, pitch, roll, azimuth, elevation, velocity
|
|
|
|
alt = int(round(alt))
|
2014-09-18 16:50:09 +01:00
|
|
|
if self.session.emane.version >= self.session.emane.EMANE091:
|
2014-05-25 21:08:41 +01:00
|
|
|
event.append(nemid, latitude=lat, longitude=long, altitude=alt)
|
|
|
|
self.session.emane.service.publish(0, event)
|
|
|
|
else:
|
|
|
|
event.set(0, nemid, lat, long, alt)
|
|
|
|
self.session.emane.service.publish(emaneeventlocation.EVENT_ID,
|
2013-08-29 15:21:13 +01:00
|
|
|
emaneeventservice.PLATFORMID_ANY,
|
|
|
|
emaneeventservice.NEMID_ANY,
|
|
|
|
emaneeventservice.COMPONENTID_ANY,
|
|
|
|
event.export())
|
2014-09-17 23:00:11 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def setnempositions(self, moved_netifs):
|
|
|
|
''' Several NEMs have moved, from e.g. a WaypointMobilityModel
|
|
|
|
calculation. Generate an EMANE Location Event having several
|
|
|
|
entries for each netif that has moved.
|
|
|
|
'''
|
|
|
|
if len(moved_netifs) == 0:
|
|
|
|
return
|
|
|
|
if self.session.emane.service is None:
|
|
|
|
if self.verbose:
|
|
|
|
self.info("position service not available")
|
|
|
|
return
|
2014-09-18 16:50:09 +01:00
|
|
|
|
|
|
|
if self.session.emane.version >= self.session.emane.EMANE091:
|
2014-05-25 21:08:41 +01:00
|
|
|
event = LocationEvent()
|
|
|
|
else:
|
|
|
|
event = emaneeventlocation.EventLocation(len(moved_netifs))
|
2013-08-29 15:21:13 +01:00
|
|
|
i = 0
|
|
|
|
for netif in moved_netifs:
|
|
|
|
nemid = self.getnemid(netif)
|
|
|
|
ifname = netif.localname
|
|
|
|
if nemid is None:
|
|
|
|
self.info("nemid for %s is unknown" % ifname)
|
|
|
|
continue
|
|
|
|
(x, y, z) = netif.node.getposition()
|
|
|
|
(lat, long, alt) = self.session.location.getgeo(x, y, z)
|
|
|
|
if self.verbose:
|
|
|
|
self.info("setnempositions %d %s (%s) x,y,z=(%d,%d,%s)"
|
|
|
|
"(%.6f,%.6f,%.6f)" % \
|
|
|
|
(i, ifname, nemid, x, y, z, lat, long, alt))
|
|
|
|
# altitude must be an integer or warning is printed
|
|
|
|
alt = int(round(alt))
|
2014-09-18 16:50:09 +01:00
|
|
|
if self.session.emane.version >= self.session.emane.EMANE091:
|
2014-05-25 21:08:41 +01:00
|
|
|
event.append(nemid, latitude=lat, longitude=long, altitude=alt)
|
|
|
|
else:
|
|
|
|
event.set(i, nemid, lat, long, alt)
|
2013-08-29 15:21:13 +01:00
|
|
|
i += 1
|
2014-09-18 16:50:09 +01:00
|
|
|
|
|
|
|
if self.session.emane.version >= self.session.emane.EMANE091:
|
2014-05-25 21:08:41 +01:00
|
|
|
self.session.emane.service.publish(0, event)
|
|
|
|
else:
|
|
|
|
self.session.emane.service.publish(emaneeventlocation.EVENT_ID,
|
2013-08-29 15:21:13 +01:00
|
|
|
emaneeventservice.PLATFORMID_ANY,
|
|
|
|
emaneeventservice.NEMID_ANY,
|
|
|
|
emaneeventservice.COMPONENTID_ANY,
|
|
|
|
event.export())
|
2014-09-17 23:00:11 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
|