class EmaneGlobalModel(EmaneModel): """ Global EMANE configuration options. """ _DEFAULT_DEV = "ctrl0" name = "emane" emulator_xml = "/usr/share/emane/manifest/nemmanager.xml" emulator_defaults = { "eventservicedevice": _DEFAULT_DEV, "eventservicegroup": "224.1.2.8:45703", "otamanagerdevice": _DEFAULT_DEV, "otamanagergroup": "224.1.2.8:45702" } emulator_config = emanemanifest.parse(emulator_xml, emulator_defaults) emulator_config.insert( 0, Configuration(_id="platform_id_start", _type=ConfigDataTypes.INT32, default="1", label="Starting Platform ID (core)")) nem_config = [ Configuration(_id="nem_id_start", _type=ConfigDataTypes.INT32, default="1", label="Starting NEM ID (core)") ] @classmethod def configurations(cls): return cls.emulator_config + cls.nem_config @classmethod def config_groups(cls): emulator_len = len(cls.emulator_config) config_len = len(cls.configurations()) return [ ConfigGroup("Platform Attributes", 1, emulator_len), ConfigGroup("NEM Parameters", emulator_len + 1, config_len) ] def __init__(self, session, object_id=None): super(EmaneGlobalModel, self).__init__(session, object_id) def build_xml_files(self, config, interface=None): raise NotImplementedError
class TestConfigurableOptions(ConfigurableOptions): name_one = "value1" name_two = "value2" options = [ Configuration( _id=name_one, _type=ConfigDataTypes.STRING, label=name_one ), Configuration( _id=name_two, _type=ConfigDataTypes.STRING, label=name_two ) ]
class EmaneBypassModel(emanemodel.EmaneModel): name = "emane_bypass" # values to ignore, when writing xml files config_ignore = {"none"} # mac definitions mac_library = "bypassmaclayer" mac_config = [ Configuration( _id="none", _type=ConfigDataTypes.BOOL, default="0", options=["True", "False"], label="There are no parameters for the bypass model." ) ] # phy definitions phy_library = "bypassphylayer" phy_config = [] # override config groups @classmethod def config_groups(cls): return [ ConfigGroup("Bypass Parameters", 1, 1), ]
def parse(manifest_path, defaults): """ Parses a valid emane manifest file and converts the provided configuration values into ones used by core. :param str manifest_path: absolute manifest file path :param dict defaults: used to override default values for configurations :return: list of core configuration values :rtype: list """ # no results when emane bindings are not present if not manifest: return [] # load configuration file manifest_file = manifest.Manifest(manifest_path) manifest_configurations = manifest_file.getAllConfiguration() configurations = [] for config_name in sorted(manifest_configurations): config_info = manifest_file.getConfigurationInfo(config_name) # map type to internal config data type value for core config_type = config_info.get("numeric") if not config_type: config_type = config_info.get("nonnumeric") config_type_name = config_type["type"] config_type_value = _type_value(config_type_name) # get default values, using provided defaults if config_name in defaults: config_default = defaults[config_name] else: config_value = config_info["values"] config_default = _get_default(config_type_name, config_value) # map to possible values used as options within the gui config_regex = config_info.get("regex") possible = _get_possible(config_type_name, config_regex) # define description and account for gui quirks config_descriptions = config_name if config_name.endswith("uri"): config_descriptions = "%s file" % config_descriptions configuration = Configuration( _id=config_name, _type=config_type_value, default=config_default, options=possible, label=config_descriptions ) configurations.append(configuration) return configurations
class EmaneTdmaModel(emanemodel.EmaneModel): # model name name = "emane_tdma" # mac configuration mac_library = "tdmaeventschedulerradiomodel" mac_xml = "/usr/share/emane/manifest/tdmaeventschedulerradiomodel.xml" mac_defaults = { "pcrcurveuri": "/usr/share/emane/xml/models/mac/tdmaeventscheduler/tdmabasemodelpcr.xml", } mac_config = emanemanifest.parse(mac_xml, mac_defaults) # add custom schedule options and ignore it when writing emane xml schedule_name = "schedule" default_schedule = os.path.join(constants.CORE_DATA_DIR, "examples", "tdma", "schedule.xml") mac_config.insert( 0, Configuration(_id=schedule_name, _type=ConfigDataTypes.STRING, default=default_schedule, label="TDMA schedule file (core)")) config_ignore = {schedule_name} def post_startup(self): """ Logic to execute after the emane manager is finished with startup. :return: nothing """ # get configured schedule config = self.session.emane.get_configs(node_id=self.object_id, config_type=self.name) if not config: return schedule = config[self.schedule_name] # get the set event device event_device = self.session.emane.event_device # initiate tdma schedule logging.info("setting up tdma schedule: schedule(%s) device(%s)", schedule, event_device) utils.check_cmd( ["emaneevent-tdmaschedule", "-i", event_device, schedule])
class Ns2ScriptedMobility(WayPointMobility): """ Handles the ns-2 script format, generated by scengen/setdest or BonnMotion. """ name = "ns2script" options = [ Configuration(_id="file", _type=ConfigDataTypes.STRING, label="mobility script file"), Configuration(_id="refresh_ms", _type=ConfigDataTypes.UINT32, default="50", label="refresh time (ms)"), Configuration(_id="loop", _type=ConfigDataTypes.BOOL, default="1", options=["On", "Off"], label="loop"), Configuration(_id="autostart", _type=ConfigDataTypes.STRING, label="auto-start seconds (0.0 for runtime)"), Configuration(_id="map", _type=ConfigDataTypes.STRING, label="node mapping (optional, e.g. 0:1,1:2,2:3)"), Configuration(_id="script_start", _type=ConfigDataTypes.STRING, label="script file to run upon start"), Configuration(_id="script_pause", _type=ConfigDataTypes.STRING, label="script file to run upon pause"), Configuration(_id="script_stop", _type=ConfigDataTypes.STRING, label="script file to run upon stop") ] @classmethod def config_groups(cls): return [ ConfigGroup("ns-2 Mobility Script Parameters", 1, len(cls.configurations())) ] def __init__(self, session, object_id): """ Creates a Ns2ScriptedMobility instance. :param core.session.Session session: CORE session instance :param int object_id: object id :param config: values """ super(Ns2ScriptedMobility, self).__init__(session=session, object_id=object_id) self._netifs = {} self._netifslock = threading.Lock() self.file = None self.refresh_ms = None self.loop = None self.autostart = None self.nodemap = {} self.script_start = None self.script_pause = None self.script_stop = None def update_config(self, config): self.file = config["file"] logging.info("ns-2 scripted mobility configured for WLAN %d using file: %s", self.object_id, self.file) self.refresh_ms = int(config["refresh_ms"]) self.loop = config["loop"].lower() == "on" self.autostart = config["autostart"] self.parsemap(config["map"]) self.script_start = config["script_start"] self.script_pause = config["script_pause"] self.script_stop = config["script_stop"] self.readscriptfile() self.copywaypoints() self.setendtime() def readscriptfile(self): """ Read in mobility script from a file. This adds waypoints to a priority queue, sorted by waypoint time. Initial waypoints are stored in a separate dict. :return: nothing """ filename = self.findfile(self.file) try: f = open(filename, "r") except IOError: logging.exception("ns-2 scripted mobility failed to load file: %s", self.file) return logging.info("reading ns-2 script file: %s" % filename) ln = 0 ix = iy = iz = None inodenum = None for line in f: ln += 1 if line[:2] != '$n': continue try: if line[:8] == "$ns_ at ": if ix is not None and iy is not None: self.addinitial(self.map(inodenum), ix, iy, iz) ix = iy = iz = None # waypoints: # $ns_ at 1.00 "$node_(6) setdest 500.0 178.0 25.0" parts = line.split() time = float(parts[2]) nodenum = parts[3][1 + parts[3].index('('):parts[3].index(')')] x = float(parts[5]) y = float(parts[6]) z = None speed = float(parts[7].strip('"')) self.addwaypoint(time, self.map(nodenum), x, y, z, speed) elif line[:7] == "$node_(": # initial position (time=0, speed=0): # $node_(6) set X_ 780.0 parts = line.split() time = 0.0 nodenum = parts[0][1 + parts[0].index('('):parts[0].index(')')] if parts[2] == 'X_': if ix is not None and iy is not None: self.addinitial(self.map(inodenum), ix, iy, iz) ix = iy = iz = None ix = float(parts[3]) elif parts[2] == 'Y_': iy = float(parts[3]) elif parts[2] == 'Z_': iz = float(parts[3]) self.addinitial(self.map(nodenum), ix, iy, iz) ix = iy = iz = None inodenum = nodenum else: raise ValueError except ValueError: logging.exception("skipping line %d of file %s '%s'", ln, self.file, line) continue if ix is not None and iy is not None: self.addinitial(self.map(inodenum), ix, iy, iz) def findfile(self, file_name): """ Locate a script file. If the specified file doesn't exist, look in the same directory as the scenario file, or in the default configs directory (~/.core/configs). This allows for sample files without absolute path names. :param str file_name: file name to find :return: absolute path to the file :rtype: str """ if os.path.exists(file_name): return file_name if self.session.file_name is not None: d = os.path.dirname(self.session.file_name) sessfn = os.path.join(d, file_name) if os.path.exists(sessfn): return sessfn if self.session.user is not None: userfn = os.path.join('/home', self.session.user, '.core', 'configs', file_name) if os.path.exists(userfn): return userfn return file_name def parsemap(self, mapstr): """ Parse a node mapping string, given as a configuration parameter. :param str mapstr: mapping string to parse :return: nothing """ self.nodemap = {} if mapstr.strip() == "": return for pair in mapstr.split(","): parts = pair.split(":") try: if len(parts) != 2: raise ValueError self.nodemap[int(parts[0])] = int(parts[1]) except ValueError: logging.exception("ns-2 mobility node map error") def map(self, nodenum): """ Map one node number (from a script file) to another. :param str nodenum: node id to map :return: mapped value or the node id itself :rtype: int """ nodenum = int(nodenum) return self.nodemap.get(nodenum, nodenum) def startup(self): """ Start running the script if autostart is enabled. Move node to initial positions when any autostart time is specified. Ignore the script if autostart is an empty string (can still be started via GUI controls). :return: nothing """ if self.autostart == '': logging.info("not auto-starting ns-2 script for %s" % self.wlan.name) return try: t = float(self.autostart) except ValueError: logging.exception("Invalid auto-start seconds specified '%s' for %s", self.autostart, self.wlan.name) return self.movenodesinitial() logging.info("scheduling ns-2 script for %s autostart at %s" % (self.wlan.name, t)) self.state = self.STATE_RUNNING self.session.event_loop.add_event(t, self.run) def start(self): """ Handle the case when un-paused. :return: nothing """ logging.info("starting script") laststate = self.state super(Ns2ScriptedMobility, self).start() if laststate == self.STATE_PAUSED: self.statescript("unpause") def run(self): """ Start is pressed or autostart is triggered. :return: nothing """ super(Ns2ScriptedMobility, self).run() self.statescript("run") def pause(self): """ Pause the mobility script. :return: nothing """ super(Ns2ScriptedMobility, self).pause() self.statescript("pause") def stop(self, move_initial=True): """ Stop the mobility script. :param bool move_initial: flag to check if we should move node to initial position :return: nothing """ super(Ns2ScriptedMobility, self).stop(move_initial=move_initial) self.statescript("stop") def statescript(self, typestr): """ State of the mobility script. :param str typestr: state type string :return: nothing """ filename = None if typestr == "run" or typestr == "unpause": filename = self.script_start elif typestr == "pause": filename = self.script_pause elif typestr == "stop": filename = self.script_stop if filename is None or filename == '': return filename = self.findfile(filename) args = ["/bin/sh", filename, typestr] utils.check_cmd(args, cwd=self.session.session_dir, env=self.session.get_environment())
class BasicRangeModel(WirelessModel): """ Basic Range wireless model, calculates range between nodes and links and unlinks nodes based on this distance. This was formerly done from the GUI. """ name = "basic_range" options = [ Configuration(_id="range", _type=ConfigDataTypes.UINT32, default="275", label="wireless range (pixels)"), Configuration(_id="bandwidth", _type=ConfigDataTypes.UINT32, default="54000000", label="bandwidth (bps)"), Configuration(_id="jitter", _type=ConfigDataTypes.FLOAT, default="0.0", label="transmission jitter (usec)"), Configuration(_id="delay", _type=ConfigDataTypes.FLOAT, default="5000.0", label="transmission delay (usec)"), Configuration(_id="error", _type=ConfigDataTypes.FLOAT, default="0.0", label="error rate (%)") ] @classmethod def config_groups(cls): return [ ConfigGroup("Basic Range Parameters", 1, len(cls.configurations())) ] def __init__(self, session, object_id): """ Create a BasicRangeModel instance. :param core.session.Session session: related core session :param int object_id: object id :param dict config: values """ super(BasicRangeModel, self).__init__(session=session, object_id=object_id) self.session = session self.wlan = session.get_object(object_id) self._netifs = {} self._netifslock = threading.Lock() self.range = None self.bw = None self.delay = None self.loss = None self.jitter = None def values_from_config(self, config): """ Values to convert to link parameters. :param dict config: values to convert :return: nothing """ self.range = float(config["range"]) logging.info("basic range model configured for WLAN %d using range %d", self.wlan.objid, self.range) self.bw = int(config["bandwidth"]) if self.bw == 0.0: self.bw = None self.delay = float(config["delay"]) if self.delay == 0.0: self.delay = None self.loss = float(config["error"]) if self.loss == 0.0: self.loss = None self.jitter = float(config["jitter"]) if self.jitter == 0.0: self.jitter = None def setlinkparams(self): """ Apply link parameters to all interfaces. This is invoked from WlanNode.setmodel() after the position callback has been set. """ with self._netifslock: for netif in self._netifs: self.wlan.linkconfig(netif, bw=self.bw, delay=self.delay, loss=self.loss, duplicate=None, jitter=self.jitter) def get_position(self, netif): """ Retrieve network interface position. :param netif: network interface position to retrieve :return: network interface position """ with self._netifslock: return self._netifs[netif] def set_position(self, netif, x=None, y=None, z=None): """ A node has moved; given an interface, a new (x,y,z) position has been set; calculate the new distance between other nodes and link or unlink node pairs based on the configured range. :param netif: network interface to set position for :param x: x position :param y: y position :param z: z position :return: nothing """ self._netifslock.acquire() self._netifs[netif] = (x, y, z) if x is None or y is None: self._netifslock.release() return for netif2 in self._netifs: self.calclink(netif, netif2) self._netifslock.release() position_callback = set_position def update(self, moved, moved_netifs): """ Node positions have changed without recalc. Update positions from node.position, then re-calculate links for those that have moved. Assumes bidirectional links, with one calculation per node pair, where one of the nodes has moved. :param bool moved: flag is it was moved :param list moved_netifs: moved network interfaces :return: nothing """ with self._netifslock: while len(moved_netifs): netif = moved_netifs.pop() nx, ny, nz = netif.node.getposition() if netif in self._netifs: self._netifs[netif] = (nx, ny, nz) for netif2 in self._netifs: if netif2 in moved_netifs: continue self.calclink(netif, netif2) def calclink(self, netif, netif2): """ Helper used by set_position() and update() to calculate distance between two interfaces and perform linking/unlinking. Sends link/unlink messages and updates the WlanNode's linked dict. :param netif: interface one :param netif2: interface two :return: nothing """ if netif == netif2: return try: x, y, z = self._netifs[netif] x2, y2, z2 = self._netifs[netif2] if x2 is None or y2 is None: return d = self.calcdistance((x, y, z), (x2, y2, z2)) # ordering is important, to keep the wlan._linked dict organized a = min(netif, netif2) b = max(netif, netif2) with self.wlan._linked_lock: linked = self.wlan.linked(a, b) logging.debug("checking range netif1(%s) netif2(%s): linked(%s) actual(%s) > config(%s)", a.name, b.name, linked, d, self.range) if d > self.range: if linked: logging.debug("was linked, unlinking") self.wlan.unlink(a, b) self.sendlinkmsg(a, b, unlink=True) else: if not linked: logging.debug("was not linked, linking") self.wlan.link(a, b) self.sendlinkmsg(a, b) except KeyError: logging.exception("error getting interfaces during calclinkS") @staticmethod def calcdistance(p1, p2): """ Calculate the distance between two three-dimensional points. :param tuple p1: point one :param tuple p2: point two :return: distance petween the points :rtype: float """ a = p1[0] - p2[0] b = p1[1] - p2[1] c = 0 if p1[2] is not None and p2[2] is not None: c = p1[2] - p2[2] return math.hypot(math.hypot(a, b), c) def update_config(self, config): """ Configuration has changed during runtime. :param dict config: values to update configuration :return: nothing """ self.values_from_config(config) self.setlinkparams() return True def create_link_data(self, interface1, interface2, message_type): """ Create a wireless link/unlink data message. :param core.coreobj.PyCoreNetIf interface1: interface one :param core.coreobj.PyCoreNetIf interface2: interface two :param message_type: link message type :return: link data :rtype: LinkData """ return LinkData( message_type=message_type, node1_id=interface1.node.objid, node2_id=interface2.node.objid, network_id=self.wlan.objid, link_type=LinkTypes.WIRELESS.value ) def sendlinkmsg(self, netif, netif2, unlink=False): """ Send a wireless link/unlink API message to the GUI. :param core.coreobj.PyCoreNetIf netif: interface one :param core.coreobj.PyCoreNetIf netif2: interface two :param bool unlink: unlink or not :return: nothing """ if unlink: message_type = MessageFlags.DELETE.value else: message_type = MessageFlags.ADD.value link_data = self.create_link_data(netif, netif2, message_type) self.session.broadcast_link(link_data) def all_link_data(self, flags): """ Return a list of wireless link messages for when the GUI reconnects. :param flags: link flags :return: all link data :rtype: list """ all_links = [] with self.wlan._linked_lock: for a in self.wlan._linked: for b in self.wlan._linked[a]: if self.wlan._linked[a][b]: all_links.append(self.create_link_data(a, b, flags)) return all_links
class EmaneModel(WirelessModel): """ EMANE models inherit from this parent class, which takes care of handling configuration messages based on the list of configurable parameters. Helper functions also live here. """ # default mac configuration settings mac_library = None mac_xml = None mac_defaults = {} mac_config = [] # default phy configuration settings, using the universal model phy_library = None phy_xml = "/usr/share/emane/manifest/emanephy.xml" phy_defaults = { "subid": "1", "propagationmodel": "2ray", "noisemode": "none" } phy_config = emanemanifest.parse(phy_xml, phy_defaults) # support for external configurations external_config = [ Configuration("external", ConfigDataTypes.BOOL, default="0"), Configuration("platformendpoint", ConfigDataTypes.STRING, default="127.0.0.1:40001"), Configuration("transportendpoint", ConfigDataTypes.STRING, default="127.0.0.1:50002") ] config_ignore = set() @classmethod def configurations(cls): return cls.mac_config + cls.phy_config + cls.external_config @classmethod def config_groups(cls): mac_len = len(cls.mac_config) phy_len = len(cls.phy_config) + mac_len config_len = len(cls.configurations()) return [ ConfigGroup("MAC Parameters", 1, mac_len), ConfigGroup("PHY Parameters", mac_len + 1, phy_len), ConfigGroup("External Parameters", phy_len + 1, config_len) ] def build_xml_files(self, config, interface=None): """ Builds xml files for this emane model. Creates a nem.xml file that points to both mac.xml and phy.xml definitions. :param dict config: emane model configuration for the node and interface :param interface: interface for the emane node :return: nothing """ nem_name = emanexml.nem_file_name(self, interface) mac_name = emanexml.mac_file_name(self, interface) phy_name = emanexml.phy_file_name(self, interface) # check if this is external transport_type = "virtual" if interface and interface.transport_type == "raw": transport_type = "raw" transport_name = emanexml.transport_file_name(self.object_id, transport_type) # create nem xml file nem_file = os.path.join(self.session.session_dir, nem_name) emanexml.create_nem_xml(self, config, nem_file, transport_name, mac_name, phy_name) # create mac xml file mac_file = os.path.join(self.session.session_dir, mac_name) emanexml.create_mac_xml(self, config, mac_file) # create phy xml file phy_file = os.path.join(self.session.session_dir, phy_name) emanexml.create_phy_xml(self, config, phy_file) def post_startup(self): """ Logic to execute after the emane manager is finished with startup. :return: nothing """ logging.info("emane model(%s) has no post setup tasks", self.name) def update(self, moved, moved_netifs): """ Invoked from MobilityModel when nodes are moved; this causes emane location events to be generated for the nodes in the moved list, making EmaneModels compatible with Ns2ScriptedMobility. :param bool moved: were nodes moved :param list moved_netifs: interfaces that were moved :return: """ try: wlan = self.session.get_object(self.object_id) wlan.setnempositions(moved_netifs) except KeyError: logging.exception("error during update") def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None, jitter=None, netif2=None): """ Invoked when a Link Message is received. Default is unimplemented. :param core.netns.vif.Veth netif: interface one :param bw: bandwidth to set to :param delay: packet delay to set to :param loss: packet loss to set to :param duplicate: duplicate percentage to set to :param jitter: jitter to set to :param core.netns.vif.Veth netif2: interface two :return: nothing """ logging.warn("emane model(%s) does not support link configuration", self.name)
class SessionConfig(ConfigurableManager, ConfigurableOptions): """ Session configuration object. """ name = "session" options = [ Configuration(_id="controlnet", _type=ConfigDataTypes.STRING, label="Control Network"), Configuration(_id="controlnet0", _type=ConfigDataTypes.STRING, label="Control Network 0"), Configuration(_id="controlnet1", _type=ConfigDataTypes.STRING, label="Control Network 1"), Configuration(_id="controlnet2", _type=ConfigDataTypes.STRING, label="Control Network 2"), Configuration(_id="controlnet3", _type=ConfigDataTypes.STRING, label="Control Network 3"), Configuration(_id="controlnet_updown_script", _type=ConfigDataTypes.STRING, label="Control Network Script"), Configuration(_id="enablerj45", _type=ConfigDataTypes.BOOL, default="1", options=["On", "Off"], label="Enable RJ45s"), Configuration(_id="preservedir", _type=ConfigDataTypes.BOOL, default="0", options=["On", "Off"], label="Preserve session dir"), Configuration(_id="enablesdt", _type=ConfigDataTypes.BOOL, default="0", options=["On", "Off"], label="Enable SDT3D output"), Configuration(_id="sdturl", _type=ConfigDataTypes.STRING, default=Sdt.DEFAULT_SDT_URL, label="SDT3D URL") ] config_type = RegisterTlvs.UTILITY.value def __init__(self): super(SessionConfig, self).__init__() self.set_configs(self.default_values()) def get_config(self, _id, node_id=ConfigurableManager._default_node, config_type=ConfigurableManager._default_type, default=None): value = super(SessionConfig, self).get_config(_id, node_id, config_type, default) if value == "": value = default return value def get_config_bool(self, name, default=None): value = self.get_config(name) if value is None: return default return value.lower() == "true" def get_config_int(self, name, default=None): value = self.get_config(name, default=default) if value is not None: value = int(value) return value