Beispiel #1
0
 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
Beispiel #2
0
def address_exists(config, name, value):
    ip = IPRoute()
    if value.version == 4:
        family = socket.AF_INET
    elif value.version == 6:
        family = socket.AF_INET6
    else:
        raise AssertionError("Unknown version {}".format(value.version))
    if ip.get_addr(family=family, address=value.ip, prefixlen=value.prefixlen):
        raise ConfigError(name, "No such address {}".format(value))
Beispiel #3
0
    def initdb(self):

        # flush all the DB objects
        with self.exclusive:

            # explicitly cleanup object references
            for event in tuple(self._event_map):
                del self._event_map[event]

            self._flush_db()

            # if the command socket is not provided, create it
            if self._nl_own:
                if self.nl is not None:
                    self.nl.close()
                self.nl = IPRoute(sndbuf=self._sndbuf,
                                  rcvbuf=self._rcvbuf,
                                  async_qsize=0)  # OBS: legacy design
            # setup monitoring socket
            if self.mnl is not None:
                self._flush_mnl()
                self.mnl.close()
            self.mnl = self.nl.clone()
            try:
                self.mnl.bind(groups=self.nl_bind_groups,
                              async_cache=self._nl_async)
            except:
                self.mnl.close()
                if self._nl_own is None:
                    self.nl.close()
                raise

            # explicitly cleanup references
            for key in tuple(self._deferred):
                del self._deferred[key]

            for module in self._plugins:
                if (module.groups & self.nl_bind_groups) != module.groups:
                    continue
                for plugin in module.spec:
                    self._deferred[plugin['name']] = module.spec
                    if plugin['name'] in self._loaded:
                        delattr(self, plugin['name'])
                        self._loaded.remove(plugin['name'])

            # start service threads
            for tspec in (('_mthread', '_serve_main', 'IPDB main event loop'),
                          ('_cthread', '_serve_cb', 'IPDB cb event loop')):
                tg = getattr(self, tspec[0], None)
                if not getattr(tg, 'is_alive', lambda: False)():
                    tx = threading.Thread(name=tspec[2],
                                          target=getattr(self, tspec[1]))
                    setattr(self, tspec[0], tx)
                    tx.setDaemon(True)
                    tx.start()
Beispiel #4
0
    def run(self):
        nat = {}
        clients = []

        srv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        srv.bind((self.addr, self.port))
        ipr = IPRoute()
        ipr.bind()

        poll = select.poll()
        poll.register(ipr, select.POLLIN | select.POLLPRI)
        poll.register(srv, select.POLLIN | select.POLLPRI)

        while True:
            events = poll.poll()
            for (fd, event) in events:
                if fd == ipr.fileno():
                    bufsize = ipr.getsockopt(socket.SOL_SOCKET,
                                             socket.SO_RCVBUF) // 2
                    data = ipr.recv(bufsize)
                    cookie = struct.unpack('I', data[8:12])[0]
                    if cookie == 0:
                        for address in clients:
                            srv.sendto(data, address)
                    else:
                        srv.sendto(data, nat[cookie])
                else:
                    data, address = srv.recvfrom(16384)
                    if data is None:
                        clients.remove(address)
                        continue
                    cookie = struct.unpack('I', data[8:12])[0]
                    nat[cookie] = address
                    ipr.sendto(data, (0, 0))
Beispiel #5
0
def address_exists(cls, config, value):
    ip = IPRoute()
    if value.version == 4:
        family = socket.AF_INET
    elif value.version == 6:
        family = socket.AF_INET6
    else:
        raise AssertionError("Unknown version {}".format(value.version))
    if ip.get_addr(family=family, address=value.ip, prefixlen=value.prefixlen):
        raise OptionCheckError("No such address {}".format(value),
                               option=cls.__name__)
Beispiel #6
0
def address_exists(option, config, value):
    ip = IPRoute()
    if value.version == 4:
        family = socket.AF_INET
    elif value.version == 6:
        family = socket.AF_INET6
    else:
        raise AssertionError("Unknown version {}".format(value.version))
    if ip.get_addr(family=family, address=value.ip, prefixlen=value.prefixlen):
        raise OptionCheckError("No such address {}".format(value),
                               option=option.__name__)
Beispiel #7
0
    def run(self):
        nat = {}
        clients = []

        srv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        srv.bind((self.addr, self.port))
        ipr = IPRoute()
        ipr.bind()

        poll = select.poll()
        poll.register(ipr, select.POLLIN | select.POLLPRI)
        poll.register(srv, select.POLLIN | select.POLLPRI)

        while True:
            events = poll.poll()
            for (fd, event) in events:
                if fd == ipr.fileno():
                    bufsize = ipr.getsockopt(socket.SOL_SOCKET,
                                             socket.SO_RCVBUF) // 2
                    data = ipr.recv(bufsize)
                    cookie = struct.unpack('I', data[8:12])[0]
                    if cookie == 0:
                        for address in clients:
                            srv.sendto(data, address)
                    else:
                        srv.sendto(data, nat[cookie])
                else:
                    data, address = srv.recvfrom(16384)
                    if data is None:
                        clients.remove(address)
                        continue
                    cookie = struct.unpack('I', data[8:12])[0]
                    nat[cookie] = address
                    ipr.sendto(data, (0, 0))
    def initdb(self, nl=None):
        '''
        Restart IPRoute channel, and create all the DB
        from scratch. Can be used when sync is lost.
        '''
        self.nl = nl or IPRoute()
        self.nl.monitor = True
        self.nl.bind(async=True)

        # resolvers
        self.interfaces = Dotkeys()
        self.routes = RoutingTables(ipdb=self)
        self.by_name = Dotkeys()
        self.by_index = Dotkeys()

        # caches
        self.ipaddr = {}
        self.neighbors = {}

        # load information
        links = self.nl.get_links()
        for link in links:
            self.device_put(link, skip_slaves=True)
        for link in links:
            self.update_slaves(link)
        self.update_addr(self.nl.get_addr())
        self.update_neighbors(self.nl.get_neighbors())
        routes = self.nl.get_routes()
        self.update_routes(routes)
Beispiel #9
0
    def run(self) -> int:
        try:
            with IPRoute() as ipr:
                links = ipr.get_links("all")
                routes = ipr.get_default_routes(family=AF_INET6)
        except Exception as e:
            LOG.exception(f"Failed to get list of valid interfaces: {e}")
            return -1

        for link in links:
            if link.get_attr("IFLA_IFNAME") != self.interface:
                continue

            self.interface_id = link["index"]
            break
        if not self.interface_id:
            LOG.error(f"Unable to find {self.interface} index")
            return 1

        for route in routes:
            multi_routes = route.get_attr("RTA_MULTIPATH")
            for mr in multi_routes:
                if mr["oif"] == self.interface_id:
                    return 0

        return 2
Beispiel #10
0
 def __init__(self, ns_info):
     self.__ns_info = ns_info
     self.name = ns_info['name']
     self.ip = IPRoute()
     self.__interfaces = {}
     self.__bridges = {}
     self.logger_topo = None
Beispiel #11
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))
Beispiel #12
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()
Beispiel #13
0
    def initdb(self):

        # flush all the DB objects
        with self.exclusive:

            # explicitly cleanup object references
            for event in tuple(self._event_map):
                del self._event_map[event]

            self._flush_db()

            # if the command socket is not provided, create it
            if self._nl_own:
                if self.nl is not None:
                    self.nl.close()
                self.nl = IPRoute(sndbuf=self._sndbuf,
                                  rcvbuf=self._rcvbuf,
                                  async_qsize=0)  # OBS: legacy design
            # setup monitoring socket
            if self.mnl is not None:
                self._flush_mnl()
                self.mnl.close()
            self.mnl = self.nl.clone()
            try:
                self.mnl.bind(groups=self.nl_bind_groups,
                              async_cache=self._nl_async)
            except:
                self.mnl.close()
                if self._nl_own is None:
                    self.nl.close()
                raise

            # explicitly cleanup references
            for key in tuple(self._deferred):
                del self._deferred[key]

            for module in self._plugins:
                if (module.groups & self.nl_bind_groups) != module.groups:
                    continue
                for plugin in module.spec:
                    self._deferred[plugin['name']] = module.spec
                    if plugin['name'] in self._loaded:
                        delattr(self, plugin['name'])
                        self._loaded.remove(plugin['name'])

            # start service threads
            for tspec in (('_mthread', '_serve_main', 'IPDB main event loop'),
                          ('_cthread', '_serve_cb', 'IPDB cb event loop')):
                tg = getattr(self, tspec[0], None)
                if not getattr(tg, 'is_alive', lambda: False)():
                    tx = threading.Thread(name=tspec[2],
                                          target=getattr(self, tspec[1]))
                    setattr(self, tspec[0], tx)
                    tx.setDaemon(True)
                    tx.start()
Beispiel #14
0
    def initdb(self):
        # common event map, empty by default, so all the
        # events aer just ignored
        self.release(complete=False)
        self._stop = False
        # explicitly cleanup object references
        for event in tuple(self._event_map):
            del self._event_map[event]

        # if the command socket is not provided, create it
        if self._nl_own:
            self.nl = IPRoute()
        # setup monitoring socket
        self.mnl = self.nl.clone()
        try:
            self.mnl.bind(groups=self.nl_bind_groups, async=self._nl_async)
        except:
            self.mnl.close()
            if self._nl_own is None:
                self.nl.close()
            raise

        # explicitly cleanup references
        for key in tuple(self._deferred):
            del self._deferred[key]

        for module in self._plugins:
            if (module.groups & self.nl_bind_groups) != module.groups:
                continue
            for plugin in module.spec:
                self._deferred[plugin['name']] = module.spec
                if plugin['name'] in self._loaded:
                    delattr(self, plugin['name'])
                    self._loaded.remove(plugin['name'])

        # start the monitoring thread
        self._mthread = threading.Thread(name="IPDB event loop",
                                         target=self.serve_forever)
        self._mthread.setDaemon(True)
        self._mthread.start()
Beispiel #15
0
    def test_isinstance(self):
        from pyroute2 import IPRSocket
        from pyroute2 import IPRoute
        from pyroute2.iproute import IPRoute as IPRoute_real
        from pyroute2.netlink.rtnl.iprsocket import IPRSocket as IPRSocket_real

        ipr1 = IPRoute()
        ipr2 = IPRoute_real()

        ips1 = IPRSocket()
        ips2 = IPRSocket_real()

        # positive
        assert isinstance(ips1, IPRSocket)
        assert isinstance(ips2, IPRSocket)
        assert isinstance(ips1, IPRSocket_real)
        assert isinstance(ips2, IPRSocket_real)

        assert isinstance(ipr1, IPRoute)
        assert isinstance(ipr2, IPRoute)
        assert isinstance(ipr1, IPRoute_real)
        assert isinstance(ipr2, IPRoute_real)

        # negative
        assert not isinstance(ips1, IPRoute)
        assert not isinstance(ips2, IPRoute)
        assert not isinstance(ips1, IPRoute_real)
        assert not isinstance(ips2, IPRoute_real)

        # this must succeed -- IPRoute is a subclass of IPRSocket
        assert isinstance(ipr1, IPRSocket)
        assert isinstance(ipr2, IPRSocket)
        assert isinstance(ipr1, IPRSocket_real)
        assert isinstance(ipr2, IPRSocket_real)

        ips1.close()
        ips2.close()
        ipr1.close()
        ipr2.close()
