Example #1
0
    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)
Example #2
0
    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
Example #3
0
    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")
Example #4
0
    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")
Example #5
0
    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
Example #6
0
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
Example #7
0
    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
Example #8
0
    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")
Example #9
0
    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
Example #10
0
    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())
Example #11
0
    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"])
Example #12
0
    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"])
Example #13
0
    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()
Example #14
0
    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)
Example #15
0
    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)
Example #16
0
 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)
Example #17
0
    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
Example #18
0
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
Example #19
0
    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)
Example #20
0
File: lxd.py Project: rudyeila/core
 def stop_container(self):
     utils.check_cmd("lxc delete --force {name}".format(name=self.name))
Example #21
0
File: lxd.py Project: rudyeila/core
 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
Example #22
0
    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
Example #23
0
 def stop_container(self):
     utils.check_cmd("docker rm -f {name}".format(name=self.name))
Example #24
0
    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)