Esempio n. 1
0
    def create_interface(self):
        ifname = self.__intf_info["ifname"]
        IP_ROUTE = IPRoute()
        if len(IP_ROUTE.link_lookup(ifname=ifname)) > 0:
            self.logger_topo.warning(
                "ip link {} exists so not create it.".format(ifname))
            IP_ROUTE.close()
            return

        if self.__peer:
            if len(IP_ROUTE.link_lookup(ifname=self.__peer)) > 0:
                self.logger_topo.warning(
                    "ip link {} exists so not create it.".format(ifname))
                IP_ROUTE.close()
                return
        else:
            # Close IP_ROUTE first anyway
            IP_ROUTE.close()
            ps_intf = r"^\d+: (?P<intf>[\w-]+): "
            p_intf = re.compile(ps_intf, re.MULTILINE)
            _, out, _ = exec_cmd_in_namespace(self.__namespace, ["ip", "link"])
            m_intf = p_intf.findall(out)
            if ifname in m_intf:
                self.logger_topo.warning(
                    "ip link {} exists in namespace {} so not create it.".
                    format(ifname, self.__namespace))
                return

        MAIN_IPDB = IPDB()
        MAIN_IPDB.create(ifname=ifname,
                         kind="veth" if self.__peer else "dummy",
                         peer=self.__peer).commit()
        with MAIN_IPDB.interfaces[ifname] as veth:
            try:
                veth.net_ns_fd = self.__namespace
            except netlink.exceptions.NetlinkError as e:
                MAIN_IPDB.release()
                if e.code == 17:  # "File exists"
                    pass
                else:
                    raise e

        MAIN_IPDB.release()
        self.logger_topo.info(
            "interface {} in namespace {} is created, peer: {}.".format(
                ifname, self.__namespace, self.__peer))
Esempio n. 2
0
    def create(self):
        """
        Main function to build all infrasim virtual network referring to
        resolved topology
        """
        self.__load()

        self.logger_topo.info("[Define openvswitches]")
        for _, ovs in self.__openvswitch.items():
            ovs.add_vswitch()
            ovs.add_all_ports()
            # FIXME: substitute for below code
            # self.__vswitch_ex.set_interface("phy-br-ex", "int-br-ex")
            # self.__vswitch_int.set_interface("int-br-ex", "phy-br-ex")
            ovs.add_interface_d()

        self.logger_topo.info("[Define namespaces]")
        for _, ns in self.__namespace.items():
            ns.create_namespace()
            ns.create_all_interfaces(ref=self.__connection)

        self.logger_topo.info("[Set openvswitch ports up]")
        IP_ROUTE = IPRoute()
        for _, ovs in self.__openvswitch.items():
            idx = IP_ROUTE.link_lookup(ifname=ovs.name)[0]
            IP_ROUTE.link("set", index=idx, state="up")
            self.logger_topo.info("set openvswitch {} up.".format(ovs.name))

        for _, ovs_port in self.__connection.items():
            idx = IP_ROUTE.link_lookup(ifname=ovs_port)[0]
            IP_ROUTE.link("set", index=idx, state="up")
            self.logger_topo.info("set port {} up.".format(ovs_port))

        self.logger_topo.info("[Set namespace interfaces up]")
        for _, ns in self.__namespace.items():
            ns.create_interface_d()
            ns.link_up_all()
            ns.create_routes()

        self.logger_topo.info("[Setup portforward]")
        self.__port_forward.build()

        IP_ROUTE.close()
def main():
    """
    Program entry point when run interactively.
    """

    # prepare option parser
    parser = ArgumentParser(usage="usage: %(prog)s [options]",
                            description="Executes a UDP broadcast to test for "
                            "connectivity")
    parser.add_argument("-i",
                        "--interface",
                        dest="interface",
                        default="poprow0",
                        action="store",
                        metavar="ifname",
                        help="Interface to use [default: %(default)s]")
    parser.add_argument("-p",
                        "--port",
                        dest="port",
                        type=int,
                        default=12345,
                        action="store",
                        help="UDP port [default: %(default)s]",
                        metavar="PORT")
    parser.add_argument(
        "-n",
        "--count",
        dest="count",
        type=int,
        default=100,
        action="store",
        help="Number of packets to send [default: %(default)s]",
        metavar="COUNT")
    parser.add_argument("-t",
                        "--interval",
                        dest="interval",
                        type=float,
                        default=0.2,
                        action="store",
                        help="Packet interval [default: %(default)s]")
    parser.add_argument("-v", "--verbose", dest="verbose", action="store_true")

    # parse options
    args = parser.parse_args()

    # copy options
    interface = args.interface
    port = args.port
    count = args.count
    interval = args.interval
    verbose = args.verbose

    ip = IPRoute()
    if_index = ip.link_lookup(ifname=interface)

    if len(if_index) == 0:
        print >> sys.stderr, "Can't find interface %s" % interface
        sys.exit(1)

    # from the given interface find unicast and broadcast addresses
    if_info = ip.get_addr(index=if_index[0])[0]
    address = if_info.get_attr("IFA_ADDRESS")
    netmask = if_info["prefixlen"]
    network = ipaddr.IPv4Network("%s/%d" % (address, netmask))
    broadcast = str(network.broadcast)

    # get hostname as additional information to send in the packets
    hostname = socket.gethostname()

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

    i = 0
    run = True
    while i < count and run:
        try:
            payload = "POPROWPING %d %s" % (i, hostname)
            if verbose:
                print >> sys.stderr, "Sending packet #%d to %s:%d" % \
                      (i+1, broadcast, port)
            sock.sendto(payload, (broadcast, port))
            i += 1
            time.sleep(interval)
        except SystemExit:
            run = False
            if verbose:
                print >> sys.stderr, "Catching SystemExit. Quitting"
        except KeyboardInterrupt:
            run = False
            if verbose:
                print >> sys.stderr, "Keyboard Interrupt. Quitting"
        except:
            run = False
            if verbose:
                print >> sys.stderr, "Error while sending packet. Quitting"

    sock.close()
