def validate_service(self, node: CoreNode, service: "CoreService") -> int: """ Run the validation command(s) for a service. :param node: node to validate service for :param service: service to validate :return: service validation status """ logging.debug("validating node(%s) service(%s)", node.name, service.name) cmds = service.validate if not service.custom: cmds = service.get_validate(node) status = 0 for cmd in cmds: logging.debug("validating service(%s) using: %s", service.name, cmd) try: node.cmd(cmd) except CoreCommandError as e: logging.debug( "node(%s) service(%s) validate failed", node.name, service.name ) logging.debug("cmd(%s): %s", e.cmd, e.output) status = -1 break return status
def __init__(self, *args, **kwds): ns.network.Node.__init__(self) # ns-3 ID starts at 0, CORE uses 1 _id = self.GetId() + 1 if '_id' not in kwds: kwds['_id'] = _id CoreNode.__init__(self, *args, **kwds)
def create_service_files(self, node: CoreNode, service: "CoreService") -> None: """ Creates node service files. :param node: node to reconfigure service for :param service: service to reconfigure :return: nothing """ # get values depending on if custom or not config_files = service.configs if not service.custom: config_files = service.get_configs(node) for file_name in config_files: logging.debug( "generating service config custom(%s): %s", service.custom, file_name ) if service.custom: cfg = service.config_data.get(file_name) if cfg is None: cfg = service.generate_config(node, file_name) # cfg may have a file:/// url for copying from a file try: if self.copy_service_file(node, file_name, cfg): continue except IOError: logging.exception("error copying service file: %s", file_name) continue else: cfg = service.generate_config(node, file_name) node.nodefile(file_name, cfg)
def ping(from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes): address = ip_prefixes.ip4_address(to_node.id) try: from_node.cmd(f"ping -c 1 {address}") status = 0 except CoreCommandError as e: status = e.returncode return status
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 custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface: # TUN/TAP is not ready for addressing yet; the device may # take some time to appear, and installing it into a # namespace after it has been bound removes addressing; # save addresses with the interface now iface_id = node.newtuntap(iface_data.id, iface_data.name) node.attachnet(iface_id, self) iface = node.get_iface(iface_id) iface.set_mac(iface_data.mac) for ip in iface_data.get_ips(): iface.add_ip(ip) if self.session.state == EventTypes.RUNTIME_STATE: self.session.emane.start_iface(self, iface) return iface
def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startpcap.sh traffic logging script. """ cfg = """ #!/bin/sh # set tcpdump options here (see 'man tcpdump' for help) # (-s snap length, -C limit pcap file length, -n disable name resolution) DUMPOPTS="-s 12288 -C 10 -n" if [ "x$1" = "xstart" ]; then """ for iface in node.get_ifaces(): if hasattr(iface, "control") and iface.control is True: cfg += "# " redir = "< /dev/null" cfg += "tcpdump ${DUMPOPTS} -w %s.%s.pcap -i %s %s &\n" % ( node.name, iface.name, iface.name, redir, ) cfg += """ elif [ "x$1" = "xstop" ]; then mkdir -p ${SESSION_DIR}/pcap mv *.pcap ${SESSION_DIR}/pcap fi; """ return cfg
def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a RADVD router advertisement daemon config file using the network address of each interface. """ cfg = "# auto-generated by RADVD service (utility.py)\n" for iface in node.get_ifaces(control=False): prefixes = list(map(cls.subnetentry, iface.ips())) if len(prefixes) < 1: continue cfg += ("""\ interface %s { AdvSendAdvert on; MinRtrAdvInterval 3; MaxRtrAdvInterval 10; AdvDefaultPreference low; AdvHomeAgentFlag off; """ % iface.name) for prefix in prefixes: if prefix == "": continue cfg += ("""\ prefix %s { AdvOnLink on; AdvAutonomous on; AdvRouterAddr on; }; """ % prefix) cfg += "};\n" return cfg
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): """ Add a network interface. If we are attaching to a CoreNs3Net, this will be a TunTap. Otherwise dispatch to CoreNode.newnetif(). """ if not addrlist: addrlist = [] if not isinstance(net, CoreNs3Net): return CoreNode.newnetif(self, net, addrlist, hwaddr, ifindex, ifname) ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname, net=net) self.attachnet(ifindex, net) netif = self.netif(ifindex) netif.sethwaddr(hwaddr) for addr in make_tuple(addrlist): netif.addaddr(addr) addrstr = netif.addrlist[0] (addr, mask) = addrstr.split('/') tap = net._tapdevs[netif] tap.SetAttribute( "IpAddress", ns.network.Ipv4AddressValue(ns.network.Ipv4Address(addr))) tap.SetAttribute( "Netmask", ns.network.Ipv4MaskValue(ns.network.Ipv4Mask("/" + mask))) ns.core.Simulator.Schedule(ns.core.Time('0'), netif.install) return ifindex
def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router ospf6\n" rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid for iface in node.get_ifaces(control=False): cfg += " interface %s area 0.0.0.0\n" % iface.name cfg += "!\n" return cfg
def create_interface(node: CoreNode, network: CoreNetworkBase, interface_data: InterfaceData): """ Create an interface for a node on a network using provided interface data. :param node: node to create interface for :param network: network to associate interface with :param interface_data: interface data :return: created interface """ node.newnetif( network, addrlist=interface_data.get_addresses(), hwaddr=interface_data.mac, ifindex=interface_data.id, ifname=interface_data.name, ) return node.netif(interface_data.id)
def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "#!/bin/sh\n" cfg += "# auto-generated by StaticRoute service (utility.py)\n#\n" cfg += "# NOTE: this service must be customized to be of any use\n" cfg += "# Below are samples that you can uncomment and edit.\n#\n" for iface in node.get_ifaces(control=False): cfg += "\n".join(map(cls.routestr, iface.ips())) cfg += "\n" return cfg
def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): ip4 = iface.get_ip4() if ip4: return str(ip4.ip) return "0.0.0.0"
def copy_service_file(self, node: CoreNode, filename: str, cfg: str) -> bool: """ Given a configured service filename and config, determine if the config references an existing file that should be copied. Returns True for local files, False for generated. :param node: node to copy service for :param filename: file name for a configured service :param cfg: configuration string :return: True if successful, False otherwise """ if cfg[:7] == "file://": src = cfg[7:] src = src.split("\n")[0] src = utils.expand_corepath(src, node.session, node) # TODO: glob here node.nodefilecopy(filename, src, mode=0o644) return True return False
def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router ospf\n" rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): for ip4 in iface.ip4s: cfg += f" network {ip4} area 0\n" cfg += "!\n" return cfg
def generate_bird_iface_config(cls, node: CoreNode) -> str: """ Use only bare interfaces descriptions in generated protocol configurations. This has the slight advantage of being the same everywhere. """ cfg = "" for iface in node.get_ifaces(control=False): cfg += ' interface "%s";\n' % iface.name return cfg
def firstipv4prefix(node: CoreNode, prefixlen: int = 24) -> str: """ Similar to QuaggaService.routerid(). Helper to return the first IPv4 prefix of a node, using the supplied prefix length. This ignores the interface's prefix length, so e.g. '/32' can turn into '/24'. """ for iface in node.get_ifaces(control=False): ip4 = iface.get_ip4() if ip4: return f"{ip4.ip}/{prefixlen}" return "0.0.0.0/%s" % prefixlen
def generatehtml(cls, node: CoreNode, filename: str) -> str: body = ("""\ <!-- generated by utility.py:HttpService --> <h1>%s web server</h1> <p>This is the default web page for this server.</p> <p>The web server software is running but no content has been added, yet.</p> """ % node.name) for iface in node.get_ifaces(control=False): body += "<li>%s - %s</li>\n" % (iface.name, [str(x) for x in iface.ips()]) return "<html><body>%s</body></html>" % body
def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ cmd = cls.startup[0] ifaces = node.get_ifaces(control=False) if len(ifaces) > 0: iface_names = map(lambda x: x.name, ifaces) cmd += " -i " cmd += " -i ".join(iface_names) return (cmd, )
def generate_quagga_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. Other services that depend on zebra will have hooks that are invoked here. """ # we could verify here that filename == Quagga.conf cfg = "" for iface in node.get_ifaces(): cfg += "interface %s\n" % iface.name # include control interfaces in addressing but not routing daemons if iface.control: cfg += " " cfg += "\n ".join(map(cls.addrstr, iface.ips())) cfg += "\n" continue cfgv4 = "" cfgv6 = "" want_ipv4 = False want_ipv6 = False for s in node.services: if cls.name not in s.dependencies: continue if not (isinstance(s, QuaggaService) or issubclass(s, QuaggaService)): continue iface_config = s.generate_quagga_iface_config(node, iface) if s.ipv4_routing: want_ipv4 = True if s.ipv6_routing: want_ipv6 = True cfgv6 += iface_config else: cfgv4 += iface_config if want_ipv4: cfg += " " cfg += "\n ".join(map(cls.addrstr, iface.ip4s)) cfg += "\n" cfg += cfgv4 if want_ipv6: cfg += " " cfg += "\n ".join(map(cls.addrstr, iface.ip6s)) cfg += "\n" cfg += cfgv6 cfg += "!\n" for s in node.services: if cls.name not in s.dependencies: continue if not (isinstance(s, QuaggaService) or issubclass(s, QuaggaService)): continue cfg += s.generate_quagga_config(node) return cfg
def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "#!/bin/sh\n" cfg += "# auto-generated by DefaultMulticastRoute service (utility.py)\n" cfg += "# the first interface is chosen below; please change it " cfg += "as needed\n" for iface in node.get_ifaces(control=False): rtcmd = "ip route add 224.0.0.0/4 dev" cfg += "%s %s\n" % (rtcmd, iface.name) cfg += "\n" break return cfg
def service_reconfigure(self, node: CoreNode, service: "CoreService") -> None: """ Reconfigure a node service. :param node: node to reconfigure service for :param service: service to reconfigure :return: nothing """ config_files = service.configs if not service.custom: config_files = service.get_configs(node) for file_name in config_files: file_path = Path(file_name) if file_name[:7] == "file:///": # TODO: implement this raise NotImplementedError cfg = service.config_data.get(file_name) if cfg is None: cfg = service.generate_config(node, file_name) node.create_file(file_path, cfg)
def generate_config(cls, node: CoreNode, filename: str) -> str: emane_manager = node.session.emane cfg = "" for iface in node.get_ifaces(): if not isinstance(iface.net, EmaneNet): continue emane_net = iface.net config = emane_manager.get_iface_config(emane_net, iface) if emanexml.is_external(config): nem_id = emane_manager.get_nem_id(iface) cfg += f"emanegentransportxml {iface.name}-platform.xml\n" cfg += f"emanetransportd -r -l 0 -d transportdaemon{nem_id}.xml\n" return cfg
def stop_service(self, node: CoreNode, service: "CoreService") -> int: """ Stop a service on a node. :param node: node to stop a service on :param service: service to stop :return: status for stopping the services """ status = 0 for args in service.shutdown: try: node.cmd(args) except CoreCommandError as e: self.session.exception( ExceptionLevels.ERROR, "services", f"error stopping service {service.name}: {e.stderr}", node.id, ) logging.exception("error running stop command %s", args) status = -1 return status
def movenode(self, node: CoreNode, dt: float) -> bool: """ Calculate next node location and update its coordinates. Returns True if the node's position has changed. :param node: node to move :param dt: move factor :return: True if node was moved, False otherwise """ if node.id not in self.points: return False x1, y1, z1 = node.getposition() x2, y2, z2 = self.points[node.id].coords speed = self.points[node.id].speed # instantaneous move (prevents dx/dy == 0.0 below) if speed == 0: self.setnodeposition(node, x2, y2, z2) del self.points[node.id] return True # speed can be a velocity vector or speed value if isinstance(speed, (float, int)): # linear speed value alpha = math.atan2(y2 - y1, x2 - x1) sx = speed * math.cos(alpha) sy = speed * math.sin(alpha) else: # velocity vector sx = speed[0] sy = speed[1] # calculate dt * speed = distance moved dx = sx * dt dy = sy * dt # prevent overshoot if abs(dx) > abs(x2 - x1): dx = x2 - x1 if abs(dy) > abs(y2 - y1): dy = y2 - y1 if dx == 0.0 and dy == 0.0: if self.endtime < (self.lasttime - self.timezero): # the last node to reach the last waypoint determines this # script's endtime self.endtime = self.lasttime - self.timezero del self.points[node.id] return False if (x1 + dx) < 0.0: dx = 0.0 - x1 if (y1 + dy) < 0.0: dy = 0.0 - y1 self.setnodeposition(node, x1 + dx, y1 + dy, z1) return True
def generate_frr_config(cls, node: CoreNode) -> str: ifname = "eth0" for iface in node.get_ifaces(): if iface.name != "lo": ifname = iface.name break cfg = "router mfea\n!\n" cfg += "router igmp\n!\n" cfg += "router pim\n" cfg += " !ip pim rp-address 10.0.0.1\n" cfg += " ip pim bsr-candidate %s\n" % ifname cfg += " ip pim rp-candidate %s\n" % ifname cfg += " !ip pim spt-threshold interval 10 bytes 80000\n" return cfg
def startup_service( self, node: CoreNode, service: "CoreService", wait: bool = False ) -> int: """ Startup a node service. :param node: node to reconfigure service for :param service: service to reconfigure :param wait: determines if we should wait to validate startup :return: status of startup """ cmds = service.startup if not service.custom: cmds = service.get_startup(node) status = 0 for cmd in cmds: try: node.cmd(cmd, wait) except CoreCommandError: logging.exception("error starting command") status = -1 return status
def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a script to invoke dhclient on all interfaces. """ cfg = "#!/bin/sh\n" cfg += "# auto-generated by DHCPClient service (utility.py)\n" cfg += "# uncomment this mkdir line and symlink line to enable client-" cfg += "side DNS\n# resolution based on the DHCP server response.\n" cfg += "#mkdir -p /var/run/resolvconf/interface\n" for iface in node.get_ifaces(control=False): cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % iface.name cfg += " /var/run/resolvconf/resolv.conf\n" cfg += "/sbin/dhclient -nw -pf /var/run/dhclient-%s.pid" % iface.name cfg += " -lf /var/run/dhclient-%s.lease %s\n" % (iface.name, iface.name) return cfg
def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startup script for MgenActor. Because mgenActor does not daemonize, it can cause problems in some situations when launched directly using vcmd. """ cfg = "#!/bin/sh\n" cfg += "# auto-generated by nrl.py:MgenActor.generateconfig()\n" comments = "" cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name ifaces = node.get_ifaces(control=False) if len(ifaces) == 0: return "" cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" return cfg
def generate_config(cls, node: CoreNode, filename: str) -> str: routes = [] ifaces = node.get_ifaces() if ifaces: iface = ifaces[0] for ip in iface.ips(): net = ip.cidr if net.size > 1: router = net[1] routes.append(str(router)) cfg = "#!/bin/sh\n" cfg += "# auto-generated by DefaultRoute service (utility.py)\n" for route in routes: cfg += f"ip route add default via {route}\n" return cfg