def shutdown(self): if self.serverintf: try: utils.check_cmd([ constants.OVS_BIN, "del-port", self.bridge_name, self.serverintf ]) except CoreCommandError: logging.exception( "error deleting server interface %s to controlnet bridge %s", self.serverintf, self.bridge_name) if self.updown_script: try: logging.info("interface %s updown script (%s shutdown) called", self.bridge_name, self.updown_script) utils.check_cmd( [self.updown_script, self.bridge_name, "shutdown"]) except CoreCommandError: logging.exception("error during updown script shutdown") OvsNet.shutdown(self)
def startup(self): """ Interface startup logic. :return: nothing :raises CoreCommandError: when there is a command exception """ utils.check_cmd([ constants.IP_BIN, "link", "add", "name", self.localname, "type", "veth", "peer", "name", self.name, ]) utils.check_cmd( [constants.IP_BIN, "link", "set", self.localname, "up"]) self.up = True
def stopdaemons(self): """ Kill the appropriate EMANE daemons. """ # TODO: we may want to improve this if we had the PIDs from the specific EMANE daemons that we"ve started args = ["killall", "-q", "emane"] stop_emane_on_host = False for node in self.getnodes(): if hasattr(node, "transport_type") and node.transport_type == "raw": stop_emane_on_host = True continue if node.up: node.cmd(args, wait=False) # TODO: RJ45 node if stop_emane_on_host: try: utils.check_cmd(args) utils.check_cmd(["killall", "-q", "emanetransportd"]) except CoreCommandError: logging.exception("error shutting down emane daemons")
def startup(self): """ Start a new namespace node by invoking the vnoded process that allocates a new namespace. Bring up the loopback device and set the hostname. :return: nothing """ with self.lock: self.makenodedir() if self.up: raise ValueError("starting a node that is already up") # create a new namespace for this node using vnoded vnoded = [ constants.VNODED_BIN, "-v", "-c", self.ctrlchnlname, "-l", self.ctrlchnlname + ".log", "-p", self.ctrlchnlname + ".pid", ] if self.nodedir: vnoded += ["-C", self.nodedir] env = self.session.get_environment(state=False) env["NODE_NUMBER"] = str(self.id) env["NODE_NAME"] = str(self.name) output = utils.check_cmd(vnoded, env=env) self.pid = int(output) # create vnode client self.client = client.VnodeClient(self.name, self.ctrlchnlname) # bring up the loopback interface logging.debug("bringing up loopback interface") self.network_cmd([constants.IP_BIN, "link", "set", "lo", "up"]) # set hostname for node logging.debug("setting hostname: %s", self.name) self.network_cmd(["hostname", self.name]) # mark node as up self.up = True # create private directories self.privatedir("/var/run") self.privatedir("/var/log")
def shutdown(self): if not self.up: logging.info("exiting shutdown, object is not up") return ebtables_queue.stopupdateloop(self) try: utils.check_cmd( [constants.IP_BIN, "link", "set", self.bridge_name, "down"]) utils.check_cmd([constants.OVS_BIN, "del-br", self.bridge_name]) ebtables_commands( utils.check_cmd, [ [ constants.EBTABLES_BIN, "-D", "FORWARD", "--logical-in", self.bridge_name, "-j", self.bridge_name, ], [constants.EBTABLES_BIN, "-X", self.bridge_name], ], ) except CoreCommandError: logging.exception("error bringing bridge down and removing it") # removes veth pairs used for bridge-to-bridge connections for interface in self.netifs(): interface.shutdown() self._netif.clear() self._linked.clear() del self.session self.up = False
def get_ipv4_addresses(hostname): if hostname == "localhost": addresses = [] args = [constants.IP_BIN, "-o", "-f", "inet", "addr", "show"] output = utils.check_cmd(args) for line in output.split(os.linesep): split = line.split() if not split: continue interface_name = split[1] address = split[3] if not address.startswith("127."): addresses.append((interface_name, address)) return addresses else: # TODO: handle other hosts raise NotImplementedError
def detectoldbridge(self): """ Occasionally, control net bridges from previously closed sessions are not cleaned up. Check if there are old control net bridges and delete them """ output = utils.check_cmd([constants.OVS_BIN, "list-br"]) output = output.strip() if output: for line in output.split("\n"): bride_name = line.split(".") if bride_name[0] == "b" and bride_name[1] == self.id: logging.error( "older session may still be running with conflicting id for bridge: %s", line) return True return False
def emane_check(self): """ Check if emane is installed and load models. :return: nothing """ try: # check for emane emane_version = utils.check_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) except CoreCommandError: logging.info("emane is not installed")
def startup(self): """ Linux bridge starup logic. :return: nothing :raises CoreCommandError: when there is a command exception """ utils.check_cmd([constants.BRCTL_BIN, "addbr", self.brname]) # turn off spanning tree protocol and forwarding delay utils.check_cmd([constants.BRCTL_BIN, "stp", self.brname, "off"]) utils.check_cmd([constants.BRCTL_BIN, "setfd", self.brname, "0"]) utils.check_cmd([constants.IP_BIN, "link", "set", self.brname, "up"]) # create a new ebtables chain for this bridge ebtablescmds( utils.check_cmd, [ [constants.EBTABLES_BIN, "-N", self.brname, "-P", self.policy], [ constants.EBTABLES_BIN, "-A", "FORWARD", "--logical-in", self.brname, "-j", self.brname, ], ], ) # turn off multicast snooping so mcast forwarding occurs w/o IGMP joins snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % self.brname if os.path.exists(snoop): with open(snoop, "w") as snoop_file: snoop_file.write("0") self.up = True
def connectnode(self, ifname, othernode, otherifname): """ Connect a node. :param str ifname: name of interface to connect :param core.nodes.CoreNodeBase othernode: node to connect to :param str otherifname: interface name to connect to :return: nothing """ tmplen = 8 tmp1 = "tmp." + "".join( [random.choice(string.ascii_lowercase) for _ in range(tmplen)]) tmp2 = "tmp." + "".join( [random.choice(string.ascii_lowercase) for _ in range(tmplen)]) utils.check_cmd([ constants.IP_BIN, "link", "add", "name", tmp1, "type", "veth", "peer", "name", tmp2, ]) utils.check_cmd( [constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)]) self.network_cmd( [constants.IP_BIN, "link", "set", tmp1, "name", ifname]) interface = CoreInterface(node=self, name=ifname, mtu=_DEFAULT_MTU) self.addnetif(interface, self.newifindex()) utils.check_cmd([ constants.IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid) ]) othernode.network_cmd( [constants.IP_BIN, "link", "set", tmp2, "name", otherifname]) other_interface = CoreInterface(node=othernode, name=otherifname, mtu=_DEFAULT_MTU) othernode.addnetif(other_interface, othernode.newifindex())
def startup(self): """ Startup functionality for the control network. :return: nothing :raises CoreCommandError: when there is a command exception """ if self.detectoldbridge(): return CoreNetwork.startup(self) if self.hostid: addr = self.prefix.addr(self.hostid) else: addr = self.prefix.max_addr() logging.info("added control network bridge: %s %s", self.brname, self.prefix) if self.assign_address: addrlist = ["%s/%s" % (addr, self.prefix.prefixlen)] self.addrconfig(addrlist=addrlist) logging.info("address %s", addr) if self.updown_script: logging.info( "interface %s updown script (%s startup) called", self.brname, self.updown_script, ) utils.check_cmd([self.updown_script, self.brname, "startup"]) if self.serverintf: # sets the interface as a port of the bridge utils.check_cmd( [constants.BRCTL_BIN, "addif", self.brname, self.serverintf]) # bring interface up utils.check_cmd( [constants.IP_BIN, "link", "set", self.serverintf, "up"])
def restorestate(self): """ Restore the addresses and other interface state after using it. :return: nothing :raises CoreCommandError: when there is a command exception """ for addr in self.old_addrs: if addr[1] is None: utils.check_cmd([ constants.IP_BIN, "addr", "add", addr[0], "dev", self.localname ]) else: utils.check_cmd([ constants.IP_BIN, "addr", "add", addr[0], "brd", addr[1], "dev", self.localname ]) if self.old_up: utils.check_cmd( [constants.IP_BIN, "link", "set", self.localname, "up"])
def shutdown(self): """ Bring the interface down. Remove any addresses and queuing disciplines. :return: nothing """ if not self.up: return try: utils.check_cmd( [constants.IP_BIN, "link", "set", self.localname, "down"]) utils.check_cmd( [constants.IP_BIN, "addr", "flush", "dev", self.localname]) utils.check_cmd([ constants.TC_BIN, "qdisc", "del", "dev", self.localname, "root" ]) except CoreCommandError: logging.exception("error shutting down") self.up = False self.restorestate()
def ebcommit(self, wlan): """ Perform ebtables atomic commit using commands built in the self.cmds list. :return: nothing """ # save kernel ebtables snapshot to a file args = self.ebatomiccmd(["--atomic-save", ]) utils.check_cmd(args) # modify the table file using queued ebtables commands for c in self.cmds: args = self.ebatomiccmd(c) utils.check_cmd(args) self.cmds = [] # commit the table file to the kernel args = self.ebatomiccmd(["--atomic-commit", ]) utils.check_cmd(args) try: os.unlink(self.atomic_file) except OSError: logging.exception("error removing atomic file: %s", self.atomic_file)
def startdaemons(self): """ 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 = ["emane", "-d", "-l", loglevel] if realtime: emanecmd += ("-r", ) 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) run_emane_on_host = False for node in self.getnodes(): if hasattr(node, "transport_type") and node.transport_type == "raw": run_emane_on_host = True continue path = self.session.session_dir n = node.id # control network not yet started here self.session.add_remove_control_interface(node, 0, remove=False, conf_required=False) if otanetidx > 0: logging.info("adding ota device ctrl%d", otanetidx) self.session.add_remove_control_interface(node, otanetidx, remove=False, conf_required=False) if eventservicenetidx >= 0: logging.info("adding event service device ctrl%d", eventservicenetidx) self.session.add_remove_control_interface(node, eventservicenetidx, remove=False, conf_required=False) # multicast route is needed for OTA data 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 args = emanecmd + [ "-f", os.path.join(path, "emane%d.log" % n), os.path.join(path, "platform%d.xml" % n), ] output = node.check_cmd(args) logging.info("node(%s) emane daemon running: %s", node.name, args) logging.info("node(%s) emane daemon output: %s", node.name, output) if not run_emane_on_host: return path = self.session.session_dir emanecmd += ["-f", os.path.join(path, "emane.log")] args = emanecmd + [os.path.join(path, "platform.xml")] utils.check_cmd(args, cwd=path) logging.info("host emane daemon running: %s", args)
def buildtransportxml(self): """ Calls emanegentransportxml using a platform.xml file to build the transportdaemon*.xml. """ utils.check_cmd(["emanegentransportxml", "platform.xml"], cwd=self.session.session_dir)
def __init__( self, node=None, name=None, session=None, mtu=1458, remoteip=None, _id=None, localip=None, ttl=255, key=None, start=True, ): """ Creates a GreTap instance. :param core.nodes.base.CoreNode node: related core node :param str name: interface name :param core.emulator.session.Session session: core session instance :param mtu: interface mtu :param str remoteip: remote address :param int _id: object id :param str localip: local address :param ttl: ttl value :param key: gre tap key :param bool start: start flag :raises CoreCommandError: when there is a command exception """ CoreInterface.__init__(self, node=node, name=name, mtu=mtu) self.session = session if _id is None: # from PyCoreObj _id = ((id(self) >> 16) ^ (id(self) & 0xFFFF)) & 0xFFFF self.id = _id sessionid = self.session.short_session_id() # interface name on the local host machine self.localname = "gt.%s.%s" % (self.id, sessionid) self.transport_type = "raw" if not start: self.up = False return if remoteip is None: raise ValueError("missing remote IP required for GRE TAP device") args = [ constants.IP_BIN, "link", "add", self.localname, "type", "gretap", "remote", str(remoteip), ] if localip: args += ["local", str(localip)] if ttl: args += ["ttl", str(ttl)] if key: args += ["key", str(key)] utils.check_cmd(args) args = [constants.IP_BIN, "link", "set", self.localname, "up"] utils.check_cmd(args) self.up = True
from core.constants import QUAGGA_STATE_DIR # this is the /etc/core/core.conf default from core.emulator.session import Session from core.nodes import ipaddress from core.utils import check_cmd quagga_sbin_search = ("/usr/local/sbin", "/usr/sbin", "/usr/lib/quagga") quagga_path = "zebra" # sanity check that zebra is installed try: for p in quagga_sbin_search: if os.path.exists(os.path.join(p, "zebra")): quagga_path = p break check_cmd( [os.path.join(quagga_path, "zebra"), "-u", "root", "-g", "root", "-v"]) except OSError: sys.stderr.write("ERROR: running zebra failed\n") sys.exit(1) class ManetNode(core.nodes.base.CoreNode): """ An Lxc namespace node configured for Quagga OSPFv3 MANET MDR """ conftemp = Template("""\ interface eth0 ip address $ipaddr ipv6 ospf6 instance-id 65 ipv6 ospf6 hello-interval 2 ipv6 ospf6 dead-interval 6 ipv6 ospf6 retransmit-interval 5
def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None, jitter=None, netif2=None, devname=None): """ Configure link parameters by applying tc queuing disciplines on the interface. """ if not devname: devname = netif.localname tc = [constants.TC_BIN, "qdisc", "replace", "dev", devname] parent = ["root"] # attempt to set bandwidth and update as needed if value changed bandwidth_changed = netif.setparam("bw", bw) if bandwidth_changed: # from tc-tbf(8): minimum value for burst is rate / kernel_hz if bw > 0: if self.up: burst = max(2 * netif.mtu, bw / 1000) limit = 0xffff # max IP payload tbf = [ "tbf", "rate", str(bw), "burst", str(burst), "limit", str(limit) ] logging.info("linkconfig: %s" % [tc + parent + ["handle", "1:"] + tbf]) utils.check_cmd(tc + parent + ["handle", "1:"] + tbf) netif.setparam("has_tbf", True) elif netif.getparam("has_tbf") and bw <= 0: tcd = [] + tc tcd[2] = "delete" if self.up: utils.check_cmd(tcd + parent) netif.setparam("has_tbf", False) # removing the parent removes the child netif.setparam("has_netem", False) if netif.getparam("has_tbf"): parent = ["parent", "1:1"] netem = ["netem"] delay_changed = netif.setparam("delay", delay) if loss is not None: loss = int(loss) loss_changed = netif.setparam("loss", loss) if duplicate is not None: duplicate = int(duplicate) duplicate_changed = netif.setparam("duplicate", duplicate) jitter_changed = netif.setparam("jitter", jitter) # if nothing changed return if not any([ bandwidth_changed, delay_changed, loss_changed, duplicate_changed, jitter_changed ]): return # jitter and delay use the same delay statement if delay is not None: netem += ["delay", "%sus" % delay] else: netem += ["delay", "0us"] if jitter is not None: netem += ["%sus" % jitter, "25%"] if loss is not None and loss > 0: netem += ["loss", "%s%%" % min(loss, 100)] if duplicate is not None and duplicate > 0: netem += ["duplicate", "%s%%" % min(duplicate, 100)] if delay <= 0 and jitter <= 0 and loss <= 0 and duplicate <= 0: # possibly remove netem if it exists and parent queue wasn"t removed if not netif.getparam("has_netem"): return tc[2] = "delete" if self.up: logging.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"]], )) utils.check_cmd(tc + parent + ["handle", "10:"]) netif.setparam("has_netem", False) elif len(netem) > 1: if self.up: logging.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"] + netem], )) utils.check_cmd(tc + parent + ["handle", "10:"] + netem) netif.setparam("has_netem", True)
def stop_container(self): utils.check_cmd("lxc delete --force {name}".format(name=self.name))
def create_container(self): utils.check_cmd("lxc launch {image} {name}".format(name=self.name, image=self.image)) data = self.get_info() self.pid = data["state"]["pid"] return self.pid
def newveth(self, ifindex=None, ifname=None, net=None): """ Create a new interface. :param int ifindex: index for the new interface :param str ifname: name for the new interface :param core.nodes.base.CoreNetworkBase net: network to associate interface with :return: nothing """ with self.lock: if ifindex is None: ifindex = self.newifindex() if ifname is None: ifname = "eth%d" % ifindex sessionid = self.session.short_session_id() try: suffix = "%x.%s.%s" % (self.id, ifindex, sessionid) except TypeError: suffix = "%s.%s.%s" % (self.id, ifindex, sessionid) localname = "veth" + suffix if len(localname) >= 16: raise ValueError("interface local name (%s) too long" % localname) name = localname + "p" if len(name) >= 16: raise ValueError("interface name (%s) too long" % name) veth = Veth(node=self, name=name, localname=localname, net=net, start=self.up) if self.up: utils.check_cmd([ constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid) ]) self.check_cmd([ constants.IP_BIN, "link", "set", veth.name, "name", ifname ]) self.check_cmd([ constants.ETHTOOL_BIN, "-K", ifname, "rx", "off", "tx", "off" ]) veth.name = ifname if self.up: # TODO: potentially find better way to query interface ID # retrieve interface information output = self.check_cmd(["ip", "link", "show", veth.name]) logging.debug("interface command output: %s", output) output = output.split("\n") veth.flow_id = int(output[0].strip().split(":")[0]) + 1 logging.debug("interface flow index: %s - %s", veth.name, veth.flow_id) # TODO: mimic packed hwaddr # veth.hwaddr = MacAddress.from_string(output[1].strip().split()[1]) logging.debug("interface mac: %s - %s", veth.name, veth.hwaddr) try: self.addnetif(veth, ifindex) except ValueError as e: veth.shutdown() del veth raise e return ifindex
def stop_container(self): utils.check_cmd("docker rm -f {name}".format(name=self.name))
def linkconfig( self, netif, bw=None, delay=None, loss=None, duplicate=None, jitter=None, netif2=None, devname=None, ): """ Configure link parameters by applying tc queuing disciplines on the interface. :param core.nodes.interface.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 :param devname: device name :return: nothing """ if devname is None: devname = netif.localname tc = [constants.TC_BIN, "qdisc", "replace", "dev", devname] parent = ["root"] changed = False if netif.setparam("bw", bw): # from tc-tbf(8): minimum value for burst is rate / kernel_hz if bw is not None: burst = max(2 * netif.mtu, bw / 1000) # max IP payload limit = 0xFFFF tbf = [ "tbf", "rate", str(bw), "burst", str(burst), "limit", str(limit) ] if bw > 0: if self.up: logging.debug("linkconfig: %s" % ([tc + parent + ["handle", "1:"] + tbf], )) utils.check_cmd(tc + parent + ["handle", "1:"] + tbf) netif.setparam("has_tbf", True) changed = True elif netif.getparam("has_tbf") and bw <= 0: tcd = [] + tc tcd[2] = "delete" if self.up: utils.check_cmd(tcd + parent) netif.setparam("has_tbf", False) # removing the parent removes the child netif.setparam("has_netem", False) changed = True if netif.getparam("has_tbf"): parent = ["parent", "1:1"] netem = ["netem"] changed = max(changed, netif.setparam("delay", delay)) if loss is not None: loss = float(loss) changed = max(changed, netif.setparam("loss", loss)) if duplicate is not None: duplicate = int(duplicate) changed = max(changed, netif.setparam("duplicate", duplicate)) changed = max(changed, netif.setparam("jitter", jitter)) if not changed: return # jitter and delay use the same delay statement if delay is not None: netem += ["delay", "%sus" % delay] if jitter is not None: if delay is None: netem += ["delay", "0us", "%sus" % jitter, "25%"] else: netem += ["%sus" % jitter, "25%"] if loss is not None and loss > 0: netem += ["loss", "%s%%" % min(loss, 100)] if duplicate is not None and duplicate > 0: netem += ["duplicate", "%s%%" % min(duplicate, 100)] delay_check = delay is None or delay <= 0 jitter_check = jitter is None or jitter <= 0 loss_check = loss is None or loss <= 0 duplicate_check = duplicate is None or duplicate <= 0 if all([delay_check, jitter_check, loss_check, duplicate_check]): # possibly remove netem if it exists and parent queue wasn't removed if not netif.getparam("has_netem"): return tc[2] = "delete" if self.up: logging.debug("linkconfig: %s" % ([tc + parent + ["handle", "10:"]], )) utils.check_cmd(tc + parent + ["handle", "10:"]) netif.setparam("has_netem", False) elif len(netem) > 1: if self.up: logging.debug("linkconfig: %s" % ([tc + parent + ["handle", "10:"] + netem], )) utils.check_cmd(tc + parent + ["handle", "10:"] + netem) netif.setparam("has_netem", True)