Esempio n. 4
0
class InterfaceConfigTest(unittest.TestCase):
    '''Tests all aspects of the NetworkInterfaceConfig API

    A dummy interface (dummy0) is used to test APIs and read back configuration. Testing
    may report a false positive if this interface exists for whatever reason.'''
    def __init__(self, *args, **kwargs):
        super(InterfaceConfigTest, self).__init__(*args, **kwargs)
        self.iproute_api = IPRoute()

    def setUp(self):
        self.iproute_api.link_create(name='dummy0', kind='dummy')


    def tearDown(self):
        idx = self.iproute_api.link_lookup(ifname='dummy0')[0]
        self.iproute_api.link_remove(idx)
        self.iproute_api.close()

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_nonexistent_interface(self):
        '''Tests that InvalidNetworkDevice is raised if an interface is non-existent'''
        with self.assertRaises(InvalidNetworkDevice):
            NetworkInterfaceConfig('nonexistent0')

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_empty_configuration(self):
        '''Confirms that we properly handle no configuration data on an interface'''
        # The interface has just been created as part of setup, there shouldn't be any IPs
        interface_cfg = NetworkInterfaceConfig('dummy0')

        if interface_cfg.get_ips():
            self.fail("dummy configuration returned an IP!")

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_add_ipv4(self):
        '''Adds an IPv4 address, and then confirms it via get_ips()'''
        interface_cfg = NetworkInterfaceConfig('dummy0')

        interface_cfg.add_v4_ip(ip_address='10.0.241.123',
                                prefix_length=24)

        # Retrieve the IPs on the interface and make sure its the only one
        # plus that it is the correct IP
        ips = interface_cfg.get_ips()
        self.assertEqual(len(ips), 1, "dummy interface either didn't get the IP or has multiple!")
        self.assertEqual(ips[0]['ip_address'], '10.0.241.123', "IP assignment failure!")
        self.assertEqual(ips[0]['family'], AF_INET)
        self.assertEqual(ips[0]['broadcast'], '10.0.241.255', "IP assignment failure!")
        self.assertEqual(ips[0]['prefix_length'], 24, "IP assignment failure!")

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_add_ipv6(self):
        '''Adds an IPv6 address and then confirms it'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        interface_cfg.add_v6_ip('fd00:a3b1:78a2::1', 64)
        ips = interface_cfg.get_ips()
        self.assertEqual(len(ips), 1, "dummy interface either didn't get the IP or has multiple!")
        self.assertEqual(ips[0]['ip_address'], 'fd00:a3b1:78a2::1', "IP assignment failure!")
        self.assertEqual(ips[0]['family'], AF_INET6, "IP assignment failure!")
        self.assertEqual(ips[0]['prefix_length'], 64, "IP assignment failure!")

        # FIXME: Write tests using different IPv6 notations

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_remove_ipv6(self):
        '''Removes an IPv6 address and confirms it'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        interface_cfg.add_v6_ip('fd00:a3b1:78a2::1', 64)
        interface_cfg.remove_ip('fd00:a3b1:78a2::1')

        if interface_cfg.get_ips():
            self.fail("dummy configuration returned an IP!")

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_duplicate_ip_failure(self):
        '''Tests duplicate address protection'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        interface_cfg.add_v4_ip(ip_address='10.0.241.123',
                                prefix_length=24)

        with self.assertRaises(DuplicateIPError):
            interface_cfg.add_v4_ip(ip_address='10.0.241.123',
                                    prefix_length=24)

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_remove_ipv4(self):
        '''Tests interface deconfiguration of v4 addresses'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        interface_cfg.add_v4_ip(ip_address='10.0.241.123',
                                prefix_length=24)

        # Now remove the IP
        interface_cfg.remove_ip('10.0.241.123')

        # And make sure its gone
        if interface_cfg.get_ips():
            self.fail("dummy configuration returned an IP!")

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_invalid_ip_check(self):
        '''Makes sure we raise ValueError on if we pass in an invalid IP'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(ValueError):
            interface_cfg.add_v4_ip(ip_address='ImNotAnIP!',
                                    prefix_length=1337)

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_network_address_rejection(self):
        '''Prefixes >/24 require a dedicated network address that can't be used as an IP'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(ValueError):
            interface_cfg.add_v4_ip(ip_address='192.168.1.192',
                                    prefix_length=26)

    # pylint: disable=invalid-name
    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_broadcast_address_rejection(self):
        '''Rejects if we try using a broadcast address of a prefix'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(ValueError):
            interface_cfg.add_v4_ip(ip_address='192.168.1.191',
                                    prefix_length=26)

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_ipv4_loopback_address_rejection(self):
        '''Rejects if we try using a loopback address'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(ValueError):
            interface_cfg.add_v4_ip(ip_address='127.0.1.2',
                                    prefix_length=24)

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_ipv4_multicast_rejection(self):
        '''Reject if we try to assign a multicast address'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(ValueError):
            interface_cfg.add_v4_ip(ip_address='224.0.0.1',
                                    prefix_length=24)

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_ipv4_class_e_rejection(self):
        '''Reject if we try to use a class E address'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(ValueError):
            interface_cfg.add_v4_ip(ip_address='240.0.0.1',
                                    prefix_length=24)

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_ipv4_link_local_rejection(self):
        '''Reject if we try to use a link-local address'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(ValueError):
            interface_cfg.add_v4_ip(ip_address='169.254.1.1',
                                    prefix_length=24)

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_ipv6_loopback_address_reject(self):
        '''Rejects if we try to assign loopback'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(ValueError):
            interface_cfg.add_v6_ip(ip_address='::1',
                                    prefix_length=128)

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_ipv6_multicast_reject(self):
        '''Rejects if address is IPv6 multicast'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(ValueError):
            interface_cfg.add_v6_ip(ip_address="ff05::1:3",
                                    prefix_length=128)

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_ipv6_reserved_reject(self):
        '''Rejects if the IP is in reserved address space'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(ValueError):
            interface_cfg.add_v6_ip(ip_address='dead:beef::',
                                    prefix_length=64)

    @unittest.skipIf(os.getuid() != 0, 'must be run as root')
    def test_check_for_nonexistent_ip(self):
        '''Tests IPNotFound response when getting information for a specific IP'''
        interface_cfg = NetworkInterfaceConfig('dummy0')
        with self.assertRaises(IPNotFound):
            interface_cfg.get_full_ip_info("10.0.21.123")