Beispiel #16
0
def connection_down(parsed_args):
    ipsec_connection = load_ipsec_connection(parsed_args)
    connection_name = ipsec_connection['name']
    ip_route = IPRoute()
    if is_connection_up(ip_route, ipsec_connection):
        ipsec_result = ipsec('down', connection_name)
        if ipsec_result.status != 0:
            raise DockerIPSecError('Failed to disconnect VPN: {0}\n{1}'.format(
                connection_name, ipsec_result.output))

    filter_func = functools.partial(comment_matches_ipsec_connection,
                                    connection_name)
    remove_iptables_rules(filter_func)
Beispiel #17
0
    def test_isinstance(self):
        from pyroute2 import IPRSocket
        from pyroute2 import IPRoute
        from pyroute2.iproute import IPRoute as IPRoute_real
        from pyroute2.netlink.rtnl.iprsocket import IPRSocket as IPRSocket_real

        ipr1 = IPRoute()
        ipr2 = IPRoute_real()

        ips1 = IPRSocket()
        ips2 = IPRSocket_real()

        # positive
        assert isinstance(ips1, IPRSocket)
        assert isinstance(ips2, IPRSocket)
        assert isinstance(ips1, IPRSocket_real)
        assert isinstance(ips2, IPRSocket_real)

        assert isinstance(ipr1, IPRoute)
        assert isinstance(ipr2, IPRoute)
        assert isinstance(ipr1, IPRoute_real)
        assert isinstance(ipr2, IPRoute_real)

        # negative
        assert not isinstance(ips1, IPRoute)
        assert not isinstance(ips2, IPRoute)
        assert not isinstance(ips1, IPRoute_real)
        assert not isinstance(ips2, IPRoute_real)

        # this must succeed -- IPRoute is a subclass of IPRSocket
        assert isinstance(ipr1, IPRSocket)
        assert isinstance(ipr2, IPRSocket)
        assert isinstance(ipr1, IPRSocket_real)
        assert isinstance(ipr2, IPRSocket_real)

        ips1.close()
        ips2.close()
        ipr1.close()
        ipr2.close()
Beispiel #18
0
    def __init__(self, addr):
        IPRoute.__init__(self)
        self.proxy = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.proxy.bind(('0.0.0.0', 3547))
        self.proxy_addr = addr
        self.proxy_queue = Queue()

        def recv():
            while True:
                (data, addr) = self.proxy.recvfrom(16384)
                self.proxy_queue.put(data)

        self.pthread = threading.Thread(target=recv)
        self.pthread.setDaemon(True)
        self.pthread.start()

        def sendto(buf, *argv, **kwarg):
            return self.proxy.sendto(buf, (self.proxy_addr, 3546))

        def recv(*argv, **kwarg):
            return self.proxy_queue.get()

        self._sendto = sendto
        self._recv = recv
Beispiel #19
0
    def __init__(self, addr):
        IPRoute.__init__(self)
        self.proxy = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.proxy.bind(('0.0.0.0', 3547))
        self.proxy_addr = addr
        self.proxy_queue = Queue()

        def recv():
            while True:
                (data, addr) = self.proxy.recvfrom(16384)
                self.proxy_queue.put(data)

        self.pthread = threading.Thread(target=recv)
        self.pthread.setDaemon(True)
        self.pthread.start()

        def sendto(buf, *argv, **kwarg):
            return self.proxy.sendto(buf, (self.proxy_addr, 3546))

        def recv(*argv, **kwarg):
            return self.proxy_queue.get()

        self._sendto = sendto
        self._recv = recv
Beispiel #20
0
def add_docker_networks(parsed_args):
    ipsec_connection = load_ipsec_connection(parsed_args)
    connection_name = ipsec_connection['name']
    docker_networks = parsed_args.dockerNetworks

    docker_client = docker.DockerClient()
    docker_network_to_ip_network = functools.partial(
        ip_network_for_docker_network, docker_client)
    docker_ip_networks = tuple(
        map(docker_network_to_ip_network, docker_networks))
    ip_route = IPRoute()
    if not is_connection_up(ip_route, ipsec_connection):
        raise DockerIPSecError(
            'IPSec connection {0} is not connected!'.format(connection_name))

    add_ip_networks(ip_route, docker_ip_networks, connection_name)
Beispiel #21
0
    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()
Beispiel #22
0
    def initdb(self, nl=None):
        '''
        Restart IPRoute channel, and create all the DB
        from scratch. Can be used when sync is lost.
        '''
        self.nl = nl or IPRoute()

        # resolvers
        self.interfaces = Dotkeys()
        self.routes = RoutingTableSet(ipdb=self,
                                      ignore_rtables=self._ignore_rtables)
        self.by_name = View(src=self.interfaces,
                            constraint=lambda k, v: isinstance(k, basestring))
        self.by_index = View(src=self.interfaces,
                             constraint=lambda k, v: isinstance(k, int))

        # caches
        self.ipaddr = {}
        self.neighbours = {}

        try:
            self.nl.bind(async=self._nl_async)
            # load information
            links = self.nl.get_links()
            for link in links:
                self.device_put(link, skip_slaves=True)
            for link in links:
                self.update_slaves(link)
            # bridge info
            links = self.nl.get_vlans()
            for link in links:
                self.update_dev(link)
            #
            self.update_addr(self.nl.get_addr())
            self.update_neighbours(self.nl.get_neighbours())
            routes4 = self.nl.get_routes(family=AF_INET)
            routes6 = self.nl.get_routes(family=AF_INET6)
            self.update_routes(routes4)
            self.update_routes(routes6)
        except Exception as e:
            try:
                self.nl.close()
            except:
                pass
            raise e
Beispiel #23
0
def connection_up(parsed_args):
    ipsec_connection = load_ipsec_connection(parsed_args)
    connection_name = ipsec_connection['name']
    docker_networks = parsed_args.dockerNetworks
    if len(docker_networks) > 0:
        docker_client = docker.DockerClient()
        docker_network_to_ip_network = functools.partial(
            ip_network_for_docker_network, docker_client)
        docker_ip_networks = tuple(
            map(docker_network_to_ip_network, docker_networks))
    else:
        docker_ip_networks = tuple()

    ip_route = IPRoute()
    if not is_connection_up(ip_route, ipsec_connection):
        ipsec_result = ipsec('up', connection_name)
        if ipsec_result.status != 0:
            raise DockerIPSecError('Failed to connect VPN: {0}\n{1}'.format(
                connection_name, ipsec_result.output))

    add_ip_networks(ip_route, docker_ip_networks, connection_name)
Beispiel #24
0
    def initdb(self):
        # common event map, empty by default, so all the
        # events aer just ignored
        self.release(complete=False)
        self._stop = False
        # explicitly cleanup object references
        for event in tuple(self._event_map):
            del self._event_map[event]

        # if the command socket is not provided, create it
        if self._nl_own:
            self.nl = IPRoute()
        # setup monitoring socket
        self.mnl = self.nl.clone()
        try:
            self.mnl.bind(groups=self.nl_bind_groups, async=self._nl_async)
        except:
            self.mnl.close()
            if self._nl_own is None:
                self.nl.close()
            raise

        # explicitly cleanup references
        for key in tuple(self._deferred):
            del self._deferred[key]

        for module in self._plugins:
            if (module.groups & self.nl_bind_groups) != module.groups:
                continue
            for plugin in module.spec:
                self._deferred[plugin['name']] = module.spec
                if plugin['name'] in self._loaded:
                    delattr(self, plugin['name'])
                    self._loaded.remove(plugin['name'])

        # start the monitoring thread
        self._mthread = threading.Thread(name="IPDB event loop",
                                         target=self.serve_forever)
        self._mthread.setDaemon(True)
        self._mthread.start()
def enable_static_routes(static_routes):
    """
    启用默认路由
    :param static_routes: list 默认路由列表
    """
    with IPRoute() as route:
        for r in static_routes:
            if_id = route.link_lookup(
                ifname=r['interface'])[0] if 'interface' in r else None
            args = {
                'dst': r['net'],
                'oif': if_id,
                'gateway': r.get('gateway'),
                'metric': 1
            }
            args = {k: v for k, v in args.iteritems() if v}
            try:
                route.route('add', **args)
            except NetlinkError as e:
                if e.code == 17:
                    continue
                else:
                    raise
Beispiel #26
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")
Beispiel #27
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()
Beispiel #28
0
def NetNServer(netns, rcvch, cmdch, flags=os.O_CREAT):
    '''
    The netns server supposed to be started automatically by NetNS.
    It has two communication channels: one simplex to forward incoming
    netlink packets, `rcvch`, and other synchronous duplex to get
    commands and send back responses, `cmdch`.

    Channels should support standard socket API, should be compatible
    with poll/select and should be able to transparently pickle objects.
    NetNS uses `multiprocessing.Pipe` for this purpose, but it can be
    any other implementation with compatible API.

    The first parameter, `netns`, is a netns name. Depending on the
    `flags`, the netns can be created automatically. The `flags` semantics
    is exactly the same as for `open(2)` system call.

    ...

    The server workflow is simple. The startup sequence::

        1. Create or open a netns.

        2. Start `IPRoute` instance. It will be used only on the low level,
           the `IPRoute` will not parse any packet.

        3. Start poll/select loop on `cmdch` and `IPRoute`.

    On the startup, the server sends via `cmdch` the status packet. It can be
    `None` if all is OK, or some exception.

    Further data handling, depending on the channel, server side::

        1. `IPRoute`: read an incoming netlink packet and send it unmodified
           to the peer via `rcvch`. The peer, polling `rcvch`, can handle
           the packet on its side.

        2. `cmdch`: read tuple (cmd, argv, kwarg). If the `cmd` starts with
           "send", then take `argv[0]` as a packet buffer, treat it as one
           netlink packet and substitute PID field (offset 12, uint32) with
           its own. Strictly speaking, it is not mandatory for modern netlink
           implementations, but it is required by the protocol standard.

    '''
    signal.signal(signal.SIGINT, signal.SIG_IGN)
    try:
        nsfd = setns(netns, flags)
    except OSError as e:
        cmdch.send(e)
        return e.errno
    except Exception as e:
        cmdch.send(OSError(errno.ECOMM, str(e), netns))
        return 255

    #
    try:
        ipr = IPRoute()
        rcvch_lock = ipr._sproxy.lock
        ipr._s_channel = rcvch
        poll = select.poll()
        poll.register(ipr, select.POLLIN | select.POLLPRI)
        poll.register(cmdch, select.POLLIN | select.POLLPRI)
    except Exception as e:
        cmdch.send(e)
        return 255

    # all is OK so far
    cmdch.send(None)
    # 8<-------------------------------------------------------------
    while True:
        events = poll.poll()
        for (fd, event) in events:
            if fd == ipr.fileno():
                bufsize = ipr.getsockopt(SOL_SOCKET, SO_RCVBUF) // 2
                with rcvch_lock:
                    rcvch.send(ipr.recv(bufsize))
            elif fd == cmdch.fileno():
                try:
                    cmdline = cmdch.recv()
                    if cmdline is None:
                        poll.unregister(ipr)
                        poll.unregister(cmdch)
                        ipr.close()
                        os.close(nsfd)
                        return
                    (cmd, argv, kwarg) = cmdline
                    if cmd[:4] == 'send':
                        # Achtung
                        #
                        # It's a hack, but we just have to do it: one
                        # must use actual pid in netlink messages
                        #
                        # FIXME: there can be several messages in one
                        # call buffer; but right now we can ignore it
                        msg = argv[0][:12]
                        msg += struct.pack("I", os.getpid())
                        msg += argv[0][16:]
                        argv = list(argv)
                        argv[0] = msg
                    cmdch.send(getattr(ipr, cmd)(*argv, **kwarg))
                except Exception as e:
                    e.tb = traceback.format_exc()
                    cmdch.send(e)
