Source code for switchyard.llnetbase

from abc import ABCMeta,abstractmethod
from collections import namedtuple

from .pcapffi import pcap_devices
from .lib.logging import log_debug, log_warn
from .lib.exceptions import *
from .lib.address import *

ReceivedPacket = namedtuple('ReceivedPacket', 
    ['timestamp', 'input_port', 'packet'])

def _start_usercode(entryfunction, netobj, codeargdict):
    '''
    figure out how to correctly start the user code.  warn if
    args are passed on the command line, but the code doesn't 
    accept them.
    '''
    # p22, python3 lang ref
    numargs = entryfunction.__code__.co_argcount
    takenet = numargs >= 1
    takeposargs = numargs - 1 # number of positional args fn takes, less the net obj
    takestarargs = entryfunction.__code__.co_flags & 0x04 == 0x04
    takekw = entryfunction.__code__.co_flags & 0x08 == 0x08

    posargs = []
    args = codeargdict['args']
    kwargs = codeargdict['kwargs']

    if takeposargs > 0:
        if len(args) < takeposargs and not takestarargs:
            raise RuntimeError("Your code requires {} arguments in addition to the net object, but you've passed {} arguments via the -g command-line option.".format(takeposargs, len(args)))
        posargs = args[:takeposargs]
        args = args[takeposargs:]

    if args and not takestarargs:
        log_warn("User code arguments passed on command line, "
            "but the user code doesn't take arguments")
    if kwargs and not takekw:
        log_warn("User code keyword args passed on command line, "
            "but the user code doesn't take kwargs")

    if not takenet:
        raise RuntimeError("Your code does not appear to accept at "
            "least one parameter for the net object")

    # omg, this sucks.
    if takeposargs and takestarargs and takekw:
        entryfunction(netobj, *posargs, *args, **kwargs)
    elif takeposargs and takestarargs:
        entryfunction(netobj, *posargs, *args)
    elif takeposargs:
        entryfunction(netobj, *posargs)
    elif takestarargs and takekw:
        entryfunction(netobj, *args, **kwargs)
    elif takestarargs:
        entryfunction(netobj, *args)
    elif takekw:
        entryfunction(netobj, **kwargs)
    else:
        entryfunction(netobj)

[docs]class LLNetBase(metaclass=ABCMeta): ''' Base class for the low-level networking library in Python. "net" objects are constructed from classes derived from this class. ''' def __init__(self, name=None): self._devupdown_callback = None self._devinfo = {} # dict(str -> Interface) def set_devupdown_callback(self, callback): ''' Set the callback function to be invoked when an interface goes up or down. The arguments to the callback are: Interface (object representing the interface that has changed status), string (either 'up' or 'down'). (function) -> None ''' self._devupdown_callback = callback def intf_down(self, interface): ''' Can be called when an interface goes down. FIXME: doesn't really do anything at this point. ''' intf = self._devinfo.get(interface, None) if intf and self._devupdown_callback: self._devupdown_callback(intf, 'down') def intf_up(self, interface): ''' Can be called when an interface is put in service. FIXME: not currently used; more needs to be done to correctly put a new intf into service. ''' if interface.name not in self._devinfo: self._devinfo[interface.name] = interface if self._devupdown_callback: self._devupdown_callback(interface, 'up') else: raise ValueError("Interface already registered")
[docs] def interfaces(self): ''' Return a list of interfaces incident on this node/router. Each item in the list is an Interface object, each of which includes name, ethaddr, ipaddr, and netmask attributes. ''' return list(self._devinfo.values())
[docs] def ports(self): ''' Alias for interfaces() method. ''' return self.interfaces()
[docs] def interface_by_name(self, name): ''' Given a device name, return the corresponding interface object ''' if name in self._devinfo: return self._devinfo[name] raise KeyError("No device named {}".format(name))
[docs] def port_by_name(self, name): ''' Alias for interface_by_name ''' return self.interface_by_name(name)
[docs] def interface_by_ipaddr(self, ipaddr): ''' Given an IP address, return the interface that 'owns' this address ''' ipaddr = IPAddr(ipaddr) for devname,iface in self._devinfo.items(): if iface.ipaddr == ipaddr: return iface raise KeyError("No device has IP address {}".format(ipaddr))
[docs] def port_by_ipaddr(self, ipaddr): ''' Alias for interface_by_ipaddr ''' return self.interface_by_ipaddr(ipaddr)
[docs] def interface_by_macaddr(self, macaddr): ''' Given a MAC address, return the interface that 'owns' this address ''' macaddr = EthAddr(macaddr) for devname,iface in self._devinfo.items(): if iface.ethaddr == macaddr: return iface raise KeyError("No device has MAC address {}".format(macaddr))
[docs] def port_by_macaddr(self, macaddr): ''' Alias for interface_by_macaddr ''' return self.interface_by_macaddr(macaddr)
@property def testmode(self): ''' Returns True if running in test mode and False if running in live/real mode. ''' raise NotImplementedError("This property must be overridden by derived classes") @abstractmethod
[docs] def recv_packet(self, timeout=None): ''' Receive a packet on any port/interface. If a non-None timeout is given, the method will block for up to timeout seconds. If no packet is available, the exception NoPackets will be raised. If the Switchyard framework is being shut down, the Shutdown exception will be raised. If a packet is available, the ReceivedPacket named tuple (timestamp, input_port, packet) will be returned. ''' raise NoPackets()
@abstractmethod
[docs] def send_packet(self, output_port, packet): ''' Send a packet out the given output port/interface. Returns None. ''' pass
@abstractmethod def shutdown(self): pass @property def name(self): pass def _lookup_devname(self, ifnum): for devname,iface in self._devinfo.items(): if iface.ifnum == ifnum: return devname raise KeyError("No device has ifnum {}".format(ifnum))