# # CORE # Copyright (c)2010-2014 the Boeing Company. # See the LICENSE file included in this distribution. # # author: Jeff Ahrenholz # ''' nodes.py: definition of an EmaneNode class for implementing configuration control of an EMANE emulation. An EmaneNode has several attached NEMs that share the same MAC+PHY model. ''' import sys import os.path from core.api import coreapi from core.coreobj import PyCoreNet try: from emanesh.events import EventService from emanesh.events import LocationEvent except Exception, e: pass try: import emaneeventservice import emaneeventlocation except Exception, e: ''' Don't require all CORE users to have EMANE libeventservice and its Python bindings installed. ''' 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): ''' Record an interface to numerical ID mapping. The Emane controller 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 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(): # nementry = self.model.buildplatformxmlnementry(doc, self, netif) # # # 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) 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")) 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 if "virtual" in type.lower(): 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")) if flowcontrol: trans.appendChild(emane.xmlparam(transdoc, "flowcontrolenable", "on")) emane.xmlwrite(transdoc, self.transportxmlname(type.lower())) def transportxmlname(self, type): ''' Return the string name for the Transport XML file, e.g. 'n3transvirtual.xml' ''' return "n%strans%s.xml" % (self.objid, type) def installnetifs(self, do_netns=True): ''' 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. ''' if self.session.emane.genlocationevents() and \ 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(): if do_netns and "virtual" in netif.transport_type.lower(): netif.install() netif.setaddrs() if not self.session.emane.genlocationevents(): 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) def deinstallnetifs(self): ''' Uninstall TAP devices. This invokes their shutdown method for 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)) if self.session.emane.version >= self.session.emane.EMANE091: event = LocationEvent() else: event = emaneeventlocation.EventLocation(1) # altitude must be an integer or warning is printed # unused: yaw, pitch, roll, azimuth, elevation, velocity alt = int(round(alt)) if self.session.emane.version >= self.session.emane.EMANE091: 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, emaneeventservice.PLATFORMID_ANY, emaneeventservice.NEMID_ANY, emaneeventservice.COMPONENTID_ANY, event.export()) 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 if self.session.emane.version >= self.session.emane.EMANE091: event = LocationEvent() else: event = emaneeventlocation.EventLocation(len(moved_netifs)) 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)) if self.session.emane.version >= self.session.emane.EMANE091: event.append(nemid, latitude=lat, longitude=long, altitude=alt) else: event.set(i, nemid, lat, long, alt) i += 1 if self.session.emane.version >= self.session.emane.EMANE091: self.session.emane.service.publish(0, event) else: self.session.emane.service.publish(emaneeventlocation.EVENT_ID, emaneeventservice.PLATFORMID_ANY, emaneeventservice.NEMID_ANY, emaneeventservice.COMPONENTID_ANY, event.export())