Beispiel #29
0
class IPDB(object):
    '''
    The class that maintains information about network setup
    of the host. Monitoring netlink events allows it to react
    immediately. It uses no polling.
    '''

    def __init__(self, nl=None, mode='implicit',
                 restart_on_error=None, nl_async=None,
                 sndbuf=1048576, rcvbuf=1048576,
                 nl_bind_groups=RTMGRP_DEFAULTS,
                 ignore_rtables=None, callbacks=None,
                 sort_addresses=False, plugins=None):
        plugins = plugins or ['interfaces', 'routes', 'rules']
        pmap = {'interfaces': interfaces,
                'routes': routes,
                'rules': rules}
        self.mode = mode
        self.txdrop = False
        self._stdout = sys.stdout
        self._ipaddr_set = SortedIPaddrSet if sort_addresses else IPaddrSet
        self._event_map = {}
        self._deferred = {}
        self._ensure = []
        self._loaded = set()
        self._mthread = None
        self._nl_own = nl is None
        self._nl_async = config.ipdb_nl_async if nl_async is None else True
        self.mnl = None
        self.nl = nl
        self._sndbuf = sndbuf
        self._rcvbuf = rcvbuf
        self.nl_bind_groups = nl_bind_groups
        self._plugins = [pmap[x] for x in plugins if x in pmap]
        if isinstance(ignore_rtables, int):
            self._ignore_rtables = [ignore_rtables, ]
        elif isinstance(ignore_rtables, (list, tuple, set)):
            self._ignore_rtables = ignore_rtables
        else:
            self._ignore_rtables = []
        self._stop = False
        # see also 'register_callback'
        self._post_callbacks = {}
        self._pre_callbacks = {}

        # local event queues
        # - callbacks event queue
        self._cbq = queue.Queue(maxsize=8192)
        self._cbq_drop = 0
        # - users event queue
        self._evq = None
        self._evq_lock = threading.Lock()
        self._evq_drop = 0

        # locks and events
        self.exclusive = threading.RLock()
        self._shutdown_lock = threading.Lock()

        # register callbacks
        #
        # examples::
        #   def cb1(ipdb, msg, event):
        #       print(event, msg)
        #   def cb2(...):
        #       ...
        #
        #   # default mode: post
        #   IPDB(callbacks=[cb1, cb2])
        #   # specify the mode explicitly
        #   IPDB(callbacks=[(cb1, 'pre'), (cb2, 'post')])
        #
        for cba in callbacks or []:
            if not isinstance(cba, (tuple, list, set)):
                cba = (cba, )
            self.register_callback(*cba)

        # load information
        self.restart_on_error = restart_on_error if \
            restart_on_error is not None else nl is None

        # init the database
        self.initdb()

        # init the dir() cache
        self.__dir_cache__ = [i for i in self.__class__.__dict__.keys()
                              if i[0] != '_']
        self.__dir_cache__.extend(list(self._deferred.keys()))

        def cleanup(ref):
            ipdb_obj = ref()
            if ipdb_obj is not None:
                ipdb_obj.release()
        atexit.register(cleanup, weakref.ref(self))

    def __dir__(self):
        return self.__dir_cache__

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.release()

    def _flush_db(self):

        def flush(idx):
            for key in tuple(idx.keys()):
                try:
                    del idx[key]
                except KeyError:
                    pass
        idx_list = []
        if 'interfaces' in self._loaded:
            for (key, dev) in self.by_name.items():
                try:
                    # FIXME
                    self.interfaces._detach(key,
                                            dev['index'],
                                            dev.nlmsg)
                except KeyError:
                    pass
            idx_list.append(self.ipaddr)
            idx_list.append(self.neighbours)
        if 'routes' in self._loaded:
            idx_list.extend([self.routes.tables[x] for x
                             in self.routes.tables.keys()])
        if 'rules' in self._loaded:
            idx_list.append(self.rules)
        for idx in idx_list:
            flush(idx)

    def initdb(self):

        # flush all the DB objects
        with self.exclusive:

            # explicitly cleanup object references
            for event in tuple(self._event_map):
                del self._event_map[event]

            self._flush_db()

            # if the command socket is not provided, create it
            if self._nl_own:
                if self.nl is not None:
                    self.nl.close()
                self.nl = IPRoute(sndbuf=self._sndbuf, rcvbuf=self._rcvbuf)
            # setup monitoring socket
            if self.mnl is not None:
                self._flush_mnl()
                self.mnl.close()
            self.mnl = self.nl.clone()
            try:
                self.mnl.bind(groups=self.nl_bind_groups,
                              async_cache=self._nl_async)
            except:
                self.mnl.close()
                if self._nl_own is None:
                    self.nl.close()
                raise

            # explicitly cleanup references
            for key in tuple(self._deferred):
                del self._deferred[key]

            for module in self._plugins:
                if (module.groups & self.nl_bind_groups) != module.groups:
                    continue
                for plugin in module.spec:
                    self._deferred[plugin['name']] = module.spec
                    if plugin['name'] in self._loaded:
                        delattr(self, plugin['name'])
                        self._loaded.remove(plugin['name'])

            # start service threads
            for tspec in (('_mthread', '_serve_main', 'IPDB main event loop'),
                          ('_cthread', '_serve_cb', 'IPDB cb event loop')):
                tg = getattr(self, tspec[0], None)
                if not getattr(tg, 'is_alive', lambda: False)():
                    tx = threading.Thread(name=tspec[2],
                                          target=getattr(self, tspec[1]))
                    setattr(self, tspec[0], tx)
                    tx.setDaemon(True)
                    tx.start()

    def __getattribute__(self, name):
        deferred = super(IPDB, self).__getattribute__('_deferred')
        if name in deferred:
            register = []
            spec = deferred[name]
            for plugin in spec:
                obj = plugin['class'](self, **plugin['kwarg'])
                setattr(self, plugin['name'], obj)
                register.append(obj)
                self._loaded.add(plugin['name'])
                del deferred[plugin['name']]
            for obj in register:
                if hasattr(obj, '_register'):
                    obj._register()
                if hasattr(obj, '_event_map'):
                    for event in obj._event_map:
                        if event not in self._event_map:
                            self._event_map[event] = []
                        self._event_map[event].append(obj._event_map[event])
        return super(IPDB, self).__getattribute__(name)

    def register_callback(self, callback, mode='post'):
        '''
        IPDB callbacks are routines executed on a RT netlink
        message arrival. There are two types of callbacks:
        "post" and "pre" callbacks.

        ...

        "Post" callbacks are executed after the message is
        processed by IPDB and all corresponding objects are
        created or deleted. Using ipdb reference in "post"
        callbacks you will access the most up-to-date state
        of the IP database.

        "Post" callbacks are executed asynchronously in
        separate threads. These threads can work as long
        as you want them to. Callback threads are joined
        occasionally, so for a short time there can exist
        stopped threads.

        ...

        "Pre" callbacks are synchronous routines, executed
        before the message gets processed by IPDB. It gives
        you the way to patch arriving messages, but also
        places a restriction: until the callback exits, the
        main event IPDB loop is blocked.

        Normally, only "post" callbacks are required. But in
        some specific cases "pre" also can be useful.

        ...

        The routine, `register_callback()`, takes two arguments:
            - callback function
            - mode (optional, default="post")

        The callback should be a routine, that accepts three
        arguments::

            cb(ipdb, msg, action)

        Arguments are:

            - **ipdb** is a reference to IPDB instance, that invokes
                the callback.
            - **msg** is a message arrived
            - **action** is just a msg['event'] field

        E.g., to work on a new interface, you should catch
        action == 'RTM_NEWLINK' and with the interface index
        (arrived in msg['index']) get it from IPDB::

            index = msg['index']
            interface = ipdb.interfaces[index]
        '''
        lock = threading.Lock()

        def safe(*argv, **kwarg):
            with lock:
                callback(*argv, **kwarg)

        safe.hook = callback
        safe.lock = lock
        safe.uuid = uuid32()

        if mode == 'post':
            self._post_callbacks[safe.uuid] = safe
        elif mode == 'pre':
            self._pre_callbacks[safe.uuid] = safe
        else:
            raise KeyError('Unknown callback mode')
        return safe.uuid

    def unregister_callback(self, cuid, mode='post'):
        if mode == 'post':
            cbchain = self._post_callbacks
        elif mode == 'pre':
            cbchain = self._pre_callbacks
        else:
            raise KeyError('Unknown callback mode')
        safe = cbchain[cuid]
        with safe.lock:
            ret = cbchain.pop(cuid)
        return ret

    def eventqueue(self, qsize=8192, block=True, timeout=None):
        '''
        Initializes event queue and returns event queue context manager.
        Once the context manager is initialized, events start to be collected,
        so it is possible to read initial state from the system witout losing
        last moment changes, and once that is done, start processing events.

        Example:

            ipdb = IPDB()
            with ipdb.eventqueue() as evq:
                my_state = ipdb.<needed_attribute>...
                for msg in evq:
                    update_state_by_msg(my_state, msg)
        '''
        return _evq_context(self, qsize, block, timeout)

    def eventloop(self, qsize=8192, block=True, timeout=None):
        """
        Event generator for simple cases when there is no need for initial
        state setup. Initialize event queue and yield events as they happen.
        """
        with self.eventqueue(qsize=qsize, block=block, timeout=timeout) as evq:
            for msg in evq:
                yield msg

    def release(self):
        '''
        Shutdown IPDB instance and sync the state. Since
        IPDB is asyncronous, some operations continue in the
        background, e.g. callbacks. So, prior to exit the
        script, it is required to properly shutdown IPDB.

        The shutdown sequence is not forced in an interactive
        python session, since it is easier for users and there
        is enough time to sync the state. But for the scripts
        the `release()` call is required.
        '''
        with self._shutdown_lock:
            if self._stop:
                log.warning("shutdown in progress")
                return
            self._stop = True
            self._cbq.put(ShutdownException("shutdown"))

            if self._mthread is not None:
                self._flush_mnl()
                self._mthread.join()

            if self.mnl is not None:
                self.mnl.close()
                self.mnl = None

            if self._nl_own:
                self.nl.close()
                self.nl = None

            self._flush_db()

    def _flush_mnl(self):
        if self.mnl is not None:
            # terminate the main loop
            for t in range(3):
                try:
                    msg = ifinfmsg()
                    msg['index'] = 1
                    msg.reset()
                    self.mnl.put(msg, RTM_GETLINK)
                except Exception as e:
                    log.error("shutdown error: %s", e)
                    # Just give up.
                    # We can not handle this case

    def create(self, kind, ifname, reuse=False, **kwarg):
        return self.interfaces.add(kind, ifname, reuse, **kwarg)

    def ensure(self, cmd='add', reachable=None, condition=None):
        if cmd == 'reset':
            self._ensure = []
        elif cmd == 'run':
            for f in self._ensure:
                f()
        elif cmd == 'add':
            if isinstance(reachable, basestring):
                reachable = reachable.split(':')
                if len(reachable) == 1:
                    f = partial(test_reachable_icmp, reachable[0])
                else:
                    raise NotImplementedError()
                self._ensure.append(f)
            else:
                if sys.stdin.isatty():
                    pprint(self._ensure, stream=self._stdout)
        elif cmd == 'print':
            pprint(self._ensure, stream=self._stdout)
        elif cmd == 'get':
            return self._ensure
        else:
            raise NotImplementedError()

    def items(self):
        # TODO: add support for filters?

        # iterate interfaces
        for ifname in getattr(self, 'by_name', {}):
            yield (('interfaces', ifname), self.interfaces[ifname])

        # iterate routes
        for table in getattr(getattr(self, 'routes', None),
                             'tables', {}):
            for key, route in self.routes.tables[table].items():
                yield (('routes', table, key), route)

    def dump(self):
        ret = {}
        for key, obj in self.items():
            ptr = ret
            for step in key[:-1]:
                if step not in ptr:
                    ptr[step] = {}
                ptr = ptr[step]
            ptr[key[-1]] = obj
        return ret

    def load(self, config, ptr=None):
        if ptr is None:
            ptr = self

        for key in config:
            obj = getattr(ptr, key, None)
            if obj is not None:
                if hasattr(obj, 'load'):
                    obj.load(config[key])
                else:
                    self.load(config[key], ptr=obj)
            elif hasattr(ptr, 'add'):
                ptr.add(**config[key])

        return self

    def review(self):
        ret = {}
        for key, obj in self.items():
            ptr = ret
            try:
                rev = obj.review()
            except TypeError:
                continue

            for step in key[:-1]:
                if step not in ptr:
                    ptr[step] = {}
                ptr = ptr[step]
            ptr[key[-1]] = rev

        if not ret:
            raise TypeError('no transaction started')
        return ret

    def drop(self):
        ok = False
        for key, obj in self.items():
            try:
                obj.drop()
            except TypeError:
                continue
            ok = True
        if not ok:
            raise TypeError('no transaction started')

    def commit(self, transactions=None, phase=1):
        # what to commit: either from transactions argument, or from
        # started transactions on existing objects
        if transactions is None:
            # collect interface transactions
            txlist = [(x, x.current_tx) for x
                      in getattr(self, 'by_name', {}).values()
                      if x.local_tx.values()]
            # collect route transactions
            for table in getattr(getattr(self, 'routes', None),
                                 'tables', {}).keys():
                txlist.extend([(x, x.current_tx) for x in
                               self.routes.tables[table]
                               if x.local_tx.values()])
            transactions = txlist

        snapshots = []
        removed = []

        tx_ipdb_prio = []
        tx_main = []
        tx_prio1 = []
        tx_prio2 = []
        tx_prio3 = []
        for (target, tx) in transactions:
            # 8<------------------------------
            # first -- explicit priorities
            if tx['ipdb_priority']:
                tx_ipdb_prio.append((target, tx))
                continue
            # 8<------------------------------
            # routes
            if isinstance(target, BaseRoute):
                tx_prio3.append((target, tx))
                continue
            # 8<------------------------------
            # intefaces
            kind = target.get('kind', None)
            if kind in ('vlan', 'vxlan', 'gre', 'tuntap', 'vti', 'vti6',
                        'vrf'):
                tx_prio1.append((target, tx))
            elif kind in ('bridge', 'bond'):
                tx_prio2.append((target, tx))
            else:
                tx_main.append((target, tx))
            # 8<------------------------------

        # explicitly sorted transactions
        tx_ipdb_prio = sorted(tx_ipdb_prio,
                              key=lambda x: x[1]['ipdb_priority'],
                              reverse=True)

        # FIXME: this should be documented
        #
        # The final transactions order:
        # 1. any txs with ipdb_priority (sorted by that field)
        #
        # Then come default priorities (no ipdb_priority specified):
        # 2. all the rest
        # 3. vlan, vxlan, gre, tuntap, vti, vrf
        # 4. bridge, bond
        # 5. routes
        transactions = tx_ipdb_prio + tx_main + tx_prio1 + tx_prio2 + tx_prio3

        try:
            for (target, tx) in transactions:
                if target['ipdb_scope'] == 'detached':
                    continue
                if tx['ipdb_scope'] == 'remove':
                    tx['ipdb_scope'] = 'shadow'
                    removed.append((target, tx))
                if phase == 1:
                    s = (target, target.pick(detached=True))
                    snapshots.append(s)
                # apply the changes, but NO rollback -- only phase 1
                target.commit(transaction=tx,
                              commit_phase=phase,
                              commit_mask=phase)
                # if the commit above fails, the next code
                # branch will run rollbacks
        except Exception:
            if phase == 1:
                # run rollbacks for ALL the collected transactions,
                # even successful ones
                self.fallen = transactions
                txs = filter(lambda x: not ('create' ==
                                            x[0]['ipdb_scope'] ==
                                            x[1]['ipdb_scope']), snapshots)
                self.commit(transactions=txs, phase=2)
            raise
        else:
            if phase == 1:
                for (target, tx) in removed:
                    target['ipdb_scope'] = 'detached'
                    target.detach()
        finally:
            if phase == 1:
                for (target, tx) in transactions:
                    target.drop(tx.uid)

        return self

    def watchdog(self, wdops='RTM_NEWLINK', **kwarg):
        return Watchdog(self, wdops, kwarg)

    def _serve_cb(self):
        ###
        # Callbacks thread working on a dedicated event queue.
        ###

        while not self._stop:
            msg = self._cbq.get()
            self._cbq.task_done()
            if isinstance(msg, ShutdownException):
                return
            elif isinstance(msg, Exception):
                raise msg
            for cb in tuple(self._post_callbacks.values()):
                try:
                    cb(self, msg, msg['event'])
                except:
                    pass

    def _serve_main(self):
        ###
        # Main monitoring cycle. It gets messages from the
        # default iproute queue and updates objects in the
        # database.
        ###

        while not self._stop:
            try:
                messages = self.mnl.get()
                ##
                # Check it again
                #
                # NOTE: one should not run callbacks or
                # anything like that after setting the
                # _stop flag, since IPDB is not valid
                # anymore
                if self._stop:
                    break
            except Exception as e:
                with self.exclusive:
                    if self._evq:
                        self._evq.put(e)
                        return
                if self.restart_on_error:
                    log.error('Restarting IPDB instance after '
                              'error:\n%s', traceback.format_exc())
                    try:
                        self.initdb()
                    except:
                        log.error('Error restarting DB:\n%s',
                                  traceback.format_exc())
                        return
                    continue
                else:
                    log.error('Emergency shutdown, cleanup manually')
                    raise RuntimeError('Emergency shutdown')

            for msg in messages:
                # Run pre-callbacks
                # NOTE: pre-callbacks are synchronous
                for (cuid, cb) in tuple(self._pre_callbacks.items()):
                    try:
                        cb(self, msg, msg['event'])
                    except:
                        pass

                with self.exclusive:
                    event = msg.get('event', None)
                    if event in self._event_map:
                        for func in self._event_map[event]:
                            func(msg)

                    # Post-callbacks
                    try:
                        self._cbq.put_nowait(msg)
                        if self._cbq_drop:
                            log.warning('dropped %d events',
                                        self._cbq_drop)
                            self._cbq_drop = 0
                    except queue.Full:
                        self._cbq_drop += 1
                    except Exception:
                        log.error('Emergency shutdown, cleanup manually')
                        raise RuntimeError('Emergency shutdown')

                    #
                    # Why not to put these two pieces of the code
                    # it in a routine?
                    #
                    # TODO: run performance tests with routines

                    # Users event queue
                    if self._evq:
                        try:
                            self._evq.put_nowait(msg)
                            if self._evq_drop:
                                log.warning("dropped %d events",
                                            self._evq_drop)
                                self._evq_drop = 0
                        except queue.Full:
                            self._evq_drop += 1
                        except Exception as e:
                            log.error('Emergency shutdown, cleanup manually')
                            raise RuntimeError('Emergency shutdown')