Esempio n. 5
0
def main():
    """
    Program entry point when run interactively.
    """

    # prepare option parser
    parser = ArgumentParser(usage="usage: %(prog)s [options]",
                            description="Listens to UDP broadcasts to test for "
                                        "connectivity")
    parser.add_argument("-i", "--interface", dest="interface",
                        default="poprow0", action="store", metavar="ifname",
                        help="Interface to use [default: %(default)s]")
    parser.add_argument("-p", "--port", dest="port", type=int, default=12345,
                        action="store", help="UDP port [default: %(default)s]",
                        metavar="PORT")
    parser.add_argument("-f", "--filename", dest="filename", default="",
                        action="store",
                        help="Output file where to store results [default: "
                             "stdout]", metavar="FILENAME")
    parser.add_argument("-v", "--verbose", dest="verbose", action="store_true")
    parser.add_argument("-d", "--daemon", dest="daemon", action="store_true",
                        help="Daemonize the process")


    # parse options
    args = parser.parse_args()

    # copy options
    interface = args.interface
    port = args.port
    filename = args.filename
    verbose = args.verbose
    daemon = args.daemon

    ip = IPRoute()
    if_index = ip.link_lookup(ifname=interface)

    if len(if_index) == 0:
        print >> sys.stderr, "Can't find interface %s" % interface
        sys.exit(1)

    # from the given interface find unicast and broadcast addresses
    if_info = ip.get_addr(index=if_index[0])[0]
    address = if_info.get_attr("IFA_ADDRESS")
    netmask = if_info["prefixlen"]
    network = ipaddr.IPv4Network("%s/%d" % (address, netmask))
    broadcast = str(network.broadcast)

    # get hostname as additional information for the log file
    hostname = socket.gethostname()

    if not daemon:
        signal.signal(signal.SIGTERM, signal_handler)
    else:
        daemonize()

    # setup output file
    if filename == "":
        outfile = sys.stdout
    else:
        outfile = open(filename, "w")

    if verbose:
        print >> sys.stderr, "Host with IP %s listening for packets on " \
                             "broadcast IP %s" % (address, broadcast)

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((broadcast, port))

    run = True
    while run:
        try:
            datagram = sock.recvfrom(1024)
            payload = datagram[0].split(' ')
            src = datagram[1][0]
            src_port = datagram[1][1]
            # check that this is really our app message
            if payload[0] != "POPROWPING":
                continue
            count = int(payload[1])
            remote_host = payload[2]
            if verbose:
                print >> sys.stderr, "Received packet #%d from %s:%d" % \
                      (count, src, src_port)
            outfile.write("%s,%s,%s,%s,%d\n" % (hostname, address, remote_host,
                                                src, count))
            outfile.flush()
        except SystemExit:
            run = False
            if verbose:
                print >> sys.stderr, "Catching SystemExit. Quitting"
        except KeyboardInterrupt:
            run = False
            if verbose:
                print >> sys.stderr, "Keyboard Interrupt. Quitting"
        except:
            run = False
            if verbose:
                print >> sys.stderr, "Error while receiving packet. Quitting"

    sock.close()
    outfile.flush()
    outfile.close()
