""" coreobj.py: defines the basic objects for emulation: the PyCoreObj base class, along with PyCoreNode, PyCoreNet, and PyCoreNetIf """ import os import shutil import socket import threading from socket import AF_INET from socket import AF_INET6 from core.api import coreapi from core.data import NodeData, LinkData from core.enumerations import LinkTlvs from core.enumerations import LinkTypes from core.misc import ipaddress class Position(object): """ Helper class for Cartesian coordinate position """ def __init__(self, x=None, y=None, z=None): self.x = None self.y = None self.z = None self.set(x, y, z) def set(self, x=None, y=None, z=None): """ Returns True if the position has actually changed. """ if self.x == x and self.y == y and self.z == z: return False self.x = x self.y = y self.z = z return True def get(self): """ Fetch the (x,y,z) position tuple. """ return self.x, self.y, self.z class PyCoreObj(object): """ Base class for pycore objects (nodes and nets) """ apitype = None def __init__(self, session, objid=None, name=None, start=True): self.session = session if objid is None: objid = session.get_object_id() self.objid = objid if name is None: name = "o%s" % self.objid self.name = name # ifindex is key, PyCoreNetIf instance is value self._netif = {} self.ifindex = 0 self.canvas = None self.icon = None self.opaque = None self.position = Position() def startup(self): """ Each object implements its own startup method. """ raise NotImplementedError def shutdown(self): """ Each object implements its own shutdown method. """ raise NotImplementedError def setposition(self, x=None, y=None, z=None): """ Set the (x,y,z) position of the object. """ return self.position.set(x=x, y=y, z=z) def getposition(self): """ Return an (x,y,z) tuple representing this object's position. """ return self.position.get() def ifname(self, ifindex): return self.netif(ifindex).name def netifs(self, sort=False): """ Iterate over attached network interfaces. """ if sort: return map(lambda k: self._netif[k], sorted(self._netif.keys())) else: return self._netif.itervalues() def numnetif(self): """ Return the attached interface count. """ return len(self._netif) def getifindex(self, netif): for ifindex in self._netif: if self._netif[ifindex] is netif: return ifindex return -1 def newifindex(self): while self.ifindex in self._netif: self.ifindex += 1 ifindex = self.ifindex self.ifindex += 1 return ifindex def data(self, message_type): """ Build a data object for this node. :param message_type: purpose for the data object we are creating :return: node data object :rtype: core.data.NodeData """ if self.apitype is None: return None x, y, z = self.getposition() model = None if hasattr(self, "type"): model = self.type emulation_server = None if hasattr(self, "server"): emulation_server = self.server services = None if hasattr(self, "services") and len(self.services) != 0: nodeservices = [] for s in self.services: nodeservices.append(s._name) services = "|".join(nodeservices) node_data = NodeData( message_type=message_type, id=self.objid, node_type=self.apitype, name=self.name, emulation_id=self.objid, canvas=self.canvas, icon=self.icon, opaque=self.opaque, x_position=x, y_position=y, model=model, emulation_server=emulation_server, services=services ) return node_data def all_link_data(self, flags): """ Build CORE API Link Messages for this object. There is no default method for PyCoreObjs as PyCoreNodes do not implement this but PyCoreNets do. """ return [] class PyCoreNode(PyCoreObj): """ Base class for nodes """ def __init__(self, session, objid=None, name=None, start=True): """ Initialization for node objects. """ PyCoreObj.__init__(self, session, objid, name, start=start) self.services = [] if not hasattr(self, "type"): self.type = None self.nodedir = None def nodeid(self): return self.objid def addservice(self, service): if service is not None: self.services.append(service) def makenodedir(self): if self.nodedir is None: self.nodedir = \ os.path.join(self.session.session_dir, self.name + ".conf") os.makedirs(self.nodedir) self.tmpnodedir = True else: self.tmpnodedir = False def rmnodedir(self): if hasattr(self.session.options, 'preservedir'): if self.session.options.preservedir == '1': return if self.tmpnodedir: shutil.rmtree(self.nodedir, ignore_errors=True) def addnetif(self, netif, ifindex): if ifindex in self._netif: raise ValueError, "ifindex %s already exists" % ifindex self._netif[ifindex] = netif netif.netindex = ifindex def delnetif(self, ifindex): if ifindex not in self._netif: raise ValueError, "ifindex %s does not exist" % ifindex netif = self._netif.pop(ifindex) netif.shutdown() del netif def netif(self, ifindex, net=None): if ifindex in self._netif: return self._netif[ifindex] else: return None def attachnet(self, ifindex, net): if ifindex not in self._netif: raise ValueError, "ifindex %s does not exist" % ifindex self._netif[ifindex].attachnet(net) def detachnet(self, ifindex): if ifindex not in self._netif: raise ValueError, "ifindex %s does not exist" % ifindex self._netif[ifindex].detachnet() def setposition(self, x=None, y=None, z=None): changed = PyCoreObj.setposition(self, x=x, y=y, z=z) if not changed: # save extra interface range calculations return for netif in self.netifs(sort=True): netif.setposition(x, y, z) def commonnets(self, obj, want_ctrl=False): """ Given another node or net object, return common networks between this node and that object. A list of tuples is returned, with each tuple consisting of (network, interface1, interface2). """ r = [] for netif1 in self.netifs(): if not want_ctrl and hasattr(netif1, 'control'): continue for netif2 in obj.netifs(): if netif1.net == netif2.net: r += (netif1.net, netif1, netif2), return r class PyCoreNet(PyCoreObj): """ Base class for networks """ linktype = LinkTypes.WIRED.value def __init__(self, session, objid, name, start=True): """ Initialization for network objects. """ PyCoreObj.__init__(self, session, objid, name, start=start) self._linked = {} self._linked_lock = threading.Lock() def attach(self, netif): i = self.newifindex() self._netif[i] = netif netif.netifi = i with self._linked_lock: self._linked[netif] = {} def detach(self, netif): del self._netif[netif.netifi] netif.netifi = None with self._linked_lock: del self._linked[netif] def netifparamstolink(self, netif): """ Helper for tolinkmsgs() to build TLVs having link parameters from interface parameters. """ delay = netif.getparam('delay') bw = netif.getparam('bw') loss = netif.getparam('loss') duplicate = netif.getparam('duplicate') jitter = netif.getparam('jitter') tlvdata = "" if delay is not None: tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DELAY.value, delay) if bw is not None: tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.BANDWIDTH.value, bw) if loss is not None: tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.PER.value, str(loss)) if duplicate is not None: tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DUP.value, str(duplicate)) if jitter is not None: tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.JITTER.value, jitter) return tlvdata def all_link_data(self, flags): """ Build CORE API Link Messages for this network. Each link message describes a link between this network and a node. """ all_links = [] # build a link message from this network node to each node having a # connected interface for netif in self.netifs(sort=True): if not hasattr(netif, "node"): continue otherobj = netif.node uni = False if otherobj is None: # two layer-2 switches/hubs linked together via linknet() if not hasattr(netif, "othernet"): continue otherobj = netif.othernet if otherobj.objid == self.objid: continue netif.swapparams('_params_up') upstream_params = netif.getparams() netif.swapparams('_params_up') if netif.getparams() != upstream_params: uni = True unidirectional = 0 if uni: unidirectional = 1 interface2_ip4 = None interface2_ip4_mask = None interface2_ip6 = None interface2_ip6_mask = None for address in netif.addrlist: ip, sep, mask = address.partition('/') mask = int(mask) if ipaddress.is_ipv4_address(ip): family = AF_INET ipl = socket.inet_pton(family, ip) interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl) interface2_ip4_mask = mask else: family = AF_INET6 ipl = socket.inet_pton(family, ip) interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl) interface2_ip6_mask = mask # TODO: not currently used # loss = netif.getparam('loss') link_data = LinkData( message_type=flags, node1_id=self.objid, node2_id=otherobj.objid, link_type=self.linktype, unidirectional=unidirectional, interface2_id=otherobj.getifindex(netif), interface2_mac=netif.hwaddr, interface2_ip4=interface2_ip4, interface2_ip4_mask=interface2_ip4_mask, interface2_ip6=interface2_ip6, interface2_ip6_mask=interface2_ip6_mask, delay=netif.getparam("delay"), bandwidth=netif.getparam("bw"), dup=netif.getparam("duplicate"), jitter=netif.getparam("jitter") ) all_links.append(link_data) if not uni: continue netif.swapparams('_params_up') link_data = LinkData( message_type=0, node1_id=otherobj.objid, node2_id=self.objid, unidirectional=1, delay=netif.getparam("delay"), bandwidth=netif.getparam("bw"), dup=netif.getparam("duplicate"), jitter=netif.getparam("jitter") ) netif.swapparams('_params_up') all_links.append(link_data) return all_links class PyCoreNetIf(object): """ Base class for interfaces. """ def __init__(self, node, name, mtu): self.node = node self.name = name if not isinstance(mtu, (int, long)): raise ValueError self.mtu = mtu self.net = None self._params = {} self.addrlist = [] self.hwaddr = None self.poshook = 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): pass def shutdown(self): pass def attachnet(self, net): if self.net: self.detachnet() self.net = None net.attach(self) self.net = net def detachnet(self): if self.net is not None: self.net.detach(self) def addaddr(self, addr): self.addrlist.append(addr) def deladdr(self, addr): self.addrlist.remove(addr) def sethwaddr(self, addr): self.hwaddr = addr def getparam(self, key): """ Retrieve a parameter from the _params dict, or None if the parameter does not exist. """ if key not in self._params: return None return self._params[key] def getparams(self): """ Return (key, value) pairs from the _params dict. """ r = [] for k in sorted(self._params.keys()): r.append((k, self._params[k])) return r def setparam(self, key, value): """ Set a parameter in the _params dict. Returns True if the parameter has changed. """ if key in self._params: if self._params[key] == value: return False elif self._params[key] <= 0 and value <= 0: # treat None and 0 as unchanged values return False self._params[key] = value return True def swapparams(self, name): """ Swap out the _params 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. """ 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 to any position hook (self.poshook) handler. """ if self.poshook is not None: self.poshook(self, x, y, z)