core-extra/daemon/core/nodes/interface.py

529 lines
15 KiB
Python

"""
virtual ethernet classes that implement the interfaces available under Linux.
"""
import logging
import time
from builtins import int, range
from core import utils
from core.errors import CoreCommandError
from core.nodes.netclient import get_net_client
class CoreInterface(object):
"""
Base class for network interfaces.
"""
def __init__(self, session, node, name, mtu, server=None):
"""
Creates a PyCoreNetIf instance.
:param core.emulator.session.Session session: core session instance
:param core.nodes.base.CoreNode node: node for interface
:param str name: interface name
:param int mtu: mtu value
:param core.emulator.distributed.DistributedServer server: remote server node
will run on, default is None for localhost
"""
self.session = session
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
self.server = server
use_ovs = session.options.get_config("ovs") == "True"
self.net_client = get_net_client(use_ovs, self.net_cmd)
def net_cmd(self, args, env=None, cwd=None, wait=True):
"""
Runs a command on the host system or distributed servers.
:param str args: command to run
:param dict env: environment to run command with
:param str cwd: directory to run command in
:param bool wait: True to wait for status, False otherwise
:return: combined stdout and stderr
:rtype: str
:raises CoreCommandError: when a non-zero exit status occurs
"""
if self.server is None:
return utils.check_cmd(args, env, cwd, wait)
else:
return self.server.remote_cmd(args, env, cwd, wait)
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.
"""
def __init__(
self, session, node, name, localname, mtu=1500, server=None, start=True
):
"""
Creates a VEth instance.
:param core.emulator.session.Session session: core session instance
:param core.nodes.base.CoreNode node: related core node
:param str name: interface name
:param str localname: interface local name
:param int mtu: interface mtu
:param core.emulator.distributed.DistributedServer server: remote server node
will run on, default is None for localhost
:param bool start: start flag
:raises CoreCommandError: when there is a command exception
"""
# note that net arg is ignored
CoreInterface.__init__(self, session, node, name, mtu, server)
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
"""
self.net_client.create_veth(self.localname, self.name)
self.net_client.device_up(self.localname)
self.up = True
def shutdown(self):
"""
Interface shutdown logic.
:return: nothing
"""
if not self.up:
return
if self.node:
try:
self.node.node_net_client.device_flush(self.name)
except CoreCommandError:
logging.exception("error shutting down interface")
if self.localname:
try:
self.net_client.delete_device(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
"""
def __init__(
self, session, node, name, localname, mtu=1500, server=None, start=True
):
"""
Create a TunTap instance.
:param core.emulator.session.Session session: core session instance
:param core.nodes.base.CoreNode node: related core node
:param str name: interface name
:param str localname: local interface name
:param int mtu: interface mtu
:param core.emulator.distributed.DistributedServer server: remote server node
will run on, default is None for localhost
:param bool start: start flag
"""
CoreInterface.__init__(self, session, node, name, mtu, server)
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.node_net_client.device_flush(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():
try:
self.net_client.device_show(self.localname)
return 0
except CoreCommandError:
return 1
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():
try:
self.node.node_net_client.device_show(self.name)
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)
self.net_client.device_ns(self.localname, netns)
self.node.node_net_client.device_name(self.localname, self.name)
self.node.node_net_client.device_up(self.name)
def setaddrs(self):
"""
Set interface addresses based on self.addrlist.
:return: nothing
"""
self.waitfordevicenode()
for addr in self.addrlist:
self.node.node_net_client.create_address(self.name, str(addr))
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,
server=None,
):
"""
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 int mtu: interface mtu
:param str remoteip: remote address
:param int _id: object id
:param str localip: local address
:param int ttl: ttl value
:param int key: gre tap key
:param bool start: start flag
:param core.emulator.distributed.DistributedServer server: remote server node
will run on, default is None for localhost
:raises CoreCommandError: when there is a command exception
"""
CoreInterface.__init__(self, session, node, name, mtu, server)
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")
self.net_client.create_gretap(self.localname, remoteip, localip, ttl, key)
self.net_client.device_up(self.localname)
self.up = True
def shutdown(self):
"""
Shutdown logic for a GreTap.
:return: nothing
"""
if self.localname:
try:
self.net_client.device_down(self.localname)
self.net_client.delete_device(self.localname)
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 []