Beispiel #30
0
# -*- coding: utf-8 -*-

from __future__ import absolute_import, division, print_function, unicode_literals

import os
import ipaddress
import threading

#import pyroute2
# for pyinstaller
# again for nuitka
from pyroute2.iproute import IPRoute


IP = IPRoute()


def _transform_attrs_inplace(result):
    for item in result:
        item['attrs'] = dict(item['attrs'])
    return result


def is_physical_iface(iface):
    device_path = os.readlink(os.path.join('/sys/class/net', iface))
    return not device_path.startswith('../../devices/virtual/')


def get_physical_ifaces():
    ifaces = os.listdir('/sys/class/net')
    return [iface for iface in ifaces if is_physical_iface(iface)]
Beispiel #31
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)
Beispiel #32
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
Beispiel #33
0
class IPDB(object):
    '''
    The class that maintains information about network setup
    of the host. Monitoring netlink events allows it to react
    immediately. It uses no polling.
    '''

    def __init__(self, nl=None, mode='implicit',
                 restart_on_error=None, nl_async=None,
                 nl_bind_groups=RTNL_GROUPS,
                 ignore_rtables=None, callbacks=None,
                 sort_addresses=False):
        self.mode = mode
        self.sort_addresses = sort_addresses
        self._event_map = {}
        self._deferred = {}
        self._loaded = set()
        self._mthread = None
        self._nl_own = nl is None
        self._nl_async = config.ipdb_nl_async if nl_async is None else True
        self.mnl = None
        self.nl = nl
        self.nl_bind_groups = nl_bind_groups
        self._plugins = [interface, route, rule]
        if isinstance(ignore_rtables, int):
            self._ignore_rtables = [ignore_rtables, ]
        elif isinstance(ignore_rtables, (list, tuple, set)):
            self._ignore_rtables = ignore_rtables
        else:
            self._ignore_rtables = []
        self._stop = False
        # see also 'register_callback'
        self._post_callbacks = {}
        self._pre_callbacks = {}
        self._cb_threads = {}

        # locks and events
        self.exclusive = threading.RLock()
        self._shutdown_lock = threading.Lock()

        # register callbacks
        #
        # examples::
        #   def cb1(ipdb, msg, event):
        #       print(event, msg)
        #   def cb2(...):
        #       ...
        #
        #   # default mode: post
        #   IPDB(callbacks=[cb1, cb2])
        #   # specify the mode explicitly
        #   IPDB(callbacks=[(cb1, 'pre'), (cb2, 'post')])
        #
        for cba in callbacks or []:
            if not isinstance(cba, (tuple, list, set)):
                cba = (cba, )
            self.register_callback(*cba)

        # load information
        self.restart_on_error = restart_on_error if \
            restart_on_error is not None else nl is None

        # init the database
        self.initdb()

        #
        atexit.register(self.release)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.release()

    def initdb(self):
        # common event map, empty by default, so all the
        # events aer just ignored
        self.release(complete=False)
        self._stop = False
        # explicitly cleanup object references
        for event in tuple(self._event_map):
            del self._event_map[event]

        # if the command socket is not provided, create it
        if self._nl_own:
            self.nl = IPRoute()
        # setup monitoring socket
        self.mnl = self.nl.clone()
        try:
            self.mnl.bind(groups=self.nl_bind_groups, async=self._nl_async)
        except:
            self.mnl.close()
            if self._nl_own is None:
                self.nl.close()
            raise

        # explicitly cleanup references
        for key in tuple(self._deferred):
            del self._deferred[key]

        for module in self._plugins:
            if (module.groups & self.nl_bind_groups) != module.groups:
                continue
            for plugin in module.spec:
                self._deferred[plugin['name']] = module.spec
                if plugin['name'] in self._loaded:
                    delattr(self, plugin['name'])
                    self._loaded.remove(plugin['name'])

        # start the monitoring thread
        self._mthread = threading.Thread(name="IPDB event loop",
                                         target=self.serve_forever)
        self._mthread.setDaemon(True)
        self._mthread.start()

    def __getattribute__(self, name):
        deferred = super(IPDB, self).__getattribute__('_deferred')
        if name in deferred:
            register = []
            spec = deferred[name]
            for plugin in spec:
                obj = plugin['class'](self, **plugin['kwarg'])
                setattr(self, plugin['name'], obj)
                register.append(obj)
                self._loaded.add(plugin['name'])
                del deferred[plugin['name']]
            for obj in register:
                if hasattr(obj, '_register'):
                    obj._register()
                if hasattr(obj, '_event_map'):
                    for event in obj._event_map:
                        if event not in self._event_map:
                            self._event_map[event] = []
                        self._event_map[event].append(obj._event_map[event])
        return super(IPDB, self).__getattribute__(name)

    def register_callback(self, callback, mode='post'):
        '''
        IPDB callbacks are routines executed on a RT netlink
        message arrival. There are two types of callbacks:
        "post" and "pre" callbacks.

        ...

        "Post" callbacks are executed after the message is
        processed by IPDB and all corresponding objects are
        created or deleted. Using ipdb reference in "post"
        callbacks you will access the most up-to-date state
        of the IP database.

        "Post" callbacks are executed asynchronously in
        separate threads. These threads can work as long
        as you want them to. Callback threads are joined
        occasionally, so for a short time there can exist
        stopped threads.

        ...

        "Pre" callbacks are synchronous routines, executed
        before the message gets processed by IPDB. It gives
        you the way to patch arriving messages, but also
        places a restriction: until the callback exits, the
        main event IPDB loop is blocked.

        Normally, only "post" callbacks are required. But in
        some specific cases "pre" also can be useful.

        ...

        The routine, `register_callback()`, takes two arguments:
            - callback function
            - mode (optional, default="post")

        The callback should be a routine, that accepts three
        arguments::

            cb(ipdb, msg, action)

        Arguments are:

            - **ipdb** is a reference to IPDB instance, that invokes
                the callback.
            - **msg** is a message arrived
            - **action** is just a msg['event'] field

        E.g., to work on a new interface, you should catch
        action == 'RTM_NEWLINK' and with the interface index
        (arrived in msg['index']) get it from IPDB::

            index = msg['index']
            interface = ipdb.interfaces[index]
        '''
        lock = threading.Lock()

        def safe(*argv, **kwarg):
            with lock:
                callback(*argv, **kwarg)

        safe.hook = callback
        safe.lock = lock
        safe.uuid = uuid32()

        if mode == 'post':
            self._post_callbacks[safe.uuid] = safe
        elif mode == 'pre':
            self._pre_callbacks[safe.uuid] = safe
        return safe.uuid

    def unregister_callback(self, cuid, mode='post'):
        if mode == 'post':
            cbchain = self._post_callbacks
        elif mode == 'pre':
            cbchain = self._pre_callbacks
        else:
            raise KeyError('Unknown callback mode')
        safe = cbchain[cuid]
        with safe.lock:
            cbchain.pop(cuid)
        for t in tuple(self._cb_threads.get(cuid, ())):
            t.join(3)
        ret = self._cb_threads.get(cuid, ())
        return ret

    def release(self, complete=True):
        '''
        Shutdown IPDB instance and sync the state. Since
        IPDB is asyncronous, some operations continue in the
        background, e.g. callbacks. So, prior to exit the
        script, it is required to properly shutdown IPDB.

        The shutdown sequence is not forced in an interactive
        python session, since it is easier for users and there
        is enough time to sync the state. But for the scripts
        the `release()` call is required.
        '''
        with self._shutdown_lock:
            if self._stop:
                return
            self._stop = True
            if self.mnl is not None:
                # terminate the main loop
                for t in range(3):
                    try:
                        msg = ifinfmsg()
                        msg['index'] = 1
                        msg.reset()
                        self.mnl.put(msg, RTM_GETLINK)
                    except Exception as e:
                        logging.warning("shutdown error: %s", e)
                        # Just give up.
                        # We can not handle this case

            if self._mthread is not None:
                self._mthread.join()

            if self.mnl is not None:
                self.mnl.close()
                self.mnl = None
                if complete or self._nl_own:
                    self.nl.close()
                    self.nl = None

        with self.exclusive:
                # collect all the callbacks
                for cuid in tuple(self._cb_threads):
                    for t in tuple(self._cb_threads[cuid]):
                        t.join()

                # flush all the objects
                def flush(idx):
                    for key in tuple(idx.keys()):
                        try:
                            del idx[key]
                        except KeyError:
                            pass
                idx_list = []

                if 'interfaces' in self._loaded:
                    for (key, dev) in self.by_name.items():
                        try:
                            # FIXME
                            self.interfaces._detach(key,
                                                    dev['index'],
                                                    dev.nlmsg)
                        except KeyError:
                            pass
                    idx_list.append(self.ipaddr)
                    idx_list.append(self.neighbours)

                if 'routes' in self._loaded:
                    idx_list.extend([self.routes.tables[x] for x
                                     in self.routes.tables.keys()])

                if 'rules' in self._loaded:
                    idx_list.append(self.rules)

                for idx in idx_list:
                    flush(idx)

    def create(self, kind, ifname, reuse=False, **kwarg):
        return self.interfaces.add(kind, ifname, reuse, **kwarg)

    def commit(self, transactions=None, phase=1):
        # what to commit: either from transactions argument, or from
        # started transactions on existing objects
        if transactions is None:
            # collect interface transactions
            txlist = [(x, x.current_tx) for x
                      in self.by_name.values() if x.local_tx.values()]
            # collect route transactions
            for table in self.routes.tables.keys():
                txlist.extend([(x, x.current_tx) for x in
                               self.routes.tables[table]
                               if x.local_tx.values()])
            txlist = sorted(txlist,
                            key=lambda x: x[1]['ipdb_priority'],
                            reverse=True)
            transactions = txlist

        snapshots = []
        removed = []

        try:
            for (target, tx) in transactions:
                if target['ipdb_scope'] == 'detached':
                    continue
                if tx['ipdb_scope'] == 'remove':
                    tx['ipdb_scope'] = 'shadow'
                    removed.append((target, tx))
                if phase == 1:
                    s = (target, target.pick(detached=True))
                    snapshots.append(s)
                target.commit(transaction=tx,
                              commit_phase=phase,
                              commit_mask=phase)
        except Exception:
            if phase == 1:
                self.fallen = transactions
                self.commit(transactions=snapshots, phase=2)
            raise
        else:
            if phase == 1:
                for (target, tx) in removed:
                    target['ipdb_scope'] = 'detached'
                    target.detach()
        finally:
            if phase == 1:
                for (target, tx) in transactions:
                    target.drop(tx.uid)

    def watchdog(self, action='RTM_NEWLINK', **kwarg):
        return Watchdog(self, action, kwarg)

    def serve_forever(self):
        ###
        # Main monitoring cycle. It gets messages from the
        # default iproute queue and updates objects in the
        # database.
        #
        # Should not be called manually.
        ###

        while not self._stop:
            try:
                messages = self.mnl.get()
                ##
                # Check it again
                #
                # NOTE: one should not run callbacks or
                # anything like that after setting the
                # _stop flag, since IPDB is not valid
                # anymore
                if self._stop:
                    break
            except:
                log.error('Restarting IPDB instance after '
                          'error:\n%s', traceback.format_exc())
                if self.restart_on_error:
                    try:
                        self.initdb()
                    except:
                        log.error('Error restarting DB:\n%s',
                                  traceback.format_exc())
                        return
                    continue
                else:
                    raise RuntimeError('Emergency shutdown')

            for msg in messages:
                # Run pre-callbacks
                # NOTE: pre-callbacks are synchronous
                for (cuid, cb) in tuple(self._pre_callbacks.items()):
                    try:
                        cb(self, msg, msg['event'])
                    except:
                        pass

                with self.exclusive:
                    event = msg.get('event', None)
                    if event in self._event_map:
                        for func in self._event_map[event]:
                            func(msg)

                # run post-callbacks
                # NOTE: post-callbacks are asynchronous
                for (cuid, cb) in tuple(self._post_callbacks.items()):
                    t = threading.Thread(name="IPDB callback %s" % (id(cb)),
                                         target=cb,
                                         args=(self, msg, msg['event']))
                    t.start()
                    if cuid not in self._cb_threads:
                        self._cb_threads[cuid] = set()
                    self._cb_threads[cuid].add(t)

                # occasionally join cb threads
                for cuid in tuple(self._cb_threads):
                    for t in tuple(self._cb_threads.get(cuid, ())):
                        t.join(0)
                        if not t.is_alive():
                            try:
                                self._cb_threads[cuid].remove(t)
                            except KeyError:
                                pass
                            if len(self._cb_threads.get(cuid, ())) == 0:
                                del self._cb_threads[cuid]

    def init_ipaddr_set(self):
        if self.sort_addresses:
            return SortedIPaddrSet()
        else:
            return IPaddrSet()
