def run(self, ctx): if not ctx.args.infile: return # position, orientation and velocities are specified as separate # EEL sentences - however orientation and velocity events must # be specified with their associated location triplets. # Store the most recently specified location for each nem and # reuse it as optional orientation and velocity sentences are # parsed. self._location_cache = {} handlers = { 'allinformedpathloss':self.allinformed_pathloss, 'location':self.location_gps, 'orientation':self.orientation, 'velocity':self.velocity, 'pathloss':self.pathloss, 'fadingselection':self.fadingselection, 'antennaprofile':self.antennaprofile } if not ctx.args.eventservicedevice: message = 'Wrapper emane.emanephyinit mandatory argument "eventservicedevice" ' \ 'not specified. Quitting.' raise RuntimeError(message) sequencer = EELSequencer(ctx.args.infile, ctx.args.starttime, list(handlers.keys())) mcgroup, port = ctx.args.eventservicegroup.split(':') service = EventService((mcgroup, int(port), ctx.args.eventservicedevice)) with open(ctx.args.outfile, 'a') as lfd: for eventlist in sequencer: for eventtime, moduleid, eventtype, eventargs in eventlist: events = handlers[eventtype](moduleid, eventtype, eventargs) for nem, event in list(events.items()): service.publish(nem, event) logline = 'process eventtype "%s" to nems {%s}' % \ (eventtype, ','.join(map(str, sorted(events.keys())))) print(logline) self.log(lfd, logline)
def run(self, ctx): if not ctx.args.infile: return handlers = { 'allinformedpathloss':self.allinformed_pathloss, 'location':self.location_gps, 'pathloss':self.pathloss, 'fadingselection':self.fadingselection, 'antennaprofile':self.antennaprofile } if not ctx.args.eventservicedevice: message = 'Wrapper emane.emanephyinit mandatory argument "eventservicedevice" ' \ 'not specified. Quitting.' raise RuntimeError(message) sequencer = EELSequencer(ctx.args.infile, ctx.args.starttime, list(handlers.keys())) mcgroup,port = ctx.args.eventservicegroup.split(':') service = EventService((mcgroup, int(port), ctx.args.eventservicedevice)) with open(ctx.args.outfile,'a') as lfd: for moduleid, eventtype, eventargline in sequencer: eventargs = eventargline.split() events = handlers[eventtype](moduleid, eventtype, eventargs) for nem,event in list(events.items()): service.publish(nem, event) logline = 'process eventtype "%s" to nems {%s}' % \ (eventtype,','.join(map(str, sorted(events.keys())))) print(logline) self.log(lfd, logline)
#!/usr/bin/env python try: from emane.events import EventService from emane.events import PathlossEvent except: from emanesh.events import EventService from emanesh.events import PathlossEvent # create the event service service = EventService(('224.1.2.8',45703,'emanenode0')) # create an event setting the pathloss between 1 & 10 event = PathlossEvent() event.append(1,forward=90) event.append(10,forward=90) # publish the event service.publish(1,event) service.publish(10,event) # create an event setting the pathloss between 9 & 10 event = PathlossEvent() event.append(9,forward=90) event.append(10,forward=90) # publish the event service.publish(9,event) service.publish(10,event)
class EmaneManager(ModelManager): """ EMANE controller object. Lives in a Session instance and is used for building EMANE config files for all EMANE networks in this emulation, and for controlling the EMANE daemons. """ name: str = "emane" config_type: RegisterTlvs = RegisterTlvs.EMULATION_SERVER NOT_READY: int = 2 EVENTCFGVAR: str = "LIBEMANEEVENTSERVICECONFIG" DEFAULT_LOG_LEVEL: int = 3 def __init__(self, session: "Session") -> None: """ Creates a Emane instance. :param session: session this manager is tied to :return: nothing """ super().__init__() self.session: "Session" = session self.nems_to_ifaces: Dict[int, CoreInterface] = {} self.ifaces_to_nems: Dict[CoreInterface, int] = {} self._emane_nets: Dict[int, EmaneNet] = {} self._emane_node_lock: threading.Lock = threading.Lock() # port numbers are allocated from these counters self.platformport: int = self.session.options.get_config_int( "emane_platform_port", 8100) self.transformport: int = self.session.options.get_config_int( "emane_transform_port", 8200) self.doeventloop: bool = False self.eventmonthread: Optional[threading.Thread] = None # model for global EMANE configuration options self.emane_config: EmaneGlobalModel = EmaneGlobalModel(session) self.set_configs(self.emane_config.default_values()) # link monitor self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self) self.service: Optional[EventService] = None self.eventchannel: Optional[Tuple[str, int, str]] = None self.event_device: Optional[str] = None self.emane_check() def next_nem_id(self) -> int: nem_id = int(self.get_config("nem_id_start")) while nem_id in self.nems_to_ifaces: nem_id += 1 return nem_id def get_iface_config(self, emane_net: EmaneNet, iface: CoreInterface) -> Dict[str, str]: """ Retrieve configuration for a given interface. :param emane_net: emane network the interface is connected to :param iface: interface running emane :return: net, node, or interface model configuration """ model_name = emane_net.model.name # don"t use default values when interface config is the same as net # note here that using iface.node.id as key allows for only one type # of each model per node; # TODO: use both node and interface as key # Adamson change: first check for iface config keyed by "node:iface.name" # (so that nodes w/ multiple interfaces of same conftype can have # different configs for each separate interface) key = 1000 * iface.node.id if iface.node_id is not None: key += iface.node_id # try retrieve interface specific configuration, avoid getting defaults config = self.get_configs(node_id=key, config_type=model_name) # otherwise retrieve the interfaces node configuration, avoid using defaults if not config: config = self.get_configs(node_id=iface.node.id, config_type=model_name) # get non interface config, when none found if not config: # with EMANE 0.9.2+, we need an extra NEM XML from # model.buildnemxmlfiles(), so defaults are returned here config = self.get_configs(node_id=emane_net.id, config_type=model_name) return config def config_reset(self, node_id: int = None) -> None: super().config_reset(node_id) self.set_configs(self.emane_config.default_values()) def emane_check(self) -> None: """ Check if emane is installed and load models. :return: nothing """ # check for emane path = utils.which("emane", required=False) if not path: logging.info("emane is not installed") return # get version emane_version = utils.cmd("emane --version") logging.info("using emane: %s", emane_version) # load default emane models self.load_models(EMANE_MODELS) # load custom models custom_models_path = self.session.options.get_config( "emane_models_dir") if custom_models_path: emane_models = utils.load_classes(custom_models_path, EmaneModel) self.load_models(emane_models) def deleteeventservice(self) -> None: if self.service: for fd in self.service._readFd, self.service._writeFd: if fd >= 0: os.close(fd) for f in self.service._socket, self.service._socketOTA: if f: f.close() self.service = None self.event_device = None def initeventservice(self, filename: str = None, shutdown: bool = False) -> None: """ Re-initialize the EMANE Event service. The multicast group and/or port may be configured. """ self.deleteeventservice() if shutdown: return # Get the control network to be used for events group, port = self.get_config("eventservicegroup").split(":") self.event_device = self.get_config("eventservicedevice") eventnetidx = self.session.get_control_net_index(self.event_device) if eventnetidx < 0: logging.error("invalid emane event service device provided: %s", self.event_device) return # make sure the event control network is in place eventnet = self.session.add_remove_control_net(net_index=eventnetidx, remove=False, conf_required=False) if eventnet is not None: # direct EMANE events towards control net bridge self.event_device = eventnet.brname self.eventchannel = (group, int(port), self.event_device) # disabled otachannel for event service # only needed for e.g. antennaprofile events xmit by models logging.info("using %s for event service traffic", self.event_device) try: self.service = EventService(eventchannel=self.eventchannel, otachannel=None) except EventServiceException: logging.exception("error instantiating emane EventService") def load_models(self, emane_models: List[Type[EmaneModel]]) -> None: """ Load EMANE models and make them available. """ for emane_model in emane_models: logging.debug("loading emane model: %s", emane_model.__name__) emane_prefix = self.session.options.get_config( "emane_prefix", default=DEFAULT_EMANE_PREFIX) emane_model.load(emane_prefix) self.models[emane_model.name] = emane_model def add_node(self, emane_net: EmaneNet) -> None: """ Add EMANE network object to this manager. :param emane_net: emane node to add :return: nothing """ with self._emane_node_lock: if emane_net.id in self._emane_nets: raise CoreError( f"duplicate emane network({emane_net.id}): {emane_net.name}" ) self._emane_nets[emane_net.id] = emane_net def getnodes(self) -> Set[CoreNode]: """ Return a set of CoreNodes that are linked to an EMANE network, e.g. containers having one or more radio interfaces. """ nodes = set() for emane_net in self._emane_nets.values(): for iface in emane_net.get_ifaces(): nodes.add(iface.node) return nodes def setup(self) -> EmaneState: """ Setup duties for EMANE manager. :return: SUCCESS, NOT_NEEDED, NOT_READY in order to delay session instantiation """ logging.debug("emane setup") with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] if isinstance(node, EmaneNet): logging.debug("adding emane node: id(%s) name(%s)", node.id, node.name) self.add_node(node) if not self._emane_nets: logging.debug("no emane nodes in session") return EmaneState.NOT_NEEDED # check if bindings were installed if EventService is None: raise CoreError("EMANE python bindings are not installed") # control network bridge required for EMANE 0.9.2 # - needs to exist when eventservice binds to it (initeventservice) otadev = self.get_config("otamanagerdevice") netidx = self.session.get_control_net_index(otadev) logging.debug("emane ota manager device: index(%s) otadev(%s)", netidx, otadev) if netidx < 0: logging.error( "EMANE cannot start, check core config. invalid OTA device provided: %s", otadev, ) return EmaneState.NOT_READY self.session.add_remove_control_net(net_index=netidx, remove=False, conf_required=False) eventdev = self.get_config("eventservicedevice") logging.debug("emane event service device: eventdev(%s)", eventdev) if eventdev != otadev: netidx = self.session.get_control_net_index(eventdev) logging.debug("emane event service device index: %s", netidx) if netidx < 0: logging.error( "emane cannot start due to invalid event service device: %s", eventdev, ) return EmaneState.NOT_READY self.session.add_remove_control_net(net_index=netidx, remove=False, conf_required=False) self.check_node_models() return EmaneState.SUCCESS def startup(self) -> EmaneState: """ After all the EMANE networks have been added, build XML files and start the daemons. :return: SUCCESS, NOT_NEEDED, NOT_READY in order to delay session instantiation """ self.reset() status = self.setup() if status != EmaneState.SUCCESS: return status self.starteventmonitor() self.buildeventservicexml() with self._emane_node_lock: logging.info("emane building xmls...") for node_id in sorted(self._emane_nets): emane_net = self._emane_nets[node_id] if not emane_net.model: logging.error("emane net(%s) has no model", emane_net.name) continue for iface in emane_net.get_ifaces(): self.start_iface(emane_net, iface) if self.links_enabled(): self.link_monitor.start() return EmaneState.SUCCESS def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: if not iface.node: logging.error( "emane net(%s) connected interface(%s) missing node", emane_net.name, iface.name, ) return control_net = self.session.add_remove_control_net(0, remove=False, conf_required=False) nem_id = self.next_nem_id() self.set_nem(nem_id, iface) self.write_nem(iface, nem_id) emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id) config = self.get_iface_config(emane_net, iface) emane_net.model.build_xml_files(config, iface) self.start_daemon(iface) self.install_iface(emane_net, iface) def set_nem(self, nem_id: int, iface: CoreInterface) -> None: if nem_id in self.nems_to_ifaces: raise CoreError(f"adding duplicate nem: {nem_id}") self.nems_to_ifaces[nem_id] = iface self.ifaces_to_nems[iface] = nem_id def get_iface(self, nem_id: int) -> Optional[CoreInterface]: return self.nems_to_ifaces.get(nem_id) def get_nem_id(self, iface: CoreInterface) -> Optional[int]: return self.ifaces_to_nems.get(iface) def write_nem(self, iface: CoreInterface, nem_id: int) -> None: path = os.path.join(self.session.session_dir, "emane_nems") try: with open(path, "a") as f: f.write(f"{iface.node.name} {iface.name} {nem_id}\n") except IOError: logging.exception("error writing to emane nem file") def links_enabled(self) -> bool: return self.get_config("link_enabled") == "1" def poststartup(self) -> None: """ Retransmit location events now that all NEMs are active. """ if not self.genlocationevents(): return with self._emane_node_lock: for node_id in sorted(self._emane_nets): emane_net = self._emane_nets[node_id] logging.debug("post startup for emane node: %s - %s", emane_net.id, emane_net.name) emane_net.model.post_startup() for iface in emane_net.get_ifaces(): iface.setposition() def reset(self) -> None: """ Remove all EMANE networks from the dictionary, reset port numbers and nem id counters """ with self._emane_node_lock: self._emane_nets.clear() self.nems_to_ifaces.clear() self.ifaces_to_nems.clear() def shutdown(self) -> None: """ stop all EMANE daemons """ with self._emane_node_lock: if not self._emane_nets: return logging.info("stopping EMANE daemons") if self.links_enabled(): self.link_monitor.stop() self.deinstall_ifaces() self.stopdaemons() self.stopeventmonitor() def check_node_models(self) -> None: """ Associate EMANE model classes with EMANE network nodes. """ for node_id in self._emane_nets: emane_net = self._emane_nets[node_id] logging.debug("checking emane model for node: %s", node_id) # skip nodes that already have a model set if emane_net.model: logging.debug("node(%s) already has model(%s)", emane_net.id, emane_net.model.name) continue # set model configured for node, due to legacy messaging configuration # before nodes exist model_name = self.node_models.get(node_id) if not model_name: logging.error("emane node(%s) has no node model", node_id) raise ValueError("emane node has no model set") config = self.get_model_config(node_id=node_id, model_name=model_name) logging.debug("setting emane model(%s) config(%s)", model_name, config) model_class = self.models[model_name] emane_net.setmodel(model_class, config) def get_nem_link( self, nem1: int, nem2: int, flags: MessageFlags = MessageFlags.NONE) -> Optional[LinkData]: iface1 = self.get_iface(nem1) if not iface1: logging.error("invalid nem: %s", nem1) return None node1 = iface1.node iface2 = self.get_iface(nem2) if not iface2: logging.error("invalid nem: %s", nem2) return None node2 = iface2.node if iface1.net != iface2.net: return None emane_net = iface1.net color = self.session.get_link_color(emane_net.id) return LinkData( message_type=flags, type=LinkTypes.WIRELESS, node1_id=node1.id, node2_id=node2.id, network_id=emane_net.id, color=color, ) def buildeventservicexml(self) -> None: """ Build the libemaneeventservice.xml file if event service options were changed in the global config. """ need_xml = False default_values = self.emane_config.default_values() for name in ["eventservicegroup", "eventservicedevice"]: a = default_values[name] b = self.get_config(name) if a != b: need_xml = True if not need_xml: # reset to using default config self.initeventservice() return try: group, port = self.get_config("eventservicegroup").split(":") except ValueError: logging.exception("invalid eventservicegroup in EMANE config") return dev = self.get_config("eventservicedevice") emanexml.create_event_service_xml(group, port, dev, self.session.session_dir) self.session.distributed.execute( lambda x: emanexml.create_event_service_xml( group, port, dev, self.session.session_dir, x)) def start_daemon(self, iface: CoreInterface) -> None: """ Start one EMANE daemon per node having a radio. Add a control network even if the user has not configured one. """ logging.info("starting emane daemons...") loglevel = str(EmaneManager.DEFAULT_LOG_LEVEL) cfgloglevel = self.session.options.get_config_int("emane_log_level") realtime = self.session.options.get_config_bool("emane_realtime", default=True) if cfgloglevel: logging.info("setting user-defined emane log level: %d", cfgloglevel) loglevel = str(cfgloglevel) emanecmd = f"emane -d -l {loglevel}" if realtime: emanecmd += " -r" node = iface.node if iface.is_virtual(): otagroup, _otaport = self.get_config("otamanagergroup").split(":") otadev = self.get_config("otamanagerdevice") otanetidx = self.session.get_control_net_index(otadev) eventgroup, _eventport = self.get_config( "eventservicegroup").split(":") eventdev = self.get_config("eventservicedevice") eventservicenetidx = self.session.get_control_net_index(eventdev) # control network not yet started here self.session.add_remove_control_iface(node, 0, remove=False, conf_required=False) if otanetidx > 0: logging.info("adding ota device ctrl%d", otanetidx) self.session.add_remove_control_iface(node, otanetidx, remove=False, conf_required=False) if eventservicenetidx >= 0: logging.info("adding event service device ctrl%d", eventservicenetidx) self.session.add_remove_control_iface(node, eventservicenetidx, remove=False, conf_required=False) # multicast route is needed for OTA data logging.info("OTA GROUP(%s) OTA DEV(%s)", otagroup, otadev) node.node_net_client.create_route(otagroup, otadev) # multicast route is also needed for event data if on control network if eventservicenetidx >= 0 and eventgroup != otagroup: node.node_net_client.create_route(eventgroup, eventdev) # start emane log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log") platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml") args = f"{emanecmd} -f {log_file} {platform_xml}" node.cmd(args) logging.info("node(%s) emane daemon running: %s", node.name, args) else: path = self.session.session_dir log_file = os.path.join(path, f"{iface.name}-emane.log") platform_xml = os.path.join(path, f"{iface.name}-platform.xml") emanecmd += f" -f {log_file} {platform_xml}" node.host_cmd(emanecmd, cwd=path) logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd) def stopdaemons(self) -> None: """ Kill the appropriate EMANE daemons. """ kill_emaned = "killall -q emane" for node_id in sorted(self._emane_nets): emane_net = self._emane_nets[node_id] for iface in emane_net.get_ifaces(): node = iface.node if not node.up: continue if iface.is_raw(): node.host_cmd(kill_emaned, wait=False) else: node.cmd(kill_emaned, wait=False) def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: config = self.get_iface_config(emane_net, iface) external = config.get("external", "0") if isinstance(iface, TunTap) and external == "0": iface.set_ips() # at this point we register location handlers for generating # EMANE location events if self.genlocationevents(): iface.poshook = emane_net.setnemposition iface.setposition() def deinstall_ifaces(self) -> None: """ Uninstall TUN/TAP virtual interfaces. """ for key in sorted(self._emane_nets): emane_net = self._emane_nets[key] for iface in emane_net.get_ifaces(): if iface.is_virtual(): iface.shutdown() iface.poshook = None def doeventmonitor(self) -> bool: """ Returns boolean whether or not EMANE events will be monitored. """ # this support must be explicitly turned on; by default, CORE will # generate the EMANE events when nodes are moved return self.session.options.get_config_bool("emane_event_monitor") def genlocationevents(self) -> bool: """ Returns boolean whether or not EMANE events will be generated. """ # By default, CORE generates EMANE location events when nodes # are moved; this can be explicitly disabled in core.conf tmp = self.session.options.get_config_bool("emane_event_generate") if tmp is None: tmp = not self.doeventmonitor() return tmp def starteventmonitor(self) -> None: """ Start monitoring EMANE location events if configured to do so. """ logging.info("emane start event monitor") if not self.doeventmonitor(): return if self.service is None: logging.error("Warning: EMANE events will not be generated " "because the emaneeventservice\n binding was " "unable to load " "(install the python-emaneeventservice bindings)") return self.doeventloop = True self.eventmonthread = threading.Thread(target=self.eventmonitorloop, daemon=True) self.eventmonthread.start() def stopeventmonitor(self) -> None: """ Stop monitoring EMANE location events. """ self.doeventloop = False if self.service is not None: self.service.breakloop() # reset the service, otherwise nextEvent won"t work self.initeventservice(shutdown=True) if self.eventmonthread is not None: self.eventmonthread.join() self.eventmonthread = None def eventmonitorloop(self) -> None: """ Thread target that monitors EMANE location events. """ if self.service is None: return logging.info( "subscribing to EMANE location events. (%s)", threading.currentThread().getName(), ) while self.doeventloop is True: _uuid, _seq, events = self.service.nextEvent() # this occurs with 0.9.1 event service if not self.doeventloop: break for event in events: nem, eid, data = event if eid == LocationEvent.IDENTIFIER: self.handlelocationevent(nem, eid, data) logging.info( "unsubscribing from EMANE location events. (%s)", threading.currentThread().getName(), ) def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None: """ Handle an EMANE location event. """ events = LocationEvent() events.restore(data) for event in events: txnemid, attrs = event if ("latitude" not in attrs or "longitude" not in attrs or "altitude" not in attrs): logging.warning("dropped invalid location event") continue # yaw,pitch,roll,azimuth,elevation,velocity are unhandled lat = attrs["latitude"] lon = attrs["longitude"] alt = attrs["altitude"] logging.debug("emane location event: %s,%s,%s", lat, lon, alt) self.handlelocationeventtoxyz(txnemid, lat, lon, alt) def handlelocationeventtoxyz(self, nemid: int, lat: float, lon: float, alt: float) -> bool: """ Convert the (NEM ID, lat, long, alt) from a received location event into a node and x,y,z coordinate values, sending a Node Message. Returns True if successfully parsed and a Node Message was sent. """ # convert nemid to node number iface = self.get_iface(nemid) if iface is None: logging.info("location event for unknown NEM %s", nemid) return False n = iface.node.id # convert from lat/long/alt to x,y,z coordinates x, y, z = self.session.location.getxyz(lat, lon, alt) x = int(x) y = int(y) z = int(z) logging.debug( "location event NEM %s (%s, %s, %s) -> (%s, %s, %s)", nemid, lat, lon, alt, x, y, z, ) xbit_check = x.bit_length() > 16 or x < 0 ybit_check = y.bit_length() > 16 or y < 0 zbit_check = z.bit_length() > 16 or z < 0 if any([xbit_check, ybit_check, zbit_check]): logging.error( "Unable to build node location message, received lat/long/alt " "exceeds coordinate space: NEM %s (%d, %d, %d)", nemid, x, y, z, ) return False # generate a node message for this location update try: node = self.session.get_node(n, NodeBase) except CoreError: logging.exception( "location event NEM %s has no corresponding node %s", nemid, n) return False # don"t use node.setposition(x,y,z) which generates an event node.position.set(x, y, z) node.position.set_geo(lon, lat, alt) self.session.broadcast_node(node) return True def emanerunning(self, node: CoreNode) -> bool: """ Return True if an EMANE process associated with the given node is running, False otherwise. """ args = "pkill -0 -x emane" try: node.cmd(args) result = True except CoreCommandError: result = False return result def publish_pathloss(self, nem1: int, nem2: int, rx1: float, rx2: float) -> None: """ Publish pathloss events between provided nems, using provided rx power. :param nem1: interface one for pathloss :param nem2: interface two for pathloss :param rx1: received power from nem2 to nem1 :param rx2: received power from nem1 to nem2 :return: nothing """ event = PathlossEvent() event.append(nem1, forward=rx1) event.append(nem2, forward=rx2) self.service.publish(nem1, event) self.service.publish(nem2, event)
#!/usr/bin/env python try: from emane.events import EventService from emane.events import LocationEvent except: from emanesh.events import EventService from emanesh.events import LocationEvent # create the event service service = EventService(('224.1.2.8', 45703, 'emanenode0')) # create an event setting 10's position event = LocationEvent() event.append(10, latitude=40.031290, longitude=-74.523095, altitude=3.000000) # publish the event service.publish(0, event)
#!/usr/bin/env python try: from emane.events import EventService from emane.events import PathlossEvent except: from emanesh.events import EventService from emanesh.events import PathlossEvent # create the event service service = EventService(('224.1.2.8', 45703, 'emanenode0')) # create an event setting the pathloss between 1 & 10 event = PathlossEvent() event.append(1, forward=90) event.append(10, forward=90) # publish the event service.publish(1, event) service.publish(10, event) # create an event setting the pathloss between 9 & 10 event = PathlossEvent() event.append(9, forward=90) event.append(10, forward=90) # publish the event service.publish(9, event) service.publish(10, event)