""" virtual ethernet classes that implement the interfaces available under Linux. """ import logging import time from builtins import int, range from core import CoreCommandError, constants, utils utils.check_executables([constants.IP_BIN]) class CoreInterface(object): """ Base class for network interfaces. """ def __init__(self, node, name, mtu): """ Creates a PyCoreNetIf instance. :param core.nodes.base.CoreNode node: node for interface :param str name: interface name :param mtu: mtu value """ self.node = node self.name = name if not isinstance(mtu, int): raise ValueError self.mtu = mtu self.net = None self._params = {} self.addrlist = [] self.hwaddr = None # placeholder position hook self.poshook = lambda a, b, c, d: None # used with EMANE self.transport_type = None # interface index on the network self.netindex = None # index used to find flow data self.flow_id = None def startup(self): """ Startup method for the interface. :return: nothing """ pass def shutdown(self): """ Shutdown method for the interface. :return: nothing """ pass def attachnet(self, net): """ Attach network. :param core.nodes.base.CoreNetworkBase net: network to attach :return: nothing """ if self.net: self.detachnet() self.net = None net.attach(self) self.net = net def detachnet(self): """ Detach from a network. :return: nothing """ if self.net is not None: self.net.detach(self) def addaddr(self, addr): """ Add address. :param str addr: address to add :return: nothing """ self.addrlist.append(addr) def deladdr(self, addr): """ Delete address. :param str addr: address to delete :return: nothing """ self.addrlist.remove(addr) def sethwaddr(self, addr): """ Set hardware address. :param core.nodes.ipaddress.MacAddress addr: hardware address to set to. :return: nothing """ self.hwaddr = addr def getparam(self, key): """ Retrieve a parameter from the, or None if the parameter does not exist. :param key: parameter to get value for :return: parameter value """ return self._params.get(key) def getparams(self): """ Return (key, value) pairs for parameters. """ parameters = [] for k in sorted(self._params.keys()): parameters.append((k, self._params[k])) return parameters def setparam(self, key, value): """ Set a parameter value, returns True if the parameter has changed. :param key: parameter name to set :param value: parameter value :return: True if parameter changed, False otherwise """ # treat None and 0 as unchanged values logging.debug("setting param: %s - %s", key, value) if value is None or value < 0: return False current_value = self._params.get(key) if current_value is not None and current_value == value: return False self._params[key] = value return True def swapparams(self, name): """ Swap out parameters dict for name. If name does not exist, intialize it. This is for supporting separate upstream/downstream parameters when two layer-2 nodes are linked together. :param str name: name of parameter to swap :return: nothing """ tmp = self._params if not hasattr(self, name): setattr(self, name, {}) self._params = getattr(self, name) setattr(self, name, tmp) def setposition(self, x, y, z): """ Dispatch position hook handler. :param x: x position :param y: y position :param z: z position :return: nothing """ self.poshook(self, x, y, z) def __lt__(self, other): """ Used for comparisons of this object. :param other: other interface :return: true if less than, false otherwise :rtype: bool """ return id(self) < id(other) class Veth(CoreInterface): """ Provides virtual ethernet functionality for core nodes. """ # TODO: network is not used, why was it needed? def __init__(self, node, name, localname, mtu=1500, net=None, start=True): """ Creates a VEth instance. :param core.nodes.base.CoreNode node: related core node :param str name: interface name :param str localname: interface local name :param mtu: interface mtu :param net: network :param bool start: start flag :raises CoreCommandError: when there is a command exception """ # note that net arg is ignored CoreInterface.__init__(self, node=node, name=name, mtu=mtu) self.localname = localname self.up = False if start: self.startup() 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 shutdown(self): """ Interface shutdown logic. :return: nothing """ if not self.up: return if self.node: try: self.node.network_cmd( [constants.IP_BIN, "-6", "addr", "flush", "dev", self.name] ) except CoreCommandError: logging.exception("error shutting down interface") if self.localname: try: utils.check_cmd([constants.IP_BIN, "link", "delete", self.localname]) except CoreCommandError: logging.info("link already removed: %s", self.localname) self.up = False class TunTap(CoreInterface): """ TUN/TAP virtual device in TAP mode """ # TODO: network is not used, why was it needed? def __init__(self, node, name, localname, mtu=1500, net=None, start=True): """ Create a TunTap instance. :param core.nodes.base.CoreNode node: related core node :param str name: interface name :param str localname: local interface name :param mtu: interface mtu :param core.nodes.base.CoreNetworkBase net: related network :param bool start: start flag """ CoreInterface.__init__(self, node=node, name=name, mtu=mtu) self.localname = localname self.up = False self.transport_type = "virtual" if start: self.startup() def startup(self): """ Startup logic for a tunnel tap. :return: nothing """ # TODO: more sophisticated TAP creation here # Debian does not support -p (tap) option, RedHat does. # For now, this is disabled to allow the TAP to be created by another # system (e.g. EMANE"s emanetransportd) # check_call(["tunctl", "-t", self.name]) # self.install() self.up = True def shutdown(self): """ Shutdown functionality for a tunnel tap. :return: nothing """ if not self.up: return try: self.node.network_cmd( [constants.IP_BIN, "-6", "addr", "flush", "dev", self.name] ) except CoreCommandError: logging.exception("error shutting down tunnel tap") self.up = False def waitfor(self, func, attempts=10, maxretrydelay=0.25): """ Wait for func() to return zero with exponential backoff. :param func: function to wait for a result of zero :param int attempts: number of attempts to wait for a zero result :param float maxretrydelay: maximum retry delay :return: True if wait succeeded, False otherwise :rtype: bool """ delay = 0.01 result = False for i in range(1, attempts + 1): r = func() if r == 0: result = True break msg = "attempt %s failed with nonzero exit status %s" % (i, r) if i < attempts + 1: msg += ", retrying..." logging.info(msg) time.sleep(delay) delay += delay if delay > maxretrydelay: delay = maxretrydelay else: msg += ", giving up" logging.info(msg) return result def waitfordevicelocal(self): """ Check for presence of a local device - tap device may not appear right away waits :return: wait for device local response :rtype: int """ logging.debug("waiting for device local: %s", self.localname) def localdevexists(): args = [constants.IP_BIN, "link", "show", self.localname] return utils.cmd(args) self.waitfor(localdevexists) def waitfordevicenode(self): """ Check for presence of a node device - tap device may not appear right away waits. :return: nothing """ logging.debug("waiting for device node: %s", self.name) def nodedevexists(): args = [constants.IP_BIN, "link", "show", self.name] try: self.node.network_cmd(args) return 0 except CoreCommandError: return 1 count = 0 while True: result = self.waitfor(nodedevexists) if result: break # TODO: emane specific code # check if this is an EMANE interface; if so, continue # waiting if EMANE is still running should_retry = count < 5 is_emane_running = self.node.session.emane.emanerunning(self.node) if all([should_retry, self.net.is_emane, is_emane_running]): count += 1 else: raise RuntimeError("node device failed to exist") def install(self): """ Install this TAP into its namespace. This is not done from the startup() method but called at a later time when a userspace program (running on the host) has had a chance to open the socket end of the TAP. :return: nothing :raises CoreCommandError: when there is a command exception """ self.waitfordevicelocal() netns = str(self.node.pid) utils.check_cmd( [constants.IP_BIN, "link", "set", self.localname, "netns", netns] ) self.node.network_cmd( [constants.IP_BIN, "link", "set", self.localname, "name", self.name] ) self.node.network_cmd([constants.IP_BIN, "link", "set", self.name, "up"]) def setaddrs(self): """ Set interface addresses based on self.addrlist. :return: nothing """ self.waitfordevicenode() for addr in self.addrlist: self.node.network_cmd( [constants.IP_BIN, "addr", "add", str(addr), "dev", self.name] ) class GreTap(CoreInterface): """ GRE TAP device for tunneling between emulation servers. Uses the "gretap" tunnel device type from Linux which is a GRE device having a MAC address. The MAC address is required for bridging. """ 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 def shutdown(self): """ Shutdown logic for a GreTap. :return: nothing """ if self.localname: try: args = [constants.IP_BIN, "link", "set", self.localname, "down"] utils.check_cmd(args) args = [constants.IP_BIN, "link", "del", self.localname] utils.check_cmd(args) except CoreCommandError: logging.exception("error during shutdown") self.localname = None def data(self, message_type): """ Data for a gre tap. :param message_type: message type for data :return: None """ return None def all_link_data(self, flags): """ Retrieve link data. :param flags: link flags :return: link data :rtype: list[core.emulator.data.LinkData] """ return []