Beispiel #34
0
class IPDB(object):
    '''
    The class that maintains information about network setup
    of the host. Monitoring netlink events allows it to react
    immediately. It uses no polling.
    '''

    def __init__(self, nl=None, mode='implicit',
                 restart_on_error=None, nl_async=None,
                 sndbuf=1048576, rcvbuf=1048576,
                 nl_bind_groups=RTMGRP_DEFAULTS,
                 ignore_rtables=None, callbacks=None,
                 sort_addresses=False, plugins=None):
        plugins = plugins or ['interfaces', 'routes', 'rules']
        pmap = {'interfaces': interfaces,
                'routes': routes,
                'rules': rules}
        self.mode = mode
        self.txdrop = False
        self._stdout = sys.stdout
        self._ipaddr_set = SortedIPaddrSet if sort_addresses else IPaddrSet
        self._event_map = {}
        self._deferred = {}
        self._ensure = []
        self._loaded = set()
        self._mthread = None
        self._nl_own = nl is None
        self._nl_async = config.ipdb_nl_async if nl_async is None else True
        self.mnl = None
        self.nl = nl
        self._sndbuf = sndbuf
        self._rcvbuf = rcvbuf
        self.nl_bind_groups = nl_bind_groups
        self._plugins = [pmap[x] for x in plugins if x in pmap]
        if isinstance(ignore_rtables, int):
            self._ignore_rtables = [ignore_rtables, ]
        elif isinstance(ignore_rtables, (list, tuple, set)):
            self._ignore_rtables = ignore_rtables
        else:
            self._ignore_rtables = []
        self._stop = False
        # see also 'register_callback'
        self._post_callbacks = {}
        self._pre_callbacks = {}

        # local event queues
        # - callbacks event queue
        self._cbq = queue.Queue(maxsize=8192)
        self._cbq_drop = 0
        # - users event queue
        self._evq = None
        self._evq_lock = threading.Lock()
        self._evq_drop = 0

        # locks and events
        self.exclusive = threading.RLock()
        self._shutdown_lock = threading.Lock()

        # register callbacks
        #
        # examples::
        #   def cb1(ipdb, msg, event):
        #       print(event, msg)
        #   def cb2(...):
        #       ...
        #
        #   # default mode: post
        #   IPDB(callbacks=[cb1, cb2])
        #   # specify the mode explicitly
        #   IPDB(callbacks=[(cb1, 'pre'), (cb2, 'post')])
        #
        for cba in callbacks or []:
            if not isinstance(cba, (tuple, list, set)):
                cba = (cba, )
            self.register_callback(*cba)

        # load information
        self.restart_on_error = restart_on_error if \
            restart_on_error is not None else nl is None

        # init the database
        self.initdb()

        # init the dir() cache
        self.__dir_cache__ = [i for i in self.__class__.__dict__.keys()
                              if i[0] != '_']
        self.__dir_cache__.extend(list(self._deferred.keys()))

        def cleanup(ref):
            ipdb_obj = ref()
            if ipdb_obj is not None:
                ipdb_obj.release()
        atexit.register(cleanup, weakref.ref(self))

    def __dir__(self):
        return self.__dir_cache__

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.release()

    def _flush_db(self):

        def flush(idx):
            for key in tuple(idx.keys()):
                try:
                    del idx[key]
                except KeyError:
                    pass
        idx_list = []
        if 'interfaces' in self._loaded:
            for (key, dev) in self.by_name.items():
                try:
                    # FIXME
                    self.interfaces._detach(key,
                                            dev['index'],
                                            dev.nlmsg)
                except KeyError:
                    pass
            idx_list.append(self.ipaddr)
            idx_list.append(self.neighbours)
        if 'routes' in self._loaded:
            idx_list.extend([self.routes.tables[x] for x
                             in self.routes.tables.keys()])
        if 'rules' in self._loaded:
            idx_list.append(self.rules)
        for idx in idx_list:
            flush(idx)

    def initdb(self):

        # flush all the DB objects
        with self.exclusive:

            # explicitly cleanup object references
            for event in tuple(self._event_map):
                del self._event_map[event]

            self._flush_db()

            # if the command socket is not provided, create it
            if self._nl_own:
                if self.nl is not None:
                    self.nl.close()
                self.nl = IPRoute(sndbuf=self._sndbuf, rcvbuf=self._rcvbuf)
            # setup monitoring socket
            if self.mnl is not None:
                self._flush_mnl()
                self.mnl.close()
            self.mnl = self.nl.clone()
            try:
                self.mnl.bind(groups=self.nl_bind_groups,
                              async_cache=self._nl_async)
            except:
                self.mnl.close()
                if self._nl_own is None:
                    self.nl.close()
                raise

            # explicitly cleanup references
            for key in tuple(self._deferred):
                del self._deferred[key]

            for module in self._plugins:
                if (module.groups & self.nl_bind_groups) != module.groups:
                    continue
                for plugin in module.spec:
                    self._deferred[plugin['name']] = module.spec
                    if plugin['name'] in self._loaded:
                        delattr(self, plugin['name'])
                        self._loaded.remove(plugin['name'])

            # start service threads
            for tspec in (('_mthread', '_serve_main', 'IPDB main event loop'),
                          ('_cthread', '_serve_cb', 'IPDB cb event loop')):
                tg = getattr(self, tspec[0], None)
                if not getattr(tg, 'is_alive', lambda: False)():
                    tx = threading.Thread(name=tspec[2],
                                          target=getattr(self, tspec[1]))
                    setattr(self, tspec[0], tx)
                    tx.setDaemon(True)
                    tx.start()

    def __getattribute__(self, name):
        deferred = super(IPDB, self).__getattribute__('_deferred')
        if name in deferred:
            register = []
            spec = deferred[name]
            for plugin in spec:
                obj = plugin['class'](self, **plugin['kwarg'])
                setattr(self, plugin['name'], obj)
                register.append(obj)
                self._loaded.add(plugin['name'])
                del deferred[plugin['name']]
            for obj in register:
                if hasattr(obj, '_register'):
                    obj._register()
                if hasattr(obj, '_event_map'):
                    for event in obj._event_map:
                        if event not in self._event_map:
                            self._event_map[event] = []
                        self._event_map[event].append(obj._event_map[event])
        return super(IPDB, self).__getattribute__(name)

    def register_callback(self, callback, mode='post'):
        '''
        IPDB callbacks are routines executed on a RT netlink
        message arrival. There are two types of callbacks:
        "post" and "pre" callbacks.

        ...

        "Post" callbacks are executed after the message is
        processed by IPDB and all corresponding objects are
        created or deleted. Using ipdb reference in "post"
        callbacks you will access the most up-to-date state
        of the IP database.

        "Post" callbacks are executed asynchronously in
        separate threads. These threads can work as long
        as you want them to. Callback threads are joined
        occasionally, so for a short time there can exist
        stopped threads.

        ...

        "Pre" callbacks are synchronous routines, executed
        before the message gets processed by IPDB. It gives
        you the way to patch arriving messages, but also
        places a restriction: until the callback exits, the
        main event IPDB loop is blocked.

        Normally, only "post" callbacks are required. But in
        some specific cases "pre" also can be useful.

        ...

        The routine, `register_callback()`, takes two arguments:
            - callback function
            - mode (optional, default="post")

        The callback should be a routine, that accepts three
        arguments::

            cb(ipdb, msg, action)

        Arguments are:

            - **ipdb** is a reference to IPDB instance, that invokes
                the callback.
            - **msg** is a message arrived
            - **action** is just a msg['event'] field

        E.g., to work on a new interface, you should catch
        action == 'RTM_NEWLINK' and with the interface index
        (arrived in msg['index']) get it from IPDB::

            index = msg['index']
            interface = ipdb.interfaces[index]
        '''
        lock = threading.Lock()

        def safe(*argv, **kwarg):
            with lock:
                callback(*argv, **kwarg)

        safe.hook = callback
        safe.lock = lock
        safe.uuid = uuid32()

        if mode == 'post':
            self._post_callbacks[safe.uuid] = safe
        elif mode == 'pre':
            self._pre_callbacks[safe.uuid] = safe
        else:
            raise KeyError('Unknown callback mode')
        return safe.uuid

    def unregister_callback(self, cuid, mode='post'):
        if mode == 'post':
            cbchain = self._post_callbacks
        elif mode == 'pre':
            cbchain = self._pre_callbacks
        else:
            raise KeyError('Unknown callback mode')
        safe = cbchain[cuid]
        with safe.lock:
            ret = cbchain.pop(cuid)
        return ret

    def eventqueue(self, qsize=8192, block=True, timeout=None):
        '''
        Initializes event queue and returns event queue context manager.
        Once the context manager is initialized, events start to be collected,
        so it is possible to read initial state from the system witout losing
        last moment changes, and once that is done, start processing events.

        Example:

            ipdb = IPDB()
            with ipdb.eventqueue() as evq:
                my_state = ipdb.<needed_attribute>...
                for msg in evq:
                    update_state_by_msg(my_state, msg)
        '''
        return _evq_context(self, qsize, block, timeout)

    def eventloop(self, qsize=8192, block=True, timeout=None):
        """
        Event generator for simple cases when there is no need for initial
        state setup. Initialize event queue and yield events as they happen.
        """
        with self.eventqueue(qsize=qsize, block=block, timeout=timeout) as evq:
            for msg in evq:
                yield msg

    def release(self):
        '''
        Shutdown IPDB instance and sync the state. Since
        IPDB is asyncronous, some operations continue in the
        background, e.g. callbacks. So, prior to exit the
        script, it is required to properly shutdown IPDB.

        The shutdown sequence is not forced in an interactive
        python session, since it is easier for users and there
        is enough time to sync the state. But for the scripts
        the `release()` call is required.
        '''
        with self._shutdown_lock:
            if self._stop:
                log.warning("shutdown in progress")
                return
            self._stop = True
            self._cbq.put(ShutdownException("shutdown"))

            if self._mthread is not None:
                self._flush_mnl()
                self._mthread.join()

            if self.mnl is not None:
                self.mnl.close()
                self.mnl = None

            if self._nl_own:
                self.nl.close()
                self.nl = None

            self._flush_db()

    def _flush_mnl(self):
        if self.mnl is not None:
            # terminate the main loop
            for t in range(3):
                try:
                    msg = ifinfmsg()
                    msg['index'] = 1
                    msg.reset()
                    self.mnl.put(msg, RTM_GETLINK)
                except Exception as e:
                    log.error("shutdown error: %s", e)
                    # Just give up.
                    # We can not handle this case

    def create(self, kind, ifname, reuse=False, **kwarg):
        return self.interfaces.add(kind, ifname, reuse, **kwarg)

    def ensure(self, cmd='add', reachable=None, condition=None):
        if cmd == 'reset':
            self._ensure = []
        elif cmd == 'run':
            for f in self._ensure:
                f()
        elif cmd == 'add':
            if isinstance(reachable, basestring):
                reachable = reachable.split(':')
                if len(reachable) == 1:
                    f = partial(test_reachable_icmp, reachable[0])
                else:
                    raise NotImplementedError()
                self._ensure.append(f)
            else:
                if sys.stdin.isatty():
                    pprint(self._ensure, stream=self._stdout)
        elif cmd == 'print':
            pprint(self._ensure, stream=self._stdout)
        elif cmd == 'get':
            return self._ensure
        else:
            raise NotImplementedError()

    def items(self):
        # TODO: add support for filters?

        # iterate interfaces
        for ifname in getattr(self, 'by_name', {}):
            yield (('interfaces', ifname), self.interfaces[ifname])

        # iterate routes
        for table in getattr(getattr(self, 'routes', None),
                             'tables', {}):
            for key, route in self.routes.tables[table].items():
                yield (('routes', table, key), route)

    def dump(self):
        ret = {}
        for key, obj in self.items():
            ptr = ret
            for step in key[:-1]:
                if step not in ptr:
                    ptr[step] = {}
                ptr = ptr[step]
            ptr[key[-1]] = obj
        return ret

    def load(self, config, ptr=None):
        if ptr is None:
            ptr = self

        for key in config:
            obj = getattr(ptr, key, None)
            if obj is not None:
                if hasattr(obj, 'load'):
                    obj.load(config[key])
                else:
                    self.load(config[key], ptr=obj)
            elif hasattr(ptr, 'add'):
                ptr.add(**config[key])

        return self

    def review(self):
        ret = {}
        for key, obj in self.items():
            ptr = ret
            try:
                rev = obj.review()
            except TypeError:
                continue

            for step in key[:-1]:
                if step not in ptr:
                    ptr[step] = {}
                ptr = ptr[step]
            ptr[key[-1]] = rev

        if not ret:
            raise TypeError('no transaction started')
        return ret

    def drop(self):
        ok = False
        for key, obj in self.items():
            try:
                obj.drop()
            except TypeError:
                continue
            ok = True
        if not ok:
            raise TypeError('no transaction started')

    def commit(self, transactions=None, phase=1):
        # what to commit: either from transactions argument, or from
        # started transactions on existing objects
        if transactions is None:
            # collect interface transactions
            txlist = [(x, x.current_tx) for x
                      in getattr(self, 'by_name', {}).values()
                      if x.local_tx.values()]
            # collect route transactions
            for table in getattr(getattr(self, 'routes', None),
                                 'tables', {}).keys():
                txlist.extend([(x, x.current_tx) for x in
                               self.routes.tables[table]
                               if x.local_tx.values()])
            transactions = txlist

        snapshots = []
        removed = []

        tx_ipdb_prio = []
        tx_main = []
        tx_prio1 = []
        tx_prio2 = []
        tx_prio3 = []
        for (target, tx) in transactions:
            # 8<------------------------------
            # first -- explicit priorities
            if tx['ipdb_priority']:
                tx_ipdb_prio.append((target, tx))
                continue
            # 8<------------------------------
            # routes
            if isinstance(target, BaseRoute):
                tx_prio3.append((target, tx))
                continue
            # 8<------------------------------
            # intefaces
            kind = target.get('kind', None)
            if kind in ('vlan', 'vxlan', 'gre', 'tuntap', 'vti', 'vti6',
                        'vrf'):
                tx_prio1.append((target, tx))
            elif kind in ('bridge', 'bond'):
                tx_prio2.append((target, tx))
            else:
                tx_main.append((target, tx))
            # 8<------------------------------

        # explicitly sorted transactions
        tx_ipdb_prio = sorted(tx_ipdb_prio,
                              key=lambda x: x[1]['ipdb_priority'],
                              reverse=True)

        # FIXME: this should be documented
        #
        # The final transactions order:
        # 1. any txs with ipdb_priority (sorted by that field)
        #
        # Then come default priorities (no ipdb_priority specified):
        # 2. all the rest
        # 3. vlan, vxlan, gre, tuntap, vti, vrf
        # 4. bridge, bond
        # 5. routes
        transactions = tx_ipdb_prio + tx_main + tx_prio1 + tx_prio2 + tx_prio3

        try:
            for (target, tx) in transactions:
                if target['ipdb_scope'] == 'detached':
                    continue
                if tx['ipdb_scope'] == 'remove':
                    tx['ipdb_scope'] = 'shadow'
                    removed.append((target, tx))
                if phase == 1:
                    s = (target, target.pick(detached=True))
                    snapshots.append(s)
                # apply the changes, but NO rollback -- only phase 1
                target.commit(transaction=tx,
                              commit_phase=phase,
                              commit_mask=phase)
                # if the commit above fails, the next code
                # branch will run rollbacks
        except Exception:
            if phase == 1:
                # run rollbacks for ALL the collected transactions,
                # even successful ones
                self.fallen = transactions
                txs = filter(lambda x: not ('create' ==
                                            x[0]['ipdb_scope'] ==
                                            x[1]['ipdb_scope']), snapshots)
                self.commit(transactions=txs, phase=2)
            raise
        else:
            if phase == 1:
                for (target, tx) in removed:
                    target['ipdb_scope'] = 'detached'
                    target.detach()
        finally:
            if phase == 1:
                for (target, tx) in transactions:
                    target.drop(tx.uid)

        return self

    def watchdog(self, wdops='RTM_NEWLINK', **kwarg):
        return Watchdog(self, wdops, kwarg)

    def _serve_cb(self):
        ###
        # Callbacks thread working on a dedicated event queue.
        ###

        while not self._stop:
            msg = self._cbq.get()
            self._cbq.task_done()
            if isinstance(msg, ShutdownException):
                return
            elif isinstance(msg, Exception):
                raise msg
            for cb in tuple(self._post_callbacks.values()):
                try:
                    cb(self, msg, msg['event'])
                except:
                    pass

    def _serve_main(self):
        ###
        # Main monitoring cycle. It gets messages from the
        # default iproute queue and updates objects in the
        # database.
        ###

        while not self._stop:
            try:
                messages = self.mnl.get()
                ##
                # Check it again
                #
                # NOTE: one should not run callbacks or
                # anything like that after setting the
                # _stop flag, since IPDB is not valid
                # anymore
                if self._stop:
                    break
            except Exception as e:
                with self.exclusive:
                    if self._evq:
                        self._evq.put(e)
                        return
                if self.restart_on_error:
                    log.error('Restarting IPDB instance after '
                              'error:\n%s', traceback.format_exc())
                    try:
                        self.initdb()
                    except:
                        log.error('Error restarting DB:\n%s',
                                  traceback.format_exc())
                        return
                    continue
                else:
                    log.error('Emergency shutdown, cleanup manually')
                    raise RuntimeError('Emergency shutdown')

            for msg in messages:
                # Run pre-callbacks
                # NOTE: pre-callbacks are synchronous
                for (cuid, cb) in tuple(self._pre_callbacks.items()):
                    try:
                        cb(self, msg, msg['event'])
                    except:
                        pass

                with self.exclusive:
                    event = msg.get('event', None)
                    if event in self._event_map:
                        for func in self._event_map[event]:
                            func(msg)

                    # Post-callbacks
                    try:
                        self._cbq.put_nowait(msg)
                        if self._cbq_drop:
                            log.warning('dropped %d events',
                                        self._cbq_drop)
                            self._cbq_drop = 0
                    except queue.Full:
                        self._cbq_drop += 1
                    except Exception:
                        log.error('Emergency shutdown, cleanup manually')
                        raise RuntimeError('Emergency shutdown')

                    #
                    # Why not to put these two pieces of the code
                    # it in a routine?
                    #
                    # TODO: run performance tests with routines

                    # Users event queue
                    if self._evq:
                        try:
                            self._evq.put_nowait(msg)
                            if self._evq_drop:
                                log.warning("dropped %d events",
                                            self._evq_drop)
                                self._evq_drop = 0
                        except queue.Full:
                            self._evq_drop += 1
                        except Exception as e:
                            log.error('Emergency shutdown, cleanup manually')
                            raise RuntimeError('Emergency shutdown')