Esempio n. 6
0
class InfrasimNamespace(object):
    def __init__(self, vswitch_instance, ns_info):
        self.__ns_info = ns_info
        self.name = ns_info['name']
        self.ip = IPRoute()
        # self.ipdb = IPDB(nl=NetNS(self.name))
        self.main_ipdb = IPDB()
        self.__vswitch = vswitch_instance

    @staticmethod
    def get_namespaces_list():
        return netns.listnetns()

    def build_one_namespace(self):
        self._create_namespace()

        for intf in self.__ns_info["interfaces"]:
            # get name
            ifname = intf["ifname"]
            if intf.get("pair") is False:
                self.create_single_virtual_intf_in_ns(intf)
            else:
                global interface_index
                self.create_ip_link_in_ns(ifname,
                                          "veth{}".format(interface_index))
                if 'bridge' in intf:
                    self.create_bridge(intf=ifname,
                                       br_name=intf['bridge']['ifname'])
                self.__vswitch.add_port("veth{}".format(interface_index))
                idx = self.ip.link_lookup(
                    ifname="veth{}".format(interface_index))[0]
                self.ip.link("set", index=idx, state="up")
                interface_index += 1

    def _create_namespace(self):
        if self.name in self.get_namespaces_list():
            print "name space {} exists.".format(self.name)
            return
        netns.create(self.name)

    def del_namespace(self):
        if self.name in netns.listnetns():
            netns.remove(self.name)

    def create_single_virtual_intf_in_ns(self, intf):
        ifname = intf['ifname']
        if len(self.ip.link_lookup(ifname=ifname)) > 0:
            print "ip link {} exists so not create it.".format(ifname)
            return

        self.main_ipdb.create(ifname=ifname, kind="dummy").commit()
        with self.main_ipdb.interfaces[ifname] as veth:
            veth.net_ns_fd = self.name

        if 'bridge' in intf:
            self.create_bridge(intf=ifname, br_name=intf['bridge']['ifname'])

    def create_ip_link_in_ns(self, ifname, peername):
        if len(self.ip.link_lookup(ifname=ifname)) > 0:
            print "ip link {} exists so not create it.".format(ifname)
            return

        if len(self.ip.link_lookup(ifname=peername)) > 0:
            print "ip link {} exists so not create it.".format(peername)
            return

        # create link peer
        self.main_ipdb.create(ifname=ifname, kind="veth",
                              peer=peername).commit()
        with self.main_ipdb.interfaces[ifname] as veth:
            veth.net_ns_fd = self.name

    def exec_cmd_in_namespace(self, cmd):
        start_process(["ip", "netns", "exec", self.name] + cmd)

    def link_up_all(self):
        # setup lo
        # self.exec_cmd_in_namespace(["ifdown", "lo"])
        # self.exec_cmd_in_namespace(["ifup", "lo"])
        self.exec_cmd_in_namespace(["ip", "link", "set", "dev", "lo", "up"])

        for intf_info in self.__ns_info["interfaces"]:
            if "bridge" in intf_info:
                self.exec_cmd_in_namespace(
                    ["ip", "link", "set", "dev", intf_info["ifname"], "up"])
                self.exec_cmd_in_namespace(
                    ["ifdown", intf_info["bridge"]["ifname"]])
                self.exec_cmd_in_namespace(
                    ["ifup", intf_info["bridge"]["ifname"]])
            else:
                self.exec_cmd_in_namespace(["ifdown", intf_info["ifname"]])
                self.exec_cmd_in_namespace(["ifup", intf_info["ifname"]])

    def create_bridge(self, intf="einf0", br_name="br0"):
        self.exec_cmd_in_namespace(["brctl", "addbr", "{}".format(br_name)])
        self.exec_cmd_in_namespace(
            ["brctl", "addif", "{}".format(br_name), intf])
        self.exec_cmd_in_namespace(
            ["brctl", "setfd", "{}".format(br_name), "0"])
        self.exec_cmd_in_namespace(
            ["brctl", "sethello", "{}".format(br_name), "1"])
        self.exec_cmd_in_namespace(
            ["brctl", "stp", "{}".format(br_name), "no"])
        self.exec_cmd_in_namespace(["ifconfig", intf, "promisc"])

    def build_ns_configuration(self):
        netns_path = "/etc/netns"
        ns_network_dir = os.path.join(netns_path, self.name, "network")

        if_down_dir = os.path.join(ns_network_dir, "if-down.d")
        if not os.path.exists(if_down_dir):
            os.makedirs(if_down_dir)

        if_post_down_dir = os.path.join(ns_network_dir, "if-post-down.d")
        if not os.path.exists(if_post_down_dir):
            os.makedirs(if_post_down_dir)

        if_pre_up_dir = os.path.join(ns_network_dir, "if-pre-up.d")
        if not os.path.exists(if_pre_up_dir):
            os.makedirs(if_pre_up_dir)

        if_up_dir = os.path.join(ns_network_dir, "if-up.d")
        if not os.path.exists(if_up_dir):
            os.makedirs(if_up_dir)

        content = ""
        content += "auto lo\n"
        content += "iface lo inet loopback\n"
        content += "\n"

        intf_list = []
        for intf_info in self.__ns_info["interfaces"]:
            intf_obj = Interface(intf_info)
            intf_list.append(intf_obj)

        for iobj in intf_list:
            content += iobj.compose()

        with open(os.path.join(ns_network_dir, "interfaces"), "w") as f:
            f.write(content)