Beispiel #35
0
 def __init__(self, *args, **kwargs):
     super(InterfaceConfigTest, self).__init__(*args, **kwargs)
     self.iproute_api = IPRoute()
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()
Beispiel #37
0
import os
import shutil
import json
import re
import subprocess
import yaml
from pyroute2 import netlink
from pyroute2 import IPDB
from pyroute2 import netns
from pyroute2.iproute import IPRoute

IP_ROUTE = IPRoute()
MAIN_IPDB = IPDB()


def start_process(args):
    """
    Shell command agent
    """
    try:
        p = subprocess.Popen(args,
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        out, err = p.communicate()
        return (p.returncode, out, err)
    except OSError:
        return (-1, None, None)


def exec_cmd_in_namespace(ns, cmd):
Beispiel #38
0
def NetNServer(netns, rcvch, cmdch, flags=os.O_CREAT):
    '''
    The netns server supposed to be started automatically by NetNS.
    It has two communication channels: one simplex to forward incoming
    netlink packets, `rcvch`, and other synchronous duplex to get
    commands and send back responses, `cmdch`.

    Channels should support standard socket API, should be compatible
    with poll/select and should be able to transparently pickle objects.
    NetNS uses `multiprocessing.Pipe` for this purpose, but it can be
    any other implementation with compatible API.

    The first parameter, `netns`, is a netns name. Depending on the
    `flags`, the netns can be created automatically. The `flags` semantics
    is exactly the same as for `open(2)` system call.

    ...

    The server workflow is simple. The startup sequence::

        1. Create or open a netns.

        2. Start `IPRoute` instance. It will be used only on the low level,
           the `IPRoute` will not parse any packet.

        3. Start poll/select loop on `cmdch` and `IPRoute`.

    On the startup, the server sends via `cmdch` the status packet. It can be
    `None` if all is OK, or some exception.

    Further data handling, depending on the channel, server side::

        1. `IPRoute`: read an incoming netlink packet and send it unmodified
           to the peer via `rcvch`. The peer, polling `rcvch`, can handle
           the packet on its side.

        2. `cmdch`: read tuple (cmd, argv, kwarg). If the `cmd` starts with
           "send", then take `argv[0]` as a packet buffer, treat it as one
           netlink packet and substitute PID field (offset 12, uint32) with
           its own. Strictly speaking, it is not mandatory for modern netlink
           implementations, but it is required by the protocol standard.

    '''
    try:
        nsfd = setns(netns, flags)
    except OSError as e:
        cmdch.send(e)
        return e.errno
    except Exception as e:
        cmdch.send(OSError(errno.ECOMM, str(e), netns))
        return 255

    #
    try:
        ipr = IPRoute()
        rcvch_lock = ipr._sproxy.lock
        ipr._s_channel = rcvch
        poll = select.poll()
        poll.register(ipr, select.POLLIN | select.POLLPRI)
        poll.register(cmdch, select.POLLIN | select.POLLPRI)
    except Exception as e:
        cmdch.send(e)
        return 255

    # all is OK so far
    cmdch.send(None)
    # 8<-------------------------------------------------------------
    while True:
        events = poll.poll()
        for (fd, event) in events:
            if fd == ipr.fileno():
                bufsize = ipr.getsockopt(SOL_SOCKET, SO_RCVBUF) // 2
                with rcvch_lock:
                    rcvch.send(ipr.recv(bufsize))
            elif fd == cmdch.fileno():
                try:
                    cmdline = cmdch.recv()
                    if cmdline is None:
                        poll.unregister(ipr)
                        poll.unregister(cmdch)
                        ipr.close()
                        os.close(nsfd)
                        return
                    (cmd, argv, kwarg) = cmdline
                    if cmd[:4] == 'send':
                        # Achtung
                        #
                        # It's a hack, but we just have to do it: one
                        # must use actual pid in netlink messages
                        #
                        # FIXME: there can be several messages in one
                        # call buffer; but right now we can ignore it
                        msg = argv[0][:12]
                        msg += struct.pack("I", os.getpid())
                        msg += argv[0][16:]
                        argv = list(argv)
                        argv[0] = msg
                    cmdch.send(getattr(ipr, cmd)(*argv, **kwarg))
                except Exception as e:
                    e.tb = traceback.format_exc()
                    cmdch.send(e)
Beispiel #39
0
class IPDB(object):
    '''
    The class that maintains information about network setup
    of the host. Monitoring netlink events allows it to react
    immediately. It uses no polling.
    '''
    def __init__(self,
                 nl=None,
                 mode='implicit',
                 restart_on_error=None,
                 nl_async=None,
                 nl_bind_groups=RTNL_GROUPS,
                 ignore_rtables=None,
                 callbacks=None,
                 sort_addresses=False,
                 plugins=None):
        plugins = plugins or ['interfaces', 'routes', 'rules']
        pmap = {'interfaces': interfaces, 'routes': routes, 'rules': rules}
        self.mode = mode
        self.sort_addresses = sort_addresses
        self._event_map = {}
        self._deferred = {}
        self._loaded = set()
        self._mthread = None
        self._nl_own = nl is None
        self._nl_async = config.ipdb_nl_async if nl_async is None else True
        self.mnl = None
        self.nl = nl
        self.nl_bind_groups = nl_bind_groups
        self._plugins = [pmap[x] for x in plugins if x in pmap]
        if isinstance(ignore_rtables, int):
            self._ignore_rtables = [
                ignore_rtables,
            ]
        elif isinstance(ignore_rtables, (list, tuple, set)):
            self._ignore_rtables = ignore_rtables
        else:
            self._ignore_rtables = []
        self._stop = False
        # see also 'register_callback'
        self._post_callbacks = {}
        self._pre_callbacks = {}
        self._cb_threads = {}

        # locks and events
        self.exclusive = threading.RLock()
        self._shutdown_lock = threading.Lock()

        # register callbacks
        #
        # examples::
        #   def cb1(ipdb, msg, event):
        #       print(event, msg)
        #   def cb2(...):
        #       ...
        #
        #   # default mode: post
        #   IPDB(callbacks=[cb1, cb2])
        #   # specify the mode explicitly
        #   IPDB(callbacks=[(cb1, 'pre'), (cb2, 'post')])
        #
        for cba in callbacks or []:
            if not isinstance(cba, (tuple, list, set)):
                cba = (cba, )
            self.register_callback(*cba)

        # load information
        self.restart_on_error = restart_on_error if \
            restart_on_error is not None else nl is None

        # init the database
        self.initdb()

        #
        atexit.register(self.release)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.release()

    def initdb(self):
        # common event map, empty by default, so all the
        # events aer just ignored
        self.release(complete=False)
        self._stop = False
        # explicitly cleanup object references
        for event in tuple(self._event_map):
            del self._event_map[event]

        # if the command socket is not provided, create it
        if self._nl_own:
            self.nl = IPRoute()
        # setup monitoring socket
        self.mnl = self.nl.clone()
        try:
            self.mnl.bind(groups=self.nl_bind_groups, async=self._nl_async)
        except:
            self.mnl.close()
            if self._nl_own is None:
                self.nl.close()
            raise

        # explicitly cleanup references
        for key in tuple(self._deferred):
            del self._deferred[key]

        for module in self._plugins:
            if (module.groups & self.nl_bind_groups) != module.groups:
                continue
            for plugin in module.spec:
                self._deferred[plugin['name']] = module.spec
                if plugin['name'] in self._loaded:
                    delattr(self, plugin['name'])
                    self._loaded.remove(plugin['name'])

        # start the monitoring thread
        self._mthread = threading.Thread(name="IPDB event loop",
                                         target=self.serve_forever)
        self._mthread.setDaemon(True)
        self._mthread.start()

    def __getattribute__(self, name):
        deferred = super(IPDB, self).__getattribute__('_deferred')
        if name in deferred:
            register = []
            spec = deferred[name]
            for plugin in spec:
                obj = plugin['class'](self, **plugin['kwarg'])
                setattr(self, plugin['name'], obj)
                register.append(obj)
                self._loaded.add(plugin['name'])
                del deferred[plugin['name']]
            for obj in register:
                if hasattr(obj, '_register'):
                    obj._register()
                if hasattr(obj, '_event_map'):
                    for event in obj._event_map:
                        if event not in self._event_map:
                            self._event_map[event] = []
                        self._event_map[event].append(obj._event_map[event])
        return super(IPDB, self).__getattribute__(name)

    def register_callback(self, callback, mode='post'):
        '''
        IPDB callbacks are routines executed on a RT netlink
        message arrival. There are two types of callbacks:
        "post" and "pre" callbacks.

        ...

        "Post" callbacks are executed after the message is
        processed by IPDB and all corresponding objects are
        created or deleted. Using ipdb reference in "post"
        callbacks you will access the most up-to-date state
        of the IP database.

        "Post" callbacks are executed asynchronously in
        separate threads. These threads can work as long
        as you want them to. Callback threads are joined
        occasionally, so for a short time there can exist
        stopped threads.

        ...

        "Pre" callbacks are synchronous routines, executed
        before the message gets processed by IPDB. It gives
        you the way to patch arriving messages, but also
        places a restriction: until the callback exits, the
        main event IPDB loop is blocked.

        Normally, only "post" callbacks are required. But in
        some specific cases "pre" also can be useful.

        ...

        The routine, `register_callback()`, takes two arguments:
            - callback function
            - mode (optional, default="post")

        The callback should be a routine, that accepts three
        arguments::

            cb(ipdb, msg, action)

        Arguments are:

            - **ipdb** is a reference to IPDB instance, that invokes
                the callback.
            - **msg** is a message arrived
            - **action** is just a msg['event'] field

        E.g., to work on a new interface, you should catch
        action == 'RTM_NEWLINK' and with the interface index
        (arrived in msg['index']) get it from IPDB::

            index = msg['index']
            interface = ipdb.interfaces[index]
        '''
        lock = threading.Lock()

        def safe(*argv, **kwarg):
            with lock:
                callback(*argv, **kwarg)

        safe.hook = callback
        safe.lock = lock
        safe.uuid = uuid32()

        if mode == 'post':
            self._post_callbacks[safe.uuid] = safe
        elif mode == 'pre':
            self._pre_callbacks[safe.uuid] = safe
        return safe.uuid

    def unregister_callback(self, cuid, mode='post'):
        if mode == 'post':
            cbchain = self._post_callbacks
        elif mode == 'pre':
            cbchain = self._pre_callbacks
        else:
            raise KeyError('Unknown callback mode')
        safe = cbchain[cuid]
        with safe.lock:
            cbchain.pop(cuid)
        for t in tuple(self._cb_threads.get(cuid, ())):
            t.join(3)
        ret = self._cb_threads.get(cuid, ())
        return ret

    def release(self, complete=True):
        '''
        Shutdown IPDB instance and sync the state. Since
        IPDB is asyncronous, some operations continue in the
        background, e.g. callbacks. So, prior to exit the
        script, it is required to properly shutdown IPDB.

        The shutdown sequence is not forced in an interactive
        python session, since it is easier for users and there
        is enough time to sync the state. But for the scripts
        the `release()` call is required.
        '''
        with self._shutdown_lock:
            if self._stop:
                return
            self._stop = True
            if self.mnl is not None:
                # terminate the main loop
                for t in range(3):
                    try:
                        msg = ifinfmsg()
                        msg['index'] = 1
                        msg.reset()
                        self.mnl.put(msg, RTM_GETLINK)
                    except Exception as e:
                        logging.warning("shutdown error: %s", e)
                        # Just give up.
                        # We can not handle this case

            if self._mthread is not None:
                self._mthread.join()

            if self.mnl is not None:
                self.mnl.close()
                self.mnl = None
                if complete or self._nl_own:
                    self.nl.close()
                    self.nl = None

        with self.exclusive:
            # collect all the callbacks
            for cuid in tuple(self._cb_threads):
                for t in tuple(self._cb_threads[cuid]):
                    t.join()

            # flush all the objects
            def flush(idx):
                for key in tuple(idx.keys()):
                    try:
                        del idx[key]
                    except KeyError:
                        pass

            idx_list = []

            if 'interfaces' in self._loaded:
                for (key, dev) in self.by_name.items():
                    try:
                        # FIXME
                        self.interfaces._detach(key, dev['index'], dev.nlmsg)
                    except KeyError:
                        pass
                idx_list.append(self.ipaddr)
                idx_list.append(self.neighbours)

            if 'routes' in self._loaded:
                idx_list.extend(
                    [self.routes.tables[x] for x in self.routes.tables.keys()])

            if 'rules' in self._loaded:
                idx_list.append(self.rules)

            for idx in idx_list:
                flush(idx)

    def create(self, kind, ifname, reuse=False, **kwarg):
        return self.interfaces.add(kind, ifname, reuse, **kwarg)

    def commit(self, transactions=None, phase=1):
        # what to commit: either from transactions argument, or from
        # started transactions on existing objects
        if transactions is None:
            # collect interface transactions
            txlist = [(x, x.current_tx) for x in self.by_name.values()
                      if x.local_tx.values()]
            # collect route transactions
            for table in self.routes.tables.keys():
                txlist.extend([(x, x.current_tx)
                               for x in self.routes.tables[table]
                               if x.local_tx.values()])
            txlist = sorted(txlist,
                            key=lambda x: x[1]['ipdb_priority'],
                            reverse=True)
            transactions = txlist

        snapshots = []
        removed = []

        try:
            for (target, tx) in transactions:
                if target['ipdb_scope'] == 'detached':
                    continue
                if tx['ipdb_scope'] == 'remove':
                    tx['ipdb_scope'] = 'shadow'
                    removed.append((target, tx))
                if phase == 1:
                    s = (target, target.pick(detached=True))
                    snapshots.append(s)
                target.commit(transaction=tx,
                              commit_phase=phase,
                              commit_mask=phase)
        except Exception:
            if phase == 1:
                self.fallen = transactions
                self.commit(transactions=snapshots, phase=2)
            raise
        else:
            if phase == 1:
                for (target, tx) in removed:
                    target['ipdb_scope'] = 'detached'
                    target.detach()
        finally:
            if phase == 1:
                for (target, tx) in transactions:
                    target.drop(tx.uid)

    def watchdog(self, action='RTM_NEWLINK', **kwarg):
        return Watchdog(self, action, kwarg)

    def serve_forever(self):
        ###
        # Main monitoring cycle. It gets messages from the
        # default iproute queue and updates objects in the
        # database.
        #
        # Should not be called manually.
        ###

        while not self._stop:
            try:
                messages = self.mnl.get()
                ##
                # Check it again
                #
                # NOTE: one should not run callbacks or
                # anything like that after setting the
                # _stop flag, since IPDB is not valid
                # anymore
                if self._stop:
                    break
            except:
                log.error('Restarting IPDB instance after '
                          'error:\n%s', traceback.format_exc())
                if self.restart_on_error:
                    try:
                        self.initdb()
                    except:
                        log.error('Error restarting DB:\n%s',
                                  traceback.format_exc())
                        return
                    continue
                else:
                    raise RuntimeError('Emergency shutdown')

            for msg in messages:
                # Run pre-callbacks
                # NOTE: pre-callbacks are synchronous
                for (cuid, cb) in tuple(self._pre_callbacks.items()):
                    try:
                        cb(self, msg, msg['event'])
                    except:
                        pass

                with self.exclusive:
                    event = msg.get('event', None)
                    if event in self._event_map:
                        for func in self._event_map[event]:
                            func(msg)

                # run post-callbacks
                # NOTE: post-callbacks are asynchronous
                for (cuid, cb) in tuple(self._post_callbacks.items()):
                    t = threading.Thread(name="IPDB callback %s" % (id(cb)),
                                         target=cb,
                                         args=(self, msg, msg['event']))
                    t.start()
                    if cuid not in self._cb_threads:
                        self._cb_threads[cuid] = set()
                    self._cb_threads[cuid].add(t)

                # occasionally join cb threads
                for cuid in tuple(self._cb_threads):
                    for t in tuple(self._cb_threads.get(cuid, ())):
                        t.join(0)
                        if not t.is_alive():
                            try:
                                self._cb_threads[cuid].remove(t)
                            except KeyError:
                                pass
                            if len(self._cb_threads.get(cuid, ())) == 0:
                                del self._cb_threads[cuid]

    def init_ipaddr_set(self):
        if self.sort_addresses:
            return SortedIPaddrSet()
        else:
            return IPaddrSet()