Esempio n. 7
0
class NetworkInterfaceConfig(object):
    '''High-level abstraction of a network interface's configuration

    NetworkInterfaceConfig is designed to abstract most of the pain of
    work with (rt)netlink directly, and use pythonic methods for manipulating
    network configuration. At a high level, this interface is protocol-neutral,
    though only support for IPv4 and IPv6 has been added.

    Each function call does sanity checks to try and prevent undefined behavior,
    such as defining a link-local address by accident. For ease of use, this
    interface does not use the kernel netlink bind() functionality, and instead
    works in an async fashion. Manipulation of interfaces is rechecked at
    the end of each function to make sure that the state changed properly, and
    to make sure there wasn't some sort of silent failure.

    A single IPRoute() socket is open per instance of this class for performance
    reasons. This class is thread-safe.
    '''
    def __init__(self, interface):
        '''Manipulations the configuration of a given interface

        Args:
            interface - name of the interface to manipulate (i.e. 'eth0')

        Raises:
            InvalidNetworkDevice - if the interface does not exist
        '''

        self.interface = interface
        self.iproute_api = IPRoute()

        # Confirm this is a valid interface
        # This will chuck a IndexError exception if it doesn't exist
        self.interface_index = self._get_interface_index()

    def __del__(self):
        self.iproute_api.close()

    def _get_interface_index(self):
        '''Private API to get the interface index number

        Raises:
            InvalidNetworkDevice - if an interface isn't found by pyroute2
        '''

        try:
            idx = self.iproute_api.link_lookup(ifname=self.interface)[0]
        except IndexError:
            # IndexError means the interface wasn't found. Send a cleaner message up the pipe
            raise InvalidNetworkDevice("Interface not found")

        return idx

    def get_ips(self):
        '''Returns all a list IPs for a given interface. None if no IPs are configures

        IPs are returned in a list of dictionaries for easy enumeration.

        Keys that are always available:
            ip_address - assigned address
            family - protocol family of the returned IP. Either AF_INET, or AF_INET6
            prefix_length - Length of the network prefix assigned to this interface
                            This is the CIDR representation of the netmask.

            if family == AF_INET
                broadcast - broadcast address for an interface
        '''

        # I'd like to use label= here, but IPv6 information is not returned
        # when doing so. Possibly a bug in pyroute2
        ip_cfgs = self.iproute_api.get_addr(index=self.interface_index)

        # get_addr returns IPs in a nasty to praise format. We need to look at the attrs
        # section of each item we get, and find IFA_ADDRESS, and check the second element
        # of the tuple to get the IP address

        # Here's an example of what we're praising
        #
        # [{'attrs': [['IFA_ADDRESS', '10.0.241.123'],
        #    ['IFA_LOCAL', '10.0.241.123'],
        #    ['IFA_BROADCAST', '10.0.241.255'],
        #    ['IFA_LABEL', 'dummy0'],
        #    ['IFA_CACHEINFO',
        #     {'cstamp': 181814615,
        #      'ifa_prefered': 4294967295,
        #      'ifa_valid': 4294967295,
        #      'tstamp': 181814615}]],
        #      'event': 'RTM_NEWADDR',
        #      'family': 2,
        #      'flags': 128,
        #      'header': {'error': None,
        #                 'flags': 2,
        #                 'length': 80,
        #                 'pid': 4294395048,
        #                 'sequence_number': 255,
        #                 'type': 20},
        #      'index': 121,
        #      'prefixlen': 24,
        #      'scope': 0}]'''

        # Walk the list
        ips = []
        for ip_cfg in ip_cfgs:
            ip_address = None
            family = None
            broadcast = None
            prefixlen = None

            ip_attrs = ip_cfg['attrs']
            for attribute in ip_attrs:
                if attribute[0] == "IFA_ADDRESS":
                    ip_address = attribute[1]
                if attribute[0] == "IFA_BROADCAST":
                    broadcast = attribute[1]
            prefixlen = ip_cfg['prefixlen']
            family = ip_cfg['family'] # 2 for AF_INET, 10 for AF_INET6

            # Build IP dictionary
            ip_dict = {'ip_address': ip_address,
                       'prefix_length': prefixlen,
                       'family' : family}

            # Handle v4-only information
            if broadcast:
                ip_dict['broadcast'] = broadcast

            # Push it onto the list to be returned
            ips.append(ip_dict)

        # And break!
        return ips

    def get_full_ip_info(self, wanted_address):
        '''Returns an ip_dict for an individual IP address. Format identical to get_ips()

        Args:
            wanted_address - a valid v4 or v6 address to search for. Value is normalized
                             by ipaddress.ip_address when returning

        Raises:
            ValueError - wanted_address is not a valid IP address
            IPNotFound - this interface doesn't have this IP address
        '''
        wanted_address = check.validate_and_normalize_ip(wanted_address)

        ips = self.get_ips()

        # Walk the IP table and find the specific IP we want
        for ip_address in ips:
            if ip_address['ip_address'] == wanted_address:
                return ip_address

        # If we get here, the IP wasn't found
        raise IPNotFound("IP not found on interface")

    def add_v4_ip(self, ip_address, prefix_length):
        '''Wrapper for add_ip - adds an IPv4 address to this interface

            Args:
                ip_address = IPv4 address to add
                prefix_length - Network prefix size; netmask and broadcast addresses

             Raises: - see add_ip()
        '''

        ip_dict = {'ip_address': ip_address,
                   'family': AF_INET,
                   'prefix_length': prefix_length}

        self.add_ip(ip_dict)

    def add_v6_ip(self, ip_address, prefix_length):
        '''Wrapper for add_ip - adds an IPv6 address to this interface

            Args:
                ip_address - IPv6 address to add
                prefix_length - Network prefix size; netmask and broadcast addresses

             Raises: - see add_ip()
        '''
        ip_dict = {'ip_address': ip_address,
                   'family': AF_INET6,
                   'prefix_length': prefix_length}

        self.add_ip(ip_dict)

    def add_ip(self, ip_dict):
        '''Adds an IP to an interface. Lower-level function to add an IP address

        Args:
            ip_dict - takes an IP dictionary (see get_ips) and adds it to an interface
                      directorly

        Raises:
            ValueError
                IP address invalid. See message for more info
            DuplicateIPError
                This IP is already configured
            InterfaceConfigurationError
                The ip_dict was valid, but the IP failed add
        '''
        check.validate_and_normalize_ip_dict(ip_dict)

        # Throw an error if we try to add an existing address.
        existing_ip_check = None
        try:
            existing_ip_check = self.get_full_ip_info(ip_dict['ip_address'])
        except IPNotFound:
            pass

        if existing_ip_check:
            raise DuplicateIPError("This IP has already been assigned!")

        # We call add slightly differently based on socket family
        if ip_dict['family'] == AF_INET:
            self.iproute_api.addr('add',
                                  index=self.interface_index,
                                  family=AF_INET,
                                  address=ip_dict['ip_address'],
                                  broadcast=ip_dict['broadcast'],
                                  prefixlen=ip_dict['prefix_length'])

        if ip_dict['family'] == AF_INET6:
            self.iproute_api.addr('add',
                                  index=self.interface_index,
                                  family=AF_INET6,
                                  address=ip_dict['ip_address'],
                                  prefixlen=ip_dict['prefix_length'])

        # Do a sanity check and make sure the IP actually got added
        ip_check = self.get_full_ip_info(ip_dict['ip_address'])

        if not (ip_check['ip_address'] == ip_dict['ip_address'] and
                ip_check['prefix_length'] == ip_dict['prefix_length']):
            raise InterfaceConfigurationError("IP failed to add!")

    def remove_ip(self, ip_address):
        '''Removes an IP from an interface. Full details are looked up via
            get_full_ip_info for removal

            Args:
                ip_address - IP address to remove

            Raises:
                ValueError
                    The IP address provided was invalid
                IPNotFound
                    The IP is not configured on this interface
                InterfaceConfigurationError
                    The IP address was valid, but the IP was not successfully removed
        '''

        # San check
        ip_address = check.validate_and_normalize_ip(ip_address)

        # Get the full set of IP information
        ip_info = self.get_full_ip_info(ip_address)

        # Attempt to delete
        if ip_info['family'] == AF_INET:
            self.iproute_api.addr('delete',
                                  index=self.interface_index,
                                  address=ip_info['ip_address'],
                                  broadcast=ip_info['broadcast'],
                                  prefixlen=ip_info['prefix_length'])

        if ip_info['family'] == AF_INET6:
            self.iproute_api.addr('delete',
                                  index=self.interface_index,
                                  address=ip_info['ip_address'],
                                  prefixlen=ip_info['prefix_length'])

        # Confirm the delete. get_full_ip_info will throw an exception if it can't find it
        try:
            self.get_full_ip_info(ip_address)
        except IPNotFound:
            # We got it!
            return

        # Didn't get it. Throw an exception and bail
        raise InterfaceConfigurationError("IP deletion failure")

    def add_default_gateway(self, gateway, prefix_length):
        '''Adds a default gateway for a given prefix length'''
        raise NotImplementedError

    def add_static_route(self, source, destination):
        '''Sets a static route for an interface'''
        raise NotImplementedError

    def add_route(self, route_info):
        '''Adds a route for a given interface'''
        raise NotImplementedError

    def remove_route(self, route_info):
        '''Removes a route from an interface'''
        raise NotImplementedError

    def get_routes(self):
        '''Gets routes for an interface'''

        # The only way to get routes for an interface is to pull the entire routing table, and
        # filter entries for this interface. Miserable interfaces are miserable. Furthermore,
        # I can only get the v4 and v6 routes as a separate transaction

        # In theory, we can apply a filter to returned routes, but I can't figure out if
        # we can exclude things like cache entries, so we'll just grab stuff on a per family
        # level, and filter for ourselves

        # Pull the v4 and v6 global routing table
        v4_routing_table = self.iproute_api.get_routes(family=AF_INET)
        v6_routing_table = self.iproute_api.get_routes(family=AF_INET6)
        kernel_routing_table = self._filter_routing_table(v4_routing_table + v6_routing_table)

        # _filter_routing_table got rid of most of the junk we don't care about
        # so now we need to walk the table and make it something far similar to
        # praise without ripping our hair out. We also filter out link-local
        # addresses in this step because we need the full prefix to know if its
        # a link-local network

        routing_table = []
        for route in kernel_routing_table:
            route_dict = {}

            # Let's get the easy stuff first
            for attribute in route['attrs']:
                if attribute[0] == 'RTA_PREFSRC':
                    route_dict['source'] = attribute[1]
                if attribute[0] == 'RTA_DST':
                    route_dict['destination'] = attribute[1]
                if attribute[0] == 'RTA_GATEWAY':
                    route_dict['gateway'] = attribute[1]

            # Family is mapped straight through so AF_INET and AF_INET6 just match
            route_dict['family'] = route['family']

            # Attach prefixes if they're non-zero
            if route['src_len'] != 0 and 'source' in route_dict:
                route_dict['source'] += ("/%s" % route['src_len'])
            if route['dst_len'] != 0 and 'destination' in route_dict:
                route_dict['destination'] += ("/%s" % route['dst_len'])

                # Check for link-local here
                if ipaddress.ip_network(route_dict['destination']).is_link_local:
                    continue # skip the route

            if route['dst_len'] != 0 and 'gateway' in route_dict:
                route_dict['gateway'] += ("/%s" % route['dst_len'])

            # Map the protocol to something human-readable
            route_dict['protocol'] = map_protocol_number_to_string(route['proto'])
            route_dict['type'] = determine_route_type(route_dict)

            routing_table.append(route_dict)
        return routing_table

    def determine_if_route_exists(self):
        '''Checks if a route exists'''
        raise NotImplementedError

    def validate_route_dict(self):
        '''Validates a routing information dict'''
        raise NotImplementedError

    def _filter_routing_table(self, routing_table):
        '''Internal API. Takes output of get_routes, and filters down to what we care about'''

        # For every configured IP address, a couple of automatic routes are
        # generated that we're not interested in.
        #
        # Understanding this code requires an understanding of how the Linux kernel
        # handles routing and routing tables. For each interface, the kernel has a possible
        # 255 tables to hold routes. These exist to allow for specific routing rules and
        # preferences as the system goes from table 255->1 in determining which route
        # will be used.
        #
        # On a default configuration, only three routing tables are defined:
        # 255 - local
        # 254 - main
        # 253 - default
        #
        # Table names are controlled in /etc/iproutes.d/rt_tables
        #
        # The local table is special as it can only be added to by the kernel, and removing
        # entries from it is explicitly done "at your own risk". It defines which IPs this
        # machine owns so any attempt to communicate on it comes back to itself. As such
        # we can simply filter out 255 to make our lives considerably easier
        #
        # Unfortunately, filtering 255 doesn't get rid of all the "line noise" so to speak.
        #
        # From this point forward, I've had to work from kernel source, and the source of
        # iproute2 to understand what's going on from netlayer. But basically, here's the
        # rundown of what we need to do
        #
        # Case 1 - Cached Entries
        # This is a surprising complicated case. Cached entries are used by the kernel for
        # automatically configured routes. I haven't seen the kernel v4 table populated, but
        # that may just be because of my local usage
        #
        # IPv6 is a different story. Routing information for IPv6 can come in the form of
        # routing announcements, static configuration, and so forth. It seems all IPv6 info is
        # is marked as a cached entry. This is made more complicated that the kernel stores
        # various IPv6 routing information in the table for "random" hosts accessed. For example.
        # on my home system ...
        #
        # mcasadevall@perdition:~/workspace/mcdynipd$ route -6 -C
        # Kernel IPv6 routing cache
        # Destination                    Next Hop                   Flag Met Ref Use If
        # 2001:4860:4860::8888/128       fe80::4216:7eff:fe6c:492   UGDAC 0   0    47 eth0
        # 2607:f8b0:4001:c09::bc/128     fe80::4216:7eff:fe6c:492   UGDAC 0   1    17 eth0
        # 2607:f8b0:4004:808::1013/128   fe80::4216:7eff:fe6c:492   UGDAC 0   1    21 eth0
        # 2607:f8b0:4009:805::1005/128   fe80::4216:7eff:fe6c:492   UGDAC 0   0     3 eth0
        # 2607:f8b0:4009:80a::200e/128   fe80::4216:7eff:fe6c:492   UGDAC 0   0    85 eth0
        # 2607:f8b0:400d:c04::bd/128     fe80::4216:7eff:fe6c:492   UGDAC 0   1    76 eth0
        #
        # 2607:f8b0::/32 is owned by Google, and these were connections my system made to Google
        # systems. Looking at my router (which runs a 6to4 HE tunnel), it appears 6to4 is handled
        # via static routing and should show up sans CACHEINFO (untested - needs confirmation).
        #
        # Digging into iproute2, the proto field defines what defined a route. Here's the list
        # defined in the source
        #
        # ----------------------------------------------------------------------------
        # #define RTPROT_UNSPEC   0
        # #define RTPROT_REDIRECT 1       /* Route installed by ICMP redirects;
        #                                     not used by current IPv4 */
        # #define RTPROT_KERNEL   2       /* Route installed by kernel            */
        # #define RTPROT_BOOT     3       /* Route installed during boot          */
        # #define RTPROT_STATIC   4       /* Route installed by administrator     */
        #
        # /* Values of protocol >= RTPROT_STATIC are not interpreted by kernel;
        #    they are just passed from user and back as is.
        #    It will be used by hypothetical multiple routing daemons.
        #    Note that protocol values should be standardized in order to
        #    avoid conflicts.
        #  */
        #
        # #define RTPROT_GATED    8       /* Apparently, GateD */
        # #define RTPROT_RA       9       /* RDISC/ND router advertisements */
        # #define RTPROT_MRT      10      /* Merit MRT */
        # #define RTPROT_ZEBRA    11      /* Zebra */
        # #define RTPROT_BIRD     12      /* BIRD */
        # #define RTPROT_DNROUTED 13      /* DECnet routing daemon */
        # #define RTPROT_XORP     14      /* XORP */
        # #define RTPROT_NTK      15      /* Netsukuku */
        # #define RTPROT_DHCP     16      /* DHCP client */
        # #define RTPROT_MROUTED  17      /* Multicast daemon */
        # ----------------------------------------------------------------------------
        #
        # Looking at the behavior of the kernel, if a given prefix has a route, it will
        # place a proto 2 entry for it with no routing address. Cached table entries
        # exist to the next point:
        #
        # 2001:4860:4860::8888/128       fe80::4216:7eff:fe6c:492   UGDAC 0   0    47 eth0
        #
        # (this shows up as proto 9 in the routing table)
        #
        # To get the default route of a device in IPv6, we need entries that ONLY have RTA_GATEWAY
        # and not RTA_DEST, regardless of protocol. Otherwise, we filter out proto 9. This gets
        # output identical to route aside from the multicast address (ff00::/8)
        #
        # As a final note to this saga, after I coded this, I found rtnetlink is documented on
        # Linux systems. Run man 7 rtnetlink to save yourself a source dive :(

        filtered_table = []
        non_cached_table = []

        # Pass 1. Exclude table 255, and any entries that have RTA_CACHEINFO
        for route in routing_table:
            # If this is a 255 entry, ignore it, we don't care
            if route['table'] == 255:
                continue

            # Now the table cache
            cached_entry = False
            destination_address = None
            gateway_address = None

            routing_attributes = route['attrs']
            for attribute in routing_attributes:
                if attribute[0] == 'RTA_CACHEINFO':
                    cached_entry = True
                if attribute[0] == 'RTA_DST':
                    destination_address = attribute[1]
                if attribute[0] == 'RTA_GATEWAY':
                    gateway_address = attribute[1]

            # If its not a cached entry, always keep it
            if not cached_entry:
                non_cached_table.append(route)
                continue

            # Keep it if proto != 9
            if route['proto'] != 9:
                non_cached_table.append(route)
                continue

            # If it only has DST or GATEWAY, its a default route, keep it
            if ((gateway_address and not destination_address) or
                    (destination_address and not gateway_address)):
                non_cached_table.append(route)
                continue

        for route in non_cached_table:
            # Like IP addresses, most of the route information is stored
            # in attributes. RTA_OIF (OIF = Outbound Interface), contains
            # the index number of the interface this route is assigned to
            routing_attributes = route['attrs']

            for attribute in routing_attributes:
                if attribute[0] == 'RTA_OIF' and attribute[1] == self.interface_index:
                    filtered_table.append(route)

        # filtered_table should just contain our OIFs, pass this back up for
        # processing
        return filtered_table